akm-cli 0.10.261014-beta → 0.10.261016-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 +118 -0
- package/README.md +7 -8
- package/SECURITY.md +3 -3
- package/STABILITY.md +2 -1
- package/dist/assets/hints/cli-hints-full.md +6 -4
- package/dist/assets/hints/cli-hints-short.md +4 -2
- package/dist/assets/opencode/akm-plugin.js +16 -8
- package/dist/assets/prompts/workflow-unit-preamble.md +3 -1
- package/dist/cli.js +2 -2
- package/dist/commands/health/plugin-staleness.js +86 -22
- package/dist/commands/health/semantic-search-check.js +2 -2
- package/dist/commands/read/search-cli.js +2 -2
- package/dist/commands/read/show.js +15 -3
- package/dist/core/asset/markdown.js +9 -0
- package/dist/core/config/config-walker.js +35 -32
- package/dist/core/config/schema/connections.js +6 -3
- package/dist/core/config/schema/harnesses.js +8 -1
- package/dist/core/config/schema/primitives.js +12 -1
- package/dist/core/config/schema/sources-bundles.js +2 -2
- package/dist/core/npm-package.js +124 -0
- package/dist/{integrations/harnesses/opencode/runtime → core}/tar.js +25 -16
- package/dist/integrations/harnesses/opencode/runtime/binary.js +8 -75
- package/dist/integrations/harnesses/opencode/runtime/index.js +5 -1
- package/dist/llm/embedders/local.js +23 -58
- package/dist/llm/embedders/onnx-runtime.js +121 -0
- package/dist/output/renderers.js +2 -2
- package/dist/output/shapes/helpers.js +2 -0
- package/dist/output/text/show-format.js +7 -0
- package/dist/registry/semver.js +12 -0
- package/dist/scripts/akm-migrate-node.js +21638 -5760
- package/dist/scripts/akm-migrate.js +21645 -5767
- package/dist/setup/semantic-assets.js +9 -6
- package/dist/setup/setup.js +56 -2
- package/dist/setup/steps/embeddings.js +21 -4
- package/dist/storage/database.js +17 -57
- package/dist/tasks/backends/cron.js +19 -0
- package/docs/architecture/akm-core-principles.md +3 -2
- package/docs/architecture/internals/health-advisories.md +2 -2
- package/docs/architecture/internals/storage-locations.md +1 -0
- package/docs/architecture/specs/ref.md +4 -3
- package/docs/architecture/testing/testing-workflow.md +28 -20
- package/docs/guides/discover-and-load.md +12 -4
- package/docs/guides/getting-started.md +23 -20
- package/docs/guides/recipes/headless-install.md +13 -8
- package/docs/guides/use-with-any-agent.md +4 -1
- package/docs/maintainers/release-checklist.md +15 -8
- package/docs/migration/v0.9-to-v0.10.md +17 -0
- package/docs/reference/cli.md +42 -12
- package/docs/reference/configuration.md +37 -15
- package/package.json +6 -9
- package/schemas/akm-config.json +535 -302
- package/dist/standalone/onnxruntime-library.js +0 -52
|
@@ -2,14 +2,16 @@
|
|
|
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
|
-
* Semantic-search asset preparation for the
|
|
6
|
-
* runtime (onnxruntime-node
|
|
7
|
-
*
|
|
5
|
+
* Semantic-search asset preparation for setup. For the built-in local embedder
|
|
6
|
+
* that is its runtime (onnxruntime-node's binding and library) and its model,
|
|
7
|
+
* both fetched into akm's cache; for an embedding endpoint, a check that it
|
|
8
|
+
* answers.
|
|
8
9
|
*/
|
|
9
10
|
import * as p from "../cli/clack.js";
|
|
10
11
|
import { isHttpUrl } from "../core/common.js";
|
|
11
12
|
import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL } from "../llm/embedder.js";
|
|
12
13
|
import { effectiveEmbedding } from "../llm/embedders/connection.js";
|
|
14
|
+
import { ONNXRUNTIME_VERSION } from "../llm/embedders/onnx-runtime.js";
|
|
13
15
|
// Approximate first-download size of the default local model (DEFAULT_LOCAL_MODEL), used in the setup note.
|
|
14
16
|
const LOCAL_MODEL_APPROX_SIZE_MB = 127;
|
|
15
17
|
export function isRemoteEmbeddingConfig(embedding) {
|
|
@@ -23,7 +25,8 @@ export function describeSemanticSearchAssets(embedding) {
|
|
|
23
25
|
return [`• Embedding endpoint: ${embedding?.provider ?? "custom"} / ${embedding?.model} (no local model download)`];
|
|
24
26
|
}
|
|
25
27
|
return [
|
|
26
|
-
`•
|
|
28
|
+
`• ONNX Runtime ${ONNXRUNTIME_VERSION}, the local embedder's runtime (~86 MB download, once)`,
|
|
29
|
+
`• Local embedding model: ${embedding?.localModel ?? DEFAULT_LOCAL_MODEL} (~${LOCAL_MODEL_APPROX_SIZE_MB} MB download, once)`,
|
|
27
30
|
];
|
|
28
31
|
}
|
|
29
32
|
export async function prepareSemanticSearchAssets(config) {
|
|
@@ -32,7 +35,7 @@ export async function prepareSemanticSearchAssets(config) {
|
|
|
32
35
|
const spin = p.spinner();
|
|
33
36
|
spin.start(remote
|
|
34
37
|
? "Checking remote embedding endpoint..."
|
|
35
|
-
: `Downloading local
|
|
38
|
+
: `Downloading the local embedder's runtime and model (${embedding?.localModel ?? DEFAULT_LOCAL_MODEL})...`);
|
|
36
39
|
const result = await checkEmbeddingAvailability(embedding);
|
|
37
40
|
if (!result.available) {
|
|
38
41
|
spin.stop("Semantic-search assets could not be prepared.");
|
|
@@ -50,6 +53,6 @@ export async function prepareSemanticSearchAssets(config) {
|
|
|
50
53
|
return { ok: false, reason: "local-model-download", message: result.message };
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
|
-
spin.stop(remote ? "Remote embedding endpoint is ready." : "Local
|
|
56
|
+
spin.stop(remote ? "Remote embedding endpoint is ready." : "Local embedder's runtime and model downloaded and ready.");
|
|
54
57
|
return { ok: true };
|
|
55
58
|
}
|
package/dist/setup/setup.js
CHANGED
|
@@ -19,7 +19,9 @@ import path from "node:path";
|
|
|
19
19
|
import { isDeepStrictEqual } from "node:util";
|
|
20
20
|
import * as p from "../cli/clack.js";
|
|
21
21
|
import { akmInit } from "../commands/sources/init.js";
|
|
22
|
+
import { targetRows } from "../commands/targets.js";
|
|
22
23
|
import { deriveBundleIds } from "../core/bundle-id.js";
|
|
24
|
+
import { toErrorMessage } from "../core/common.js";
|
|
23
25
|
import { bundleEntryToSourceEntry, DEFAULT_CONFIG, defaultConnection, loadUserConfig, mutateConfigWithPrecommit, parseAndValidateConfigText, primaryBundlePath, validateCompleteConfig, } from "../core/config/config.js";
|
|
24
26
|
import { readConfigText } from "../core/config/config-io.js";
|
|
25
27
|
import { listTopLevelConfigKeys } from "../core/config/config-schema.js";
|
|
@@ -30,6 +32,8 @@ import { getConfigPath, getDefaultStashDir, isTransientStashPath } from "../core
|
|
|
30
32
|
import { warn, warnOnce } from "../core/warn.js";
|
|
31
33
|
import { akmIndex } from "../indexer/indexer.js";
|
|
32
34
|
import { readLockfile } from "../integrations/lockfile.js";
|
|
35
|
+
import { checkEmbeddingAvailability } from "../llm/embedder.js";
|
|
36
|
+
import { effectiveEmbedding } from "../llm/embedders/connection.js";
|
|
33
37
|
import { detectEnvironment, renderDetectionSummary } from "./detect.js";
|
|
34
38
|
import { bail, prompt } from "./prompt.js";
|
|
35
39
|
import { prepareSemanticSearchAssets } from "./semantic-assets.js";
|
|
@@ -305,6 +309,50 @@ function finalizeSetupDraft(draft) {
|
|
|
305
309
|
delete finalized.defaultBundle;
|
|
306
310
|
return finalized;
|
|
307
311
|
}
|
|
312
|
+
/** Where a process of `config` runs, as `akm info` and `akm health` say it. */
|
|
313
|
+
function processRow(config, process) {
|
|
314
|
+
return targetRows({ config }).find((row) => row.type === "process" && row.process === process);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* What `--yes` and `--from` do for local embeddings once the config is saved: fetch the local embedder's runtime and
|
|
318
|
+
* model and load them. Undefined when the saved config does not embed locally.
|
|
319
|
+
*/
|
|
320
|
+
async function prepareLocalEmbeddings(config, noProbe) {
|
|
321
|
+
if (processRow(config, "embedding")?.kind !== "local")
|
|
322
|
+
return undefined;
|
|
323
|
+
if (noProbe)
|
|
324
|
+
return "skipped";
|
|
325
|
+
const result = await checkEmbeddingAvailability(effectiveEmbedding(config));
|
|
326
|
+
if (result.available)
|
|
327
|
+
return "ready";
|
|
328
|
+
warn(`[akm setup] Semantic search is blocked: ${result.message}`);
|
|
329
|
+
return `blocked: ${result.message}`;
|
|
330
|
+
}
|
|
331
|
+
/** The wizard's offer to fetch what akm's own agent runtime runs, when the saved config runs agent work there. */
|
|
332
|
+
async function offerAgentRuntimeFetch(config) {
|
|
333
|
+
if (processRow(config, "agents")?.kind !== "connection")
|
|
334
|
+
return;
|
|
335
|
+
const { agentRuntime } = await import("../integrations/agent/runtime.js");
|
|
336
|
+
const pending = agentRuntime.pendingFetch();
|
|
337
|
+
if (pending === undefined)
|
|
338
|
+
return;
|
|
339
|
+
const fetchNow = await prompt(() => p.confirm({
|
|
340
|
+
message: `Agent work runs on akm's own runtime. Fetch ${pending} for it now, rather than on the first agent run?`,
|
|
341
|
+
initialValue: true,
|
|
342
|
+
}));
|
|
343
|
+
if (!fetchNow)
|
|
344
|
+
return;
|
|
345
|
+
const spin = p.spinner();
|
|
346
|
+
spin.start(`Fetching ${pending}...`);
|
|
347
|
+
try {
|
|
348
|
+
await agentRuntime.fetch();
|
|
349
|
+
spin.stop("akm's own agent runtime is ready.");
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
spin.stop("akm's own agent runtime could not be fetched.");
|
|
353
|
+
p.log.warn(`${toErrorMessage(error)}\nThe first agent run tries again.`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
308
356
|
const NOTHING_DETECTED = {
|
|
309
357
|
harnesses: [],
|
|
310
358
|
localServers: [],
|
|
@@ -538,6 +586,8 @@ export async function runSetupWizard(opts) {
|
|
|
538
586
|
// Non-interactive setup paths never reach this interactive-only step.
|
|
539
587
|
p.log.step("Scheduled Tasks");
|
|
540
588
|
await stepScheduledTasks();
|
|
589
|
+
if (opts?.noProbe !== true)
|
|
590
|
+
await offerAgentRuntimeFetch(savedConfig);
|
|
541
591
|
if (semanticSearchMode.mode === "auto") {
|
|
542
592
|
if (semanticSearchMode.prepareAssets) {
|
|
543
593
|
const ready = await prepareSemanticSearchAssets(savedConfig);
|
|
@@ -624,12 +674,13 @@ export async function runSetupWithDefaults(opts) {
|
|
|
624
674
|
const { config, credentials } = await applyNonInteractiveDefaults(current, stashDir, opts);
|
|
625
675
|
const finalConfig = finalizeSetupDraft(config);
|
|
626
676
|
validateCompleteConfig(finalConfig);
|
|
627
|
-
const { precommit: initResult } = await saveSetupConfig(current, finalConfig, async () => {
|
|
677
|
+
const { config: savedConfig, precommit: initResult } = await saveSetupConfig(current, finalConfig, async () => {
|
|
628
678
|
storeCredentials(credentials);
|
|
629
679
|
if (opts.noInit)
|
|
630
680
|
return undefined;
|
|
631
681
|
return akmInit({ dir: stashDir, setDefault: true, persistConfig: false });
|
|
632
682
|
});
|
|
683
|
+
const semanticAssets = await prepareLocalEmbeddings(savedConfig, opts.noProbe);
|
|
633
684
|
return {
|
|
634
685
|
configPath: getConfigPath(),
|
|
635
686
|
bundleDir: stashDir,
|
|
@@ -637,6 +688,7 @@ export async function runSetupWithDefaults(opts) {
|
|
|
637
688
|
written: true,
|
|
638
689
|
fields: Object.keys(finalConfig).filter((k) => finalConfig[k] !== undefined),
|
|
639
690
|
runs: describeRuns(finalConfig),
|
|
691
|
+
...(semanticAssets ? { semanticAssets } : {}),
|
|
640
692
|
};
|
|
641
693
|
}
|
|
642
694
|
/**
|
|
@@ -736,12 +788,13 @@ export async function runSetupFromConfig(opts) {
|
|
|
736
788
|
const finalizedMerged = finalizeSetupDraft(merged);
|
|
737
789
|
// Reject an invalid merged config before touching the stash.
|
|
738
790
|
validateCompleteConfig(finalizedMerged);
|
|
739
|
-
const { precommit: initResult } = await saveSetupConfig(current, finalizedMerged, async () => {
|
|
791
|
+
const { config: savedConfig, precommit: initResult } = await saveSetupConfig(current, finalizedMerged, async () => {
|
|
740
792
|
storeCredentials(credentials);
|
|
741
793
|
if (opts.noInit)
|
|
742
794
|
return undefined;
|
|
743
795
|
return akmInit({ dir: stashDir, setDefault: true, persistConfig: false });
|
|
744
796
|
});
|
|
797
|
+
const semanticAssets = await prepareLocalEmbeddings(savedConfig, opts.noProbe);
|
|
745
798
|
return {
|
|
746
799
|
configPath: getConfigPath(),
|
|
747
800
|
bundleDir: stashDir,
|
|
@@ -749,6 +802,7 @@ export async function runSetupFromConfig(opts) {
|
|
|
749
802
|
written: true,
|
|
750
803
|
fields: Object.keys(incoming).filter((k) => incoming[k] !== undefined),
|
|
751
804
|
runs: describeRuns(finalizedMerged),
|
|
805
|
+
...(semanticAssets ? { semanticAssets } : {}),
|
|
752
806
|
};
|
|
753
807
|
}
|
|
754
808
|
// ── Setup --from <file> bootstrap helper ────────────────────────────────────
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
5
|
* Setup step 4: embeddings. A model the connection serves, akm's built-in local
|
|
6
|
-
* embedder, another endpoint, or off (which is semantic search off).
|
|
6
|
+
* embedder, another endpoint, or off (which is semantic search off). The local
|
|
7
|
+
* embedder is offered only where akm has local embeddings
|
|
8
|
+
* (`llm/embedders/onnx-runtime.ts`); everywhere else the choice is an endpoint
|
|
9
|
+
* or keyword search.
|
|
7
10
|
*/
|
|
8
11
|
import * as p from "../../cli/clack.js";
|
|
9
12
|
import { defaultConnection, } from "../../core/config/config.js";
|
|
@@ -11,6 +14,7 @@ import { apiKeyReference, resolveApiKey, storedCredential } from "../../core/con
|
|
|
11
14
|
import { sameEndpoint } from "../../core/endpoint-url.js";
|
|
12
15
|
import { UsageError } from "../../core/errors.js";
|
|
13
16
|
import { redactUrlCredentials } from "../../core/redaction.js";
|
|
17
|
+
import { hasLocalEmbeddings, noLocalEmbeddingsReason } from "../../llm/embedders/onnx-runtime.js";
|
|
14
18
|
import { isEmbeddingModel } from "../../llm/model-list.js";
|
|
15
19
|
import { prompt } from "../prompt.js";
|
|
16
20
|
import { describeSemanticSearchAssets, isRemoteEmbeddingConfig } from "../semantic-assets.js";
|
|
@@ -45,6 +49,10 @@ function warnOfDroppedKey(current, endpoint, keyGiven, interactive) {
|
|
|
45
49
|
}
|
|
46
50
|
function fromFlags(input) {
|
|
47
51
|
const { flags } = input;
|
|
52
|
+
if (flags.embedding === "local" && !hasLocalEmbeddings()) {
|
|
53
|
+
warnUser(`${noLocalEmbeddingsReason()} --embedding local is ignored: semantic search stays off.`, false);
|
|
54
|
+
return { kind: "off" };
|
|
55
|
+
}
|
|
48
56
|
if (flags.embedding === "off" || flags.embedding === "local")
|
|
49
57
|
return { kind: flags.embedding };
|
|
50
58
|
const model = flags.embedding;
|
|
@@ -81,9 +89,12 @@ async function askSource(input) {
|
|
|
81
89
|
const hasServing = servingConnection(input) !== undefined;
|
|
82
90
|
const heldModel = hasServing && current.embedding?.endpoint === undefined ? current.embedding?.model : undefined;
|
|
83
91
|
const connectionModels = [...new Set([...(heldModel ? [heldModel] : []), ...served])];
|
|
92
|
+
const local = hasLocalEmbeddings();
|
|
93
|
+
if (!local)
|
|
94
|
+
p.log.info(noLocalEmbeddingsReason());
|
|
84
95
|
const options = [
|
|
85
96
|
...connectionModels.map((model) => ({ value: `model:${model}`, label: model, hint: "served by the connection" })),
|
|
86
|
-
{ value: "local", label: "Built-in local embedder", hint: "no server needed" },
|
|
97
|
+
...(local ? [{ value: "local", label: "Built-in local embedder", hint: "no server needed" }] : []),
|
|
87
98
|
{ value: "endpoint", label: "Another endpoint" },
|
|
88
99
|
{ value: "off", label: "Off", hint: "keyword search only" },
|
|
89
100
|
];
|
|
@@ -93,7 +104,9 @@ async function askSource(input) {
|
|
|
93
104
|
? `model:${heldModel}`
|
|
94
105
|
: served[0]
|
|
95
106
|
? `model:${served[0]}`
|
|
96
|
-
:
|
|
107
|
+
: local
|
|
108
|
+
? "local"
|
|
109
|
+
: "off";
|
|
97
110
|
const choice = await prompt(() => p.select({ message: "Which embedding source should akm use?", options, initialValue }));
|
|
98
111
|
if (choice === "local" || choice === "off")
|
|
99
112
|
return { kind: choice };
|
|
@@ -104,7 +117,7 @@ async function askSource(input) {
|
|
|
104
117
|
...(current.embedding?.endpoint ? { initialValue: current.embedding.endpoint } : {}),
|
|
105
118
|
});
|
|
106
119
|
if (endpoint === undefined)
|
|
107
|
-
return { kind: "local" };
|
|
120
|
+
return { kind: local ? "local" : "off" };
|
|
108
121
|
// A key belongs to the endpoint it was given for, so the current one is offered for that endpoint alone.
|
|
109
122
|
const unchangedEndpoint = current.embedding?.endpoint !== undefined && sameEndpoint(current.embedding.endpoint, endpoint);
|
|
110
123
|
const held = unchangedEndpoint
|
|
@@ -141,6 +154,10 @@ export async function stepEmbeddings(input) {
|
|
|
141
154
|
return { answer, semantic: { mode: "off", prepareAssets: false } };
|
|
142
155
|
const after = embeddingAfter(input, answer);
|
|
143
156
|
p.note(describeSemanticSearchAssets(after).join("\n"), "Semantic Search Assets");
|
|
157
|
+
if (!input.probe) {
|
|
158
|
+
p.log.info("Offline run: semantic search is not checked or prepared now; the first `akm index` does both.");
|
|
159
|
+
return { answer, semantic: { mode: "auto", prepareAssets: false } };
|
|
160
|
+
}
|
|
144
161
|
const prepareAssets = await prompt(() => p.confirm({
|
|
145
162
|
message: isRemoteEmbeddingConfig(after)
|
|
146
163
|
? "Check the embedding endpoint and verify semantic search now?"
|
package/dist/storage/database.js
CHANGED
|
@@ -125,66 +125,32 @@ function loadBunSqlite() {
|
|
|
125
125
|
}
|
|
126
126
|
let betterSqlite3Ctor;
|
|
127
127
|
/**
|
|
128
|
-
* The
|
|
128
|
+
* The package is missing, or it carries no binding for this platform. Its N-API
|
|
129
|
+
* bindings ship prebuilt in the package (see package.json → pinNotes), so neither
|
|
130
|
+
* a build toolchain nor the Node version decides whether it loads.
|
|
129
131
|
*/
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
* and answer with the one command that fixes it.
|
|
138
|
-
*
|
|
139
|
-
* This is the most likely failure a real user hits, and it is not a broken
|
|
140
|
-
* install: a native addon is compiled (or a prebuilt binary is selected) for
|
|
141
|
-
* the Node major present at `npm install` time. Upgrade Node afterwards and the
|
|
142
|
-
* same file no longer loads.
|
|
143
|
-
*
|
|
144
|
-
* It is matched rather than described because the symptom text varies and the
|
|
145
|
-
* previous wording only named ONE of them. The prebuilt-binary path — which is
|
|
146
|
-
* now the normal path, since better-sqlite3 is pinned to a version publishing a
|
|
147
|
-
* prebuild for every supported Node (see package.json → pinNotes) — reports
|
|
148
|
-
* `Module did not self-register`, saying nothing about versions at all. A
|
|
149
|
-
* from-source build reports the explicit `NODE_MODULE_VERSION` mismatch. Asking
|
|
150
|
-
* the user to decide which bullet applies is the step worth deleting.
|
|
151
|
-
*/
|
|
152
|
-
export function abiMismatchRemedy(message) {
|
|
153
|
-
const ABI_MISMATCH_SHAPES = [
|
|
154
|
-
"did not self-register", // prebuilt binary for another ABI
|
|
155
|
-
"NODE_MODULE_VERSION", // explicit mismatch, from-source build
|
|
156
|
-
"was compiled against a different", // same, older phrasing
|
|
157
|
-
"invalid ELF header", // binary for another platform/arch entirely
|
|
158
|
-
];
|
|
159
|
-
if (!ABI_MISMATCH_SHAPES.some((shape) => message.includes(shape)))
|
|
160
|
-
return undefined;
|
|
161
|
-
return ("akm could not load 'better-sqlite3': its native binding was built for a different\n" +
|
|
162
|
-
`Node.js version than the one now running (this Node is ABI ${process.versions.modules}).\n` +
|
|
163
|
-
"This is what happens when Node is upgraded after akm is installed. It is NOT a\n" +
|
|
164
|
-
"broken install, and reinstalling akm is not required.\n" +
|
|
165
|
-
" Fix: npm rebuild better-sqlite3 # in akm's install directory\n" +
|
|
166
|
-
" Or: npm install -g akm-cli # reinstall, rebuilding against this Node\n" +
|
|
167
|
-
" Or: run akm under Bun, whose built-in SQLite driver needs no native binding.");
|
|
132
|
+
function missingBindingRemedy() {
|
|
133
|
+
return ("akm could not load 'better-sqlite3', the SQLite driver it needs on Node.js.\n" +
|
|
134
|
+
" • Its package carries prebuilt bindings for Linux and macOS on x64 and arm64 and for\n" +
|
|
135
|
+
` Windows on x64 and arm64; this machine is ${process.platform} ${process.arch}.\n` +
|
|
136
|
+
" • Reinstall akm (`npm install -g akm-cli`) if the package is missing (a global\n" +
|
|
137
|
+
" `npm i -g better-sqlite3` will NOT be resolved — Node loads it from akm's own node_modules).\n" +
|
|
138
|
+
" • Or run akm under Bun, which has a built-in SQLite driver and needs no native binding.");
|
|
168
139
|
}
|
|
169
140
|
function loadBetterSqlite3() {
|
|
170
141
|
if (!betterSqlite3Ctor) {
|
|
171
142
|
// Runtime-gated dynamic require: only reached when NOT on Bun, so Bun never
|
|
172
143
|
// resolves or loads the optional `better-sqlite3` native dependency.
|
|
173
144
|
// `better-sqlite3` is an optionalDependency, so `npm i` can succeed without
|
|
174
|
-
// it
|
|
175
|
-
//
|
|
145
|
+
// it. Convert the raw MODULE_NOT_FOUND into an actionable message instead of
|
|
146
|
+
// a cryptic onboarding crash.
|
|
176
147
|
let mod;
|
|
177
148
|
try {
|
|
178
149
|
mod = nodeRequire("better-sqlite3");
|
|
179
150
|
}
|
|
180
151
|
catch (err) {
|
|
181
|
-
// An ABI mismatch does NOT arrive here — `require` succeeds and the
|
|
182
|
-
// failure lands at construction (see openNodeDatabase). This path is a
|
|
183
|
-
// genuinely absent or unresolvable module. `abiMismatchRemedy` is still
|
|
184
|
-
// consulted because a from-source build CAN fail at load with the
|
|
185
|
-
// explicit NODE_MODULE_VERSION message.
|
|
186
152
|
const raw = err instanceof Error ? err.message : String(err);
|
|
187
|
-
throw new Error(`${
|
|
153
|
+
throw new Error(`${missingBindingRemedy()}\n Underlying load error: ${raw}`);
|
|
188
154
|
}
|
|
189
155
|
betterSqlite3Ctor = mod.default ?? mod;
|
|
190
156
|
}
|
|
@@ -201,23 +167,17 @@ function openNodeDatabase(path, opts) {
|
|
|
201
167
|
options.readonly = opts.readonly;
|
|
202
168
|
if (opts?.create === false)
|
|
203
169
|
options.fileMustExist = true;
|
|
204
|
-
// Construction, not `require`, is where an ABI mismatch surfaces.
|
|
205
|
-
// `require("better-sqlite3")` SUCCEEDS against a binding built for another
|
|
206
|
-
// Node ABI — the package resolves its `.node` file lazily — so the loader's
|
|
207
|
-
// catch never sees this error and cannot explain it. Verified against a real
|
|
208
|
-
// ABI-127 binding under Node 24 (ABI 137): `require()` returned a function
|
|
209
|
-
// and `new Database(...)` threw. Wrapping the require alone left the most
|
|
210
|
-
// likely real-world failure reported as a bare Node internals message.
|
|
211
170
|
let db;
|
|
212
171
|
try {
|
|
213
172
|
db = opts ? new BetterSqlite3(path, options) : new BetterSqlite3(path);
|
|
214
173
|
}
|
|
215
174
|
catch (err) {
|
|
175
|
+
// The package loads its binding at the first construction, not at `require`, so a platform it carries no
|
|
176
|
+
// binding for fails here.
|
|
216
177
|
const raw = err instanceof Error ? err.message : String(err);
|
|
217
|
-
|
|
218
|
-
if (!remedy)
|
|
178
|
+
if (!raw.includes("better_sqlite3.node"))
|
|
219
179
|
throw err;
|
|
220
|
-
throw new Error(`${
|
|
180
|
+
throw new Error(`${missingBindingRemedy()}\n Underlying error: ${raw}`);
|
|
221
181
|
}
|
|
222
182
|
return {
|
|
223
183
|
prepare: db.prepare.bind(db),
|
|
@@ -438,6 +438,15 @@ function readCrontab(exec) {
|
|
|
438
438
|
const stderr = (result.stderr ?? "").trim();
|
|
439
439
|
if (((result.stdout ?? "").trim() === "" && stderr === "") || /no crontab/i.test(stderr))
|
|
440
440
|
return "";
|
|
441
|
+
// `crontab` reads the spool through its setgid `crontab` group (setuid root on some distributions). A process
|
|
442
|
+
// started with no-new-privileges (an agent sandbox, a container, systemd `NoNewPrivileges=`) runs it without that
|
|
443
|
+
// privilege, and the spool refuses the read. The crontab itself is fine, so say so instead of passing on a bare
|
|
444
|
+
// `fopen` error. Without no-new-privileges a refusal has another cause and is reported as what it said.
|
|
445
|
+
if (result.noNewPrivileges && /permission denied/i.test(stderr)) {
|
|
446
|
+
throw new ConfigError("crontab -l could not read the crontab spool (permission denied). `crontab` reads it through its setgid " +
|
|
447
|
+
"(or setuid) bit, which this process, started with no-new-privileges (an agent sandbox, a container, " +
|
|
448
|
+
"systemd NoNewPrivileges=), does not grant; run this from a normal shell to see the installed tasks.", "INVALID_CONFIG_FILE", "Run the command from a normal login shell or terminal, not from a sandboxed process.");
|
|
449
|
+
}
|
|
441
450
|
throw new ConfigError(`crontab -l failed (exit ${result.status}): ${result.stderr || result.stdout || "no output"}.`, "INVALID_CONFIG_FILE", "The `crontab` binary ran but did not report success; check its output above.");
|
|
442
451
|
}
|
|
443
452
|
function writeCrontab(exec, content) {
|
|
@@ -463,6 +472,15 @@ function replaceCrontab(exec, existing, next) {
|
|
|
463
472
|
throw err;
|
|
464
473
|
}
|
|
465
474
|
}
|
|
475
|
+
/** Whether this process has Linux's no-new-privileges flag, which every child (`crontab` included) inherits. */
|
|
476
|
+
function hasNoNewPrivileges() {
|
|
477
|
+
try {
|
|
478
|
+
return /^NoNewPrivs:\s*1$/m.test(fs.readFileSync("/proc/self/status", "utf8"));
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
466
484
|
function defaultCronExec() {
|
|
467
485
|
const run = (args, input) => {
|
|
468
486
|
const r = spawnSync("crontab", args, { encoding: "utf8", ...(input !== undefined ? { input } : {}) });
|
|
@@ -471,6 +489,7 @@ function defaultCronExec() {
|
|
|
471
489
|
stdout: r.stdout ?? "",
|
|
472
490
|
stderr: r.stderr ?? "",
|
|
473
491
|
enoent: r.error?.code === "ENOENT",
|
|
492
|
+
noNewPrivileges: r.status !== 0 && hasNoNewPrivileges(),
|
|
474
493
|
};
|
|
475
494
|
};
|
|
476
495
|
return {
|
|
@@ -225,8 +225,9 @@ Why: an accidental API is a compatibility promise nobody agreed to make.
|
|
|
225
225
|
|
|
226
226
|
Enforced by: `tests/contracts/api-surface.test.ts` (the export list),
|
|
227
227
|
`tests/integration/install/package-exports.test.ts` (the `exports` map),
|
|
228
|
-
`package.json` (`engines.node`
|
|
229
|
-
`node-smoke` job in `.github/workflows/ci.yml` (Node 22
|
|
228
|
+
`package.json` (`engines.node`), the Node version check in the launchers and
|
|
229
|
+
`src/cli.ts`, and the `node-smoke` job in `.github/workflows/ci.yml` (Node 22
|
|
230
|
+
and 24).
|
|
230
231
|
|
|
231
232
|
### 10. Agent harnesses are thin, replaceable internals
|
|
232
233
|
|
|
@@ -26,7 +26,7 @@ changes.
|
|
|
26
26
|
| `connections-and-harnesses` | One table of every configured connection (endpoint, whether its key is held — a `$VAR` set in this shell, a `credentials.json` entry, or none — and whether the endpoint answers) and harness (platform, whether its binary is on `PATH`, and with a probe its `--version`), then the work that runs on them: improve, judges, agents, extract, enrich, index, embedding and curate rerank, each naming the connection or harness and the model it resolves to through the same resolver the calls use (`evidence.rows`, typed `connection`, `harness` or `process`). `warn` when a key variable is unset, an endpoint does not answer, a harness is not installed, several connections (or harnesses) are configured and none is called `default` (said once, with the fix: name one `default`), or a work row cannot resolve because of the config; `unknown` when neither a connection nor a harness is configured; otherwise `pass`. Endpoint reachability is one bounded `GET /models` and the version is the harness's own `--version`; with `--no-probe` neither runs and the message says so. | Correct the named connection, key or harness; run `akm setup` when nothing is configured. A work row whose parts run somewhere other than the row's own target lists them in `overrides`, each with the kind (connection or harness), name and model it runs on: that is a per-process pin, working as set. |
|
|
27
27
|
| `task-fail-rate` | ≥5% of scheduled task runs failed in the window (exit 143/70 recurring), or a single task_id fails at/above the same threshold. (#943) For `command`-kind (agent/LLM dispatch) failures specifically, `evidence.agentFailureReasonCounts` always carries a `detail.reason` breakdown (`timeout`, `non_zero_exit`, `spawn_failed`, …), and the warn message names the dominant reason — e.g. `(timeout-dominant: 9/12 command-task failures)` — when one reason is at least half of the counted command-task failures. | Triage as a bug: `akm task doctor`, inspect failing lane logs. For a native shell/script task, exit-143 = killed/timeout, exit-70 = internal error. For a `command`-kind (agent/LLM) task, read `detail.reason` from `akm task history` instead of the exit code — `timeout` means the dispatch's own deadline (not an outer process kill) expired; `non_zero_exit`/`spawn_failed`/`aborted`/etc. name the other failure shapes. |
|
|
28
28
|
| `data-dir-usage` | The data directory is more than 3× the live databases (`state.db` + `index.db` + `logs.db`), or one top-level subdirectory holds more than half of it; the message names that subdirectory with its size and share. | Look at the named subdirectory. `backups/tasks/` holds one snapshot per `akm migrate apply` run that rewrote a task file; everything under `backups/` is yours to prune. |
|
|
29
|
-
| `semantic-search` | What the last `akm index` recorded about semantic search, with the index's entry and vector counts read now; no model load, no request. `pass` when every entry has a vector (local: on `onnxruntime-node`) or `semanticSearchMode` is `off`; `warn` when vectors lag entries, local embeddings ran on another runtime, or
|
|
29
|
+
| `semantic-search` | What the last `akm index` recorded about semantic search, with the index's entry and vector counts read now; no model load, no request. `pass` when every entry has a vector (local: on `onnxruntime-node`) or `semanticSearchMode` is `off`; `warn` when vectors lag entries, local embeddings ran on another runtime, or akm offers no local embeddings on this platform (`unavailable: platform`, where keyword search or an endpoint is the default); `fail` when the last index found it `blocked` (endpoint or model download failed) or `unavailable: load` (the runtime could not be downloaded or did not load, for example from a `noexec` cache directory), with the reason; `unknown` before the first index. A hard check, so a `fail` fails `akm health`. | `fail`: fix the reason named, then run `akm index`; the check reads the next index's record. `warn` for the platform: `akm setup --embedding off` for keyword search, or `akm setup --embedding <model> --embedding-endpoint <url>` for a remote endpoint. |
|
|
30
30
|
| `stash-git-exposure` | `env/` or `secrets/` assets are git-tracked **and** a remote is set — `git push` can leak keys. | `git rm --cached` the files, add `env/`+`secrets/` to `.gitignore` (a rule alone does not untrack). |
|
|
31
31
|
| `session-extraction` | (#914) Derived from the `extract_sessions_seen` ledger for the last 7 days — not `improve_runs`, which the hook-driven `akm proposal extract --session-id ...` never writes. `unknown` when nothing was recorded; `warn` when every session in the window was skipped for an infrastructure reason (`llm_unavailable`, `read_failed`, `exception`, `locked_concurrent`), naming the reason and engine; otherwise `pass` with per-outcome counts. | An `unknown` machine may be deliberately not configured for extraction, or may have stopped harvesting — check for recent sessions. A `warn` names the broken engine/reason directly. |
|
|
32
32
|
| `auto-accept-validation` | Proposals passed the confidence gate but failed validation (bad frontmatter, truncation). | Review the affected pending proposals via `akm proposal list`; they were held, not lost. |
|
|
@@ -37,7 +37,7 @@ changes.
|
|
|
37
37
|
| `index-state-generation` | Durable usage links in `state.db` disagree with the current searchable index generation, which an interrupted cross-database update leaves behind. | Stop concurrent writers, then run `akm index --full` to relink state to the current index. |
|
|
38
38
|
| `cli-version` | (#950) Compares the installed akm-cli version against the latest GitHub release (the same source `akm upgrade` trusts). `--probe`-gated: `unknown` "not probed" with `--no-probe`; offline/rate-limited also degrades to `unknown`, never a false `warn`. | Run `akm upgrade` when a newer release is reported. An `unknown` result carries no staleness claim either way — check manually if it matters. |
|
|
39
39
|
| `scheduler-binary` | (#953) Reads the scheduler's recorded akm invocation for the installed tasks (the same binding `task sync`/`task doctor` already read — no crontab/launchd/schtasks text parsing) and runs it with `--version`, compared against the running CLI's version. `--probe`-gated: `unknown` "not probed" with `--no-probe`. `unknown` when no scheduled task is installed or the recorded binary cannot be executed. | A `warn` names both versions — upgrading akm through a different installer than the one active at the last `task sync` left the schedule pointed at the old binary; run `akm task sync` to rebind it. |
|
|
40
|
-
| `plugin-version` / `opencode-plugin-version` | (itlackey/akm#832,) `plugin-version` reports each installed Claude Code harness plugin's version and its declared `AKM_VERSION_RANGE` admission of the running CLI. `opencode-plugin-version` does the same for the OpenCode plugin's own bundled `akm-cli` (read from its `package.json`, never executed) — it runs in-process and shares the host's databases, so its lag matters the same way. `plugin-version`'s newest-tag lookup is `--probe`-gated: with `--no-probe` its row is `unknown` ("not probed") and only the local range check still runs. `opencode-plugin-version` reads local files only and is never gated. | A `warn` names the stale/out-of-range plugin and the update command (`claude plugin update <ref>` for the Claude plugin; update the OpenCode plugin for the bundled copy). |
|
|
40
|
+
| `plugin-version` / `opencode-plugin-version` | (itlackey/akm#832,) `plugin-version` reports each installed Claude Code harness plugin's version and its declared `AKM_VERSION_RANGE` admission of the running CLI. `opencode-plugin-version` does the same for the OpenCode plugin's own bundled `akm-cli` (read from its `package.json`, never executed) — it runs in-process and shares the host's databases, so its lag matters the same way. OpenCode 1 caches each spec in its own folder, so `opencode-plugin-version` reads the one folder of the spec the global OpenCode config names under `plugin` (`akm-opencode`, read as `akm-opencode@latest`, or `akm-opencode@next`), and for `akm-opencode@<version>` reports the plugin as pinned with no staleness verdict; with no entry, or no folder for it, it reads `packages/akm-opencode`, and it never scans the other cached specs. The newest version is read on the channel the marketplace clone follows: on the remote's default branch the newest stable tag, on any other branch (such as `next`) the release tag on that branch's head, and on a clone pinned to a tag that tag. `plugin-version`'s newest-tag lookup is `--probe`-gated: with `--no-probe` its row is `unknown` ("not probed") and only the local range check still runs. `opencode-plugin-version` reads local files only and is never gated. | A `warn` names the stale/out-of-range plugin and the update command (`claude plugin update <ref>` for the Claude plugin; update the OpenCode plugin for the bundled copy). |
|
|
41
41
|
|
|
42
42
|
## Connections, harnesses and the work on them
|
|
43
43
|
|
|
@@ -810,6 +810,7 @@ adapter recognizes — `schema.md` + `pages/` is the probe) contains:
|
|
|
810
810
|
| `$CACHE/tasks/logs/<task-id>/` | Per-run stdout/stderr log files (`<ISO-ts>.log`) | No cleanup |
|
|
811
811
|
| `$CACHE/bin/rg` | Auto-downloaded ripgrep binary | Permanent |
|
|
812
812
|
| `$CACHE/opencode/<version>/opencode` | The OpenCode binary akm's own runtime runs (about 240 MB), fetched from the npm registry on its first use | Permanent; safe to delete (fetched again) |
|
|
813
|
+
| `$CACHE/onnxruntime/<version>-<platform>-<arch>/` | `onnxruntime_binding.node` and the ONNX Runtime library it links, which the local embedder loads; fetched from the npm registry by `akm setup --embedding local` or the first local embedding (about 36 MB kept). Must be on a mount that allows executing files | Permanent; safe to delete (fetched again) |
|
|
813
814
|
| `$CACHE/index/unresolved-sources/<stash>/<name>` | Synthetic placeholder path for a configured source whose content root did not resolve this run; never written to disk, only reported as a `SearchSource.path`. Moved out of `$STASH/.akm/unresolved-sources/` (itlackey/akm#890). | N/A (not a real directory) |
|
|
814
815
|
|
|
815
816
|
Cache-backed bundles (`git`, `website`, `npm`) are materialised into `$CACHE`
|
|
@@ -132,8 +132,9 @@ runs collapsed to `-`, trimmed) and match the fragment against those slugs;
|
|
|
132
132
|
fall back to a case-insensitive match against raw heading text. The selected
|
|
133
133
|
region runs from that heading to the next heading of the same or higher level.
|
|
134
134
|
A fragment that matches no heading is an error that **lists the available
|
|
135
|
-
fragment slugs
|
|
136
|
-
`toc`
|
|
135
|
+
fragment slugs**. The outline itself is `akm show <ref> --detail brief`, whose
|
|
136
|
+
`toc` lists every heading with the slug that selects it; it replaces the
|
|
137
|
+
removed `toc` view mode.
|
|
137
138
|
|
|
138
139
|
**Every other item kind — adapter-owned selector.** When one item exposes
|
|
139
140
|
multiple exports the adapter MAY append a stable fragment
|
|
@@ -157,7 +158,7 @@ is removed. It was a second argv parser (`normalizeShowArgv` rewrote
|
|
|
157
158
|
| --- | --- |
|
|
158
159
|
| `akm show X section "Auth"` | `akm show X#auth` |
|
|
159
160
|
| `akm show X full` | `akm show X` (no fragment = whole item) |
|
|
160
|
-
| `akm show X toc` | `akm show X
|
|
161
|
+
| `akm show X toc` | `akm show X --detail brief` (its `toc` field) |
|
|
161
162
|
| `akm show X lines 10 30` | removed — every response carries `path`; slice the file |
|
|
162
163
|
| `akm show X frontmatter` | removed — if a raw-YAML projection proves necessary it returns as a `--shape` value, the designated projection axis |
|
|
163
164
|
|
|
@@ -240,19 +240,27 @@ sets the same path and restores it from a model/source-identified Actions
|
|
|
240
240
|
cache. Candidate tags and manual runs are restore-only; only the scheduled
|
|
241
241
|
default-branch run may save a cache entry.
|
|
242
242
|
|
|
243
|
-
The same gate runs real semantic index/search round-trips on `onnxruntime-node
|
|
244
|
-
asserts the `embeddingRuntime` the index reports, and compares the
|
|
245
|
-
vectors with the Transformers.js reference in
|
|
246
|
-
`tests/fixtures/embedder-reference-vectors.json`.
|
|
247
|
-
|
|
248
|
-
|
|
243
|
+
The same gate runs real semantic index/search round-trips on `onnxruntime-node`'s
|
|
244
|
+
binding, asserts the `embeddingRuntime` the index reports, and compares the
|
|
245
|
+
embedder's vectors with the Transformers.js reference in
|
|
246
|
+
`tests/fixtures/embedder-reference-vectors.json`. The binding and its library are
|
|
247
|
+
shared across runs the same way as the model: `tests/_helpers/onnx-runtime-cache.ts`
|
|
248
|
+
copies the repo-local `.ci-cache/akm/onnxruntime/` into each sandbox's akm cache
|
|
249
|
+
and keeps a folder a run fetched there, and gated CI caches that directory under
|
|
250
|
+
a key from `src/llm/embedders/onnx-runtime.ts`, which pins the ONNX Runtime
|
|
251
|
+
version. Package acceptance separately verifies
|
|
252
|
+
the ordinary npm package surface.
|
|
249
253
|
|
|
250
254
|
`tests/integration/standalone-semantic.test.ts` (the same `AKM_SEMANTIC_TESTS=1`
|
|
251
|
-
gate) builds the host's standalone binary and
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
gate) builds the host's standalone binary and asserts it embeds no `.node` file.
|
|
256
|
+
Where akm offers local embeddings it runs `akm setup --yes --embedding local`,
|
|
257
|
+
expects the binding and the library in the cache folder, and indexes a small
|
|
258
|
+
stash, asserting a ready status, a vector per entry and the native runtime.
|
|
259
|
+
Everywhere else it asserts the default: setup warns and saves keyword search, an
|
|
260
|
+
index with semantic search on reports it unavailable for the platform, `akm
|
|
261
|
+
health` warns, and search is keyword. Gated CI runs it in the Semantic Search job
|
|
262
|
+
(Linux) and in the Native Scheduler job on macOS (local embeddings) and Windows
|
|
263
|
+
(the default), the only places the binary runs on those platforms.
|
|
256
264
|
|
|
257
265
|
### What to test explicitly
|
|
258
266
|
|
|
@@ -282,7 +290,8 @@ This validates three deployment methods across four Linux families:
|
|
|
282
290
|
- bun-based install: Ubuntu, Debian, Alpine, Fedora
|
|
283
291
|
- compiled binary install: Ubuntu, Debian, Fedora
|
|
284
292
|
- npm install: Debian on `node:24-slim`, which has no compiler, python or make
|
|
285
|
-
(`npm install -g` of the packed package
|
|
293
|
+
(`npm install -g --ignore-scripts` of the packed package, as npm 12 installs by
|
|
294
|
+
default; the install's exit code and the smoke test are the check)
|
|
286
295
|
|
|
287
296
|
Binary validation currently excludes Alpine. The compiled Linux binary used in
|
|
288
297
|
this repo's Docker tests is not packaged for Alpine/musl compatibility, so the
|
|
@@ -397,9 +406,9 @@ For a local release gate without Docker, use:
|
|
|
397
406
|
./tests/release-check.sh --skip-docker
|
|
398
407
|
```
|
|
399
408
|
|
|
400
|
-
That script checks the packaged artifact: bin targets, the migrator
|
|
401
|
-
acceptance, and the
|
|
402
|
-
|
|
409
|
+
That script checks the packaged artifact: bin targets, the migrator and package
|
|
410
|
+
acceptance. Lint, the typecheck and the test suites are `bun run check`; the
|
|
411
|
+
upgrade rehearsal runs in CI.
|
|
403
412
|
|
|
404
413
|
The Release workflow runs Gated CI (real embeddings, Docker, and the
|
|
405
414
|
Linux/macOS/Windows schedulers) on the commit it publishes and requires CI to
|
|
@@ -512,17 +521,16 @@ throws naming the fix instead. Env overrides:
|
|
|
512
521
|
resolving the highest published stable version below the candidate's
|
|
513
522
|
`package.json` version.
|
|
514
523
|
- `AKM_CANDIDATE_TARBALL=<path>` — use an already-packed candidate tarball
|
|
515
|
-
|
|
516
|
-
`
|
|
517
|
-
`bun run build` first).
|
|
524
|
+
instead of running `npm pack` on the working tree (which requires
|
|
525
|
+
`dist/cli.js` — run `bun run build` first).
|
|
518
526
|
|
|
519
527
|
Fetched previous-release tarballs are cached under
|
|
520
528
|
`${TMPDIR:-/tmp}/akm-upgrade-rehearsal/previous-release/<version>/` so repeat
|
|
521
529
|
runs do not re-hit the network. It is wired into CI as the `upgrade-rehearsal`
|
|
522
530
|
job (`.github/workflows/ci.yml`), which runs on pushes to `main` and release
|
|
523
531
|
branches, on manual runs and on every commit of a pull request that is ready
|
|
524
|
-
for review (a draft pull request skips it until it is marked ready)
|
|
525
|
-
|
|
532
|
+
for review (a draft pull request skips it until it is marked ready). The Release
|
|
533
|
+
workflow requires that CI run to pass on the commit it publishes.
|
|
526
534
|
|
|
527
535
|
## Coverage Gap Guide
|
|
528
536
|
|
|
@@ -73,14 +73,21 @@ lookup to one installed bundle when a conceptId is ambiguous across bundles.
|
|
|
73
73
|
|
|
74
74
|
## 4. Load a document section
|
|
75
75
|
|
|
76
|
-
Knowledge docs and other markdown assets can be large.
|
|
77
|
-
|
|
76
|
+
Knowledge docs and other markdown assets can be large. Read the outline first,
|
|
77
|
+
then only the sections you need. `--detail brief` returns the document's `toc`
|
|
78
|
+
without the body, and appending `#<slug>` to a ref returns one section:
|
|
78
79
|
|
|
79
80
|
```sh
|
|
81
|
+
akm show knowledge/api-guide --detail brief
|
|
82
|
+
# → {"toc": [{"level": 2, "text": "Authentication", "slug": "authentication", "startLine": 12}, ...], ...}
|
|
80
83
|
akm show knowledge/api-guide#authentication
|
|
81
84
|
# → just the "Authentication" section, not the full document
|
|
82
85
|
```
|
|
83
86
|
|
|
87
|
+
Every `slug` in `toc` selects its section, including repeated headings, which
|
|
88
|
+
get `-1`, `-2`, ... suffixes. A whole-document show carries the same `toc`
|
|
89
|
+
next to the body.
|
|
90
|
+
|
|
84
91
|
Search can also return an opaque `#akm-fragment-...` selector for a bounded
|
|
85
92
|
piece of a long document. The exact selector remains the default. For memories
|
|
86
93
|
whose opening context helps interpret a later matching turn, opt into bounded
|
|
@@ -96,8 +103,9 @@ reports `contextTruncated` in JSON. The response also distinguishes canonical
|
|
|
96
103
|
`parentRef` from `selectedRef` and includes fragment position, line bounds,
|
|
97
104
|
neighbors, and separate fragment/parent size estimates.
|
|
98
105
|
|
|
99
|
-
An unmatched fragment lists the available slugs
|
|
100
|
-
|
|
106
|
+
An unmatched fragment is an error that lists the available slugs, so a guess
|
|
107
|
+
that misses still tells you what to try next; `--detail brief` is the way to
|
|
108
|
+
see them without guessing.
|
|
101
109
|
|
|
102
110
|
## 5. Record the outcome
|
|
103
111
|
|