@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,258 @@
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 { readdirSync, statSync } from "node:fs";
25
+ import { join, relative } from "node:path";
26
+ import { matchAnyGlob, readSotCache, } from "../ground/index.js";
27
+ import { alignFile } from "../hooks/post-tool-use/index.js";
28
+ import { TIER2_JACCARD_FLOOR, TOP_K_CANDIDATES, extractBlocks, isMarkdownPath, topKCandidates, } from "../hooks/sot-align-common.js";
29
+ import { logger } from "../logger.js";
30
+ import { tokenize } from "../text/jaccard.js";
31
+ const log = logger("fix-align");
32
+ /* -------------------------------------------------------------------------- */
33
+ /* Tunables */
34
+ /* -------------------------------------------------------------------------- */
35
+ const SWEEP_PASS1_CAP = 200;
36
+ const SWEEP_PASS2_CAP = 50;
37
+ const PASS2_FRACTION_ESTIMATE = 0.1; // ~10% of Pass-1 ambiguous → Pass-2
38
+ const TOKENS_PER_PASS1_CALL = 600; // empirical: Haiku dedup P1 ~600 tokens in/out
39
+ const TOKENS_PER_PASS2_CALL = 1_200;
40
+ const TOKENS_PER_CREATION_CALL = 800;
41
+ const DEFAULT_MAX_COST_TOKENS = 500_000;
42
+ const MIN_BLOCK_PROSE_LEN = 80;
43
+ const MIN_BLOCK_TOKEN_COUNT = 10;
44
+ const DEFAULT_EXCLUDES = [
45
+ ".cairn/**",
46
+ "node_modules/**",
47
+ "dist/**",
48
+ "build/**",
49
+ ".git/**",
50
+ ".vscode/**",
51
+ ".idea/**",
52
+ ];
53
+ /* -------------------------------------------------------------------------- */
54
+ /* Run */
55
+ /* -------------------------------------------------------------------------- */
56
+ export async function runFixAlign(args) {
57
+ const { repoRoot } = args;
58
+ const include = args.include ?? [];
59
+ const exclude = [...DEFAULT_EXCLUDES, ...(args.exclude ?? [])];
60
+ const maxCost = args.maxCost ?? DEFAULT_MAX_COST_TOKENS;
61
+ const skipCreation = args.skipCreation === true;
62
+ const pass1Cap = args.pass1Cap ?? SWEEP_PASS1_CAP;
63
+ const pass2Cap = args.pass2Cap ?? SWEEP_PASS2_CAP;
64
+ const filesVisited = listSourceFiles({ repoRoot, include, exclude });
65
+ const cache = readSotCache(repoRoot);
66
+ const cacheEntries = Object.values(cache.entries).filter((e) => e.tokens.length > 0);
67
+ const preflight = computePreflight({
68
+ repoRoot,
69
+ files: filesVisited,
70
+ cacheEntries,
71
+ skipCreation,
72
+ pass1Cap,
73
+ });
74
+ const result = {
75
+ preflight,
76
+ apply: null,
77
+ abortedOverBudget: false,
78
+ filesVisited,
79
+ };
80
+ if (args.dryRun === true)
81
+ return result;
82
+ if (preflight.estimatedTokens > maxCost) {
83
+ log.warn({ estimated: preflight.estimatedTokens, maxCost }, "fix-align estimate exceeds maxCost; aborting before Haiku spend");
84
+ result.abortedOverBudget = true;
85
+ return result;
86
+ }
87
+ const aggregate = {
88
+ filesAligned: 0,
89
+ blocksConsidered: 0,
90
+ tier1Aligned: 0,
91
+ tier2Aligned: 0,
92
+ decsCreated: 0,
93
+ invsCreated: 0,
94
+ augmentsDecs: 0,
95
+ augmentsInvs: 0,
96
+ pending: 0,
97
+ deferredToStaleness: 0,
98
+ descriptive: 0,
99
+ skipped: 0,
100
+ haikuPass1Calls: 0,
101
+ haikuPass2Calls: 0,
102
+ haikuCalls: 0,
103
+ };
104
+ for (const file of filesVisited) {
105
+ const alignArgs = {
106
+ repoRoot,
107
+ filePath: file,
108
+ sessionId: null,
109
+ pass1Cap,
110
+ pass2Cap,
111
+ skipCreation,
112
+ };
113
+ if (args.mocks?.mockDedupJudgePass1 !== undefined)
114
+ alignArgs.mockDedupJudgePass1 = args.mocks.mockDedupJudgePass1;
115
+ if (args.mocks?.mockDedupJudgePass2 !== undefined)
116
+ alignArgs.mockDedupJudgePass2 = args.mocks.mockDedupJudgePass2;
117
+ if (args.mocks?.mockDeltaExtract !== undefined)
118
+ alignArgs.mockDeltaExtract = args.mocks.mockDeltaExtract;
119
+ if (args.mocks?.mockDeltaClassify !== undefined)
120
+ alignArgs.mockDeltaClassify = args.mocks.mockDeltaClassify;
121
+ if (args.mocks?.mockCreationJudgePass1 !== undefined)
122
+ alignArgs.mockCreationJudgePass1 = args.mocks.mockCreationJudgePass1;
123
+ if (args.mocks?.mockCreationJudgePass2 !== undefined)
124
+ alignArgs.mockCreationJudgePass2 = args.mocks.mockCreationJudgePass2;
125
+ let fileResult;
126
+ try {
127
+ fileResult = await alignFile(alignArgs);
128
+ }
129
+ catch (err) {
130
+ log.warn({ file, err: String(err) }, "alignFile threw; continuing sweep");
131
+ continue;
132
+ }
133
+ aggregate.filesAligned += 1;
134
+ aggregate.blocksConsidered += fileResult.blocksConsidered;
135
+ aggregate.tier1Aligned += fileResult.tier1Aligned;
136
+ aggregate.tier2Aligned += fileResult.tier2Aligned;
137
+ aggregate.decsCreated += fileResult.decsCreated;
138
+ aggregate.invsCreated += fileResult.invsCreated;
139
+ aggregate.augmentsDecs += fileResult.augmentsDecs;
140
+ aggregate.augmentsInvs += fileResult.augmentsInvs;
141
+ aggregate.pending += fileResult.pending;
142
+ aggregate.deferredToStaleness += fileResult.deferredToStaleness;
143
+ aggregate.descriptive += fileResult.descriptive;
144
+ aggregate.skipped += fileResult.skipped;
145
+ aggregate.haikuPass1Calls += fileResult.haikuPass1Calls;
146
+ aggregate.haikuPass2Calls += fileResult.haikuPass2Calls;
147
+ aggregate.haikuCalls += fileResult.haikuCalls;
148
+ }
149
+ result.apply = aggregate;
150
+ return result;
151
+ }
152
+ function listSourceFiles(args) {
153
+ const out = [];
154
+ walk(args.repoRoot, args.repoRoot, out, args);
155
+ out.sort();
156
+ return out;
157
+ }
158
+ function walk(repoRoot, dir, out, args) {
159
+ let entries;
160
+ try {
161
+ entries = readdirSync(dir);
162
+ }
163
+ catch {
164
+ return;
165
+ }
166
+ for (const name of entries) {
167
+ if (name.startsWith(".") && name !== ".cairn") {
168
+ // Allow .cairn for the exclude check below; everything else
169
+ // hidden gets skipped (avoids walking node_modules-equivalents).
170
+ if (name === ".git" || name === ".vscode" || name === ".idea")
171
+ continue;
172
+ }
173
+ const abs = join(dir, name);
174
+ let s;
175
+ try {
176
+ s = statSync(abs);
177
+ }
178
+ catch {
179
+ continue;
180
+ }
181
+ const rel = relative(repoRoot, abs).split("\\").join("/");
182
+ if (s.isDirectory()) {
183
+ if (matchAnyGlob(`${rel}/`, args.exclude) || matchAnyGlob(rel, args.exclude))
184
+ continue;
185
+ walk(repoRoot, abs, out, args);
186
+ continue;
187
+ }
188
+ if (!s.isFile())
189
+ continue;
190
+ if (isMarkdownPath(rel))
191
+ continue;
192
+ if (matchAnyGlob(rel, args.exclude))
193
+ continue;
194
+ if (args.include.length > 0 && !matchAnyGlob(rel, args.include))
195
+ continue;
196
+ out.push(rel);
197
+ }
198
+ }
199
+ function computePreflight(args) {
200
+ const out = {
201
+ filesScanned: args.files.length,
202
+ blocksConsidered: 0,
203
+ shortBlocks: 0,
204
+ blocksWithTier1Candidates: 0,
205
+ blocksWithoutCandidates: 0,
206
+ estimatedPass1Calls: 0,
207
+ estimatedPass2Calls: 0,
208
+ estimatedCreationCalls: 0,
209
+ estimatedTokens: 0,
210
+ };
211
+ for (const file of args.files) {
212
+ let blocks;
213
+ try {
214
+ blocks = extractBlocks(args.repoRoot, file);
215
+ }
216
+ catch {
217
+ continue;
218
+ }
219
+ for (const block of blocks) {
220
+ out.blocksConsidered += 1;
221
+ if (block.prose.length < MIN_BLOCK_PROSE_LEN) {
222
+ out.shortBlocks += 1;
223
+ continue;
224
+ }
225
+ const blockTokens = tokenize(block.prose, { codeAware: true });
226
+ if (blockTokens.size < MIN_BLOCK_TOKEN_COUNT) {
227
+ out.shortBlocks += 1;
228
+ continue;
229
+ }
230
+ const candidates = topKCandidates(blockTokens, args.cacheEntries, TIER2_JACCARD_FLOOR, TOP_K_CANDIDATES);
231
+ if (candidates.length === 0) {
232
+ out.blocksWithoutCandidates += 1;
233
+ if (!args.skipCreation) {
234
+ // Tier 3 creation: ~1 P1 call + ~5% P2 escalation.
235
+ out.estimatedPass1Calls += 1;
236
+ out.estimatedPass2Calls += 0.05;
237
+ out.estimatedCreationCalls += 1;
238
+ }
239
+ continue;
240
+ }
241
+ out.blocksWithTier1Candidates += 1;
242
+ // Tier 2 dedup: at most pass1Cap P1 calls per block, but
243
+ // typically the first candidate matches → average ~1 P1 call.
244
+ const expectedP1 = Math.min(candidates.length, Math.max(1, args.pass1Cap));
245
+ const avgP1 = Math.min(expectedP1, 2); // empirical avg
246
+ out.estimatedPass1Calls += avgP1;
247
+ out.estimatedPass2Calls += avgP1 * PASS2_FRACTION_ESTIMATE;
248
+ }
249
+ }
250
+ out.estimatedPass1Calls = Math.ceil(out.estimatedPass1Calls);
251
+ out.estimatedPass2Calls = Math.ceil(out.estimatedPass2Calls);
252
+ out.estimatedTokens =
253
+ out.estimatedPass1Calls * TOKENS_PER_PASS1_CALL +
254
+ out.estimatedPass2Calls * TOKENS_PER_PASS2_CALL +
255
+ out.estimatedCreationCalls * TOKENS_PER_CREATION_CALL;
256
+ return out;
257
+ }
258
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/fix-align/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Layer D operator-consent gates (plan §4.4 — items 4 + 9 in
3
+ * `/Users/user/cairn-v0.5.0-deferred.md`).
4
+ *
5
+ * `cairn fix align` runs a full-repo Haiku-judge sweep + strip-replace
6
+ * pass. It is expensive (Haiku spend) and destructive (rewrites source
7
+ * prose blocks). Two gates protect the operator:
8
+ *
9
+ * - Dry-run sentinel — `--dry-run` writes
10
+ * `.cairn/state/fix-align-dryrun.json` with `{ ts, repo_head_sha,
11
+ * args_hash }`. The next non-dry-run invocation must find a
12
+ * sentinel that is fresh (≤ 30 min), points at the current HEAD,
13
+ * and matches the same flag set. Mismatch aborts before any
14
+ * Haiku call.
15
+ *
16
+ * - Dirty-tree guard — before the apply phase, the CLI checks
17
+ * `git status --porcelain` for modified / staged paths that
18
+ * intersect the include globs. Hits abort with a message asking
19
+ * the operator to commit / stash, or pass `--force`.
20
+ *
21
+ * Both gates can be bypassed with `--force` for scripted contexts
22
+ * (CI, retroactive sweeps the operator has already vetted).
23
+ */
24
+ export declare function fixAlignSentinelPath(repoRoot: string): string;
25
+ export interface FixAlignSentinelArgs {
26
+ include: readonly string[];
27
+ exclude: readonly string[];
28
+ skipCreation: boolean;
29
+ maxCost: number | null;
30
+ }
31
+ /**
32
+ * Hash the apply-phase flags so a follow-up run with different
33
+ * `--include` / `--exclude` / `--no-creation` / `--max-cost` is
34
+ * detected as drift against the dry-run.
35
+ */
36
+ export declare function hashFixAlignArgs(args: FixAlignSentinelArgs): string;
37
+ export declare function readGitHeadSha(repoRoot: string): string | null;
38
+ export declare function writeFixAlignSentinel(repoRoot: string, args: FixAlignSentinelArgs): void;
39
+ export type SentinelValidation = {
40
+ ok: true;
41
+ } | {
42
+ ok: false;
43
+ reason: "missing" | "stale" | "head-drifted" | "args-drifted";
44
+ detail: string;
45
+ };
46
+ export declare function validateFixAlignSentinel(repoRoot: string, args: FixAlignSentinelArgs, now?: number): SentinelValidation;
47
+ export interface DirtyPath {
48
+ /** Repo-relative path. */
49
+ path: string;
50
+ /** Two-character porcelain status (e.g., ` M`, `??`, `MM`). */
51
+ status: string;
52
+ }
53
+ /**
54
+ * Return modified / staged / untracked paths inside `git status
55
+ * --porcelain` whose path matches one of the include globs (or every
56
+ * dirty path when include is empty — full-repo sweep). Returns an
57
+ * empty array when the repo isn't a git tree at all.
58
+ */
59
+ export declare function gitDirtyPathsInScope(repoRoot: string, includeGlobs: readonly string[]): DirtyPath[];
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Layer D operator-consent gates (plan §4.4 — items 4 + 9 in
3
+ * `/Users/user/cairn-v0.5.0-deferred.md`).
4
+ *
5
+ * `cairn fix align` runs a full-repo Haiku-judge sweep + strip-replace
6
+ * pass. It is expensive (Haiku spend) and destructive (rewrites source
7
+ * prose blocks). Two gates protect the operator:
8
+ *
9
+ * - Dry-run sentinel — `--dry-run` writes
10
+ * `.cairn/state/fix-align-dryrun.json` with `{ ts, repo_head_sha,
11
+ * args_hash }`. The next non-dry-run invocation must find a
12
+ * sentinel that is fresh (≤ 30 min), points at the current HEAD,
13
+ * and matches the same flag set. Mismatch aborts before any
14
+ * Haiku call.
15
+ *
16
+ * - Dirty-tree guard — before the apply phase, the CLI checks
17
+ * `git status --porcelain` for modified / staged paths that
18
+ * intersect the include globs. Hits abort with a message asking
19
+ * the operator to commit / stash, or pass `--force`.
20
+ *
21
+ * Both gates can be bypassed with `--force` for scripted contexts
22
+ * (CI, retroactive sweeps the operator has already vetted).
23
+ */
24
+ import { execFileSync } from "node:child_process";
25
+ import { createHash } from "node:crypto";
26
+ import { existsSync, readFileSync } from "node:fs";
27
+ import { join } from "node:path";
28
+ import { writeFileSafe } from "../fs.js";
29
+ import { matchAnyGlob } from "../ground/glob.js";
30
+ import { logger } from "../logger.js";
31
+ const log = logger("fix-align.sentinel");
32
+ /** Sentinel TTL — operators who walk away for half an hour pay the dry-run cost again. */
33
+ const SENTINEL_TTL_MS = 30 * 60 * 1000;
34
+ export function fixAlignSentinelPath(repoRoot) {
35
+ return join(repoRoot, ".cairn", "state", "fix-align-dryrun.json");
36
+ }
37
+ /**
38
+ * Hash the apply-phase flags so a follow-up run with different
39
+ * `--include` / `--exclude` / `--no-creation` / `--max-cost` is
40
+ * detected as drift against the dry-run.
41
+ */
42
+ export function hashFixAlignArgs(args) {
43
+ const normalized = {
44
+ include: [...args.include].sort(),
45
+ exclude: [...args.exclude].sort(),
46
+ skipCreation: args.skipCreation === true,
47
+ maxCost: args.maxCost ?? null,
48
+ };
49
+ return createHash("sha256")
50
+ .update(JSON.stringify(normalized), "utf8")
51
+ .digest("hex");
52
+ }
53
+ export function readGitHeadSha(repoRoot) {
54
+ try {
55
+ const out = execFileSync("git", ["rev-parse", "HEAD"], {
56
+ cwd: repoRoot,
57
+ encoding: "utf8",
58
+ stdio: ["ignore", "pipe", "ignore"],
59
+ });
60
+ return out.trim();
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ export function writeFixAlignSentinel(repoRoot, args) {
67
+ const data = {
68
+ ts: new Date().toISOString(),
69
+ repo_head_sha: readGitHeadSha(repoRoot),
70
+ args_hash: hashFixAlignArgs(args),
71
+ };
72
+ writeFileSafe(fixAlignSentinelPath(repoRoot), `${JSON.stringify(data, null, 2)}\n`);
73
+ }
74
+ export function validateFixAlignSentinel(repoRoot, args, now = Date.now()) {
75
+ const path = fixAlignSentinelPath(repoRoot);
76
+ if (!existsSync(path)) {
77
+ return { ok: false, reason: "missing", detail: "no prior --dry-run sentinel found" };
78
+ }
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(readFileSync(path, "utf8"));
82
+ }
83
+ catch (err) {
84
+ log.warn({ err: err instanceof Error ? err.message : String(err) }, "fix-align sentinel parse failed");
85
+ return { ok: false, reason: "missing", detail: "sentinel unreadable" };
86
+ }
87
+ const ts = Date.parse(parsed.ts ?? "");
88
+ if (!Number.isFinite(ts) || now - ts > SENTINEL_TTL_MS) {
89
+ return {
90
+ ok: false,
91
+ reason: "stale",
92
+ detail: `sentinel older than ${SENTINEL_TTL_MS / 60_000} minutes`,
93
+ };
94
+ }
95
+ const head = readGitHeadSha(repoRoot);
96
+ if (head !== null && parsed.repo_head_sha !== head) {
97
+ return {
98
+ ok: false,
99
+ reason: "head-drifted",
100
+ detail: `HEAD ${head.slice(0, 7)} differs from sentinel ${(parsed.repo_head_sha ?? "?").slice(0, 7)}`,
101
+ };
102
+ }
103
+ const expected = hashFixAlignArgs(args);
104
+ if (parsed.args_hash !== expected) {
105
+ return {
106
+ ok: false,
107
+ reason: "args-drifted",
108
+ detail: "flags differ from the --dry-run invocation",
109
+ };
110
+ }
111
+ return { ok: true };
112
+ }
113
+ /**
114
+ * Return modified / staged / untracked paths inside `git status
115
+ * --porcelain` whose path matches one of the include globs (or every
116
+ * dirty path when include is empty — full-repo sweep). Returns an
117
+ * empty array when the repo isn't a git tree at all.
118
+ */
119
+ export function gitDirtyPathsInScope(repoRoot, includeGlobs) {
120
+ let out;
121
+ try {
122
+ // `--untracked-files=all` so an untracked file inside an
123
+ // untracked directory shows up as a leaf path rather than the
124
+ // collapsed `?? src/` summary line.
125
+ out = execFileSync("git", ["status", "--porcelain", "--untracked-files=all"], {
126
+ cwd: repoRoot,
127
+ encoding: "utf8",
128
+ stdio: ["ignore", "pipe", "ignore"],
129
+ });
130
+ }
131
+ catch {
132
+ return [];
133
+ }
134
+ const dirty = [];
135
+ for (const line of out.split("\n")) {
136
+ if (line.length === 0)
137
+ continue;
138
+ const status = line.slice(0, 2);
139
+ // Porcelain v1 layout: `XY <path>` where the path starts at column 3.
140
+ const path = line.slice(3).trim();
141
+ if (path.length === 0)
142
+ continue;
143
+ if (includeGlobs.length > 0 && !matchAnyGlob(path, includeGlobs))
144
+ continue;
145
+ dirty.push({ path, status });
146
+ }
147
+ return dirty;
148
+ }
149
+ //# sourceMappingURL=sentinel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../../src/fix-align/sentinel.ts"],"names":[],"mappings":"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"}
package/dist/fs.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Write `data` to `path`, creating parent directories as needed.
3
+ * Encoding is always UTF-8.
4
+ */
5
+ export declare function writeFileSafe(path: string, data: string): void;
package/dist/fs.js ADDED
@@ -0,0 +1,11 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ /**
4
+ * Write `data` to `path`, creating parent directories as needed.
5
+ * Encoding is always UTF-8.
6
+ */
7
+ export function writeFileSafe(path, data) {
8
+ mkdirSync(dirname(path), { recursive: true });
9
+ writeFileSync(path, data, "utf8");
10
+ }
11
+ //# sourceMappingURL=fs.js.map
package/dist/fs.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,IAAY;IACtD,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC"}
package/dist/gc/apply.js CHANGED
@@ -8,8 +8,9 @@
8
8
  * No push happens here — the GC sweep + canary verification + push policy is
9
9
  * orchestrated by `runGcBatch` (sweep.ts).
10
10
  */
11
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
12
- import { dirname, resolve } from "node:path";
11
+ import { rmSync } from "node:fs";
12
+ import { resolve } from "node:path";
13
+ import { writeFileSafe } from "../fs.js";
13
14
  import { simpleGit } from "simple-git";
14
15
  export async function applyCommit(opts) {
15
16
  const written = [];
@@ -26,8 +27,7 @@ export async function applyCommit(opts) {
26
27
  }
27
28
  }
28
29
  else {
29
- mkdirSync(dirname(abs), { recursive: true });
30
- writeFileSync(abs, content, "utf8");
30
+ writeFileSafe(abs, content);
31
31
  written.push(rel);
32
32
  }
33
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/gc/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAgBvC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAwB;IACxD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACjE,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC7B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,6BAA6B;YAC/B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC7C,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAClD,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACnE,MAAM,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAElD,OAAO;QACL,UAAU,EAAE,GAAG;QACf,aAAa,EAAE,OAAO;QACtB,aAAa,EAAE,OAAO;KACvB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/gc/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAgBvC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAwB;IACxD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACjE,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC7B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,6BAA6B;YAC/B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAClD,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACnE,MAAM,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAElD,OAAO;QACL,UAAU,EAAE,GAAG;QACf,aAAa,EAAE,OAAO;QACtB,aAAa,EAAE,OAAO;KACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Alignment-pending queue writer — used by Layer A (PostToolUse hook,
3
+ * pass-2-still-ambiguous + tier3-ambiguous paths) and Layer C
4
+ * (SessionStart drain, Haiku-judge-ambiguous path).
5
+ *
6
+ * The cairn-attention skill renders the file with side-by-side prose
7
+ * and an AskUserQuestion four-option pick. The filename is a content
8
+ * fingerprint slug so re-running with the same prose is idempotent.
9
+ */
10
+ import type { CommentBlock } from "../init/source-comments/index.js";
11
+ export type AlignmentPendingKind = "tier2-ambiguous" | "tier3-ambiguous";
12
+ export interface WriteAlignmentPendingArgs {
13
+ repoRoot: string;
14
+ block: CommentBlock;
15
+ kind: AlignmentPendingKind;
16
+ /** Existing entity id (Tier 2 paths only). */
17
+ existingId?: string;
18
+ /** Existing entity body (Tier 2 paths only). */
19
+ existingBody?: string;
20
+ /**
21
+ * Detector tag stamped into frontmatter — distinguishes Layer A
22
+ * (`layer-a-pass2-ambiguous`) from Layer C (`layer-c-drain-ambiguous`).
23
+ * Defaults to the Layer A detector for back-compat with the existing
24
+ * `tier3-ambiguous` smoke fixtures.
25
+ */
26
+ detector?: string;
27
+ }
28
+ export declare function writeAlignmentPending(args: WriteAlignmentPendingArgs): string;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Alignment-pending queue writer — used by Layer A (PostToolUse hook,
3
+ * pass-2-still-ambiguous + tier3-ambiguous paths) and Layer C
4
+ * (SessionStart drain, Haiku-judge-ambiguous path).
5
+ *
6
+ * The cairn-attention skill renders the file with side-by-side prose
7
+ * and an AskUserQuestion four-option pick. The filename is a content
8
+ * fingerprint slug so re-running with the same prose is idempotent.
9
+ */
10
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { stringify as stringifyYaml } from "yaml";
13
+ import { alignmentPendingDir } from "./paths.js";
14
+ import { topicSlug } from "./slug.js";
15
+ export function writeAlignmentPending(args) {
16
+ const { repoRoot, block, kind } = args;
17
+ const detector = args.detector ?? "layer-a-pass2-ambiguous";
18
+ const dir = alignmentPendingDir(repoRoot);
19
+ mkdirSync(dir, { recursive: true });
20
+ const slug = topicSlug(block.prose);
21
+ const filename = `${slug}.md`;
22
+ const abs = join(dir, filename);
23
+ if (existsSync(abs))
24
+ return `.cairn/ground/alignment-pending/${filename}`;
25
+ const now = new Date().toISOString();
26
+ const fm = {
27
+ slug,
28
+ kind,
29
+ source_file: block.file,
30
+ source_range: `${block.startLine}-${block.endLine}`,
31
+ start_line: block.startLine,
32
+ end_line: block.endLine,
33
+ start_offset: block.startOffset,
34
+ end_offset: block.endOffset,
35
+ lang: block.lang,
36
+ raw: block.raw,
37
+ detected_at: now,
38
+ detector,
39
+ severity: "soft",
40
+ };
41
+ if (args.existingId !== undefined)
42
+ fm["existing_id"] = args.existingId;
43
+ const lines = [];
44
+ lines.push("---");
45
+ lines.push(stringifyYaml(fm).trimEnd());
46
+ lines.push("---");
47
+ lines.push("");
48
+ lines.push(`# Alignment pending — ${slug} (${kind})`);
49
+ lines.push("");
50
+ lines.push(`## Block (just written at \`${block.file}:${block.startLine}-${block.endLine}\`)`);
51
+ lines.push("");
52
+ lines.push("```");
53
+ lines.push(block.prose.trim());
54
+ lines.push("```");
55
+ lines.push("");
56
+ if (args.existingId !== undefined && args.existingBody !== undefined) {
57
+ lines.push(`## Existing ${args.existingId}`);
58
+ lines.push("");
59
+ lines.push("```");
60
+ lines.push(args.existingBody.trim());
61
+ lines.push("```");
62
+ lines.push("");
63
+ }
64
+ lines.push("## How to resolve");
65
+ lines.push("");
66
+ if (kind === "tier2-ambiguous") {
67
+ lines.push("Pass 2 dedup judge returned ambiguous. cairn-attention will surface");
68
+ lines.push("a side-by-side render with `[a] same` / `[b] different` /");
69
+ lines.push("`[c] augments` / `[d] leave for later` choices via");
70
+ lines.push("`cairn_resolve_attention({ kind: \"alignment_pending\", ... })`.");
71
+ }
72
+ else {
73
+ lines.push("Pass 2 creation judge could not classify the block as");
74
+ lines.push("decision / constraint / descriptive. cairn-attention will");
75
+ lines.push("surface `[a] decision` / `[b] constraint` / `[c] descriptive` /");
76
+ lines.push("`[d] leave for later` via");
77
+ lines.push("`cairn_resolve_attention({ kind: \"alignment_pending\", ... })`.");
78
+ }
79
+ lines.push("");
80
+ writeFileSync(abs, lines.join("\n"), "utf8");
81
+ return `.cairn/ground/alignment-pending/${filename}`;
82
+ }
83
+ //# sourceMappingURL=alignment-pending.js.map