@cleocode/core 2026.8.8 → 2026.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conduit/conduit-client.d.ts.map +1 -1
- package/dist/conduit/conduit-client.js +10 -2
- package/dist/conduit/conduit-client.js.map +1 -1
- package/dist/dispatch/mvi-projection.d.ts +38 -0
- package/dist/dispatch/mvi-projection.d.ts.map +1 -1
- package/dist/dispatch/mvi-projection.js +189 -11
- package/dist/dispatch/mvi-projection.js.map +1 -1
- package/dist/docs/export-document.js +41 -3
- package/dist/docs/export-document.js.map +2 -2
- package/dist/doctor/acceptance-drift.d.ts +258 -0
- package/dist/doctor/acceptance-drift.d.ts.map +1 -0
- package/dist/doctor/acceptance-drift.js +366 -0
- package/dist/doctor/acceptance-drift.js.map +1 -0
- package/dist/doctor/index.d.ts +4 -0
- package/dist/doctor/index.d.ts.map +1 -1
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/legacy-reaper.d.ts +105 -0
- package/dist/doctor/legacy-reaper.d.ts.map +1 -0
- package/dist/doctor/legacy-reaper.js +151 -0
- package/dist/doctor/legacy-reaper.js.map +1 -0
- package/dist/doctor/malformed-task-ids.d.ts +111 -0
- package/dist/doctor/malformed-task-ids.d.ts.map +1 -0
- package/dist/doctor/malformed-task-ids.js +139 -0
- package/dist/doctor/malformed-task-ids.js.map +1 -0
- package/dist/doctor/nexus-schema-residency.d.ts +115 -0
- package/dist/doctor/nexus-schema-residency.d.ts.map +1 -0
- package/dist/doctor/nexus-schema-residency.js +178 -0
- package/dist/doctor/nexus-schema-residency.js.map +1 -0
- package/dist/error-registry.d.ts.map +1 -1
- package/dist/error-registry.js +18 -0
- package/dist/error-registry.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/llm/ai-sdk-warnings.d.ts +55 -0
- package/dist/llm/ai-sdk-warnings.d.ts.map +1 -0
- package/dist/llm/ai-sdk-warnings.js +83 -0
- package/dist/llm/ai-sdk-warnings.js.map +1 -0
- package/dist/llm/model-runner.d.ts +0 -8
- package/dist/llm/model-runner.d.ts.map +1 -1
- package/dist/llm/model-runner.js +8 -0
- package/dist/llm/model-runner.js.map +1 -1
- package/dist/llm/plugin-facade.js +73 -8
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/embedding-queue.d.ts +12 -0
- package/dist/memory/embedding-queue.d.ts.map +1 -1
- package/dist/memory/embedding-queue.js +39 -5
- package/dist/memory/embedding-queue.js.map +1 -1
- package/dist/orchestrate/worktree-complete.d.ts.map +1 -1
- package/dist/orchestrate/worktree-complete.js +26 -0
- package/dist/orchestrate/worktree-complete.js.map +1 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +25 -0
- package/dist/output.js.map +1 -1
- package/dist/release/pr-evidence.d.ts +13 -0
- package/dist/release/pr-evidence.d.ts.map +1 -1
- package/dist/release/pr-evidence.js +54 -1
- package/dist/release/pr-evidence.js.map +1 -1
- package/dist/render/session/briefing.d.ts.map +1 -1
- package/dist/render/session/briefing.js +9 -0
- package/dist/render/session/briefing.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +73 -8
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sessions/briefing.d.ts +13 -0
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +64 -7
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/handoff.d.ts +32 -4
- package/dist/sessions/handoff.d.ts.map +1 -1
- package/dist/sessions/handoff.js +7 -7
- package/dist/sessions/handoff.js.map +1 -1
- package/dist/shutdown-deadline.d.ts +144 -0
- package/dist/shutdown-deadline.d.ts.map +1 -0
- package/dist/shutdown-deadline.js +187 -0
- package/dist/shutdown-deadline.js.map +1 -0
- package/dist/shutdown.d.ts +5 -2
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +19 -14
- package/dist/shutdown.js.map +1 -1
- package/dist/spawn/branch-lock.d.ts.map +1 -1
- package/dist/spawn/branch-lock.js +33 -0
- package/dist/spawn/branch-lock.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +14 -0
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/tasks-sqlite.d.ts.map +1 -1
- package/dist/store/tasks-sqlite.js +16 -1
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/tasks/ac-immutability.d.ts.map +1 -1
- package/dist/tasks/ac-immutability.js +36 -0
- package/dist/tasks/ac-immutability.js.map +1 -1
- package/dist/tasks/add.d.ts +18 -0
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +28 -5
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/axis-filters.d.ts +53 -0
- package/dist/tasks/axis-filters.d.ts.map +1 -0
- package/dist/tasks/axis-filters.js +77 -0
- package/dist/tasks/axis-filters.js.map +1 -0
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +10 -1
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/duplicate-detector.d.ts +67 -14
- package/dist/tasks/duplicate-detector.d.ts.map +1 -1
- package/dist/tasks/duplicate-detector.js +197 -54
- package/dist/tasks/duplicate-detector.js.map +1 -1
- package/dist/tasks/evidence.d.ts +139 -0
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +294 -25
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/find.d.ts.map +1 -1
- package/dist/tasks/find.js +9 -2
- package/dist/tasks/find.js.map +1 -1
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +25 -4
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/heavy-tool-env.d.ts +20 -0
- package/dist/tasks/heavy-tool-env.d.ts.map +1 -1
- package/dist/tasks/heavy-tool-env.js +74 -4
- package/dist/tasks/heavy-tool-env.js.map +1 -1
- package/dist/tasks/heavy-tool-limit.d.ts +134 -0
- package/dist/tasks/heavy-tool-limit.d.ts.map +1 -0
- package/dist/tasks/heavy-tool-limit.js +196 -0
- package/dist/tasks/heavy-tool-limit.js.map +1 -0
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/tasks/index.js.map +1 -1
- package/dist/tasks/infer-add-params.d.ts +28 -0
- package/dist/tasks/infer-add-params.d.ts.map +1 -1
- package/dist/tasks/infer-add-params.js +96 -6
- package/dist/tasks/infer-add-params.js.map +1 -1
- package/dist/tasks/list.d.ts +30 -1
- package/dist/tasks/list.d.ts.map +1 -1
- package/dist/tasks/list.js +14 -0
- package/dist/tasks/list.js.map +1 -1
- package/dist/tasks/ops.d.ts +4 -0
- package/dist/tasks/ops.d.ts.map +1 -1
- package/dist/tasks/ops.js +2 -0
- package/dist/tasks/ops.js.map +1 -1
- package/dist/tasks/session-scope.d.ts +51 -4
- package/dist/tasks/session-scope.d.ts.map +1 -1
- package/dist/tasks/session-scope.js +68 -10
- package/dist/tasks/session-scope.js.map +1 -1
- package/dist/tasks/tool-cache.d.ts +116 -9
- package/dist/tasks/tool-cache.d.ts.map +1 -1
- package/dist/tasks/tool-cache.js +127 -16
- package/dist/tasks/tool-cache.js.map +1 -1
- package/dist/tasks/tool-semaphore.d.ts +91 -0
- package/dist/tasks/tool-semaphore.d.ts.map +1 -1
- package/dist/tasks/tool-semaphore.js +203 -14
- package/dist/tasks/tool-semaphore.js.map +1 -1
- package/dist/validation/doctor/checks.d.ts +64 -0
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/dist/validation/doctor/checks.js +249 -0
- package/dist/validation/doctor/checks.js.map +1 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +41 -5
- package/dist/validation/engine-ops.js.map +1 -1
- package/package.json +13 -13
- package/templates/CLEO-INJECTION.md +28 -44
- package/dist/backfill/adr-backfill-walker.d.ts +0 -57
- package/dist/backfill/adr-backfill-walker.d.ts.map +0 -1
- package/dist/backfill/adr-backfill-walker.js +0 -631
- package/dist/backfill/adr-backfill-walker.js.map +0 -1
- package/dist/tasks/graph-rag.d.ts +0 -49
- package/dist/tasks/graph-rag.d.ts.map +0 -1
- package/dist/tasks/graph-rag.js +0 -328
- package/dist/tasks/graph-rag.js.map +0 -1
|
@@ -1,631 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ADR Backfill Walker — T1829
|
|
3
|
-
*
|
|
4
|
-
* One-time idempotent script that parses every `.cleo/adrs/*.md` (and the 9
|
|
5
|
-
* unique ADRs in `docs/adr/`) and creates a `brain_decisions` row for each one.
|
|
6
|
-
*
|
|
7
|
-
* ## Usage
|
|
8
|
-
*
|
|
9
|
-
* ```bash
|
|
10
|
-
* # Dry-run (no writes):
|
|
11
|
-
* node packages/core/dist/tools/adr-backfill-walker.js --dry-run
|
|
12
|
-
*
|
|
13
|
-
* # Apply (writes to brain.db):
|
|
14
|
-
* node packages/core/dist/tools/adr-backfill-walker.js --apply
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* ## Idempotency
|
|
18
|
-
*
|
|
19
|
-
* Rows are skipped when an existing `brain_decisions` row already has
|
|
20
|
-
* `adrNumber` populated for that ADR number. The check is transactional:
|
|
21
|
-
* `SELECT … WHERE adr_number = ?`.
|
|
22
|
-
*
|
|
23
|
-
* ## Collisions (ADR-051..054)
|
|
24
|
-
*
|
|
25
|
-
* The four ADR numbers 051–054 each have two conflicting files in two
|
|
26
|
-
* different directories. This walker uses `.cleo/adrs/` as the canonical
|
|
27
|
-
* SSoT (per Epsilon audit decision) and SKIPS the `docs/adr/` versions.
|
|
28
|
-
* A note is written to the report for each skipped collision.
|
|
29
|
-
*
|
|
30
|
-
* ## Duplicates (ADR-031/033, ADR-032/034)
|
|
31
|
-
*
|
|
32
|
-
* ADR-033 duplicates ADR-031 and ADR-034 duplicates ADR-032. The first
|
|
33
|
-
* number is kept as canonical; the second is inserted with
|
|
34
|
-
* `supersededBy = <first>` and `confirmationState = 'superseded'`.
|
|
35
|
-
*
|
|
36
|
-
* @task T1829
|
|
37
|
-
* @epic T1824
|
|
38
|
-
*/
|
|
39
|
-
import { existsSync } from 'node:fs';
|
|
40
|
-
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
41
|
-
import { basename, join, relative } from 'node:path';
|
|
42
|
-
// ─── Config ───────────────────────────────────────────────────────────────────
|
|
43
|
-
/**
|
|
44
|
-
* ADR numbers in the collision zone (051–054).
|
|
45
|
-
* `.cleo/adrs/` versions are canonical; `docs/adr/` versions are skipped.
|
|
46
|
-
*/
|
|
47
|
-
const COLLISION_ADR_NUMBERS = new Set([51, 52, 53, 54]);
|
|
48
|
-
/**
|
|
49
|
-
* Exact-duplicate pair map: secondary ADR → canonical ADR.
|
|
50
|
-
* The secondary ADR will be inserted with `supersededBy = canonical`.
|
|
51
|
-
*/
|
|
52
|
-
const DUPLICATE_PAIRS = {
|
|
53
|
-
33: 31, // ADR-033 duplicates ADR-031
|
|
54
|
-
34: 32, // ADR-034 duplicates ADR-032
|
|
55
|
-
};
|
|
56
|
-
/**
|
|
57
|
-
* Extract ADR number from a filename like `ADR-031-foo.md`.
|
|
58
|
-
* Returns -1 if no match.
|
|
59
|
-
*/
|
|
60
|
-
function extractAdrNumberFromFilename(filename) {
|
|
61
|
-
const m = filename.match(/^ADR-(\d{3})/i);
|
|
62
|
-
return m ? parseInt(m[1], 10) : -1;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Parse the frontmatter and first heading of an ADR markdown file.
|
|
66
|
-
*
|
|
67
|
-
* Supports two formats:
|
|
68
|
-
* 1. YAML frontmatter (---…---)
|
|
69
|
-
* 2. Markdown bold-field format (**Status**: …)
|
|
70
|
-
*
|
|
71
|
-
* Falls back to sensible defaults when fields are absent.
|
|
72
|
-
*/
|
|
73
|
-
function parseAdrFrontmatter(content, filename) {
|
|
74
|
-
const adrNumber = extractAdrNumberFromFilename(filename);
|
|
75
|
-
let title = filename.replace(/\.md$/, '');
|
|
76
|
-
let status = 'accepted';
|
|
77
|
-
let date;
|
|
78
|
-
let supersedesAdrNumber;
|
|
79
|
-
let supersededByAdrNumber;
|
|
80
|
-
// ── YAML frontmatter (--- ... ---) ──────────────────────────────────────────
|
|
81
|
-
const yamlMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
82
|
-
if (yamlMatch) {
|
|
83
|
-
const yamlBlock = yamlMatch[1];
|
|
84
|
-
// title
|
|
85
|
-
const titleLine = yamlBlock.match(/^title:\s*(.+)$/m);
|
|
86
|
-
if (titleLine)
|
|
87
|
-
title = titleLine[1].trim().replace(/^['"]|['"]$/g, '');
|
|
88
|
-
// status
|
|
89
|
-
const statusLine = yamlBlock.match(/^status:\s*(.+)$/m);
|
|
90
|
-
if (statusLine)
|
|
91
|
-
status = statusLine[1].trim().toLowerCase();
|
|
92
|
-
// date
|
|
93
|
-
const dateLine = yamlBlock.match(/^(?:created|date|updated):\s*(.+)$/m);
|
|
94
|
-
if (dateLine)
|
|
95
|
-
date = dateLine[1].trim();
|
|
96
|
-
// supersedes: ADR-NNN or just NNN
|
|
97
|
-
const supersedesLine = yamlBlock.match(/^supersedes:\s*(.+)$/im);
|
|
98
|
-
if (supersedesLine) {
|
|
99
|
-
const val = supersedesLine[1].trim();
|
|
100
|
-
if (val !== '~' && val !== 'null' && val !== '') {
|
|
101
|
-
const m = val.match(/(\d{3})/);
|
|
102
|
-
if (m)
|
|
103
|
-
supersedesAdrNumber = parseInt(m[1], 10);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// superseded-by
|
|
107
|
-
const supersededByLine = yamlBlock.match(/^superseded.by:\s*(.+)$/im);
|
|
108
|
-
if (supersededByLine) {
|
|
109
|
-
const val = supersededByLine[1].trim();
|
|
110
|
-
if (val !== '~' && val !== 'null' && val !== '') {
|
|
111
|
-
const m = val.match(/(\d{3})/);
|
|
112
|
-
if (m)
|
|
113
|
-
supersededByAdrNumber = parseInt(m[1], 10);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// ── Markdown heading extraction (after possible YAML block) ─────────────────
|
|
118
|
-
// Find first H1 or H2 heading for a better title
|
|
119
|
-
const headingMatch = content.match(/^#+\s+(.+)$/m);
|
|
120
|
-
if (headingMatch && (title === filename.replace(/\.md$/, '') || yamlMatch)) {
|
|
121
|
-
// Only override with heading if we got a title from YAML that looks synthetic,
|
|
122
|
-
// or if there was no YAML title. Use heading as secondary source.
|
|
123
|
-
if (!yamlMatch?.toString().includes('title:')) {
|
|
124
|
-
title = headingMatch[1].trim();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// ── Markdown bold-field format: **Status**: ... ──────────────────────────────
|
|
128
|
-
if (!yamlMatch) {
|
|
129
|
-
// status
|
|
130
|
-
const boldStatus = content.match(/\*\*[Ss]tatus\*\*\s*:?\s*(.+?)(?:\n|$)/m);
|
|
131
|
-
if (boldStatus) {
|
|
132
|
-
// Strip markdown formatting and extra notes
|
|
133
|
-
status = boldStatus[1].replace(/\*+/g, '').split(/[·(,]/)[0].trim().toLowerCase();
|
|
134
|
-
}
|
|
135
|
-
// date
|
|
136
|
-
const boldDate = content.match(/\*\*[Dd]ate\*\*\s*:?\s*(.+?)(?:\n|$)/m);
|
|
137
|
-
if (boldDate)
|
|
138
|
-
date = boldDate[1].replace(/\*+/g, '').trim();
|
|
139
|
-
// supersedes (prose / bold)
|
|
140
|
-
const supersedesMatch = content.match(/[Ss]upersedes[:\s]+(?:ADR-)?(\d{3})/g);
|
|
141
|
-
if (supersedesMatch) {
|
|
142
|
-
const m = supersedesMatch[0].match(/(\d{3})/);
|
|
143
|
-
if (m)
|
|
144
|
-
supersedesAdrNumber = parseInt(m[1], 10);
|
|
145
|
-
}
|
|
146
|
-
// superseded-by
|
|
147
|
-
const supersededByMatch = content.match(/[Ss]uperseded[- ][Bb]y[:\s]+(?:ADR-)?(\d{3})/g);
|
|
148
|
-
if (supersededByMatch) {
|
|
149
|
-
const m = supersededByMatch[0].match(/(\d{3})/);
|
|
150
|
-
if (m)
|
|
151
|
-
supersededByAdrNumber = parseInt(m[1], 10);
|
|
152
|
-
}
|
|
153
|
-
// Get title from first heading
|
|
154
|
-
const h1 = content.match(/^#\s+(.+)$/m);
|
|
155
|
-
const h2 = content.match(/^##\s+(.+)$/m);
|
|
156
|
-
if (h1)
|
|
157
|
-
title = h1[1].trim();
|
|
158
|
-
else if (h2)
|
|
159
|
-
title = h2[1].trim();
|
|
160
|
-
}
|
|
161
|
-
// Normalise status
|
|
162
|
-
const statusLower = status.toLowerCase();
|
|
163
|
-
if (statusLower.includes('accept'))
|
|
164
|
-
status = 'accepted';
|
|
165
|
-
else if (statusLower.includes('supersed'))
|
|
166
|
-
status = 'superseded';
|
|
167
|
-
else if (statusLower.includes('reject'))
|
|
168
|
-
status = 'rejected';
|
|
169
|
-
else if (statusLower.includes('archive'))
|
|
170
|
-
status = 'archived';
|
|
171
|
-
else if (statusLower.includes('draft'))
|
|
172
|
-
status = 'draft';
|
|
173
|
-
else if (statusLower.includes('propos'))
|
|
174
|
-
status = 'proposed';
|
|
175
|
-
else
|
|
176
|
-
status = 'accepted'; // default for established ADRs
|
|
177
|
-
return { adrNumber, title, status, date, supersedesAdrNumber, supersededByAdrNumber };
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Build a concise decision text and rationale from an ADR.
|
|
181
|
-
* Uses the title + first non-heading paragraph from the body as rationale.
|
|
182
|
-
*/
|
|
183
|
-
function buildDecisionText(title, content) {
|
|
184
|
-
const decision = title.slice(0, 250);
|
|
185
|
-
// Strip YAML frontmatter if present
|
|
186
|
-
let body = content.replace(/^---[\s\S]*?---\r?\n/, '');
|
|
187
|
-
// Strip headings
|
|
188
|
-
body = body.replace(/^#+\s+.+$/gm, '').trim();
|
|
189
|
-
// Strip bold-field metadata lines like **Status**: ...
|
|
190
|
-
body = body.replace(/^\*\*[^*]+\*\*\s*:.*$/gm, '').trim();
|
|
191
|
-
// Strip horizontal rules
|
|
192
|
-
body = body.replace(/^---+$/gm, '').trim();
|
|
193
|
-
// Find first non-empty paragraph
|
|
194
|
-
const paragraphs = body
|
|
195
|
-
.split(/\n{2,}/)
|
|
196
|
-
.map((p) => p.trim())
|
|
197
|
-
.filter(Boolean);
|
|
198
|
-
const rationale = paragraphs.length > 0
|
|
199
|
-
? paragraphs[0].replace(/\n/g, ' ').replace(/\s+/g, ' ').slice(0, 500)
|
|
200
|
-
: `ADR file: ${title}`;
|
|
201
|
-
return { decision, rationale: rationale || `Architectural decision: ${title}` };
|
|
202
|
-
}
|
|
203
|
-
// ─── Database helpers ─────────────────────────────────────────────────────────
|
|
204
|
-
/** Check if a brain_decisions row with this adrNumber already exists. */
|
|
205
|
-
async function rowExistsForAdrNumber(projectRoot, adrNumber) {
|
|
206
|
-
const { getBrainDb } = await import('../store/memory-sqlite.js');
|
|
207
|
-
const { brainDecisions } = await import('../store/schema/memory-schema.js');
|
|
208
|
-
const { eq } = await import('drizzle-orm');
|
|
209
|
-
const db = await getBrainDb(projectRoot);
|
|
210
|
-
const rows = await db
|
|
211
|
-
.select({ id: brainDecisions.id })
|
|
212
|
-
.from(brainDecisions)
|
|
213
|
-
.where(eq(brainDecisions.adrNumber, adrNumber))
|
|
214
|
-
.limit(1);
|
|
215
|
-
return rows.length > 0 ? { exists: true, rowId: rows[0].id } : { exists: false };
|
|
216
|
-
}
|
|
217
|
-
/** Get the next available decision ID (D001, D002, …). */
|
|
218
|
-
async function nextDecisionId(projectRoot) {
|
|
219
|
-
const { getBrainDb } = await import('../store/memory-sqlite.js');
|
|
220
|
-
const { brainDecisions } = await import('../store/schema/memory-schema.js');
|
|
221
|
-
const { desc } = await import('drizzle-orm');
|
|
222
|
-
const db = await getBrainDb(projectRoot);
|
|
223
|
-
const rows = await db
|
|
224
|
-
.select({ id: brainDecisions.id })
|
|
225
|
-
.from(brainDecisions)
|
|
226
|
-
.orderBy(desc(brainDecisions.id))
|
|
227
|
-
.limit(1);
|
|
228
|
-
if (rows.length === 0)
|
|
229
|
-
return 'D001';
|
|
230
|
-
const last = rows[0].id;
|
|
231
|
-
const num = parseInt(last.replace(/\D/g, ''), 10);
|
|
232
|
-
return Number.isNaN(num) ? 'D001' : `D${String(num + 1).padStart(3, '0')}`;
|
|
233
|
-
}
|
|
234
|
-
/** Resolve the brain_decisions ID for a given ADR number (used for supersedure links). */
|
|
235
|
-
async function decisionIdForAdrNumber(projectRoot, adrNumber) {
|
|
236
|
-
const { getBrainDb } = await import('../store/memory-sqlite.js');
|
|
237
|
-
const { brainDecisions } = await import('../store/schema/memory-schema.js');
|
|
238
|
-
const { eq } = await import('drizzle-orm');
|
|
239
|
-
const db = await getBrainDb(projectRoot);
|
|
240
|
-
const rows = await db
|
|
241
|
-
.select({ id: brainDecisions.id })
|
|
242
|
-
.from(brainDecisions)
|
|
243
|
-
.where(eq(brainDecisions.adrNumber, adrNumber))
|
|
244
|
-
.limit(1);
|
|
245
|
-
return rows.length > 0 ? rows[0].id : null;
|
|
246
|
-
}
|
|
247
|
-
/** Write a brain_decisions row directly via Drizzle (bypasses storeDecision gate pipeline). */
|
|
248
|
-
async function insertAdrDecisionRow(projectRoot, opts) {
|
|
249
|
-
const { getBrainDb } = await import('../store/memory-sqlite.js');
|
|
250
|
-
const { brainDecisions } = await import('../store/schema/memory-schema.js');
|
|
251
|
-
const db = await getBrainDb(projectRoot);
|
|
252
|
-
const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
253
|
-
await db.insert(brainDecisions).values({
|
|
254
|
-
id: opts.id,
|
|
255
|
-
type: 'architecture',
|
|
256
|
-
decision: opts.decision,
|
|
257
|
-
rationale: opts.rationale,
|
|
258
|
-
confidence: 'high',
|
|
259
|
-
outcome: 'success',
|
|
260
|
-
qualityScore: 0.85,
|
|
261
|
-
memoryTier: 'long',
|
|
262
|
-
memoryType: 'semantic',
|
|
263
|
-
sourceConfidence: 'owner',
|
|
264
|
-
verified: true,
|
|
265
|
-
contentHash: opts.contentHash,
|
|
266
|
-
adrNumber: opts.adrNumber,
|
|
267
|
-
adrPath: opts.adrPath,
|
|
268
|
-
supersedes: opts.supersedes,
|
|
269
|
-
supersededBy: opts.supersededBy,
|
|
270
|
-
confirmationState: opts.confirmationState,
|
|
271
|
-
decidedBy: opts.decidedBy,
|
|
272
|
-
createdAt: now,
|
|
273
|
-
updatedAt: now,
|
|
274
|
-
provenanceClass: 'swept-clean',
|
|
275
|
-
peerId: 'adr-backfill-walker',
|
|
276
|
-
peerScope: 'project',
|
|
277
|
-
decisionCategory: 'architectural',
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
// ─── Main walker ──────────────────────────────────────────────────────────────
|
|
281
|
-
/**
|
|
282
|
-
* Main walker function.
|
|
283
|
-
*
|
|
284
|
-
* @param projectRoot - Absolute path to the project root.
|
|
285
|
-
* @param dryRun - When true, no writes are performed.
|
|
286
|
-
* @returns Array of results per ADR file processed.
|
|
287
|
-
*/
|
|
288
|
-
export async function runAdrBackfillWalker(projectRoot, dryRun) {
|
|
289
|
-
const results = [];
|
|
290
|
-
const cleoCanonicalsDir = join(projectRoot, '.cleo', 'adrs');
|
|
291
|
-
const docsAdrDir = join(projectRoot, 'docs', 'adr');
|
|
292
|
-
// ── Collect all ADR files ─────────────────────────────────────────────────
|
|
293
|
-
const allFiles = [];
|
|
294
|
-
// Primary: .cleo/adrs/
|
|
295
|
-
const cleoFiles = await readdir(cleoCanonicalsDir).catch(() => []);
|
|
296
|
-
for (const f of cleoFiles) {
|
|
297
|
-
if (f.endsWith('.md') && /^ADR-\d{3}/i.test(f)) {
|
|
298
|
-
allFiles.push({ dir: cleoCanonicalsDir, filename: f, source: 'cleo' });
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
// Secondary: docs/adr/ — only include ADR numbers NOT already in .cleo/adrs/
|
|
302
|
-
const cleoAdrNumbers = new Set(cleoFiles.map((f) => extractAdrNumberFromFilename(f)).filter((n) => n > 0));
|
|
303
|
-
if (existsSync(docsAdrDir)) {
|
|
304
|
-
const docsFiles = await readdir(docsAdrDir).catch(() => []);
|
|
305
|
-
for (const f of docsFiles) {
|
|
306
|
-
if (!f.endsWith('.md'))
|
|
307
|
-
continue;
|
|
308
|
-
const n = extractAdrNumberFromFilename(f);
|
|
309
|
-
if (n <= 0)
|
|
310
|
-
continue; // skip unnumbered drafts
|
|
311
|
-
if (COLLISION_ADR_NUMBERS.has(n)) {
|
|
312
|
-
// Collision: docs/adr/ version skipped; .cleo/adrs/ is canonical
|
|
313
|
-
results.push({
|
|
314
|
-
adrNumber: n,
|
|
315
|
-
filename: f,
|
|
316
|
-
filePath: join(docsAdrDir, f),
|
|
317
|
-
action: 'skipped-collision',
|
|
318
|
-
note: `ADR-${String(n).padStart(3, '0')} collision: docs/adr/ version skipped. .cleo/adrs/ is canonical. HITL needed before merging.`,
|
|
319
|
-
});
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
if (!cleoAdrNumbers.has(n)) {
|
|
323
|
-
allFiles.push({ dir: docsAdrDir, filename: f, source: 'docs' });
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
// Sort by ADR number for deterministic order
|
|
328
|
-
allFiles.sort((a, b) => {
|
|
329
|
-
const na = extractAdrNumberFromFilename(a.filename);
|
|
330
|
-
const nb = extractAdrNumberFromFilename(b.filename);
|
|
331
|
-
return na - nb;
|
|
332
|
-
});
|
|
333
|
-
// ── Process each ADR file ─────────────────────────────────────────────────
|
|
334
|
-
for (const { dir, filename } of allFiles) {
|
|
335
|
-
const filePath = join(dir, filename);
|
|
336
|
-
const adrNumber = extractAdrNumberFromFilename(filename);
|
|
337
|
-
const relPath = relative(projectRoot, filePath).replace(/\\/g, '/');
|
|
338
|
-
let content;
|
|
339
|
-
try {
|
|
340
|
-
content = await readFile(filePath, 'utf8');
|
|
341
|
-
}
|
|
342
|
-
catch (err) {
|
|
343
|
-
results.push({
|
|
344
|
-
adrNumber,
|
|
345
|
-
filename,
|
|
346
|
-
filePath,
|
|
347
|
-
action: 'skipped-error',
|
|
348
|
-
note: `Could not read file: ${String(err)}`,
|
|
349
|
-
});
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
const fm = parseAdrFrontmatter(content, filename);
|
|
353
|
-
const { decision, rationale } = buildDecisionText(fm.title, content);
|
|
354
|
-
// Idempotency check: skip if row with this adrNumber already exists
|
|
355
|
-
const existing = await rowExistsForAdrNumber(projectRoot, adrNumber);
|
|
356
|
-
if (existing.exists) {
|
|
357
|
-
results.push({
|
|
358
|
-
adrNumber,
|
|
359
|
-
filename,
|
|
360
|
-
filePath,
|
|
361
|
-
action: 'skipped-exists',
|
|
362
|
-
note: `brain_decisions row ${existing.rowId} already has adrNumber=${adrNumber}`,
|
|
363
|
-
decisionId: existing.rowId,
|
|
364
|
-
});
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
// Determine confirmation state
|
|
368
|
-
let confirmationState = 'accepted';
|
|
369
|
-
if (fm.status === 'superseded' || fm.status === 'archived' || fm.status === 'rejected') {
|
|
370
|
-
confirmationState = 'superseded';
|
|
371
|
-
}
|
|
372
|
-
else if (fm.status === 'proposed' || fm.status === 'draft') {
|
|
373
|
-
confirmationState = 'proposed';
|
|
374
|
-
}
|
|
375
|
-
// Check if this is a known duplicate (secondary of a pair)
|
|
376
|
-
const canonicalAdrNumber = DUPLICATE_PAIRS[adrNumber];
|
|
377
|
-
let supersededByDecisionId;
|
|
378
|
-
let supersededByNote;
|
|
379
|
-
if (canonicalAdrNumber !== undefined) {
|
|
380
|
-
confirmationState = 'superseded';
|
|
381
|
-
supersededByNote = `Duplicate of ADR-${String(canonicalAdrNumber).padStart(3, '0')} — marked superseded`;
|
|
382
|
-
if (!dryRun) {
|
|
383
|
-
supersededByDecisionId =
|
|
384
|
-
(await decisionIdForAdrNumber(projectRoot, canonicalAdrNumber)) ?? undefined;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
// Resolve supersedes link from frontmatter
|
|
388
|
-
let supersedesDecisionId;
|
|
389
|
-
if (fm.supersedesAdrNumber !== undefined && !dryRun) {
|
|
390
|
-
supersedesDecisionId =
|
|
391
|
-
(await decisionIdForAdrNumber(projectRoot, fm.supersedesAdrNumber)) ?? undefined;
|
|
392
|
-
}
|
|
393
|
-
const { createHash } = await import('node:crypto');
|
|
394
|
-
const contentHash = createHash('sha256')
|
|
395
|
-
.update((decision + '\n' + rationale).toLowerCase())
|
|
396
|
-
.digest('hex')
|
|
397
|
-
.slice(0, 16);
|
|
398
|
-
if (dryRun) {
|
|
399
|
-
results.push({
|
|
400
|
-
adrNumber,
|
|
401
|
-
filename,
|
|
402
|
-
filePath,
|
|
403
|
-
action: 'inserted',
|
|
404
|
-
note: `[DRY-RUN] Would insert: decision="${decision.slice(0, 80)}…" status=${confirmationState} path=${relPath}${supersededByNote ? ` note=${supersededByNote}` : ''}`,
|
|
405
|
-
supersedureTo: canonicalAdrNumber
|
|
406
|
-
? `supersededBy ADR-${String(canonicalAdrNumber).padStart(3, '0')}`
|
|
407
|
-
: undefined,
|
|
408
|
-
});
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
// Live insert
|
|
412
|
-
try {
|
|
413
|
-
const id = await nextDecisionId(projectRoot);
|
|
414
|
-
await insertAdrDecisionRow(projectRoot, {
|
|
415
|
-
id,
|
|
416
|
-
adrNumber,
|
|
417
|
-
adrPath: relPath,
|
|
418
|
-
decision,
|
|
419
|
-
rationale,
|
|
420
|
-
confirmationState,
|
|
421
|
-
decidedBy: 'owner',
|
|
422
|
-
supersedes: supersedesDecisionId,
|
|
423
|
-
supersededBy: supersededByDecisionId,
|
|
424
|
-
contentHash,
|
|
425
|
-
});
|
|
426
|
-
results.push({
|
|
427
|
-
adrNumber,
|
|
428
|
-
filename,
|
|
429
|
-
filePath,
|
|
430
|
-
action: 'inserted',
|
|
431
|
-
note: `Inserted as ${id} (adrNumber=${adrNumber}, state=${confirmationState})${supersededByNote ? ` — ${supersededByNote}` : ''}`,
|
|
432
|
-
decisionId: id,
|
|
433
|
-
supersedureTo: canonicalAdrNumber
|
|
434
|
-
? `supersededBy ADR-${String(canonicalAdrNumber).padStart(3, '0')}`
|
|
435
|
-
: undefined,
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
catch (err) {
|
|
439
|
-
results.push({
|
|
440
|
-
adrNumber,
|
|
441
|
-
filename,
|
|
442
|
-
filePath,
|
|
443
|
-
action: 'skipped-error',
|
|
444
|
-
note: `Insert failed: ${String(err)}`,
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
return results;
|
|
449
|
-
}
|
|
450
|
-
// ─── Report writer ────────────────────────────────────────────────────────────
|
|
451
|
-
/**
|
|
452
|
-
* Write the backfill report to `.cleo/agent-outputs/T1824-5-backfill-report.md`.
|
|
453
|
-
*
|
|
454
|
-
* @deprecated Since v2026.5.93 (T9795 / Saga T9787). Raw `.md` writes to
|
|
455
|
-
* `.cleo/agent-outputs/` bypass the docs registry SSoT — migrate to
|
|
456
|
-
* `cleo docs add --type note` (T9788). Removal: no earlier than
|
|
457
|
-
* v2026.6.0. See `.cleo/deprecations.yml` id:`agent-output-raw-md-write`.
|
|
458
|
-
*/
|
|
459
|
-
async function writeReport(projectRoot, results, dryRun) {
|
|
460
|
-
const inserted = results.filter((r) => r.action === 'inserted');
|
|
461
|
-
const skippedExists = results.filter((r) => r.action === 'skipped-exists');
|
|
462
|
-
const skippedCollision = results.filter((r) => r.action === 'skipped-collision');
|
|
463
|
-
const skippedError = results.filter((r) => r.action === 'skipped-error');
|
|
464
|
-
const duplicatePairs = results.filter((r) => r.supersedureTo);
|
|
465
|
-
const date = new Date().toISOString().split('T')[0];
|
|
466
|
-
const mode = dryRun ? 'DRY-RUN (no writes)' : 'APPLIED';
|
|
467
|
-
const lines = [
|
|
468
|
-
'# ADR Backfill Walker Report — T1829',
|
|
469
|
-
'',
|
|
470
|
-
`**Date**: ${date}`,
|
|
471
|
-
`**Mode**: ${mode}`,
|
|
472
|
-
`**Task**: T1829`,
|
|
473
|
-
`**Epic**: T1824`,
|
|
474
|
-
'',
|
|
475
|
-
'---',
|
|
476
|
-
'',
|
|
477
|
-
'## Summary',
|
|
478
|
-
'',
|
|
479
|
-
`| Category | Count |`,
|
|
480
|
-
`|----------|-------|`,
|
|
481
|
-
`| Inserted (or would-insert in dry-run) | ${inserted.length} |`,
|
|
482
|
-
`| Skipped — row already exists | ${skippedExists.length} |`,
|
|
483
|
-
`| Skipped — collision (HITL needed) | ${skippedCollision.length} |`,
|
|
484
|
-
`| Skipped — error | ${skippedError.length} |`,
|
|
485
|
-
`| Duplicate-pair supersession applied | ${duplicatePairs.length} |`,
|
|
486
|
-
'',
|
|
487
|
-
'---',
|
|
488
|
-
'',
|
|
489
|
-
];
|
|
490
|
-
if (inserted.length > 0) {
|
|
491
|
-
lines.push('## Inserted Rows');
|
|
492
|
-
lines.push('');
|
|
493
|
-
for (const r of inserted) {
|
|
494
|
-
const prefix = dryRun ? '[DRY-RUN] ' : '';
|
|
495
|
-
lines.push(`- **ADR-${String(r.adrNumber).padStart(3, '0')}** (\`${basename(r.filePath)}\`)`);
|
|
496
|
-
if (r.decisionId)
|
|
497
|
-
lines.push(` - Decision ID: \`${r.decisionId}\``);
|
|
498
|
-
if (r.note)
|
|
499
|
-
lines.push(` - ${r.note.replace(/^\[DRY-RUN\] /, prefix)}`);
|
|
500
|
-
if (r.supersedureTo)
|
|
501
|
-
lines.push(` - Supersession: ${r.supersedureTo}`);
|
|
502
|
-
}
|
|
503
|
-
lines.push('');
|
|
504
|
-
lines.push('---');
|
|
505
|
-
lines.push('');
|
|
506
|
-
}
|
|
507
|
-
if (skippedExists.length > 0) {
|
|
508
|
-
lines.push('## Skipped — Already Populated');
|
|
509
|
-
lines.push('');
|
|
510
|
-
for (const r of skippedExists) {
|
|
511
|
-
lines.push(`- **ADR-${String(r.adrNumber).padStart(3, '0')}**: ${r.note ?? ''}`);
|
|
512
|
-
}
|
|
513
|
-
lines.push('');
|
|
514
|
-
lines.push('---');
|
|
515
|
-
lines.push('');
|
|
516
|
-
}
|
|
517
|
-
if (skippedCollision.length > 0) {
|
|
518
|
-
lines.push('## Skipped — ADR Number Collisions (HITL Required)');
|
|
519
|
-
lines.push('');
|
|
520
|
-
lines.push('These ADR numbers exist in BOTH `.cleo/adrs/` and `docs/adr/` with **different content**.');
|
|
521
|
-
lines.push('The `.cleo/adrs/` version is canonical. Owner must decide how to renumber the `docs/adr/` versions.');
|
|
522
|
-
lines.push('');
|
|
523
|
-
for (const r of skippedCollision) {
|
|
524
|
-
lines.push(`### ADR-${String(r.adrNumber).padStart(3, '0')}`);
|
|
525
|
-
lines.push('');
|
|
526
|
-
lines.push(`- **File skipped**: \`${r.filePath}\``);
|
|
527
|
-
lines.push(`- **Reason**: ${r.note ?? ''}`);
|
|
528
|
-
lines.push('');
|
|
529
|
-
}
|
|
530
|
-
lines.push('**Action required**: File HITL request to owner for renumbering decision.');
|
|
531
|
-
lines.push('');
|
|
532
|
-
lines.push('---');
|
|
533
|
-
lines.push('');
|
|
534
|
-
}
|
|
535
|
-
if (skippedError.length > 0) {
|
|
536
|
-
lines.push('## Skipped — Errors');
|
|
537
|
-
lines.push('');
|
|
538
|
-
for (const r of skippedError) {
|
|
539
|
-
lines.push(`- **ADR-${String(r.adrNumber).padStart(3, '0')}**: ${r.note ?? ''}`);
|
|
540
|
-
}
|
|
541
|
-
lines.push('');
|
|
542
|
-
lines.push('---');
|
|
543
|
-
lines.push('');
|
|
544
|
-
}
|
|
545
|
-
if (duplicatePairs.length > 0) {
|
|
546
|
-
lines.push('## Duplicate-Pair Supersession');
|
|
547
|
-
lines.push('');
|
|
548
|
-
lines.push('The following ADRs were identified as exact duplicates within `.cleo/adrs/` and were');
|
|
549
|
-
lines.push('inserted with `confirmationState=superseded` and a `supersededBy` link to the canonical ADR.');
|
|
550
|
-
lines.push('');
|
|
551
|
-
for (const r of duplicatePairs) {
|
|
552
|
-
lines.push(`- **ADR-${String(r.adrNumber).padStart(3, '0')}** → ${r.supersedureTo} (\`${basename(r.filePath)}\`)`);
|
|
553
|
-
}
|
|
554
|
-
lines.push('');
|
|
555
|
-
lines.push('---');
|
|
556
|
-
lines.push('');
|
|
557
|
-
}
|
|
558
|
-
lines.push('## Collision Details');
|
|
559
|
-
lines.push('');
|
|
560
|
-
lines.push('### ADR-051..054 Collision Pairs');
|
|
561
|
-
lines.push('');
|
|
562
|
-
lines.push('| ADR Number | `.cleo/adrs/` file (canonical) | `docs/adr/` file (skipped) |');
|
|
563
|
-
lines.push('|------------|-------------------------------|----------------------------|');
|
|
564
|
-
lines.push('| ADR-051 | `ADR-051-programmatic-gate-integrity.md` | `ADR-051-override-patterns.md` |');
|
|
565
|
-
lines.push('| ADR-052 | `ADR-052-caamp-keeps-commander.md` | `ADR-052-sdk-consolidation.md` |');
|
|
566
|
-
lines.push('| ADR-053 | `ADR-053-project-agnostic-release-pipeline.md` | `ADR-053-playbook-runtime.md` |');
|
|
567
|
-
lines.push('| ADR-054 | `ADR-054-manifest-unification.md` | `ADR-054-migration-system-hybrid-path-a-plus.md` |');
|
|
568
|
-
lines.push('');
|
|
569
|
-
lines.push('Owner must decide renumbering: e.g. `docs/adr/ADR-052-sdk-consolidation.md` → ADR-065.');
|
|
570
|
-
lines.push('');
|
|
571
|
-
lines.push('---');
|
|
572
|
-
lines.push('');
|
|
573
|
-
lines.push('*Generated by `packages/core/src/tools/adr-backfill-walker.ts` (T1829)*');
|
|
574
|
-
const report = lines.join('\n');
|
|
575
|
-
const reportPath = join(projectRoot, '.cleo', 'agent-outputs', 'T1824-5-backfill-report.md');
|
|
576
|
-
await mkdir(join(projectRoot, '.cleo', 'agent-outputs'), { recursive: true });
|
|
577
|
-
await writeFile(reportPath, report, 'utf8');
|
|
578
|
-
return reportPath;
|
|
579
|
-
}
|
|
580
|
-
// ─── CLI entrypoint ───────────────────────────────────────────────────────────
|
|
581
|
-
async function main() {
|
|
582
|
-
const args = process.argv.slice(2);
|
|
583
|
-
const dryRun = args.includes('--dry-run');
|
|
584
|
-
const apply = args.includes('--apply');
|
|
585
|
-
if (!dryRun && !apply) {
|
|
586
|
-
console.error('Usage: adr-backfill-walker.js --dry-run | --apply\n\n' +
|
|
587
|
-
' --dry-run Show what would be inserted, no writes\n' +
|
|
588
|
-
' --apply Write brain_decisions rows for all ADRs\n');
|
|
589
|
-
process.exit(1);
|
|
590
|
-
}
|
|
591
|
-
// Resolve project root (walk up from CWD looking for .cleo/)
|
|
592
|
-
const { getProjectRoot } = await import('../paths.js');
|
|
593
|
-
const projectRoot = getProjectRoot();
|
|
594
|
-
console.log(`[T1829] ADR Backfill Walker`);
|
|
595
|
-
console.log(`[T1829] Mode: ${dryRun ? 'DRY-RUN' : 'APPLY'}`);
|
|
596
|
-
console.log(`[T1829] Project root: ${projectRoot}`);
|
|
597
|
-
console.log('');
|
|
598
|
-
const results = await runAdrBackfillWalker(projectRoot, dryRun);
|
|
599
|
-
const inserted = results.filter((r) => r.action === 'inserted');
|
|
600
|
-
const skippedExists = results.filter((r) => r.action === 'skipped-exists');
|
|
601
|
-
const skippedCollision = results.filter((r) => r.action === 'skipped-collision');
|
|
602
|
-
const skippedError = results.filter((r) => r.action === 'skipped-error');
|
|
603
|
-
console.log(`Results:`);
|
|
604
|
-
console.log(` Inserted (or would-insert): ${inserted.length}`);
|
|
605
|
-
console.log(` Skipped (already exist): ${skippedExists.length}`);
|
|
606
|
-
console.log(` Skipped (collision/HITL): ${skippedCollision.length}`);
|
|
607
|
-
console.log(` Skipped (error): ${skippedError.length}`);
|
|
608
|
-
console.log('');
|
|
609
|
-
for (const r of results) {
|
|
610
|
-
const icon = r.action === 'inserted'
|
|
611
|
-
? '[+]'
|
|
612
|
-
: r.action === 'skipped-exists'
|
|
613
|
-
? '[=]'
|
|
614
|
-
: r.action === 'skipped-collision'
|
|
615
|
-
? '[!]'
|
|
616
|
-
: '[E]';
|
|
617
|
-
const num = String(r.adrNumber).padStart(3, '0');
|
|
618
|
-
console.log(` ${icon} ADR-${num} ${r.filename}${r.note ? ` — ${r.note}` : ''}`);
|
|
619
|
-
}
|
|
620
|
-
console.log('');
|
|
621
|
-
const reportPath = await writeReport(projectRoot, results, dryRun);
|
|
622
|
-
console.log(`[T1829] Report written to: ${reportPath}`);
|
|
623
|
-
if (skippedError.length > 0) {
|
|
624
|
-
process.exit(1);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
main().catch((err) => {
|
|
628
|
-
console.error('[T1829] Fatal error:', err);
|
|
629
|
-
process.exit(1);
|
|
630
|
-
});
|
|
631
|
-
//# sourceMappingURL=adr-backfill-walker.js.map
|