akm-cli 0.10.261016-beta → 0.10.261018-beta
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/CHANGELOG.md +144 -0
- package/README.md +8 -10
- package/SECURITY.md +7 -99
- package/STABILITY.md +57 -98
- package/dist/assets/opencode/akm-plugin.js +103 -1
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -2
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +11 -17
- package/dist/cli.js +32 -41
- package/dist/commands/feedback-cli.js +5 -4
- package/dist/commands/health.js +15 -16
- package/dist/commands/proposal/proposal-cli.js +1 -1
- package/dist/commands/read/registry-search.js +4 -1
- package/dist/commands/read/search.js +6 -6
- package/dist/commands/sources/self-update.js +9 -5
- package/dist/commands/tasks/tasks.js +12 -13
- package/dist/core/adapter/adapters/agent-skills-adapter.js +7 -7
- package/dist/core/adapter/adapters/akm-task-adapter.js +1 -1
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/adapters/claude-adapter.js +1 -1
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +1 -1
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +1 -1
- package/dist/core/adapter/adapters/opencode-adapter.js +1 -1
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -1
- package/dist/core/bundle-rename.js +17 -7
- package/dist/core/config/convert-engine-references.js +23 -7
- package/dist/core/config/convert-engines.js +94 -28
- package/dist/core/config/convert-models-overlay.js +14 -2
- package/dist/indexer/bundle-identity-guard.js +6 -6
- package/dist/indexer/index-written-assets.js +3 -3
- package/dist/indexer/indexer.js +12 -8
- package/dist/indexer/materialize-embeddings.js +12 -2
- package/dist/indexer/passes/metadata.js +103 -46
- package/dist/indexer/search/ranking.js +7 -3
- package/dist/indexer/search/search-fields.js +1 -1
- package/dist/indexer/usage/usage-events.js +30 -8
- package/dist/indexer/walk/walker.js +1 -1
- package/dist/integrations/agent/spawn.js +7 -2
- package/dist/integrations/harnesses/opencode/agent-builder.js +10 -1
- package/dist/integrations/harnesses/opencode/runtime/binary.js +6 -3
- package/dist/integrations/harnesses/opencode/runtime/index.js +18 -9
- package/dist/integrations/harnesses/opencode/session-log-v2.js +11 -1
- package/dist/output/shapes/helpers.js +3 -3
- package/dist/output/text/command-format.js +5 -6
- package/dist/scripts/akm-migrate-node.js +122 -36
- package/dist/scripts/akm-migrate.js +122 -36
- package/dist/sources/providers/git-provider.js +2 -4
- package/dist/sources/providers/provider-utils.js +8 -13
- package/dist/sources/snapshot-fetchers/website-ingest.js +6 -7
- package/dist/storage/repositories/events-repository.js +15 -0
- package/dist/storage/repositories/improve-ledger-repository.js +13 -0
- package/dist/storage/repositories/index-entries-repository.js +43 -5
- package/dist/storage/repositories/outcome-repository.js +12 -0
- package/dist/storage/repositories/proposals-repository.js +5 -5
- package/dist/storage/repositories/salience-repository.js +13 -0
- package/dist/storage/repositories/task-history-repository.js +8 -7
- package/dist/storage/repositories/workflow-runs-repository.js +10 -8
- package/docs/README.md +24 -60
- package/docs/agents/README.md +3 -8
- package/docs/architecture/README.md +18 -20
- package/docs/architecture/adapters.md +40 -43
- package/docs/architecture/architecture.md +180 -154
- package/docs/architecture/improvement.md +95 -91
- package/docs/architecture/internals/classification.md +101 -119
- package/docs/architecture/internals/fresh-host-rebuild-runbook.md +33 -35
- package/docs/architecture/internals/health-advisories.md +59 -46
- package/docs/architecture/internals/improve-workflow.md +166 -693
- package/docs/architecture/internals/indexing.md +65 -465
- package/docs/architecture/internals/storage-locations.md +301 -848
- package/docs/architecture/persisted-data-compat.md +6 -6
- package/docs/architecture/specs/okf-support.md +67 -95
- package/docs/architecture/specs/ref.md +67 -125
- package/docs/architecture/testing/testing-workflow.md +108 -168
- package/docs/architecture/workflow-engine.md +34 -33
- package/docs/guides/README.md +5 -5
- package/docs/guides/author-bundles.md +72 -98
- package/docs/guides/author-workflows.md +11 -18
- package/docs/guides/bundles.md +91 -123
- package/docs/guides/capture-knowledge.md +10 -9
- package/docs/guides/concepts.md +45 -30
- package/docs/guides/discover-and-load.md +39 -32
- package/docs/guides/getting-started.md +24 -24
- package/docs/guides/improve-the-library.md +98 -80
- package/docs/guides/recipes/headless-install.md +4 -4
- package/docs/guides/recipes/website-source.md +9 -5
- package/docs/guides/run-workflows.md +10 -11
- package/docs/guides/scheduling.md +11 -17
- package/docs/guides/use-with-any-agent.md +15 -14
- package/docs/guides/wikis.md +6 -9
- package/docs/integration/bundling-akm.md +21 -26
- package/docs/maintainers/README.md +2 -2
- package/docs/maintainers/local-development.md +3 -16
- package/docs/migration/README.md +2 -1
- package/docs/migration/release-notes/0.10.0.md +196 -23
- package/docs/migration/release-notes/README.md +2 -2
- package/docs/migration/v0.9-to-v0.10.md +103 -12
- package/docs/product-surface.md +10 -8
- package/docs/reference/README.md +26 -15
- package/docs/reference/asset-types.md +11 -12
- package/docs/reference/bundle-types.md +6 -5
- package/docs/reference/cli.md +52 -28
- package/docs/reference/configuration.md +91 -73
- package/docs/reference/data-and-telemetry.md +204 -421
- package/docs/reference/env-and-secrets.md +80 -83
- package/docs/reference/memory.md +1 -1
- package/docs/reference/refs.md +23 -36
- package/docs/reference/registry.md +138 -348
- package/docs/reference/supported-formats.md +8 -8
- package/docs/reference/tasks.md +13 -19
- package/docs/reference/website-sources.md +23 -15
- package/docs/reference/workflow-schema.md +28 -47
- package/docs/reference/workflows.md +33 -37
- package/package.json +1 -1
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* bundle's key. A bundle id is a mass identity prefix: every durable ref this
|
|
7
7
|
* tool minted (`entries.item_ref`/`bundle_id`, `proposals.ref`, a pending
|
|
8
8
|
* proposal's `proposedTarget.source`, a workflow task_history row's
|
|
9
|
-
* `target_ref`, a `workflow_runs.workflow_ref`, `
|
|
9
|
+
* `target_ref`, a `workflow_runs.workflow_ref`, a `usage_events.entry_ref`, an
|
|
10
|
+
* `events.ref`, an `asset_salience`/`asset_outcome` `asset_ref`, an
|
|
11
|
+
* `improve_ledger.ref`, `scheduler.enabled[].ref`) carries it, and a hand-edited
|
|
10
12
|
* `bundles` key strands all of it (see `src/indexer/bundle-identity-guard.ts`
|
|
11
13
|
* §11.5). This module is the rekey transaction that guard's docstring points
|
|
12
14
|
* to.
|
|
@@ -20,17 +22,17 @@
|
|
|
20
22
|
* `akm mv`/editing territory, not a config-identity rename. `--dry-run`
|
|
21
23
|
* (and every real run) lists the indexed files that still spell the old
|
|
22
24
|
* `<old>//` prefix so the operator can follow up.
|
|
23
|
-
*
|
|
24
|
-
* Left behind on purpose: `usage_events`/`asset_salience`/`asset_outcome`
|
|
25
|
-
* keyed by the old bundle prefix (learned utility/outcome history) — outside
|
|
26
|
-
* this item's scope; `warnOnBundleRenameDrift` already documents this class
|
|
27
|
-
* of gap and `akm index --full` re-mints under the new id.
|
|
28
25
|
*/
|
|
29
26
|
import fs from "node:fs";
|
|
27
|
+
import { renameUsageEventRefs } from "../indexer/usage/usage-events.js";
|
|
30
28
|
import { readLockfile, renameLockEntry } from "../integrations/lockfile.js";
|
|
29
|
+
import { renameEventRefs } from "../storage/repositories/events-repository.js";
|
|
30
|
+
import { renameImproveLedgerRefs } from "../storage/repositories/improve-ledger-repository.js";
|
|
31
31
|
import { closeDatabase, openIndexDatabase, openReadonlyExistingDatabase, } from "../storage/repositories/index-connection.js";
|
|
32
32
|
import { getFilePathsByBundle, renameEntriesBundleId } from "../storage/repositories/index-entries-repository.js";
|
|
33
|
+
import { renameAssetOutcomeRefs } from "../storage/repositories/outcome-repository.js";
|
|
33
34
|
import { countProposalsForBundleRename, renameProposalsBundleRef } from "../storage/repositories/proposals-repository.js";
|
|
35
|
+
import { renameAssetSalienceRefs } from "../storage/repositories/salience-repository.js";
|
|
34
36
|
import { countTaskHistoryTargetRefs, renameTaskHistoryTargetRefs, } from "../storage/repositories/task-history-repository.js";
|
|
35
37
|
import { countWorkflowRunRefsForRename, renameWorkflowRunRefs } from "../storage/repositories/workflow-runs-repository.js";
|
|
36
38
|
import { selectBackend } from "../tasks/backends/index.js";
|
|
@@ -262,12 +264,20 @@ export async function renameBundle(oldId, newId, options = {}, deps) {
|
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
266
|
// State: proposals.ref, a pending proposal's proposedTarget.source,
|
|
265
|
-
// workflow task_history.target_ref,
|
|
267
|
+
// workflow task_history.target_ref, workflow_runs.workflow_ref, and the
|
|
268
|
+
// learned history keyed by item ref: usage_events.entry_ref (search ranking
|
|
269
|
+
// counts feedback by it), events.ref and improve_ledger.ref (improve reads
|
|
270
|
+
// feedback and its own attempts by them), and asset_salience/asset_outcome.asset_ref.
|
|
266
271
|
if (fs.existsSync(getStateDbPath())) {
|
|
267
272
|
withStateDb((db) => {
|
|
268
273
|
renameProposalsBundleRef(db, oldId, newId);
|
|
269
274
|
renameTaskHistoryTargetRefs(db, oldId, newId);
|
|
270
275
|
renameWorkflowRunRefs(db, oldId, newId);
|
|
276
|
+
renameUsageEventRefs(db, oldId, newId);
|
|
277
|
+
renameEventRefs(db, oldId, newId);
|
|
278
|
+
renameImproveLedgerRefs(db, oldId, newId);
|
|
279
|
+
renameAssetSalienceRefs(db, oldId, newId);
|
|
280
|
+
renameAssetOutcomeRefs(db, oldId, newId);
|
|
271
281
|
});
|
|
272
282
|
}
|
|
273
283
|
// Scheduler: remove native rows still naming `<old>//` (see
|
|
@@ -85,14 +85,30 @@ export function improveStrategyDefault(out, defaults, improve) {
|
|
|
85
85
|
out.improve = { ...(isRecord(improve) ? improve : {}) };
|
|
86
86
|
out.improve.strategy ??= defaults.improveStrategy;
|
|
87
87
|
}
|
|
88
|
-
/**
|
|
89
|
-
|
|
88
|
+
/**
|
|
89
|
+
* `workflow.judgeEngine`, else `defaults.engine`, is where 0.9 ran a gate's judge. It is model work now and runs where
|
|
90
|
+
* model work does, `judge`: the default connection, else the default harness. `workflow.judgeEngine` is dropped, and
|
|
91
|
+
* a note says whether the judge moves.
|
|
92
|
+
*/
|
|
93
|
+
export function dropJudgeEngine(out, conversion, target, defaultEngine, judge) {
|
|
90
94
|
const workflow = out.workflow;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
const named = isRecord(workflow) && "judgeEngine" in workflow;
|
|
96
|
+
const engine = named ? str(workflow.judgeEngine) : defaultEngine;
|
|
97
|
+
if (named) {
|
|
98
|
+
delete workflow.judgeEngine;
|
|
99
|
+
conversion.changed = true;
|
|
100
|
+
}
|
|
101
|
+
const became = engine === undefined ? undefined : target(engine);
|
|
102
|
+
const stays = became !== undefined && judge !== undefined && isDeepStrictEqual(became, judge);
|
|
103
|
+
const kind = judge === undefined ? undefined : "connection" in judge ? "connection" : "harness";
|
|
104
|
+
if (named) {
|
|
105
|
+
conversion.notes.push(stays
|
|
106
|
+
? `workflow.judgeEngine: "${engine}" is the default ${kind}, where a gate's judge runs now; the setting is dropped.`
|
|
107
|
+
: `workflow.judgeEngine${engine === undefined ? "" : ` "${engine}"`}: dropped. A gate's judge runs on the default connection, else the default harness, so a judge that ran on another engine has moved; to keep it, make that engine the default.`);
|
|
108
|
+
}
|
|
109
|
+
else if (became !== undefined && judge !== undefined && !stays) {
|
|
110
|
+
conversion.notes.push(`workflow: with no workflow.judgeEngine, a gate's judge ran on the default engine "${engine}"; it is model work now and runs on the default ${kind}, "${"connection" in judge ? judge.connection : judge.harness}".`);
|
|
111
|
+
}
|
|
96
112
|
}
|
|
97
113
|
/** `index.defaults.*` and `index.<pass>.engine`: `index.*` and `connection`; a harness cannot run an index pass. */
|
|
98
114
|
export function convertIndexEngines(out, conversion, retarget) {
|
|
@@ -55,17 +55,32 @@ function implicitKeyVariables(name, defaults) {
|
|
|
55
55
|
return name === str(defaults.llmEngine) ? [own, "AKM_LLM_API_KEY"] : [own];
|
|
56
56
|
}
|
|
57
57
|
// ── engines → connections / harnesses ────────────────────────────────────────
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
/**
|
|
59
|
+
* `engines.<name>` with `kind: "agent"` and `platform: "opencode-sdk"` over an `llmEngine`: OpenCode ran over
|
|
60
|
+
* akm's own connection, which is what akm's own runtime is now, so it has no entry of its own.
|
|
61
|
+
*/
|
|
62
|
+
function runtimeEngine(name, engine, llmEngine, converted, conversion) {
|
|
63
|
+
converted.runtimes.set(name, llmEngine);
|
|
64
|
+
const dropped = ["model", "timeoutMs", "workspace", "bin", "agent"].filter((key) => engine[key] !== undefined);
|
|
65
|
+
const gone = dropped.length === 0 ? "" : `; its ${dropped.join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped`;
|
|
66
|
+
conversion.notes.push(`engines.${name}: opencode-sdk over llmEngine "${llmEngine}" is akm's own runtime over connection "${llmEngine}" now, with no entry of its own${gone}.`);
|
|
62
67
|
}
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
/**
|
|
69
|
+
* `engines.<name>` with `kind: "agent"`: `harnesses.<name>`. An `opencode-sdk` engine with no `llmEngine` ran the
|
|
70
|
+
* user's own OpenCode (provider, model and login from its configuration), which the `opencode` harness runs now.
|
|
71
|
+
*/
|
|
72
|
+
function agentEngineToHarness(name, engine, converted, conversion) {
|
|
73
|
+
const sdk = engine.platform === "opencode-sdk";
|
|
74
|
+
const harness = { platform: sdk ? "opencode" : engine.platform };
|
|
75
|
+
if (sdk)
|
|
76
|
+
converted.sdkHarnesses.add(name);
|
|
77
|
+
// 0.9 started `opencode serve` without an engine's `args`; the harness would pass them in place of `run`.
|
|
66
78
|
for (const key of ["bin", "args", "workspace", "agent", "timeoutMs"])
|
|
67
|
-
if (engine[key] !== undefined)
|
|
79
|
+
if (engine[key] !== undefined && !(sdk && key === "args"))
|
|
68
80
|
harness[key] = engine[key];
|
|
81
|
+
if (sdk && engine.args !== undefined) {
|
|
82
|
+
conversion.notes.push(`engines.${name}: args is dropped. 0.9 did not pass it to OpenCode, and the opencode harness would pass it in place of \`run\`.`);
|
|
83
|
+
}
|
|
69
84
|
const model = str(engine.model);
|
|
70
85
|
if (model !== undefined)
|
|
71
86
|
converted.ownModelOf.push({ kind: "harnesses", engine: name, model });
|
|
@@ -123,6 +138,7 @@ function convertEngines(engines, defaults, env, conversion) {
|
|
|
123
138
|
connections: {},
|
|
124
139
|
harnesses: {},
|
|
125
140
|
runtimes: new Map(),
|
|
141
|
+
sdkHarnesses: new Set(),
|
|
126
142
|
keyHolders: [],
|
|
127
143
|
ownModelOf: [],
|
|
128
144
|
};
|
|
@@ -132,12 +148,13 @@ function convertEngines(engines, defaults, env, conversion) {
|
|
|
132
148
|
const unknown = Object.keys(engine).filter((k) => !(engine.kind === "agent" ? AGENT_ENGINE_KEYS : LLM_ENGINE_KEYS).has(k));
|
|
133
149
|
if (unknown.length > 0)
|
|
134
150
|
conversion.notes.push(`engines.${name}: dropped ${unknown.join(", ")}, which akm does not read.`);
|
|
151
|
+
const llmEngine = str(engine.llmEngine);
|
|
135
152
|
if (engine.kind !== "agent")
|
|
136
153
|
llmEngineToConnection(name, engine, defaults, env, converted, conversion);
|
|
137
|
-
else if (engine.platform === "opencode-sdk")
|
|
138
|
-
runtimeEngine(name, engine, converted, conversion);
|
|
154
|
+
else if (engine.platform === "opencode-sdk" && llmEngine !== undefined)
|
|
155
|
+
runtimeEngine(name, engine, llmEngine, converted, conversion);
|
|
139
156
|
else
|
|
140
|
-
agentEngineToHarness(name, engine, converted);
|
|
157
|
+
agentEngineToHarness(name, engine, converted, conversion);
|
|
141
158
|
}
|
|
142
159
|
return converted;
|
|
143
160
|
}
|
|
@@ -161,12 +178,37 @@ function renameMap(names, taken, from) {
|
|
|
161
178
|
}
|
|
162
179
|
return (name) => moved.get(name) ?? name;
|
|
163
180
|
}
|
|
181
|
+
/** The entry the loader reads as the default: the one called `default`, else the only one. */
|
|
182
|
+
const defaultName = (entries) => "default" in entries ? "default" : Object.keys(entries).length === 1 ? Object.keys(entries)[0] : undefined;
|
|
183
|
+
/**
|
|
184
|
+
* The engine 0.9 ran agent work on when `defaults.engine` named none and OpenCode was installed: the one called
|
|
185
|
+
* `opencode-sdk`, else a bare OpenCode of the user's own.
|
|
186
|
+
*/
|
|
187
|
+
const IMPLICIT_AGENT_ENGINE = "opencode-sdk";
|
|
188
|
+
/**
|
|
189
|
+
* Where agent work ran when `defaults.engine` named no harness, if that is not where it runs now. A runtime was akm's
|
|
190
|
+
* own; with no `defaults.engine` it was the user's OpenCode, whenever it was installed.
|
|
191
|
+
*/
|
|
192
|
+
function agentWorkMoved(defaultEngine, runtimes, harness) {
|
|
193
|
+
if (defaultEngine !== undefined) {
|
|
194
|
+
return runtimes.has(defaultEngine) && harness !== undefined
|
|
195
|
+
? `Agent work ran on akm's own runtime (engines.${defaultEngine}); with the harness "${harness}" configured it now runs there. Remove that harness to keep agent work on akm's runtime.`
|
|
196
|
+
: undefined;
|
|
197
|
+
}
|
|
198
|
+
const ran = "Without defaults.engine, agent work ran on your own OpenCode (the implicit fallback), when it was installed";
|
|
199
|
+
return harness !== undefined
|
|
200
|
+
? `${ran}; with the harness "${harness}" configured it now runs there.`
|
|
201
|
+
: `${ran}; it now runs on akm's own runtime. To keep it on your OpenCode, enable the harness: \`akm setup --harness opencode\`.`;
|
|
202
|
+
}
|
|
164
203
|
/** `defaults.llmEngine`, else `defaults.engine`: that connection is `default`; `defaults.engine` naming one of several harnesses: that harness is. */
|
|
165
204
|
function nameDefaults(defaults, converted, conversion, written) {
|
|
166
|
-
const { connections, harnesses, runtimes, keyHolders } = converted;
|
|
167
|
-
const
|
|
205
|
+
const { connections, harnesses, runtimes, sdkHarnesses, keyHolders } = converted;
|
|
206
|
+
const implicit = sdkHarnesses.has(IMPLICIT_AGENT_ENGINE) || runtimes.has(IMPLICIT_AGENT_ENGINE);
|
|
207
|
+
const defaultEngine = str(defaults.engine) ?? (implicit ? IMPLICIT_AGENT_ENGINE : undefined);
|
|
208
|
+
const modelWorkEngine = str(defaults.llmEngine);
|
|
168
209
|
const runtimeLlm = defaultEngine === undefined ? undefined : runtimes.get(defaultEngine);
|
|
169
|
-
const
|
|
210
|
+
const modelWorkRuntimeLlm = modelWorkEngine === undefined ? undefined : runtimes.get(modelWorkEngine);
|
|
211
|
+
const defaultConnection = [modelWorkEngine, modelWorkRuntimeLlm, defaultEngine, runtimeLlm].find((n) => n !== undefined && n in connections);
|
|
170
212
|
const defaultHarness = defaultEngine !== undefined && defaultEngine in harnesses && Object.keys(harnesses).length > 1
|
|
171
213
|
? defaultEngine
|
|
172
214
|
: undefined;
|
|
@@ -177,13 +219,18 @@ function nameDefaults(defaults, converted, conversion, written) {
|
|
|
177
219
|
connections: renamed(connections, connectionName),
|
|
178
220
|
harnesses: renamed(harnesses, harnessName),
|
|
179
221
|
};
|
|
180
|
-
const defaultHarnessName =
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
222
|
+
const defaultHarnessName = defaultName(named.harnesses);
|
|
223
|
+
const hasEngines = Object.keys(connections).length + Object.keys(harnesses).length + runtimes.size > 0;
|
|
224
|
+
const moved = hasEngines ? agentWorkMoved(defaultEngine, runtimes, defaultHarnessName) : undefined;
|
|
225
|
+
if (moved !== undefined)
|
|
226
|
+
conversion.notes.push(moved);
|
|
227
|
+
// `defaults.llmEngine` could name an agent engine, which then ran akm's model work; model work runs on the default
|
|
228
|
+
// connection, else the default harness, whichever engine that was.
|
|
229
|
+
if (modelWorkEngine !== undefined && modelWorkEngine in harnesses) {
|
|
230
|
+
const now = harnessName(modelWorkEngine);
|
|
231
|
+
if (defaultName({ ...named.connections, ...written.connections }) !== undefined || defaultHarnessName !== now) {
|
|
232
|
+
conversion.notes.push(`defaults.llmEngine "${modelWorkEngine}" named an agent engine, which ran akm's model work. Model work now runs on the default connection, else the default harness; to keep it on the harness "${now}", set improve.harness to it.`);
|
|
233
|
+
}
|
|
187
234
|
}
|
|
188
235
|
for (const [kind, rename] of [
|
|
189
236
|
["connections", connectionName],
|
|
@@ -195,10 +242,9 @@ function nameDefaults(defaults, converted, conversion, written) {
|
|
|
195
242
|
conversion.notes.push(`engines.default is now ${kind}.${aside}, and "default" names another engine: an engine: default key is rewritten by \`akm migrate apply\` where it can be, but a script or scheduled command that passes --engine default now reaches the new "default"; change it to the name ${aside}.`);
|
|
196
243
|
}
|
|
197
244
|
const target = (engine) => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return runtime
|
|
201
|
-
}
|
|
245
|
+
const runtime = runtimes.get(engine);
|
|
246
|
+
if (runtime !== undefined)
|
|
247
|
+
return runtime in connections ? { connection: connectionName(runtime) } : undefined;
|
|
202
248
|
if (engine in harnesses)
|
|
203
249
|
return { harness: harnessName(engine) };
|
|
204
250
|
if (engine in connections)
|
|
@@ -210,7 +256,7 @@ function nameDefaults(defaults, converted, conversion, written) {
|
|
|
210
256
|
connection: connectionName(engine),
|
|
211
257
|
field,
|
|
212
258
|
}));
|
|
213
|
-
return { defaultConnection, defaultHarness, connectionName, harnessName, ...named, keys, target };
|
|
259
|
+
return { defaultEngine, defaultConnection, defaultHarness, connectionName, harnessName, ...named, keys, target };
|
|
214
260
|
}
|
|
215
261
|
/** What the file writes under `connections` / `harnesses` itself, read with the converted entries: the written entry wins field by field. */
|
|
216
262
|
function mergeWritten(out, naming) {
|
|
@@ -230,6 +276,24 @@ function ownModelsOf(converted, naming) {
|
|
|
230
276
|
}
|
|
231
277
|
return ownModels;
|
|
232
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Where a gate's judge and akm's other model work run now when nothing names a place: the default connection, else
|
|
281
|
+
* the default harness. With no `defaults.llmEngine`, 0.9 ran none of that work unless a setting named an engine, so
|
|
282
|
+
* a default harness that takes it now gets a note saying so and how to turn it off.
|
|
283
|
+
*/
|
|
284
|
+
function modelWorkTarget(defaults, naming, merged, inherited, conversion) {
|
|
285
|
+
const connection = defaultName({ ...inherited?.connections, ...merged.connections });
|
|
286
|
+
if (connection !== undefined)
|
|
287
|
+
return { connection };
|
|
288
|
+
const harness = defaultName({ ...inherited?.harnesses, ...merged.harnesses });
|
|
289
|
+
const entry = harness === undefined ? undefined : merged.harnesses[harness];
|
|
290
|
+
if (harness === undefined || !isRecord(entry))
|
|
291
|
+
return undefined;
|
|
292
|
+
if (str(defaults.llmEngine) === undefined && harness in naming.harnesses && entry.modelWork === undefined) {
|
|
293
|
+
conversion.notes.push(`harnesses.${harness}: akm's own model work (improve, index passes, extraction, judges) now runs on this harness, the default for it with no connection. Without defaults.llmEngine, 0.9 ran none of it unless a setting named an engine (a workflow gate's judge ran on defaults.engine). To keep the 0.9 behaviour, set \`modelWork: false\` on this harness: a call that names it still runs there, but a workflow gate's judge then needs a connection (\`akm setup --endpoint URL\`).`);
|
|
294
|
+
}
|
|
295
|
+
return entry.modelWork === false ? undefined : { harness };
|
|
296
|
+
}
|
|
233
297
|
// ── the whole conversion ─────────────────────────────────────────────────────
|
|
234
298
|
export function convertEngineConfig(raw, options = {}) {
|
|
235
299
|
const out = structuredClone(raw);
|
|
@@ -254,8 +318,9 @@ export function convertEngineConfig(raw, options = {}) {
|
|
|
254
318
|
inherited: { connections: options.inherited?.connections ?? {}, harnesses: options.inherited?.harnesses ?? {} },
|
|
255
319
|
renamed: {
|
|
256
320
|
runtime: [...converted.runtimes.values()]
|
|
257
|
-
.filter((name) => name
|
|
321
|
+
.filter((name) => name in converted.connections)
|
|
258
322
|
.map((name) => naming.connectionName(name)),
|
|
323
|
+
sdkHarness: [...converted.sdkHarnesses].map((name) => naming.harnessName(name)),
|
|
259
324
|
connection: (engine) => (engine in converted.connections ? naming.connectionName(engine) : undefined),
|
|
260
325
|
},
|
|
261
326
|
});
|
|
@@ -265,7 +330,8 @@ export function convertEngineConfig(raw, options = {}) {
|
|
|
265
330
|
if (isRecord(improve))
|
|
266
331
|
moveStrategyExecution(improve, conversion, retarget);
|
|
267
332
|
improveStrategyDefault(out, defaults, improve);
|
|
268
|
-
|
|
333
|
+
const judge = modelWorkTarget(defaults, naming, merged, options.inherited, conversion);
|
|
334
|
+
dropJudgeEngine(out, conversion, naming.target, naming.defaultEngine, judge);
|
|
269
335
|
convertIndexEngines(out, conversion, retarget);
|
|
270
336
|
// Assemble, keeping the file's key order.
|
|
271
337
|
const config = {};
|
|
@@ -44,6 +44,7 @@ function overlayRows(modelsOverlay) {
|
|
|
44
44
|
/**
|
|
45
45
|
* `models.json`: an alias is set by, nearest first, the config's own `models`, models.json's column for that
|
|
46
46
|
* connection or harness, models.json's `*` column (and the flat shorthand, which is that), the engine's own model.
|
|
47
|
+
* The exception is a harness engine's own model: it ran when a request named none, so it stands over every column.
|
|
47
48
|
*/
|
|
48
49
|
export function foldModelsOverlay(modelsOverlay, fold) {
|
|
49
50
|
const { conversion, merged, written, inherited, ownModels, renamed } = fold;
|
|
@@ -57,6 +58,10 @@ export function foldModelsOverlay(modelsOverlay, fold) {
|
|
|
57
58
|
/** Whether the config itself, the file or its base, already names a model for the alias. */
|
|
58
59
|
const configNames = (own, base, name, alias) => hasModel(own[name], alias) || hasModel(base[name], alias);
|
|
59
60
|
const platformOf = (name) => String((mergedHarnesses[name] ?? inheritedHarnesses[name])?.platform ?? name);
|
|
61
|
+
/** The column of models.json that named a harness: an `opencode-sdk` engine's was `opencode-sdk`, not its platform's. */
|
|
62
|
+
const columnOf = (name) => (renamed.sdkHarness.includes(name) ? "opencode-sdk" : platformOf(name));
|
|
63
|
+
/** Harnesses whose engine had a `model` of its own, which names every alias and so stands over the column (noted once each). */
|
|
64
|
+
const notedOwn = new Set();
|
|
60
65
|
let folded = false;
|
|
61
66
|
for (const { alias, column, profile } of overlayRows(modelsOverlay)) {
|
|
62
67
|
const p = typeof profile === "string" ? { model: profile } : isRecord(profile) ? profile : {};
|
|
@@ -76,7 +81,7 @@ export function foldModelsOverlay(modelsOverlay, fold) {
|
|
|
76
81
|
: column in writtenConnections || column in inheritedConnections
|
|
77
82
|
? [column]
|
|
78
83
|
: [];
|
|
79
|
-
const harnessNamed = [...new Set([...Object.keys(mergedHarnesses), ...Object.keys(inheritedHarnesses)])].filter((name) =>
|
|
84
|
+
const harnessNamed = [...new Set([...Object.keys(mergedHarnesses), ...Object.keys(inheritedHarnesses)])].filter((name) => columnOf(name) === column);
|
|
80
85
|
if (candidates.length === 0 && harnessNamed.length === 0) {
|
|
81
86
|
notes.push(`${where}: names no configured connection or harness, so it is not carried over.`);
|
|
82
87
|
continue;
|
|
@@ -84,7 +89,14 @@ export function foldModelsOverlay(modelsOverlay, fold) {
|
|
|
84
89
|
// The config's own choice stands, and `*` leaves what an explicit column already set.
|
|
85
90
|
const connectionNames = candidates.filter((name) => !configNames(writtenConnections, inheritedConnections, name, alias) &&
|
|
86
91
|
!(column === "*" && hasModel(mergedConnections[name], alias)));
|
|
87
|
-
const
|
|
92
|
+
const unnamed = harnessNamed.filter((name) => !configNames(writtenHarnesses, inheritedHarnesses, name, alias));
|
|
93
|
+
// The model a harness engine named is what ran when a request named none, and a harness runs its first model then,
|
|
94
|
+
// so it stands over the column, which would put the column's models first.
|
|
95
|
+
const harnessNames = unnamed.filter((name) => !ownModels.harnesses.has(name));
|
|
96
|
+
for (const name of unnamed.filter((n) => ownModels.harnesses.has(n) && !notedOwn.has(`${column}:${n}`))) {
|
|
97
|
+
notedOwn.add(`${column}:${name}`);
|
|
98
|
+
notes.push(`models.json (${column === "*" ? "all" : column}): harness "${name}" has a model of its own, which names every alias, so the column is not carried over.`);
|
|
99
|
+
}
|
|
88
100
|
if (connectionNames.length === 0 && harnessNames.length === 0) {
|
|
89
101
|
// Only the config's own choice is worth a note; an explicit column that came first is the overlay's own.
|
|
90
102
|
const keptByConfig = candidates.some((name) => configNames(writtenConnections, inheritedConnections, name, alias)) ||
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Bundle-rename startup guard.
|
|
6
6
|
*
|
|
7
7
|
* Renaming a workspace bundle id is a mass identity migration (every ref in the
|
|
8
8
|
* bundle changes without any file moving) and MUST go through the
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* every ref-keyed state table atomically. If a user instead hand-edits the
|
|
11
11
|
* `bundles` KEY in config.json, the durable index/state rows still carry the OLD
|
|
12
12
|
* bundle prefix while the config names the NEW id. Silently re-minting fresh
|
|
13
|
-
* state under the new id would strand the old rows
|
|
14
|
-
* and
|
|
13
|
+
* state under the new id would strand the old rows, so this guard detects it
|
|
14
|
+
* and warns.
|
|
15
15
|
*
|
|
16
16
|
* This guard implements the cheap heuristic form (WARN, never silently re-mint):
|
|
17
17
|
* it compares the configured bundle ids against the DISTINCT `bundle_id`
|
|
18
|
-
* (item_ref bundle prefix) values already persisted in the index —
|
|
19
|
-
*
|
|
18
|
+
* (item_ref bundle prefix) values already persisted in the index — the same
|
|
19
|
+
* prefixes key the state.db usage/salience rows.
|
|
20
20
|
* The hand-rename signature is: a configured bundle id with NO indexed rows
|
|
21
21
|
* co-existing with indexed rows under a bundle id that is no longer configured.
|
|
22
22
|
*
|
|
@@ -70,7 +70,7 @@ function indexedBundlePaths(dbPath) {
|
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
72
|
* Warn (once per process) when the config's bundle ids and the index's bundle
|
|
73
|
-
* prefixes show the hand-rename signature
|
|
73
|
+
* prefixes show the hand-rename signature. No-op for an old-shape config
|
|
74
74
|
* (no `bundles`), an absent/empty index, or when the guard already settled.
|
|
75
75
|
*/
|
|
76
76
|
export function warnOnBundleRenameDrift(config = loadConfig()) {
|
|
@@ -31,7 +31,7 @@ import { warn, warnVerbose } from "../core/warn.js";
|
|
|
31
31
|
import { closeDatabase, openExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
32
32
|
import { deleteEntriesByIds, getEntryCount, upsertEntry } from "../storage/repositories/index-entries-repository.js";
|
|
33
33
|
import { deriveEntryProvenance, deriveInstallations } from "./installations.js";
|
|
34
|
-
import { generateEmbeddingsForDb, publishTargetedEmbeddingMeta } from "./materialize-embeddings.js";
|
|
34
|
+
import { embeddingInputCap, generateEmbeddingsForDb, publishTargetedEmbeddingMeta } from "./materialize-embeddings.js";
|
|
35
35
|
import { withFileSize } from "./passes/metadata.js";
|
|
36
36
|
import { drainDirDocuments } from "./scan/drain-dir.js";
|
|
37
37
|
import { buildFileContext } from "./walk/file-context.js";
|
|
@@ -153,6 +153,7 @@ export async function indexWrittenAssets(stashDir, filePaths, options = {}) {
|
|
|
153
153
|
db.exec(`PRAGMA busy_timeout = ${WRITE_PATH_INDEX_BUSY_TIMEOUT_MS}`);
|
|
154
154
|
if (getEntryCount(db) === 0)
|
|
155
155
|
return true;
|
|
156
|
+
const config = loadConfig();
|
|
156
157
|
const targetEntryIds = new Set();
|
|
157
158
|
let mutated = false;
|
|
158
159
|
db.transaction(() => {
|
|
@@ -194,12 +195,11 @@ export async function indexWrittenAssets(stashDir, filePaths, options = {}) {
|
|
|
194
195
|
.prepare("SELECT id FROM entries WHERE file_path = ? AND item_ref <> ?")
|
|
195
196
|
.all(file, provenance.itemRef);
|
|
196
197
|
deleteEntriesByIds(db, supersededIds.map((row) => row.id));
|
|
197
|
-
targetEntryIds.add(upsertEntry(db, file, entryWithSize, provenance, contentHash));
|
|
198
|
+
targetEntryIds.add(upsertEntry(db, file, entryWithSize, provenance, contentHash, embeddingInputCap(config)));
|
|
198
199
|
mutated = true;
|
|
199
200
|
}
|
|
200
201
|
})();
|
|
201
202
|
if (mutated) {
|
|
202
|
-
const config = loadConfig();
|
|
203
203
|
await generateEmbeddingsForDb(db, config, () => { }, undefined, [...targetEntryIds]);
|
|
204
204
|
publishTargetedEmbeddingMeta(db, config);
|
|
205
205
|
}
|
package/dist/indexer/indexer.js
CHANGED
|
@@ -28,9 +28,9 @@ import { assertIndexedWorkflowSourceIdentity, WorkflowSourceIdentityError } from
|
|
|
28
28
|
import { reclassifyIndexDbContention } from "./index-db-contention.js";
|
|
29
29
|
import { deriveEntryProvenance, deriveInstallations } from "./installations.js";
|
|
30
30
|
import { indexedPathMatchesOwner, resolveConceptOwner } from "./lookup/adapter-concept-owner.js";
|
|
31
|
-
import { generateEmbeddingsForDb } from "./materialize-embeddings.js";
|
|
31
|
+
import { embeddingInputCap, generateEmbeddingsForDb } from "./materialize-embeddings.js";
|
|
32
32
|
import { canUseIncrementalSkip, computeDirFingerprint, getCachedDirState, getDirIndexState, inferZeroRowReason, } from "./passes/dir-staleness.js";
|
|
33
|
-
import { isWorkflowSkipWarning, withFileSize } from "./passes/metadata.js";
|
|
33
|
+
import { CONTENT_PROJECTION_VERSION, isWorkflowSkipWarning, withFileSize, } from "./passes/metadata.js";
|
|
34
34
|
import { drainDirDocuments } from "./scan/drain-dir.js";
|
|
35
35
|
import { purgeOldUsageEvents, USAGE_EVENT_RETENTION_DAYS } from "./usage/usage-events.js";
|
|
36
36
|
import { SEMANTIC_SEARCH_META_KEY, } from "./walk/index-context.js";
|
|
@@ -446,7 +446,7 @@ async function akmIndexReal(options) {
|
|
|
446
446
|
// …then write it in one transaction: `item_ref = <bundle>//<conceptId>`
|
|
447
447
|
// plus canonical component/adapter provenance per source root.
|
|
448
448
|
const bundleByRoot = buildIndexedSourceOwners(sources);
|
|
449
|
-
persistDirRecords(db, dirRecords, warnings, bundleByRoot);
|
|
449
|
+
persistDirRecords(db, dirRecords, warnings, bundleByRoot, embeddingInputCap(config));
|
|
450
450
|
onProgress({
|
|
451
451
|
phase: "scan",
|
|
452
452
|
message: `Scanned ${scannedDirs} ${scannedDirs === 1 ? "directory" : "directories"} and skipped ${skippedDirs}.`,
|
|
@@ -819,7 +819,9 @@ async function scanSourceDirs(db, allSourceEntries, isIncremental, builtAtMs, ha
|
|
|
819
819
|
warn(`Skipping component "${component.id}": unknown adapter id "${component.adapter}".`);
|
|
820
820
|
continue;
|
|
821
821
|
}
|
|
822
|
-
|
|
822
|
+
// The content projection's version is part of the variant: a change to what Markdown bodies index re-projects
|
|
823
|
+
// every directory once instead of keeping the old text for unchanged files.
|
|
824
|
+
const indexVariant = `${adapter.id}@${adapter.version}+content${CONTENT_PROJECTION_VERSION}`;
|
|
823
825
|
for (const removal of removals) {
|
|
824
826
|
dirRecords.push(removal);
|
|
825
827
|
scannedDirs++;
|
|
@@ -938,7 +940,7 @@ function requiresWorkflowSourcePreflight(ctxs) {
|
|
|
938
940
|
});
|
|
939
941
|
}
|
|
940
942
|
/** Phase 2 (sync): write all pre-generated scan records inside a single transaction. */
|
|
941
|
-
function persistDirRecords(db, dirRecords, warnings, bundleByRoot) {
|
|
943
|
+
function persistDirRecords(db, dirRecords, warnings, bundleByRoot, maxInputTokens) {
|
|
942
944
|
const deletedUsageEntryIds = new Set();
|
|
943
945
|
const findPersisted = db.prepare("SELECT id, content_hash, file_path, adapter_id FROM entries WHERE item_ref = ?");
|
|
944
946
|
const insertTransaction = db.transaction(() => {
|
|
@@ -1038,7 +1040,7 @@ function persistDirRecords(db, dirRecords, warnings, bundleByRoot) {
|
|
|
1038
1040
|
if (unchanged) {
|
|
1039
1041
|
continue;
|
|
1040
1042
|
}
|
|
1041
|
-
upsertEntry(db, entryPath, withFileSize(entry, entryPath), provenance, contentHash);
|
|
1043
|
+
upsertEntry(db, entryPath, withFileSize(entry, entryPath), provenance, contentHash, maxInputTokens);
|
|
1042
1044
|
}
|
|
1043
1045
|
}
|
|
1044
1046
|
// Prune the departed rows: everything under this dir NOT re-upserted above
|
|
@@ -1332,8 +1334,10 @@ export function recomputeUtilityScores(db, stateDb, onlyEntryId) {
|
|
|
1332
1334
|
// (Chunk-8 WI-8.3); its table is created by state migration 020.
|
|
1333
1335
|
if (!scoped)
|
|
1334
1336
|
purgeOldUsageEvents(stateDb, USAGE_EVENT_RETENTION_DAYS);
|
|
1335
|
-
//
|
|
1336
|
-
//
|
|
1337
|
+
// Decay is raised to the days since the last run, floored at one day. Every
|
|
1338
|
+
// run therefore applies at least one full round of EMA decay (so runs less
|
|
1339
|
+
// than a day apart each decay a full round), and a longer gap applies
|
|
1340
|
+
// proportionally more rounds. The first run counts as one day.
|
|
1337
1341
|
const lastComputedAt = getMeta(db, "last_utility_computed_at");
|
|
1338
1342
|
let elapsedDays = 1; // default for first run
|
|
1339
1343
|
if (lastComputedAt) {
|
|
@@ -32,6 +32,13 @@ export function deriveSemanticProviderFingerprint(embedding) {
|
|
|
32
32
|
}
|
|
33
33
|
return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}${template}`;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The most estimated tokens of an entry's search text that are embedded
|
|
37
|
+
* (`embedding.maxInputTokens`); the rest of the text never reaches the embedder.
|
|
38
|
+
*/
|
|
39
|
+
export function embeddingInputCap(config) {
|
|
40
|
+
return effectiveEmbedding(config)?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
41
|
+
}
|
|
35
42
|
/**
|
|
36
43
|
* The heartbeat text emitted every 15s while a provider request is in
|
|
37
44
|
* flight, and by default (not `--verbose`-only) since silence indistinguishable
|
|
@@ -103,7 +110,7 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
103
110
|
let targetEntryIds = entryIds;
|
|
104
111
|
/** Set only when every entry is (re)embedded, so the up-front "Re-embedding N entries" line names why. */
|
|
105
112
|
let rebuildReason;
|
|
106
|
-
const maxInputTokens =
|
|
113
|
+
const maxInputTokens = embeddingInputCap(config);
|
|
107
114
|
if (opts?.forceReembed) {
|
|
108
115
|
// The explicit operator override: purge and re-embed everything. The model
|
|
109
116
|
// is recorded in the SAME transaction as the purge, before any embedding
|
|
@@ -171,9 +178,12 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
171
178
|
const message = `[embed] Re-embedding ${pendingEntries.length} entr${pendingEntries.length === 1 ? "y" : "ies"} because ${rebuildReason}`;
|
|
172
179
|
onProgress({ phase: "embeddings", message });
|
|
173
180
|
}
|
|
181
|
+
// Names the size of the work before the first request, so a first index after an upgrade that
|
|
182
|
+
// re-embeds part of the corpus is not mistaken for a hang.
|
|
183
|
+
const unchangedCount = Math.max(0, getEmbeddableEntryCount(db) - pendingEntries.length);
|
|
174
184
|
onProgress({
|
|
175
185
|
phase: "embeddings",
|
|
176
|
-
message: `
|
|
186
|
+
message: `Embedding ${pendingEntries.length.toLocaleString()} changed entr${pendingEntries.length === 1 ? "y" : "ies"} (${unchangedCount.toLocaleString()} unchanged)`,
|
|
177
187
|
});
|
|
178
188
|
// Per-document sizes; the provider's actual request grouping is reported
|
|
179
189
|
// per batch below (and each skipped document is listed at the end).
|