@cortexkit/aft-bridge 0.63.4 → 0.64.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/dist/bash-host-fallback.d.ts +4 -8
- package/dist/bash-host-fallback.d.ts.map +1 -1
- package/dist/bash-host-fallback.js +7 -8
- package/dist/bash-host-fallback.js.map +1 -1
- package/dist/bash-permissions.d.ts +1 -0
- package/dist/bash-permissions.d.ts.map +1 -1
- package/dist/bash-permissions.js +23 -11
- package/dist/bash-permissions.js.map +1 -1
- package/dist/callgraph-format.d.ts.map +1 -1
- package/dist/callgraph-format.js +64 -6
- package/dist/callgraph-format.js.map +1 -1
- package/dist/config-watch.d.ts +3 -1
- package/dist/config-watch.d.ts.map +1 -1
- package/dist/config-watch.js +90 -6
- package/dist/config-watch.js.map +1 -1
- package/dist/feature-config.d.ts +128 -11
- package/dist/feature-config.d.ts.map +1 -1
- package/dist/feature-config.js +351 -33
- package/dist/feature-config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/lsp-npm-policy.d.ts.map +1 -1
- package/dist/lsp-npm-policy.js +4 -3
- package/dist/lsp-npm-policy.js.map +1 -1
- package/dist/lsp-npm-selection.d.ts +22 -0
- package/dist/lsp-npm-selection.d.ts.map +1 -0
- package/dist/lsp-npm-selection.js +97 -0
- package/dist/lsp-npm-selection.js.map +1 -0
- package/dist/npm-resolver.d.ts +4 -0
- package/dist/npm-resolver.d.ts.map +1 -1
- package/dist/npm-resolver.js +20 -0
- package/dist/npm-resolver.js.map +1 -1
- package/dist/subc-transport.d.ts.map +1 -1
- package/dist/subc-transport.js +16 -3
- package/dist/subc-transport.js.map +1 -1
- package/package.json +1 -1
package/dist/feature-config.js
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Mirrors `crates/aft/src/feature_config.rs`: the literal tool inventory, the
|
|
5
5
|
* table of retired keys, the per-block translation of those keys into
|
|
6
|
-
* canonical `disabled_tools` / `indexes` / `github` / `lsp` leaves
|
|
7
|
-
*
|
|
6
|
+
* canonical `disabled_tools` / `indexes` / `github` / `lsp` / `idle` leaves
|
|
7
|
+
* (the names retired in 0.62 or earlier, and the data-layer keys from
|
|
8
|
+
* `DATA_LAYER_KEYS_REFUSED_FROM` on, are refused by `removedConfigKeyErrors`),
|
|
8
9
|
* validation of a resolved configuration, and the migration-notice projection
|
|
9
10
|
* digest. The config parity fixtures and the shared notice-projection fixture
|
|
10
11
|
* (`crates/aft/tests/fixtures/feature_config/notice_projection.json`) keep the
|
|
11
12
|
* two implementations equal.
|
|
12
13
|
*/
|
|
13
14
|
import { createHash } from "node:crypto";
|
|
15
|
+
import { createRequire } from "node:module";
|
|
14
16
|
/** Every agent-visible tool name a registration adapter may publish (sorted). */
|
|
15
17
|
export const CANONICAL_TOOLS = [
|
|
16
18
|
"aft_callgraph",
|
|
@@ -73,24 +75,190 @@ export const REMOVED_OPENCODE_PATHS = [
|
|
|
73
75
|
"opencode.server_url",
|
|
74
76
|
"opencode.server_password_env",
|
|
75
77
|
];
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Keys the per-repository data store leaves with nothing to do. A config file
|
|
80
|
+
* that sets one gets a notice saying it no longer does anything, and
|
|
81
|
+
* `doctor --fix` deletes it, but the automatic rewrite of the user file
|
|
82
|
+
* leaves it in place. Until the engine code that reads them (index views, and
|
|
83
|
+
* the path that serves a worktree from another checkout's indexes) is deleted,
|
|
84
|
+
* the values a file supplies still reach the engine, so translation keeps them in
|
|
85
|
+
* the document. From `DATA_LAYER_KEYS_REFUSED_FROM` a config that sets one is
|
|
86
|
+
* refused. Mirrors `REMOVED_DATA_LAYER_PATHS` in
|
|
87
|
+
* `crates/aft/src/feature_config.rs`.
|
|
88
|
+
*/
|
|
89
|
+
export const REMOVED_DATA_LAYER_PATHS = [
|
|
90
|
+
"views.callgraph_copies",
|
|
91
|
+
"views.enabled",
|
|
92
|
+
"worktree.ram_overlay",
|
|
93
|
+
];
|
|
94
|
+
/**
|
|
95
|
+
* `views.idle_checkout_hours` and the key that replaces it. Translated like any
|
|
96
|
+
* retired key (and the user file rewritten) until
|
|
97
|
+
* `DATA_LAYER_KEYS_REFUSED_FROM`, then refused.
|
|
98
|
+
*/
|
|
99
|
+
export const RETIRED_IDLE_CHECKOUT_HOURS = [
|
|
100
|
+
"views.idle_checkout_hours",
|
|
101
|
+
"idle.checkout_hours",
|
|
102
|
+
];
|
|
103
|
+
/**
|
|
104
|
+
* First release, as [major, minor], that refuses the keys in
|
|
105
|
+
* `DATA_LAYER_RETIRED_PATHS` with `removed_config_key`. Earlier releases
|
|
106
|
+
* translate or keep them, with a notice.
|
|
107
|
+
*/
|
|
108
|
+
export const DATA_LAYER_KEYS_REFUSED_FROM = [0, 65];
|
|
109
|
+
/** Every key the data-layer cutover retires, refused from `DATA_LAYER_KEYS_REFUSED_FROM`. */
|
|
110
|
+
export const DATA_LAYER_RETIRED_PATHS = [
|
|
111
|
+
"views.callgraph_copies",
|
|
112
|
+
"views.enabled",
|
|
113
|
+
"views.idle_checkout_hours",
|
|
114
|
+
"worktree.ram_overlay",
|
|
115
|
+
];
|
|
116
|
+
/**
|
|
117
|
+
* The `views` block a plugin sends to configure: the resolved block without
|
|
118
|
+
* `enabled`, or `undefined` when nothing else is left. `views.enabled` does
|
|
119
|
+
* nothing from 0.64; the file that sets it still gets the removed-key notice,
|
|
120
|
+
* but the key never reaches the resolved config, as in the Rust resolver.
|
|
121
|
+
*/
|
|
122
|
+
export function viewsForConfigure(views) {
|
|
123
|
+
if (views === undefined)
|
|
124
|
+
return undefined;
|
|
125
|
+
const { enabled: _enabled, ...rest } = views;
|
|
126
|
+
return Object.keys(rest).length > 0 ? rest : undefined;
|
|
127
|
+
}
|
|
128
|
+
/** `idle.checkout_hours` default and range; mirrors `crates/aft/src/config.rs`. */
|
|
129
|
+
export const IDLE_CHECKOUT_HOURS_DEFAULT = 168;
|
|
130
|
+
export const IDLE_CHECKOUT_HOURS_MIN = 1;
|
|
131
|
+
export const IDLE_CHECKOUT_HOURS_MAX = 24 * 365;
|
|
132
|
+
/** The [major, minor] release a version string names, ignoring patch and suffixes. */
|
|
133
|
+
export function parseRelease(version) {
|
|
134
|
+
const match = /^(\d+)\.(\d+)(?:\.|$)/.exec(version);
|
|
135
|
+
return match ? [Number(match[1]), Number(match[2])] : undefined;
|
|
136
|
+
}
|
|
137
|
+
/** Manifests that carry this code's release: the bridge, or a bundle that inlines it. */
|
|
138
|
+
const AFT_PACKAGE_NAMES = new Set([
|
|
139
|
+
"@cortexkit/aft-bridge",
|
|
140
|
+
"@cortexkit/aft-opencode",
|
|
141
|
+
"@cortexkit/aft-pi",
|
|
142
|
+
"@cortexkit/aft",
|
|
143
|
+
]);
|
|
144
|
+
/**
|
|
145
|
+
* This code's release, read from the nearest AFT package manifest above it.
|
|
146
|
+
* The bridge is also bundled into the plugins and the CLI at different
|
|
147
|
+
* depths, and every AFT package is released with the same version, so the
|
|
148
|
+
* first AFT manifest found names the release. [0, 0] when none is found,
|
|
149
|
+
* which refuses nothing.
|
|
150
|
+
*/
|
|
151
|
+
function readRunningRelease() {
|
|
152
|
+
const req = createRequire(import.meta.url);
|
|
153
|
+
for (const candidate of ["../package.json", "../../package.json", "../../../package.json"]) {
|
|
154
|
+
try {
|
|
155
|
+
const manifest = req(candidate);
|
|
156
|
+
if (manifest.name && AFT_PACKAGE_NAMES.has(manifest.name) && manifest.version) {
|
|
157
|
+
return parseRelease(manifest.version) ?? [0, 0];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Not at this depth; try the next.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return [0, 0];
|
|
165
|
+
}
|
|
166
|
+
let runningRelease;
|
|
167
|
+
/** The [major, minor] release of this package, read once. */
|
|
168
|
+
export function getRunningRelease() {
|
|
169
|
+
runningRelease ??= readRunningRelease();
|
|
170
|
+
return runningRelease;
|
|
171
|
+
}
|
|
172
|
+
function releaseAtLeast(release, floor) {
|
|
173
|
+
return release[0] !== floor[0] ? release[0] > floor[0] : release[1] >= floor[1];
|
|
174
|
+
}
|
|
175
|
+
/** `0.65.0` for `DATA_LAYER_KEYS_REFUSED_FROM`, as notices print it. */
|
|
176
|
+
function refusedFromVersion() {
|
|
177
|
+
return `${DATA_LAYER_KEYS_REFUSED_FROM[0]}.${DATA_LAYER_KEYS_REFUSED_FROM[1]}.0`;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Removed settings fail only in the base and first-level harness blocks. The
|
|
181
|
+
* data-layer keys fail only from `DATA_LAYER_KEYS_REFUSED_FROM`, judged by
|
|
182
|
+
* `release` (this package's own release unless a test names one). The names
|
|
183
|
+
* retired in 0.62 or earlier (`REFUSED_RETIRED_PATHS` and the prefixed tool
|
|
184
|
+
* names) fail only when `refuseRetired` is true. The plugins pass false for
|
|
185
|
+
* both files: the AFT binary rewrites the user file to current keys when it
|
|
186
|
+
* loads it, and a project file loads with a notice rather than leaving a
|
|
187
|
+
* repository without AFT. Mirrors `removed_config_key_errors` in
|
|
188
|
+
* `crates/aft/src/feature_config.rs`, which always refuses them; the binary
|
|
189
|
+
* applies it to the user file only.
|
|
190
|
+
*/
|
|
191
|
+
export function removedConfigKeyErrors(value, release = getRunningRelease(), refuseRetired = true) {
|
|
78
192
|
if (!isRecord(value))
|
|
79
193
|
return [];
|
|
80
|
-
const errors =
|
|
194
|
+
const errors = removedBlockKeyErrors(value, release, refuseRetired);
|
|
81
195
|
if (isRecord(value.harnesses)) {
|
|
82
196
|
for (const [name, block] of Object.entries(value.harnesses)) {
|
|
83
197
|
if (!isRecord(block))
|
|
84
198
|
continue;
|
|
85
|
-
for (const error of
|
|
199
|
+
for (const error of removedBlockKeyErrors(block, release, refuseRetired)) {
|
|
86
200
|
errors.push(error.replace("removed_config_key:", `removed_config_key:harnesses.${name}.`));
|
|
87
201
|
}
|
|
88
202
|
}
|
|
89
203
|
}
|
|
90
204
|
return errors;
|
|
91
205
|
}
|
|
92
|
-
function
|
|
93
|
-
|
|
206
|
+
function removedBlockKeyErrors(value, release, refuseRetired) {
|
|
207
|
+
const paths = REMOVED_OPENCODE_PATHS.filter((path) => hasPath(value, path));
|
|
208
|
+
if (refuseRetired)
|
|
209
|
+
paths.push(...refusedRetiredNames(value));
|
|
210
|
+
if (releaseAtLeast(release, DATA_LAYER_KEYS_REFUSED_FROM))
|
|
211
|
+
paths.push(...DATA_LAYER_RETIRED_PATHS.filter((path) => hasPath(value, path)));
|
|
212
|
+
return paths.map((path) => `removed_config_key:${path}; run npx @cortexkit/aft doctor --fix`);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Names retired in 0.62 or earlier, refused from 0.64, in the order a refusal
|
|
216
|
+
* lists them. A config that still sets one is refused with
|
|
217
|
+
* `removed_config_key`; `doctor --fix` (or the automatic rewrite of the user
|
|
218
|
+
* file) replaces it with its current equivalent or deletes it. Names retired
|
|
219
|
+
* more recently (`bridge.hang_threshold` and the data-layer keys) are not
|
|
220
|
+
* listed: they are still translated, with a notice. Mirrors
|
|
221
|
+
* `REFUSED_RETIRED_PATHS` in `crates/aft/src/feature_config.rs`.
|
|
222
|
+
*/
|
|
223
|
+
export const REFUSED_RETIRED_PATHS = [
|
|
224
|
+
"sandbox.enabled",
|
|
225
|
+
"tool_surface",
|
|
226
|
+
"hoist_builtin_tools",
|
|
227
|
+
"enabled",
|
|
228
|
+
"search_index",
|
|
229
|
+
"experimental_search_index",
|
|
230
|
+
"semantic_search",
|
|
231
|
+
"experimental_semantic_search",
|
|
232
|
+
"callgraph_store",
|
|
233
|
+
"github.enabled",
|
|
234
|
+
"gh_read",
|
|
235
|
+
"gh_shim.enabled",
|
|
236
|
+
"experimental_lsp_ty",
|
|
237
|
+
"experimental_bash_rewrite",
|
|
238
|
+
"experimental_bash_compress",
|
|
239
|
+
"experimental_bash_background",
|
|
240
|
+
"experimental.bash",
|
|
241
|
+
"idle.lsp_ttl_minutes",
|
|
242
|
+
"inspect.tier2_soft_deadline_ms",
|
|
243
|
+
"inspect.max_drill_down_items",
|
|
244
|
+
];
|
|
245
|
+
/**
|
|
246
|
+
* The refused retired names one block sets: the paths in
|
|
247
|
+
* `REFUSED_RETIRED_PATHS`, then each prefixed tool name (`LEGACY_TOOL_ALIASES`)
|
|
248
|
+
* its `disabled_tools` list contains, once each and by its bare name, as the
|
|
249
|
+
* retired-key notices list it.
|
|
250
|
+
*/
|
|
251
|
+
function refusedRetiredNames(block) {
|
|
252
|
+
const names = REFUSED_RETIRED_PATHS.filter((path) => hasPath(block, path));
|
|
253
|
+
if (Array.isArray(block.disabled_tools)) {
|
|
254
|
+
for (const name of block.disabled_tools) {
|
|
255
|
+
if (typeof name !== "string" || !Object.hasOwn(LEGACY_TOOL_ALIASES, name))
|
|
256
|
+
continue;
|
|
257
|
+
if (!names.includes(name))
|
|
258
|
+
names.push(name);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return names;
|
|
94
262
|
}
|
|
95
263
|
/** Retired config paths and their replacement. */
|
|
96
264
|
export const RETIRED_PATHS = [
|
|
@@ -284,10 +452,16 @@ function translateInspectLspPaths(map, prefix, out) {
|
|
|
284
452
|
* dropped on load and removed by `doctor --fix`, and the notice says so.
|
|
285
453
|
* `bridge.hang_threshold` counted request timeouts toward restarting the
|
|
286
454
|
* bridge; a timeout now fails only its own request, and the bridge's hang
|
|
287
|
-
* watchdog judges a stuck child by its silence instead.
|
|
288
|
-
* `
|
|
455
|
+
* watchdog judges a stuck child by its silence instead.
|
|
456
|
+
* `inspect.tier2_idle_minutes` delayed the whole-repository analyses the
|
|
457
|
+
* OpenCode plugin started when a session went idle; those analyses now run
|
|
458
|
+
* only when `aft_inspect` is called. Mirrors `REMOVED_NO_EFFECT_PATHS` in
|
|
459
|
+
* `crates/aft/src/feature_config.rs`.
|
|
289
460
|
*/
|
|
290
|
-
export const REMOVED_NO_EFFECT_PATHS = [
|
|
461
|
+
export const REMOVED_NO_EFFECT_PATHS = [
|
|
462
|
+
"bridge.hang_threshold",
|
|
463
|
+
"inspect.tier2_idle_minutes",
|
|
464
|
+
];
|
|
291
465
|
/** Drop the retired keys that no longer do anything, exactly as `doctor --fix` removes them. */
|
|
292
466
|
function dropNoEffectPaths(map, prefix, out) {
|
|
293
467
|
for (const path of REMOVED_NO_EFFECT_PATHS) {
|
|
@@ -298,9 +472,67 @@ function dropNoEffectPaths(map, prefix, out) {
|
|
|
298
472
|
}
|
|
299
473
|
}
|
|
300
474
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
475
|
+
* The `idle.checkout_hours` value a retired `views.idle_checkout_hours` value
|
|
476
|
+
* becomes: a whole number clamped to the current range, or the default for
|
|
477
|
+
* anything else. Mirrors `retired_idle_checkout_hours` in
|
|
478
|
+
* `crates/aft/src/feature_config.rs`.
|
|
479
|
+
*/
|
|
480
|
+
export function retiredIdleCheckoutHours(value) {
|
|
481
|
+
const raw = typeof value === "number" && Number.isSafeInteger(value) ? value : IDLE_CHECKOUT_HOURS_DEFAULT;
|
|
482
|
+
return Math.min(IDLE_CHECKOUT_HOURS_MAX, Math.max(IDLE_CHECKOUT_HOURS_MIN, raw));
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Translate the keys the data-layer cutover retires in one block:
|
|
486
|
+
* `views.idle_checkout_hours` becomes `idle.checkout_hours` (clamped to its
|
|
487
|
+
* range) unless that is already set, exactly as `doctor --fix` rewrites it.
|
|
488
|
+
* The removed keys (`REMOVED_DATA_LAYER_PATHS`) are only listed: their values
|
|
489
|
+
* stay in the document until the code that reads them is deleted.
|
|
490
|
+
*/
|
|
491
|
+
function translateDataLayerPaths(map, prefix, out) {
|
|
492
|
+
const [retired, replacement] = RETIRED_IDLE_CHECKOUT_HOURS;
|
|
493
|
+
const hours = takeNested(map, "views", "idle_checkout_hours");
|
|
494
|
+
if (hours.found) {
|
|
495
|
+
recordRetired(out, prefix, retired);
|
|
496
|
+
if (isRecord(map.idle) && map.idle.checkout_hours !== undefined) {
|
|
497
|
+
superseded(out, `${prefix}${retired}`, `${retired} is ignored because ${replacement} is set`);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
if (!Object.hasOwn(map, "idle"))
|
|
501
|
+
map.idle = {};
|
|
502
|
+
if (isRecord(map.idle))
|
|
503
|
+
map.idle.checkout_hours = retiredIdleCheckoutHours(hours.value);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
for (const path of REMOVED_DATA_LAYER_PATHS) {
|
|
507
|
+
if (hasPath(map, path))
|
|
508
|
+
out.removedKeys.push(`${prefix}${path}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Delete the removed data-layer keys from a translated document, base and
|
|
513
|
+
* harness blocks alike, emptying containers as `doctor --fix` does. Used to
|
|
514
|
+
* preview what `doctor --fix` will remove; load-time translation keeps them.
|
|
515
|
+
*/
|
|
516
|
+
export function dropRemovedDataLayerKeys(map) {
|
|
517
|
+
const blocks = [map];
|
|
518
|
+
if (isRecord(map.harnesses)) {
|
|
519
|
+
for (const block of Object.values(map.harnesses))
|
|
520
|
+
if (isRecord(block))
|
|
521
|
+
blocks.push(block);
|
|
522
|
+
}
|
|
523
|
+
for (const block of blocks) {
|
|
524
|
+
for (const path of REMOVED_DATA_LAYER_PATHS) {
|
|
525
|
+
const [container, leaf] = path.split(".");
|
|
526
|
+
if (container && leaf)
|
|
527
|
+
takeNested(block, container, leaf);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Translate flat experimental aliases to nested settings and move the
|
|
533
|
+
* `experimental.bash` block to top-level `bash`. Graduating a block that sets
|
|
534
|
+
* a feature flag explicitly sets its omitted flags to false; otherwise the
|
|
535
|
+
* current default-on bash settings would silently enable them.
|
|
304
536
|
*/
|
|
305
537
|
function translateExperimentalPaths(map, prefix, out) {
|
|
306
538
|
for (const [oldKey, leaf] of [
|
|
@@ -335,24 +567,29 @@ function translateExperimentalPaths(map, prefix, out) {
|
|
|
335
567
|
const experimental = map.experimental;
|
|
336
568
|
const legacy = experimental.bash;
|
|
337
569
|
if (isRecord(legacy)) {
|
|
338
|
-
if (
|
|
339
|
-
|
|
570
|
+
if (Object.keys(legacy).length === 0)
|
|
571
|
+
recordRetired(out, prefix, "experimental.bash");
|
|
340
572
|
for (const leaf of Object.keys(legacy))
|
|
341
573
|
recordRetired(out, prefix, `experimental.bash.${leaf}`);
|
|
342
574
|
if (Object.hasOwn(map, "bash")) {
|
|
343
575
|
superseded(out, "experimental.bash", 'experimental.bash is ignored because top-level "bash" is already set');
|
|
344
576
|
}
|
|
345
577
|
else {
|
|
346
|
-
const bash = {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
578
|
+
const bash = {};
|
|
579
|
+
// A block with only reminder settings never opted into the feature
|
|
580
|
+
// flags, so they keep their current defaults instead of being written
|
|
581
|
+
// as false.
|
|
582
|
+
if (["rewrite", "compress", "background"].some((leaf) => Object.hasOwn(legacy, leaf))) {
|
|
583
|
+
bash.rewrite = legacy.rewrite === true;
|
|
584
|
+
bash.compress = legacy.compress === true;
|
|
585
|
+
bash.background = legacy.background === true;
|
|
586
|
+
}
|
|
351
587
|
for (const leaf of ["long_running_reminder_enabled", "long_running_reminder_interval_ms"]) {
|
|
352
588
|
if (Object.hasOwn(legacy, leaf))
|
|
353
589
|
bash[leaf] = legacy[leaf];
|
|
354
590
|
}
|
|
355
|
-
|
|
591
|
+
if (Object.keys(bash).length > 0)
|
|
592
|
+
map.bash = bash;
|
|
356
593
|
}
|
|
357
594
|
}
|
|
358
595
|
else {
|
|
@@ -376,6 +613,7 @@ userBase, blockLabel, out) {
|
|
|
376
613
|
const prefix = blockLabel === "base" ? "" : `${blockLabel}.`;
|
|
377
614
|
translateGithubAliases(map, prefix, out);
|
|
378
615
|
translateInspectLspPaths(map, prefix, out);
|
|
616
|
+
translateDataLayerPaths(map, prefix, out);
|
|
379
617
|
dropNoEffectPaths(map, prefix, out);
|
|
380
618
|
translateExperimentalPaths(map, prefix, out);
|
|
381
619
|
if (isRecord(map.sandbox) && Object.hasOwn(map.sandbox, "enabled")) {
|
|
@@ -414,11 +652,9 @@ userBase, blockLabel, out) {
|
|
|
414
652
|
return host;
|
|
415
653
|
});
|
|
416
654
|
}
|
|
655
|
+
// `experimental.bash` was already recorded, leaf by leaf, and removed by
|
|
656
|
+
// `translateExperimentalPaths`.
|
|
417
657
|
for (const [path] of RETIRED_PATHS) {
|
|
418
|
-
// A block with only reminder settings never opted into experimental
|
|
419
|
-
// bash features. Leave it unchanged instead of adding false flags.
|
|
420
|
-
if (path === "experimental.bash")
|
|
421
|
-
continue;
|
|
422
658
|
if (hasPath(map, path))
|
|
423
659
|
recordRetired(out, prefix, path);
|
|
424
660
|
}
|
|
@@ -573,6 +809,10 @@ export const NO_EFFECT_RETIRED_NOTES = [
|
|
|
573
809
|
"inspect.max_drill_down_items",
|
|
574
810
|
"inspect.max_drill_down_items never had an effect, so the setting can be removed.",
|
|
575
811
|
],
|
|
812
|
+
[
|
|
813
|
+
"inspect.tier2_idle_minutes",
|
|
814
|
+
"inspect.tier2_idle_minutes no longer does anything: AFT runs whole-repository analyses only when aft_inspect is called, so the setting can be removed.",
|
|
815
|
+
],
|
|
576
816
|
[
|
|
577
817
|
"inspect.tier2_soft_deadline_ms",
|
|
578
818
|
"inspect.tier2_soft_deadline_ms never had an effect, so the setting can be removed.",
|
|
@@ -580,15 +820,78 @@ export const NO_EFFECT_RETIRED_NOTES = [
|
|
|
580
820
|
];
|
|
581
821
|
/** The no-effect sentence for a retired key, which may carry a `harnesses.<id>.` prefix. */
|
|
582
822
|
function noEffectNote(key) {
|
|
583
|
-
return NO_EFFECT_RETIRED_NOTES.find(([path]) => key
|
|
823
|
+
return NO_EFFECT_RETIRED_NOTES.find(([path]) => namesPath(key, path))?.[1];
|
|
824
|
+
}
|
|
825
|
+
/** Whether `key`, which may carry a `harnesses.<id>.` prefix, is `path`. */
|
|
826
|
+
function namesPath(key, path) {
|
|
827
|
+
return key === path || key.endsWith(`.${path}`);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Why each removed data-layer key no longer does anything, in
|
|
831
|
+
* `REMOVED_DATA_LAYER_PATHS` order. Mirrors `REMOVED_DATA_LAYER_NOTES` in
|
|
832
|
+
* `crates/aft/src/feature_config.rs`.
|
|
833
|
+
*/
|
|
834
|
+
export const REMOVED_DATA_LAYER_NOTES = [
|
|
835
|
+
[
|
|
836
|
+
"views.callgraph_copies",
|
|
837
|
+
"views.callgraph_copies is gone because no checkout borrows another checkout's call graph any more.",
|
|
838
|
+
],
|
|
839
|
+
[
|
|
840
|
+
"views.enabled",
|
|
841
|
+
"views.enabled is gone because AFT now keeps one index store per repository and always uses it.",
|
|
842
|
+
],
|
|
843
|
+
[
|
|
844
|
+
"worktree.ram_overlay",
|
|
845
|
+
"worktree.ram_overlay is gone because every worktree now has its own index state, so there is no borrowed index to patch in memory.",
|
|
846
|
+
],
|
|
847
|
+
];
|
|
848
|
+
/**
|
|
849
|
+
* The sentence saying a file that still names `views.idle_checkout_hours` will
|
|
850
|
+
* be refused. Mirrors `retired_idle_checkout_hours_note` in
|
|
851
|
+
* `crates/aft/src/feature_config.rs`.
|
|
852
|
+
*/
|
|
853
|
+
export function retiredIdleCheckoutHoursNote() {
|
|
854
|
+
const [retired, replacement] = RETIRED_IDLE_CHECKOUT_HOURS;
|
|
855
|
+
return `${retired} is now ${replacement}; from ${refusedFromVersion()} AFT refuses a config file that still sets ${retired}.`;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* The once-per-identity notice for a config file, user or project, that still
|
|
859
|
+
* sets removed data-layer keys. Empty when it sets none. The file is never
|
|
860
|
+
* rewritten for them automatically; `doctor --fix` deletes them. Mirrors
|
|
861
|
+
* `removed_keys_notice` in `crates/aft/src/feature_config.rs`.
|
|
862
|
+
*/
|
|
863
|
+
export function removedConfigKeysNoticeMessage(configPath, translation) {
|
|
864
|
+
const removed = translation.removedKeys ?? [];
|
|
865
|
+
if (removed.length === 0)
|
|
866
|
+
return "";
|
|
867
|
+
const parts = [
|
|
868
|
+
`${configPath} sets removed keys (${removed.join(", ")}); AFT ignores them, and from ${refusedFromVersion()} it refuses a config file that sets them. Run \`npx @cortexkit/aft doctor --fix\` to remove them.`,
|
|
869
|
+
];
|
|
870
|
+
for (const [path, note] of REMOVED_DATA_LAYER_NOTES) {
|
|
871
|
+
if (removed.some((key) => namesPath(key, path)))
|
|
872
|
+
parts.push(note);
|
|
873
|
+
}
|
|
874
|
+
return parts.join(" ");
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Identity of the user-file notice for removed data-layer keys: the set of
|
|
878
|
+
* keys it names. The rewrite of other retired keys in the same file then does
|
|
879
|
+
* not repeat it, and adding another removed key does.
|
|
880
|
+
*/
|
|
881
|
+
export function removedConfigKeysNoticeDigest(translation) {
|
|
882
|
+
return noticeDigest({
|
|
883
|
+
notice: "removed_data_layer_keys_v1",
|
|
884
|
+
keys: sortedUnique(translation.removedKeys),
|
|
885
|
+
});
|
|
584
886
|
}
|
|
585
887
|
/**
|
|
586
888
|
* The once-per-identity notice for a project config file whose retired keys
|
|
587
889
|
* were translated in memory. The file itself is never written: it is shared
|
|
588
890
|
* through its repository. Keys that no longer do anything get only their own
|
|
589
891
|
* sentence, after the sentence about the keys whose current equivalents were
|
|
590
|
-
* applied (omitted when there are none)
|
|
591
|
-
*
|
|
892
|
+
* applied (omitted when there are none); removed data-layer keys the file
|
|
893
|
+
* sets follow with their own notice. Mirrors `project_retired_keys_notice` in
|
|
894
|
+
* `crates/aft/src/feature_config.rs`.
|
|
592
895
|
*/
|
|
593
896
|
export function legacyConfigNoticeMessage(configPath, translation) {
|
|
594
897
|
const equivalents = translation.retiredKeys.filter((key) => noEffectNote(key) === undefined);
|
|
@@ -598,18 +901,26 @@ export function legacyConfigNoticeMessage(configPath, translation) {
|
|
|
598
901
|
parts.push(`${configPath} uses retired keys (${equivalents.join(", ")}); AFT applied their current equivalents, with the same limits a project config has for those keys. Run \`npx @cortexkit/aft doctor --fix\` to update the file.`);
|
|
599
902
|
if (translation.retiredEnabledFalse)
|
|
600
903
|
parts.push(RETIRED_ENABLED_FALSE_INDEXES_NOTE);
|
|
904
|
+
if (equivalents.some((key) => namesPath(key, RETIRED_IDLE_CHECKOUT_HOURS[0]))) {
|
|
905
|
+
parts.push(retiredIdleCheckoutHoursNote());
|
|
906
|
+
}
|
|
601
907
|
}
|
|
602
908
|
for (const [, note] of NO_EFFECT_RETIRED_NOTES) {
|
|
603
909
|
if (noEffect.has(note))
|
|
604
910
|
parts.push(note);
|
|
605
911
|
}
|
|
912
|
+
const removed = removedConfigKeysNoticeMessage(configPath, translation);
|
|
913
|
+
if (removed)
|
|
914
|
+
parts.push(removed);
|
|
606
915
|
return parts.join(" ");
|
|
607
916
|
}
|
|
608
917
|
/**
|
|
609
918
|
* Translate every block (base plus each `harnesses.<id>`) in place. Retired
|
|
610
|
-
* keys are
|
|
919
|
+
* keys are not refused here: their current equivalents then pass through the
|
|
611
920
|
* normal user/project trust rules, exactly as if the file had spelled them
|
|
612
|
-
* out.
|
|
921
|
+
* out. (`removedConfigKeyErrors` refuses the names retired in 0.62 or earlier
|
|
922
|
+
* and the data-layer keys from `DATA_LAYER_KEYS_REFUSED_FROM`, before
|
|
923
|
+
* translation runs; `doctor --fix` still uses this translation for them.)
|
|
613
924
|
*
|
|
614
925
|
* Only the user file's base block receives the absent-base default
|
|
615
926
|
* (`aft_move`/`aft_delete`) when its legacy keys generate disables: that
|
|
@@ -622,6 +933,7 @@ export function translateConfigDocument(map, tier) {
|
|
|
622
933
|
warnings: [],
|
|
623
934
|
legacyInput: false,
|
|
624
935
|
retiredKeys: [],
|
|
936
|
+
removedKeys: [],
|
|
625
937
|
retiredEnabledFalse: false,
|
|
626
938
|
};
|
|
627
939
|
translateBlock(map, tier === "user", "base", out);
|
|
@@ -632,6 +944,7 @@ export function translateConfigDocument(map, tier) {
|
|
|
632
944
|
}
|
|
633
945
|
}
|
|
634
946
|
out.retiredKeys = sortedUnique(out.retiredKeys);
|
|
947
|
+
out.removedKeys = sortedUnique(out.removedKeys);
|
|
635
948
|
return out;
|
|
636
949
|
}
|
|
637
950
|
/** Distinct unknown names in a resolved disabled list, sorted. */
|
|
@@ -675,8 +988,13 @@ function projectBlock(block) {
|
|
|
675
988
|
const legacyPaths = {};
|
|
676
989
|
// Retired keys with no replacement are projected too: otherwise adding one
|
|
677
990
|
// to a file whose notice was already acknowledged would keep the old digest
|
|
678
|
-
// and the saved record would hide the new notice.
|
|
679
|
-
for
|
|
991
|
+
// and the saved record would hide the new notice. The data-layer keys are
|
|
992
|
+
// projected for the same reason.
|
|
993
|
+
for (const path of [
|
|
994
|
+
...RETIRED_PATHS.map(([retired]) => retired),
|
|
995
|
+
...REMOVED_NO_EFFECT_PATHS,
|
|
996
|
+
...DATA_LAYER_RETIRED_PATHS,
|
|
997
|
+
]) {
|
|
680
998
|
const dot = path.indexOf(".");
|
|
681
999
|
if (dot < 0) {
|
|
682
1000
|
if (Object.hasOwn(block, path))
|