@erclx/canon 4.37.0 → 4.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/commands/migrate.ts
CHANGED
|
@@ -263,6 +263,15 @@ function reportRecords(plan: RecordsPlan, records: number): void {
|
|
|
263
263
|
if (plan.excluded.length > 0) {
|
|
264
264
|
logInfo(`${plural(plan.excluded.length, 'file')} excluded from the sweep.`)
|
|
265
265
|
}
|
|
266
|
+
|
|
267
|
+
// A list rather than a count. A coupled file is the handful an operator has
|
|
268
|
+
// to open by hand, and a bare count gives them nothing to act on.
|
|
269
|
+
if (plan.coupled.length > 0) {
|
|
270
|
+
logInfo(
|
|
271
|
+
`${plural(plan.coupled.length, 'file')} couple to an excluded path:`,
|
|
272
|
+
)
|
|
273
|
+
for (const path of plan.coupled) logInfo(` ${path}`)
|
|
274
|
+
}
|
|
266
275
|
}
|
|
267
276
|
|
|
268
277
|
function toRecordsRecord(
|
|
@@ -279,6 +288,7 @@ function toRecordsRecord(
|
|
|
279
288
|
rewritten: plan.rewritten,
|
|
280
289
|
kept: plan.kept,
|
|
281
290
|
excluded: plan.excluded.length,
|
|
291
|
+
coupled: plan.coupled,
|
|
282
292
|
records,
|
|
283
293
|
paths: plan.entries.map((entry) => ({
|
|
284
294
|
path: entry.path,
|
package/src/migrate/plan.ts
CHANGED
package/src/migrate/records.ts
CHANGED
|
@@ -91,6 +91,13 @@ function escape(value: string): string {
|
|
|
91
91
|
* pair into two copies of the new one and shellcheck reports a pattern that can
|
|
92
92
|
* never match. The guard then stops firing in a project the move has not
|
|
93
93
|
* reached, which is silent: the index goes stale while every save succeeds.
|
|
94
|
+
*
|
|
95
|
+
* A file outside this list can still couple to one inside it, naming an
|
|
96
|
+
* excluded path as plain text while it also carries a live citation of its
|
|
97
|
+
* own, which `referencesExcluded` reports separately below. Widening this list
|
|
98
|
+
* or `EXCLUDED_SUFFIXES` to catch that case would mean guessing at a naming
|
|
99
|
+
* convention no project here declares, where the coupling check instead reads
|
|
100
|
+
* what the file's own text already says.
|
|
94
101
|
*/
|
|
95
102
|
const EXCLUDED_PREFIXES: readonly string[] = [
|
|
96
103
|
'src/migrate/',
|
|
@@ -109,6 +116,25 @@ export function isExcludedPath(path: string): boolean {
|
|
|
109
116
|
return EXCLUDED_SUFFIXES.some((suffix) => path.endsWith(suffix))
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Whether `text` names an `EXCLUDED_PREFIXES` or `EXCLUDED_PATHS` entry as
|
|
121
|
+
* literal substring text, which is how a rewritten file can couple to one this
|
|
122
|
+
* module leaves alone: a citation gets rewritten clean while a line a few away
|
|
123
|
+
* still spells the excluded path it was testing against.
|
|
124
|
+
*
|
|
125
|
+
* `EXCLUDED_SUFFIXES` plays no part here, since a suffix names a file's own
|
|
126
|
+
* shape rather than text its content could quote. The one gap this cannot
|
|
127
|
+
* close is a file that names the excluded surface by description rather than
|
|
128
|
+
* by path, such as "the exemption hook" rather than `.claude/hooks/`, which is
|
|
129
|
+
* the same limit `isExcludedPath` already carries for content it cannot parse.
|
|
130
|
+
*/
|
|
131
|
+
export function referencesExcluded(text: string): boolean {
|
|
132
|
+
return (
|
|
133
|
+
EXCLUDED_PATHS.some((path) => text.includes(path)) ||
|
|
134
|
+
EXCLUDED_PREFIXES.some((prefix) => text.includes(prefix))
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
112
138
|
/**
|
|
113
139
|
* The roots holding nothing but records, so a path under one is a record
|
|
114
140
|
* whatever it is named.
|
|
@@ -299,6 +325,7 @@ export interface RecordsPlan {
|
|
|
299
325
|
readonly collisions: readonly string[]
|
|
300
326
|
readonly entries: readonly CitationEntry[]
|
|
301
327
|
readonly excluded: readonly string[]
|
|
328
|
+
readonly coupled: readonly string[]
|
|
302
329
|
readonly rewritten: number
|
|
303
330
|
readonly kept: number
|
|
304
331
|
}
|
|
@@ -318,6 +345,7 @@ export function planRecordsMove(
|
|
|
318
345
|
const moves = planFolderMoves(root)
|
|
319
346
|
const entries: CitationEntry[] = []
|
|
320
347
|
const excluded: string[] = []
|
|
348
|
+
const coupled: string[] = []
|
|
321
349
|
let kept = 0
|
|
322
350
|
|
|
323
351
|
for (const source of sources) {
|
|
@@ -339,6 +367,8 @@ export function planRecordsMove(
|
|
|
339
367
|
kept += counts.kept
|
|
340
368
|
if (counts.rewritten === 0) continue
|
|
341
369
|
|
|
370
|
+
if (referencesExcluded(source.text)) coupled.push(source.path)
|
|
371
|
+
|
|
342
372
|
entries.push({
|
|
343
373
|
path: source.path,
|
|
344
374
|
text: rewriteText(source.text),
|
|
@@ -352,6 +382,7 @@ export function planRecordsMove(
|
|
|
352
382
|
collisions: collisions(root, moves),
|
|
353
383
|
entries,
|
|
354
384
|
excluded,
|
|
385
|
+
coupled,
|
|
355
386
|
rewritten: entries.reduce((sum, entry) => sum + entry.rewritten, 0),
|
|
356
387
|
kept,
|
|
357
388
|
}
|