@cortexkit/aft-bridge 0.57.2 → 0.58.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/bash-format.d.ts +7 -0
- package/dist/bash-format.d.ts.map +1 -1
- package/dist/bash-format.js +9 -0
- package/dist/bash-format.js.map +1 -1
- package/dist/bash-hints.d.ts +31 -3
- package/dist/bash-hints.d.ts.map +1 -1
- package/dist/bash-hints.js +40 -9
- package/dist/bash-hints.js.map +1 -1
- package/dist/bash-host-fallback.js +1 -1
- package/dist/bash-host-fallback.js.map +1 -1
- package/dist/binary-identity.d.ts +109 -0
- package/dist/binary-identity.d.ts.map +1 -0
- package/dist/binary-identity.js +295 -0
- package/dist/binary-identity.js.map +1 -0
- package/dist/bridge.d.ts +10 -1
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +74 -24
- package/dist/bridge.js.map +1 -1
- package/dist/child-process.d.ts +39 -0
- package/dist/child-process.d.ts.map +1 -0
- package/dist/child-process.js +64 -0
- package/dist/child-process.js.map +1 -0
- package/dist/coerce.d.ts +2 -0
- package/dist/coerce.d.ts.map +1 -1
- package/dist/coerce.js +29 -0
- package/dist/coerce.js.map +1 -1
- package/dist/config-error.d.ts +71 -0
- package/dist/config-error.d.ts.map +1 -0
- package/dist/config-error.js +112 -0
- package/dist/config-error.js.map +1 -0
- package/dist/config-watch.d.ts +170 -0
- package/dist/config-watch.d.ts.map +1 -0
- package/dist/config-watch.js +479 -0
- package/dist/config-watch.js.map +1 -0
- package/dist/downloader.d.ts +4 -2
- package/dist/downloader.d.ts.map +1 -1
- package/dist/downloader.js +41 -20
- package/dist/downloader.js.map +1 -1
- package/dist/durable-log.d.ts +11 -0
- package/dist/durable-log.d.ts.map +1 -1
- package/dist/durable-log.js +81 -5
- package/dist/durable-log.js.map +1 -1
- package/dist/edit-summary.d.ts +8 -0
- package/dist/edit-summary.d.ts.map +1 -1
- package/dist/edit-summary.js +7 -0
- package/dist/edit-summary.js.map +1 -1
- package/dist/feature-config.d.ts +192 -0
- package/dist/feature-config.d.ts.map +1 -0
- package/dist/feature-config.js +657 -0
- package/dist/feature-config.js.map +1 -0
- package/dist/index.d.ts +19 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -7
- package/dist/index.js.map +1 -1
- package/dist/message-detach.d.ts +25 -0
- package/dist/message-detach.d.ts.map +1 -0
- package/dist/message-detach.js +34 -0
- package/dist/message-detach.js.map +1 -0
- package/dist/migration-notices.d.ts +29 -0
- package/dist/migration-notices.d.ts.map +1 -0
- package/dist/migration-notices.js +114 -0
- package/dist/migration-notices.js.map +1 -0
- package/dist/migration.d.ts +1 -1
- package/dist/migration.d.ts.map +1 -1
- package/dist/migration.js +1 -1
- package/dist/migration.js.map +1 -1
- package/dist/npm-resolver.d.ts +1 -1
- package/dist/npm-resolver.d.ts.map +1 -1
- package/dist/npm-resolver.js +1 -1
- package/dist/npm-resolver.js.map +1 -1
- package/dist/onnx-probe.d.ts +44 -0
- package/dist/onnx-probe.d.ts.map +1 -0
- package/dist/onnx-probe.js +167 -0
- package/dist/onnx-probe.js.map +1 -0
- package/dist/onnx-runtime.d.ts +19 -4
- package/dist/onnx-runtime.d.ts.map +1 -1
- package/dist/onnx-runtime.js +146 -34
- package/dist/onnx-runtime.js.map +1 -1
- package/dist/path-lookup.d.ts +22 -0
- package/dist/path-lookup.d.ts.map +1 -0
- package/dist/path-lookup.js +51 -0
- package/dist/path-lookup.js.map +1 -0
- package/dist/resolver.d.ts +47 -21
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +254 -159
- package/dist/resolver.js.map +1 -1
- package/dist/revivable-transport.d.ts.map +1 -1
- package/dist/revivable-transport.js +12 -0
- package/dist/revivable-transport.js.map +1 -1
- package/dist/semantic-status.d.ts +10 -1
- package/dist/semantic-status.d.ts.map +1 -1
- package/dist/semantic-status.js +50 -2
- package/dist/semantic-status.js.map +1 -1
- package/dist/storage-paths.d.ts +9 -0
- package/dist/storage-paths.d.ts.map +1 -1
- package/dist/storage-paths.js +18 -1
- package/dist/storage-paths.js.map +1 -1
- package/dist/subc-transport.d.ts +58 -1
- package/dist/subc-transport.d.ts.map +1 -1
- package/dist/subc-transport.js +378 -50
- package/dist/subc-transport.js.map +1 -1
- package/dist/tar-executable.js +1 -1
- package/dist/tar-executable.js.map +1 -1
- package/dist/transport-factory.d.ts +14 -2
- package/dist/transport-factory.d.ts.map +1 -1
- package/dist/transport-factory.js +24 -16
- package/dist/transport-factory.js.map +1 -1
- package/dist/version-probe.d.ts +33 -0
- package/dist/version-probe.d.ts.map +1 -0
- package/dist/version-probe.js +108 -0
- package/dist/version-probe.js.map +1 -0
- package/package.json +2 -2
- package/dist/bash-timeout.d.ts +0 -19
- package/dist/bash-timeout.d.ts.map +0 -1
- package/dist/bash-timeout.js +0 -26
- package/dist/bash-timeout.js.map +0 -1
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature-based configuration policy shared by the OpenCode and Pi plugins.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `crates/aft/src/feature_config.rs`: the literal tool inventory, the
|
|
5
|
+
* one-release migration policy for retired keys, the per-block translation of
|
|
6
|
+
* those keys into canonical `disabled_tools` / `indexes` / `github` leaves,
|
|
7
|
+
* validation of a resolved configuration, and the migration-notice projection
|
|
8
|
+
* digest. The config parity fixtures and the shared notice-projection fixture
|
|
9
|
+
* (`crates/aft/tests/fixtures/feature_config/notice_projection.json`) keep the
|
|
10
|
+
* two implementations equal.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
/** Every agent-visible tool name a registration adapter may publish (sorted). */
|
|
14
|
+
export const CANONICAL_TOOLS = [
|
|
15
|
+
"aft_callgraph",
|
|
16
|
+
"aft_conflicts",
|
|
17
|
+
"aft_delete",
|
|
18
|
+
"aft_import",
|
|
19
|
+
"aft_inspect",
|
|
20
|
+
"aft_move",
|
|
21
|
+
"aft_outline",
|
|
22
|
+
"aft_safety",
|
|
23
|
+
"aft_search",
|
|
24
|
+
"aft_zoom",
|
|
25
|
+
"apply_patch",
|
|
26
|
+
"ast_grep_replace",
|
|
27
|
+
"ast_grep_search",
|
|
28
|
+
"bash",
|
|
29
|
+
"bash_kill",
|
|
30
|
+
"bash_status",
|
|
31
|
+
"bash_watch",
|
|
32
|
+
"bash_write",
|
|
33
|
+
"edit",
|
|
34
|
+
"glob",
|
|
35
|
+
"grep",
|
|
36
|
+
"read",
|
|
37
|
+
"write",
|
|
38
|
+
];
|
|
39
|
+
/** The seven host tool slots AFT takes over. */
|
|
40
|
+
export const HOST_TOOL_NAMES = [
|
|
41
|
+
"apply_patch",
|
|
42
|
+
"bash",
|
|
43
|
+
"edit",
|
|
44
|
+
"glob",
|
|
45
|
+
"grep",
|
|
46
|
+
"read",
|
|
47
|
+
"write",
|
|
48
|
+
];
|
|
49
|
+
/** Disables applied when the user base makes no registration choice. */
|
|
50
|
+
export const DEFAULT_DISABLED_TOOLS = ["aft_delete", "aft_move"];
|
|
51
|
+
/** Historical prefixed tool names accepted in disabled lists during the window. */
|
|
52
|
+
export const LEGACY_TOOL_ALIASES = {
|
|
53
|
+
aft_read: "read",
|
|
54
|
+
aft_write: "write",
|
|
55
|
+
aft_edit: "edit",
|
|
56
|
+
aft_apply_patch: "apply_patch",
|
|
57
|
+
aft_grep: "grep",
|
|
58
|
+
aft_glob: "glob",
|
|
59
|
+
aft_bash: "bash",
|
|
60
|
+
};
|
|
61
|
+
/** Names the historical bash registration gate removed (canonical names). */
|
|
62
|
+
export const BASH_GATE_DISABLES = [
|
|
63
|
+
"bash",
|
|
64
|
+
"bash_kill",
|
|
65
|
+
"bash_status",
|
|
66
|
+
"bash_watch",
|
|
67
|
+
"bash_write",
|
|
68
|
+
];
|
|
69
|
+
export const MIGRATION_POLICY_ID = "feature-config-v1";
|
|
70
|
+
export const POLICY_INTRODUCED_MINOR = [0, 58];
|
|
71
|
+
export const POLICY_REJECT_FROM_MINOR = [0, 59];
|
|
72
|
+
/** Retired config paths and the replacement named in their rejection. */
|
|
73
|
+
export const RETIRED_PATHS = [
|
|
74
|
+
["tool_surface", "disabled_tools"],
|
|
75
|
+
["hoist_builtin_tools", "disabled_tools"],
|
|
76
|
+
["enabled", "disabled_tools"],
|
|
77
|
+
["search_index", "indexes.trigram"],
|
|
78
|
+
["experimental_search_index", "indexes.trigram"],
|
|
79
|
+
["semantic_search", "indexes.semantic"],
|
|
80
|
+
["experimental_semantic_search", "indexes.semantic"],
|
|
81
|
+
["callgraph_store", "indexes.callgraph"],
|
|
82
|
+
["github.enabled", "github.read,github.write,github.shim"],
|
|
83
|
+
];
|
|
84
|
+
/** Index leaf, immediate legacy key and optional experimental alias. */
|
|
85
|
+
export const INDEX_INPUTS = [
|
|
86
|
+
["trigram", "search_index", "experimental_search_index"],
|
|
87
|
+
["semantic", "semantic_search", "experimental_semantic_search"],
|
|
88
|
+
["callgraph", "callgraph_store", undefined],
|
|
89
|
+
];
|
|
90
|
+
/**
|
|
91
|
+
* Registration adapters that cannot register some canonical tools because the
|
|
92
|
+
* adapter has no implementation for them. The registered set on an adapter is
|
|
93
|
+
* the canonical inventory minus resolved disables minus this list; setup
|
|
94
|
+
* reports these rows as unavailable with reason `no_implementation_on_harness`.
|
|
95
|
+
*/
|
|
96
|
+
export const ADAPTER_UNIMPLEMENTED_TOOLS = {
|
|
97
|
+
"opencode-v1": [],
|
|
98
|
+
"opencode-v2": [],
|
|
99
|
+
pi: ["apply_patch", "glob"],
|
|
100
|
+
omp: ["apply_patch", "glob"],
|
|
101
|
+
};
|
|
102
|
+
export function isKnownTool(name) {
|
|
103
|
+
return CANONICAL_TOOLS.includes(name);
|
|
104
|
+
}
|
|
105
|
+
export function isProjectProtectedTool(name) {
|
|
106
|
+
return name === "aft_safety" || HOST_TOOL_NAMES.includes(name);
|
|
107
|
+
}
|
|
108
|
+
/** Literal disabled set a legacy `tool_surface` value translates to. */
|
|
109
|
+
export function surfaceDisables(surface) {
|
|
110
|
+
switch (surface) {
|
|
111
|
+
case "all":
|
|
112
|
+
return [];
|
|
113
|
+
case "recommended":
|
|
114
|
+
return ["aft_callgraph", "aft_delete", "aft_move"];
|
|
115
|
+
case "minimal":
|
|
116
|
+
return CANONICAL_TOOLS.filter((name) => name !== "aft_outline" && name !== "aft_zoom" && name !== "aft_safety");
|
|
117
|
+
default:
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function parseMinor(version) {
|
|
122
|
+
const match = /^v?(\d+)\.(\d+)/.exec(version.trim());
|
|
123
|
+
if (!match)
|
|
124
|
+
return undefined;
|
|
125
|
+
return [Number(match[1]), Number(match[2])];
|
|
126
|
+
}
|
|
127
|
+
/** Policy phase for a package version (major/minor only; patch ignored). */
|
|
128
|
+
export function policyPhaseForVersion(version) {
|
|
129
|
+
const minor = parseMinor(version);
|
|
130
|
+
if (!minor)
|
|
131
|
+
return "window";
|
|
132
|
+
const [major, min] = minor;
|
|
133
|
+
const [rejectMajor, rejectMinor] = POLICY_REJECT_FROM_MINOR;
|
|
134
|
+
return major > rejectMajor || (major === rejectMajor && min >= rejectMinor)
|
|
135
|
+
? "rejecting"
|
|
136
|
+
: "window";
|
|
137
|
+
}
|
|
138
|
+
function isRecord(value) {
|
|
139
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
140
|
+
}
|
|
141
|
+
function removed(old, replacement) {
|
|
142
|
+
return `removed_config_key:${old}:use:${replacement}`;
|
|
143
|
+
}
|
|
144
|
+
function hasPath(map, path) {
|
|
145
|
+
const dot = path.indexOf(".");
|
|
146
|
+
if (dot < 0)
|
|
147
|
+
return Object.hasOwn(map, path);
|
|
148
|
+
const container = map[path.slice(0, dot)];
|
|
149
|
+
return isRecord(container) && Object.hasOwn(container, path.slice(dot + 1));
|
|
150
|
+
}
|
|
151
|
+
function falseRuntimeGates(map) {
|
|
152
|
+
const gates = [];
|
|
153
|
+
if (isRecord(map.backup) && map.backup.enabled === false)
|
|
154
|
+
gates.push("backup.enabled");
|
|
155
|
+
if (isRecord(map.inspect) && map.inspect.enabled === false)
|
|
156
|
+
gates.push("inspect.enabled");
|
|
157
|
+
if (map.bash === false)
|
|
158
|
+
gates.push("bash");
|
|
159
|
+
else if (isRecord(map.bash) && map.bash.enabled === false)
|
|
160
|
+
gates.push("bash.enabled");
|
|
161
|
+
return gates;
|
|
162
|
+
}
|
|
163
|
+
export function sortedUnique(names) {
|
|
164
|
+
return [...new Set(names)].sort();
|
|
165
|
+
}
|
|
166
|
+
function translateBlock(map,
|
|
167
|
+
// True only for the user file's base block, the one place the absent-base
|
|
168
|
+
// default disables may be added.
|
|
169
|
+
userBase, phase, blockLabel, out) {
|
|
170
|
+
if (Object.hasOwn(map, "gh_read"))
|
|
171
|
+
out.errors.push(removed("gh_read", "github.read"));
|
|
172
|
+
if (isRecord(map.gh_shim) && Object.hasOwn(map.gh_shim, "enabled")) {
|
|
173
|
+
out.errors.push(removed("gh_shim", "github.shim"));
|
|
174
|
+
}
|
|
175
|
+
let explicitList;
|
|
176
|
+
const rawList = map.disabled_tools;
|
|
177
|
+
if (Array.isArray(rawList) && rawList.every((entry) => typeof entry === "string")) {
|
|
178
|
+
explicitList = rawList.map((name) => {
|
|
179
|
+
const host = LEGACY_TOOL_ALIASES[name];
|
|
180
|
+
if (host === undefined)
|
|
181
|
+
return name;
|
|
182
|
+
out.legacyInput = true;
|
|
183
|
+
if (phase === "rejecting")
|
|
184
|
+
out.errors.push(removed(name, host));
|
|
185
|
+
return host;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const suppliedPaths = RETIRED_PATHS.map(([path]) => path).filter((path) => hasPath(map, path));
|
|
189
|
+
if (suppliedPaths.length > 0)
|
|
190
|
+
out.legacyInput = true;
|
|
191
|
+
const gates = falseRuntimeGates(map);
|
|
192
|
+
if (phase === "rejecting") {
|
|
193
|
+
for (const path of suppliedPaths) {
|
|
194
|
+
const replacement = RETIRED_PATHS.find(([old]) => old === path)?.[1] ?? "disabled_tools";
|
|
195
|
+
out.errors.push(removed(path, replacement));
|
|
196
|
+
}
|
|
197
|
+
if (gates.length > 0 && explicitList === undefined) {
|
|
198
|
+
out.warnings.push({
|
|
199
|
+
code: "legacy_runtime_gate_runtime_only",
|
|
200
|
+
key: blockLabel,
|
|
201
|
+
message: `${gates.join(", ")} now restricts runtime behavior only and no longer removes tool registrations; list tools in disabled_tools to unregister them`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (explicitList !== undefined)
|
|
207
|
+
map.disabled_tools = explicitList;
|
|
208
|
+
for (const [leaf, legacy, experimental] of INDEX_INPUTS) {
|
|
209
|
+
const indexes = isRecord(map.indexes) ? map.indexes : undefined;
|
|
210
|
+
const canonical = typeof indexes?.[leaf] === "boolean" ? indexes[leaf] : undefined;
|
|
211
|
+
const inputs = [];
|
|
212
|
+
if (Object.hasOwn(map, legacy)) {
|
|
213
|
+
inputs.push([legacy, map[legacy]]);
|
|
214
|
+
delete map[legacy];
|
|
215
|
+
}
|
|
216
|
+
if (experimental !== undefined && Object.hasOwn(map, experimental)) {
|
|
217
|
+
inputs.push([experimental, map[experimental]]);
|
|
218
|
+
delete map[experimental];
|
|
219
|
+
}
|
|
220
|
+
let chosen;
|
|
221
|
+
for (const [key, value] of inputs) {
|
|
222
|
+
if (typeof value !== "boolean") {
|
|
223
|
+
out.warnings.push({
|
|
224
|
+
code: "invalid_legacy_config",
|
|
225
|
+
key,
|
|
226
|
+
message: `Ignoring non-boolean ${key}; use indexes.${leaf}`,
|
|
227
|
+
});
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (canonical !== undefined) {
|
|
231
|
+
if (canonical !== value) {
|
|
232
|
+
out.warnings.push({
|
|
233
|
+
code: "superseded_legacy_config",
|
|
234
|
+
key,
|
|
235
|
+
message: `${key}=${value} is ignored because indexes.${leaf}=${canonical} is set`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
else if (chosen !== undefined) {
|
|
240
|
+
if (chosen !== value) {
|
|
241
|
+
out.warnings.push({
|
|
242
|
+
code: "superseded_legacy_config",
|
|
243
|
+
key,
|
|
244
|
+
message: `${key}=${value} is ignored because ${legacy}=${chosen} takes precedence`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
chosen = value;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (canonical === undefined && chosen !== undefined) {
|
|
253
|
+
if (!Object.hasOwn(map, "indexes"))
|
|
254
|
+
map.indexes = {};
|
|
255
|
+
if (isRecord(map.indexes))
|
|
256
|
+
map.indexes[leaf] = chosen;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (isRecord(map.github) && Object.hasOwn(map.github, "enabled")) {
|
|
260
|
+
const github = map.github;
|
|
261
|
+
const master = github.enabled;
|
|
262
|
+
delete github.enabled;
|
|
263
|
+
if (master === false) {
|
|
264
|
+
for (const leaf of ["read", "write", "shim"]) {
|
|
265
|
+
if (!Object.hasOwn(github, leaf))
|
|
266
|
+
github[leaf] = false;
|
|
267
|
+
else if (github[leaf] === true) {
|
|
268
|
+
out.warnings.push({
|
|
269
|
+
code: "superseded_legacy_config",
|
|
270
|
+
key: "github.enabled",
|
|
271
|
+
message: `github.enabled=false is ignored for github.${leaf} because github.${leaf}=true is set`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const surface = map.tool_surface;
|
|
278
|
+
const hasSurface = Object.hasOwn(map, "tool_surface");
|
|
279
|
+
const hoist = map.hoist_builtin_tools;
|
|
280
|
+
const enabled = map.enabled;
|
|
281
|
+
delete map.tool_surface;
|
|
282
|
+
delete map.hoist_builtin_tools;
|
|
283
|
+
delete map.enabled;
|
|
284
|
+
const generated = new Set();
|
|
285
|
+
let explicitSurface = false;
|
|
286
|
+
if (hasSurface) {
|
|
287
|
+
const names = surfaceDisables(surface);
|
|
288
|
+
if (names === undefined) {
|
|
289
|
+
out.warnings.push({
|
|
290
|
+
code: "invalid_legacy_config",
|
|
291
|
+
key: "tool_surface",
|
|
292
|
+
message: "Ignoring unknown tool_surface value; use disabled_tools",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
explicitSurface = true;
|
|
297
|
+
for (const name of names)
|
|
298
|
+
generated.add(name);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (hoist === false)
|
|
302
|
+
for (const name of HOST_TOOL_NAMES)
|
|
303
|
+
generated.add(name);
|
|
304
|
+
if (enabled === false) {
|
|
305
|
+
for (const name of CANONICAL_TOOLS)
|
|
306
|
+
generated.add(name);
|
|
307
|
+
out.retiredEnabledFalse = true;
|
|
308
|
+
out.warnings.push({
|
|
309
|
+
code: "legacy_enabled_false_indexes_still_build",
|
|
310
|
+
key: blockLabel === "base" ? "enabled" : `${blockLabel}.enabled`,
|
|
311
|
+
message: RETIRED_ENABLED_FALSE_INDEXES_NOTE,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
for (const gate of gates) {
|
|
315
|
+
if (gate === "backup.enabled")
|
|
316
|
+
generated.add("aft_safety");
|
|
317
|
+
else if (gate === "inspect.enabled")
|
|
318
|
+
generated.add("aft_inspect");
|
|
319
|
+
else
|
|
320
|
+
for (const name of BASH_GATE_DISABLES)
|
|
321
|
+
generated.add(name);
|
|
322
|
+
}
|
|
323
|
+
if (explicitList !== undefined) {
|
|
324
|
+
if (explicitSurface || generated.size > 0) {
|
|
325
|
+
out.warnings.push({
|
|
326
|
+
code: "superseded_legacy_config",
|
|
327
|
+
key: blockLabel,
|
|
328
|
+
message: "disabled_tools is set explicitly, so legacy tool_surface/hoist_builtin_tools/enabled and runtime gates do not change registration",
|
|
329
|
+
once: true,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
let list;
|
|
335
|
+
if (userBase) {
|
|
336
|
+
if (explicitSurface)
|
|
337
|
+
list = sortedUnique(generated);
|
|
338
|
+
else if (generated.size > 0)
|
|
339
|
+
list = sortedUnique([...generated, ...DEFAULT_DISABLED_TOOLS]);
|
|
340
|
+
}
|
|
341
|
+
else if (generated.size > 0) {
|
|
342
|
+
list = sortedUnique(generated);
|
|
343
|
+
}
|
|
344
|
+
if (list !== undefined)
|
|
345
|
+
map.disabled_tools = list;
|
|
346
|
+
if (gates.length > 0) {
|
|
347
|
+
out.warnings.push({
|
|
348
|
+
code: "legacy_runtime_gate_requires_fix",
|
|
349
|
+
key: blockLabel,
|
|
350
|
+
message: `${gates.join(", ")} still removes tool registrations during this release only; run \`npx @cortexkit/aft doctor --fix\` to record the choice in disabled_tools`,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* What a retired `enabled: false` no longer does. The translation only hides
|
|
356
|
+
* tools; it never stops indexing, so a user who relied on it to keep AFT out
|
|
357
|
+
* of a repository must also switch the indexes off.
|
|
358
|
+
*/
|
|
359
|
+
export const RETIRED_ENABLED_FALSE_INDEXES_NOTE = "enabled: false no longer turns AFT off: it is translated to disabling every tool, but the trigram, semantic and callgraph indexes still build. To keep AFT from indexing this repository, also set indexes.trigram, indexes.semantic and indexes.callgraph to false.";
|
|
360
|
+
/** The once-per-identity migration notice for a config file that used retired keys. */
|
|
361
|
+
export function legacyConfigNoticeMessage(configPath, translation) {
|
|
362
|
+
const base = `AFT config ${configPath} uses retired keys (tool_surface, hoist_builtin_tools, enabled, search_index, semantic_search, callgraph_store, github.enabled or aft_-prefixed tool names). They are translated for this release and rejected from v0.59; run \`npx @cortexkit/aft doctor --fix\` to migrate.`;
|
|
363
|
+
return translation.retiredEnabledFalse ? `${base} ${RETIRED_ENABLED_FALSE_INDEXES_NOTE}` : base;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Translate or reject every block (base plus each `harnesses.<id>`) in place.
|
|
367
|
+
*
|
|
368
|
+
* Only the user file's base block receives the absent-base default
|
|
369
|
+
* (`aft_move`/`aft_delete`) when its legacy keys generate disables: that
|
|
370
|
+
* default applies once, at user-base resolution. A project file's base block
|
|
371
|
+
* contributes only the names its own legacy keys imply, so a legacy key in a
|
|
372
|
+
* repository can never re-disable tools the user enabled.
|
|
373
|
+
*/
|
|
374
|
+
export function translateConfigDocument(map, phase, tier) {
|
|
375
|
+
const out = {
|
|
376
|
+
errors: [],
|
|
377
|
+
warnings: [],
|
|
378
|
+
legacyInput: false,
|
|
379
|
+
retiredEnabledFalse: false,
|
|
380
|
+
};
|
|
381
|
+
translateBlock(map, tier === "user", phase, "base", out);
|
|
382
|
+
if (isRecord(map.harnesses)) {
|
|
383
|
+
for (const [name, block] of Object.entries(map.harnesses)) {
|
|
384
|
+
if (isRecord(block))
|
|
385
|
+
translateBlock(block, false, phase, `harnesses.${name}`, out);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
out.errors = sortedUnique(out.errors);
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
/** Distinct unknown names in a resolved disabled list, sorted. */
|
|
392
|
+
export function unknownDisabledTools(disabled) {
|
|
393
|
+
return sortedUnique(disabled.filter((name) => !isKnownTool(name)));
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Validate a resolved configuration object. Missing fields are never replaced
|
|
397
|
+
* by defaults here; missing containers are reported instead of their children
|
|
398
|
+
* and errors are sorted by path.
|
|
399
|
+
*/
|
|
400
|
+
export function validateResolvedConfig(value) {
|
|
401
|
+
if (!isRecord(value))
|
|
402
|
+
return ["invalid_resolved_config:type:$"];
|
|
403
|
+
const errors = [];
|
|
404
|
+
const disabled = value.disabled_tools;
|
|
405
|
+
if (disabled === undefined)
|
|
406
|
+
errors.push("invalid_resolved_config:missing:disabled_tools");
|
|
407
|
+
else if (!Array.isArray(disabled) || !disabled.every((entry) => typeof entry === "string")) {
|
|
408
|
+
errors.push("invalid_resolved_config:type:disabled_tools");
|
|
409
|
+
}
|
|
410
|
+
const indexes = value.indexes;
|
|
411
|
+
if (indexes === undefined)
|
|
412
|
+
errors.push("invalid_resolved_config:missing:indexes");
|
|
413
|
+
else if (!isRecord(indexes))
|
|
414
|
+
errors.push("invalid_resolved_config:type:indexes");
|
|
415
|
+
else {
|
|
416
|
+
for (const leaf of ["callgraph", "semantic", "trigram"]) {
|
|
417
|
+
if (indexes[leaf] === undefined)
|
|
418
|
+
errors.push(`invalid_resolved_config:missing:indexes.${leaf}`);
|
|
419
|
+
else if (typeof indexes[leaf] !== "boolean") {
|
|
420
|
+
errors.push(`invalid_resolved_config:type:indexes.${leaf}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const path = (error) => error.slice(error.lastIndexOf(":") + 1);
|
|
425
|
+
return errors.sort((left, right) => path(left) < path(right) ? -1 : path(left) > path(right) ? 1 : 0);
|
|
426
|
+
}
|
|
427
|
+
const ABSENT = { absent: true };
|
|
428
|
+
function projectBlock(block) {
|
|
429
|
+
const legacyPaths = {};
|
|
430
|
+
for (const [path] of RETIRED_PATHS) {
|
|
431
|
+
const dot = path.indexOf(".");
|
|
432
|
+
if (dot < 0) {
|
|
433
|
+
if (Object.hasOwn(block, path))
|
|
434
|
+
legacyPaths[path] = block[path];
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const container = block[path.slice(0, dot)];
|
|
438
|
+
const leaf = path.slice(dot + 1);
|
|
439
|
+
if (isRecord(container) && Object.hasOwn(container, leaf))
|
|
440
|
+
legacyPaths[path] = container[leaf];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const gates = {};
|
|
444
|
+
if (isRecord(block.backup) && Object.hasOwn(block.backup, "enabled")) {
|
|
445
|
+
gates["backup.enabled"] = block.backup.enabled;
|
|
446
|
+
}
|
|
447
|
+
if (isRecord(block.inspect) && Object.hasOwn(block.inspect, "enabled")) {
|
|
448
|
+
gates["inspect.enabled"] = block.inspect.enabled;
|
|
449
|
+
}
|
|
450
|
+
if (typeof block.bash === "boolean")
|
|
451
|
+
gates.bash = block.bash;
|
|
452
|
+
else if (isRecord(block.bash) && Object.hasOwn(block.bash, "enabled")) {
|
|
453
|
+
gates["bash.enabled"] = block.bash.enabled;
|
|
454
|
+
}
|
|
455
|
+
let aliases = [];
|
|
456
|
+
let disabled = ABSENT;
|
|
457
|
+
if (Array.isArray(block.disabled_tools)) {
|
|
458
|
+
const names = block.disabled_tools.filter((entry) => typeof entry === "string");
|
|
459
|
+
aliases = sortedUnique(names.filter((name) => LEGACY_TOOL_ALIASES[name] !== undefined));
|
|
460
|
+
disabled = sortedUnique(names);
|
|
461
|
+
}
|
|
462
|
+
else if (Object.hasOwn(block, "disabled_tools")) {
|
|
463
|
+
disabled = block.disabled_tools;
|
|
464
|
+
}
|
|
465
|
+
const github = {};
|
|
466
|
+
for (const leaf of ["read", "shim", "write"]) {
|
|
467
|
+
github[leaf] =
|
|
468
|
+
isRecord(block.github) && Object.hasOwn(block.github, leaf) ? block.github[leaf] : ABSENT;
|
|
469
|
+
}
|
|
470
|
+
const indexes = {};
|
|
471
|
+
for (const [leaf, legacy, experimental] of INDEX_INPUTS) {
|
|
472
|
+
const inputs = {
|
|
473
|
+
canonical: isRecord(block.indexes) && Object.hasOwn(block.indexes, leaf)
|
|
474
|
+
? block.indexes[leaf]
|
|
475
|
+
: ABSENT,
|
|
476
|
+
[legacy]: Object.hasOwn(block, legacy) ? block[legacy] : ABSENT,
|
|
477
|
+
};
|
|
478
|
+
if (experimental !== undefined) {
|
|
479
|
+
inputs[experimental] = Object.hasOwn(block, experimental) ? block[experimental] : ABSENT;
|
|
480
|
+
}
|
|
481
|
+
indexes[leaf] = inputs;
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
legacy_paths: legacyPaths,
|
|
485
|
+
runtime_gates: gates,
|
|
486
|
+
legacy_disabled_entries: aliases,
|
|
487
|
+
disabled_tools: disabled,
|
|
488
|
+
github,
|
|
489
|
+
indexes,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Build the `notice_projection_v1` object for one raw (untranslated) config
|
|
494
|
+
* document. Only translation-relevant inputs are projected, so comments,
|
|
495
|
+
* formatting, key order and unrelated settings never change it.
|
|
496
|
+
*/
|
|
497
|
+
export function noticeProjection(raw) {
|
|
498
|
+
const blocks = {};
|
|
499
|
+
if (raw !== undefined) {
|
|
500
|
+
blocks.base = projectBlock(raw);
|
|
501
|
+
if (isRecord(raw.harnesses)) {
|
|
502
|
+
for (const [name, block] of Object.entries(raw.harnesses)) {
|
|
503
|
+
if (isRecord(block))
|
|
504
|
+
blocks[`harnesses.${name}`] = projectBlock(block);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return { projection: "notice_projection_v1", file_absent: raw === undefined, blocks };
|
|
509
|
+
}
|
|
510
|
+
/** Compact JSON with object keys sorted at every level. */
|
|
511
|
+
export function canonicalJson(value) {
|
|
512
|
+
if (Array.isArray(value))
|
|
513
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
514
|
+
if (isRecord(value)) {
|
|
515
|
+
return `{${Object.keys(value)
|
|
516
|
+
.sort()
|
|
517
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
518
|
+
.join(",")}}`;
|
|
519
|
+
}
|
|
520
|
+
return JSON.stringify(value);
|
|
521
|
+
}
|
|
522
|
+
/** SHA-256 hex digest of a canonical projection. */
|
|
523
|
+
export function noticeDigest(projection) {
|
|
524
|
+
return createHash("sha256").update(canonicalJson(projection), "utf8").digest("hex");
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* One-time notice shown when the semantic index is on only because indexes now
|
|
528
|
+
* default on. The text follows the feature-config spec, except that commands
|
|
529
|
+
* are spelled as `npx @cortexkit/aft …`: users reach this notice through the
|
|
530
|
+
* plugin, with no `aft` command on their PATH.
|
|
531
|
+
*/
|
|
532
|
+
export const SEMANTIC_COST_NOTICE = "AFT indexes now default on; the local semantic backend may download an ONNX runtime and model and use CPU. Run npx @cortexkit/aft setup to change indexes.semantic; if legacy configuration is rejected, run npx @cortexkit/aft doctor --fix first.";
|
|
533
|
+
/**
|
|
534
|
+
* Identity of the semantic cost notice. It is a constant rather than a digest
|
|
535
|
+
* of the config file, so the notice is delivered once per user config path and
|
|
536
|
+
* unrelated config edits never bring it back.
|
|
537
|
+
*/
|
|
538
|
+
export const SEMANTIC_COST_NOTICE_DIGEST = noticeDigest({ notice: "semantic_default_cost_v1" });
|
|
539
|
+
/**
|
|
540
|
+
* True when a raw config document supplies any semantic index input (the
|
|
541
|
+
* canonical `indexes.semantic` leaf or a legacy/experimental alias) in its base
|
|
542
|
+
* block or in the block of `activeHarness`. A supplied value, true or false,
|
|
543
|
+
* means the user chose the setting, so the default-on cost notice does not
|
|
544
|
+
* apply.
|
|
545
|
+
*/
|
|
546
|
+
export function suppliesSemanticIndexInput(raw, activeHarness) {
|
|
547
|
+
if (raw === undefined)
|
|
548
|
+
return false;
|
|
549
|
+
const [leaf, immediate, experimental] = INDEX_INPUTS.find(([name]) => name === "semantic") ?? [
|
|
550
|
+
"semantic",
|
|
551
|
+
"semantic_search",
|
|
552
|
+
"experimental_semantic_search",
|
|
553
|
+
];
|
|
554
|
+
const blockSupplies = (block) => {
|
|
555
|
+
if (!isRecord(block))
|
|
556
|
+
return false;
|
|
557
|
+
if (isRecord(block.indexes) && block.indexes[leaf] !== undefined)
|
|
558
|
+
return true;
|
|
559
|
+
if (block[immediate] !== undefined)
|
|
560
|
+
return true;
|
|
561
|
+
return experimental !== undefined && block[experimental] !== undefined;
|
|
562
|
+
};
|
|
563
|
+
const harnessBlock = isRecord(raw.harnesses) ? raw.harnesses[activeHarness] : undefined;
|
|
564
|
+
return blockSupplies(raw) || blockSupplies(harnessBlock);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* The semantic cost notice for one load, or null when it does not apply. It is
|
|
568
|
+
* a migration notice: it applies only to an existing config file that relied
|
|
569
|
+
* on the old default, where the semantic index is now effectively on because
|
|
570
|
+
* of the new default. That means a config file was loaded, no loaded tier
|
|
571
|
+
* supplied a semantic input, and no non-local embedding backend is configured
|
|
572
|
+
* (a remote backend downloads nothing). A fresh install has no file that ever
|
|
573
|
+
* relied on the old default, so it gets no notice.
|
|
574
|
+
*/
|
|
575
|
+
export function semanticCostNotice(options) {
|
|
576
|
+
if (!options.configFileLoaded)
|
|
577
|
+
return null;
|
|
578
|
+
if (!options.semanticEffective || options.semanticInputSupplied)
|
|
579
|
+
return null;
|
|
580
|
+
if (options.semanticBackend !== undefined && options.semanticBackend !== "fastembed")
|
|
581
|
+
return null;
|
|
582
|
+
return {
|
|
583
|
+
configPath: options.userConfigPath,
|
|
584
|
+
digest: SEMANTIC_COST_NOTICE_DIGEST,
|
|
585
|
+
message: SEMANTIC_COST_NOTICE,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
export const DEFAULT_INDEXES = {
|
|
589
|
+
trigram: true,
|
|
590
|
+
semantic: true,
|
|
591
|
+
callgraph: true,
|
|
592
|
+
};
|
|
593
|
+
/** Whole-load rejection: the candidate config must not be used at all. */
|
|
594
|
+
export class ConfigRejectedError extends Error {
|
|
595
|
+
errors;
|
|
596
|
+
constructor(errors, source) {
|
|
597
|
+
super(`AFT configuration${source ? ` at ${source}` : ""} was rejected: ${errors.join(", ")}. ` +
|
|
598
|
+
"Run `npx @cortexkit/aft doctor --fix` to migrate removed keys.");
|
|
599
|
+
this.name = "ConfigRejectedError";
|
|
600
|
+
this.errors = errors;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Union two raw disabled lists while preserving presence: an explicit empty
|
|
605
|
+
* list on either side yields an explicit (possibly empty) sorted result.
|
|
606
|
+
*/
|
|
607
|
+
export function unionDisabledTools(base, override) {
|
|
608
|
+
if (override === undefined)
|
|
609
|
+
return base === undefined ? undefined : [...base];
|
|
610
|
+
return sortedUnique([...(base ?? []), ...override]);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Merge index switches. Trusted overrides (user harness block) replace each
|
|
614
|
+
* supplied leaf; restricted overrides (project base or harness) can only turn
|
|
615
|
+
* a leaf off.
|
|
616
|
+
*/
|
|
617
|
+
export function mergeIndexes(base, override, restricted) {
|
|
618
|
+
if (override === undefined)
|
|
619
|
+
return base;
|
|
620
|
+
const merged = { ...base };
|
|
621
|
+
for (const leaf of ["trigram", "semantic", "callgraph"]) {
|
|
622
|
+
const value = override[leaf];
|
|
623
|
+
if (value === false)
|
|
624
|
+
merged[leaf] = false;
|
|
625
|
+
else if (value === true && !restricted)
|
|
626
|
+
merged[leaf] = true;
|
|
627
|
+
}
|
|
628
|
+
return merged;
|
|
629
|
+
}
|
|
630
|
+
/** Resolve raw index switches onto their defaults. */
|
|
631
|
+
export function resolveIndexes(raw) {
|
|
632
|
+
return {
|
|
633
|
+
trigram: raw?.trigram ?? DEFAULT_INDEXES.trigram,
|
|
634
|
+
semantic: raw?.semantic ?? DEFAULT_INDEXES.semantic,
|
|
635
|
+
callgraph: raw?.callgraph ?? DEFAULT_INDEXES.callgraph,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Split a project disabled list into accepted names and ignored protected
|
|
640
|
+
* slots (aft_safety and the seven host tools).
|
|
641
|
+
*/
|
|
642
|
+
export function partitionProjectDisables(names) {
|
|
643
|
+
if (names === undefined)
|
|
644
|
+
return { accepted: undefined, ignored: [] };
|
|
645
|
+
return {
|
|
646
|
+
accepted: names.filter((name) => !isProjectProtectedTool(name)),
|
|
647
|
+
ignored: sortedUnique(names.filter(isProjectProtectedTool)),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
/** The resolved disabled list of a finalized config; absence is a bug, not []. */
|
|
651
|
+
export function requireResolvedDisabledTools(config) {
|
|
652
|
+
if (config.disabled_tools === undefined) {
|
|
653
|
+
throw new ConfigRejectedError(["invalid_resolved_config:missing:disabled_tools"]);
|
|
654
|
+
}
|
|
655
|
+
return config.disabled_tools;
|
|
656
|
+
}
|
|
657
|
+
//# sourceMappingURL=feature-config.js.map
|