akm-cli 0.9.0-beta.54 → 0.9.0-beta.55
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/cli.js +5 -3
- package/dist/commands/agent/contribute-cli.js +2 -3
- package/dist/commands/env/env-cli.js +187 -202
- package/dist/commands/env/secret-cli.js +109 -121
- package/dist/commands/feedback-cli.js +152 -155
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/improve-metrics.js +754 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +18 -0
- package/dist/commands/health/windows.js +196 -0
- package/dist/commands/health.js +14 -1624
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/collapse-detector.js +3 -2
- package/dist/commands/improve/consolidate.js +636 -633
- package/dist/commands/improve/dedup.js +1 -1
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +228 -0
- package/dist/commands/improve/distill/quality-gate.js +233 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill.js +49 -575
- package/dist/commands/improve/extract-cli.js +74 -76
- package/dist/commands/improve/extract.js +6 -4
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +3 -2
- package/dist/commands/improve/improve-cli.js +14 -13
- package/dist/commands/improve/improve-result-file.js +2 -1
- package/dist/commands/improve/improve.js +6 -5
- package/dist/commands/improve/loop-stages.js +19 -21
- package/dist/commands/improve/preparation.js +4 -2
- package/dist/commands/improve/procedural.js +10 -31
- package/dist/commands/improve/recombine.js +19 -43
- package/dist/commands/improve/reflect.js +1 -1
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +48 -0
- package/dist/commands/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +2 -2
- package/dist/commands/proposal/drain.js +1 -1
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +3 -3
- package/dist/commands/proposal/proposal.js +2 -1
- package/dist/commands/proposal/propose.js +1 -1
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposals.js +5 -920
- package/dist/commands/read/remember-cli.js +132 -137
- package/dist/commands/read/search-cli.js +1 -1
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/sources/add-cli.js +90 -94
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/schema-repair.js +1 -1
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/tasks-cli.js +1 -2
- package/dist/commands/wiki-cli.js +2 -3
- package/dist/core/common.js +3 -3
- package/dist/core/config/config-schema.js +6 -0
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/events.js +2 -1
- package/dist/core/logs-db.js +8 -13
- package/dist/core/paths.js +14 -14
- package/dist/core/state-db.js +13 -1140
- package/dist/indexer/db/db.js +66 -709
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/feedback/utility-policy.js +85 -0
- package/dist/indexer/graph/graph-extraction.js +2 -1
- package/dist/indexer/index-writer-lock.js +9 -0
- package/dist/indexer/indexer.js +78 -23
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/integrations/agent/spawn.js +15 -66
- package/dist/output/text/helpers.js +13 -0
- package/dist/scripts/migrate-storage.js +6891 -7436
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +44 -43
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +24 -1607
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- package/dist/storage/repositories/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +130 -0
- package/dist/storage/repositories/index-db.js +4 -7
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/sqlite-pragmas.js +3 -3
- package/dist/tasks/runner.js +2 -1
- package/package.json +1 -1
- package/dist/commands/improve/homeostatic.js +0 -497
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { defineCommand } from "citty";
|
|
7
6
|
import * as p from "../../cli/clack.js";
|
|
8
|
-
import {
|
|
7
|
+
import { defineJsonCommand, output } from "../../cli/shared.js";
|
|
9
8
|
import { UsageError } from "../../core/errors.js";
|
|
10
9
|
import { appendEvent } from "../../core/events.js";
|
|
11
10
|
import { warn } from "../../core/warn.js";
|
|
12
|
-
import { getHyphenatedBoolean } from "../../output/context.js";
|
|
13
11
|
import { akmRemove } from "./installed-stashes.js";
|
|
14
12
|
import { akmAdd } from "./source-add.js";
|
|
15
13
|
import { addStash } from "./source-manage.js";
|
|
@@ -149,7 +147,7 @@ export async function auditInstalledStashForDangerousKeys(opts) {
|
|
|
149
147
|
return { blocked: true, exitCode: 1 };
|
|
150
148
|
}
|
|
151
149
|
// ── Command definition ────────────────────────────────────────────────────────
|
|
152
|
-
export const addCommand =
|
|
150
|
+
export const addCommand = defineJsonCommand({
|
|
153
151
|
meta: {
|
|
154
152
|
name: "add",
|
|
155
153
|
description: "Add a source (local directory, website, npm package, GitHub repo, git URL, or remote provider)",
|
|
@@ -181,48 +179,11 @@ export const addCommand = defineCommand({
|
|
|
181
179
|
},
|
|
182
180
|
},
|
|
183
181
|
async run({ args }) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (args.provider) {
|
|
190
|
-
if (shouldWarnOnPlainHttp(ref)) {
|
|
191
|
-
if (!allowInsecure) {
|
|
192
|
-
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
193
|
-
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.", "INVALID_FLAG_VALUE", "Re-run with `--allow-insecure` only after confirming the URL is trusted.");
|
|
194
|
-
}
|
|
195
|
-
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
196
|
-
}
|
|
197
|
-
let parsedOptions;
|
|
198
|
-
if (args.options) {
|
|
199
|
-
try {
|
|
200
|
-
const parsed = JSON.parse(args.options);
|
|
201
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
202
|
-
throw new UsageError("--options must be a JSON object");
|
|
203
|
-
}
|
|
204
|
-
parsedOptions = parsed;
|
|
205
|
-
}
|
|
206
|
-
catch (err) {
|
|
207
|
-
if (err instanceof UsageError)
|
|
208
|
-
throw err;
|
|
209
|
-
throw new UsageError("--options must be valid JSON");
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const result = addStash({
|
|
213
|
-
target: ref,
|
|
214
|
-
name: args.name,
|
|
215
|
-
providerType: args.provider,
|
|
216
|
-
options: parsedOptions,
|
|
217
|
-
writable: args.writable,
|
|
218
|
-
});
|
|
219
|
-
appendEvent({
|
|
220
|
-
eventType: "add",
|
|
221
|
-
metadata: { target: ref, provider: args.provider, name: args.name ?? null, writable: args.writable === true },
|
|
222
|
-
});
|
|
223
|
-
output("add", result);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
182
|
+
const ref = args.ref.trim();
|
|
183
|
+
const allowInsecure = args["allow-insecure"];
|
|
184
|
+
const allowDangerousKeys = allowInsecure;
|
|
185
|
+
// URL with --provider → stash source (remote or git provider)
|
|
186
|
+
if (args.provider) {
|
|
226
187
|
if (shouldWarnOnPlainHttp(ref)) {
|
|
227
188
|
if (!allowInsecure) {
|
|
228
189
|
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
@@ -230,64 +191,99 @@ export const addCommand = defineCommand({
|
|
|
230
191
|
}
|
|
231
192
|
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
232
193
|
}
|
|
233
|
-
|
|
234
|
-
if (args.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return;
|
|
194
|
+
let parsedOptions;
|
|
195
|
+
if (args.options) {
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(args.options);
|
|
198
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
199
|
+
throw new UsageError("--options must be a JSON object");
|
|
200
|
+
}
|
|
201
|
+
parsedOptions = parsed;
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
if (err instanceof UsageError)
|
|
205
|
+
throw err;
|
|
206
|
+
throw new UsageError("--options must be valid JSON");
|
|
207
|
+
}
|
|
248
208
|
}
|
|
249
|
-
const result =
|
|
209
|
+
const result = addStash({
|
|
210
|
+
target: ref,
|
|
211
|
+
name: args.name,
|
|
212
|
+
providerType: args.provider,
|
|
213
|
+
options: parsedOptions,
|
|
214
|
+
writable: args.writable,
|
|
215
|
+
});
|
|
216
|
+
appendEvent({
|
|
217
|
+
eventType: "add",
|
|
218
|
+
metadata: { target: ref, provider: args.provider, name: args.name ?? null, writable: args.writable === true },
|
|
219
|
+
});
|
|
220
|
+
output("add", result);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (shouldWarnOnPlainHttp(ref)) {
|
|
224
|
+
if (!allowInsecure) {
|
|
225
|
+
throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
|
|
226
|
+
"Use https:// or pass --allow-insecure if you have explicitly accepted the risk.", "INVALID_FLAG_VALUE", "Re-run with `--allow-insecure` only after confirming the URL is trusted.");
|
|
227
|
+
}
|
|
228
|
+
warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
|
|
229
|
+
}
|
|
230
|
+
const websiteOptions = buildWebsiteOptions(args);
|
|
231
|
+
if (args.type === "wiki") {
|
|
232
|
+
const { registerWikiSource } = await import("./source-add.js");
|
|
233
|
+
const result = await registerWikiSource({
|
|
250
234
|
ref,
|
|
251
235
|
name: args.name,
|
|
252
|
-
overrideType: args.type,
|
|
253
236
|
options: Object.keys(websiteOptions).length > 0 ? websiteOptions : undefined,
|
|
254
237
|
writable: args.writable,
|
|
255
238
|
});
|
|
256
239
|
appendEvent({
|
|
257
240
|
eventType: "add",
|
|
258
|
-
metadata: {
|
|
259
|
-
target: ref,
|
|
260
|
-
name: args.name ?? null,
|
|
261
|
-
overrideType: args.type ?? null,
|
|
262
|
-
writable: args.writable === true,
|
|
263
|
-
},
|
|
241
|
+
metadata: { target: ref, type: "wiki", name: args.name ?? null, writable: args.writable === true },
|
|
264
242
|
});
|
|
265
|
-
// ── Post-install env key audit ──────────────────────────────────────────
|
|
266
|
-
// Resolve the stash root from the install result and scan any env files
|
|
267
|
-
// for dangerous env var keys. When findings are present the install is
|
|
268
|
-
// gated: TTY → interactive confirmation prompt; non-TTY without
|
|
269
|
-
// --allow-insecure → hard failure (exit 1). Pass
|
|
270
|
-
// --allow-insecure to skip the prompt non-interactively.
|
|
271
|
-
const installedStashRoot = result.installed?.stashRoot ??
|
|
272
|
-
(result.sourceAdded && "stashRoot" in result.sourceAdded ? result.sourceAdded.stashRoot : undefined);
|
|
273
|
-
if (installedStashRoot) {
|
|
274
|
-
// Use the canonical installed id (most reliably resolved by akmRemove) rather
|
|
275
|
-
// than the raw user-supplied ref which may not match after URL normalisation.
|
|
276
|
-
const rollbackTarget = result.installed?.id ?? result.sourceAdded?.stashRoot ?? ref;
|
|
277
|
-
// The audit RETURNS its decision; we decide `process.exit` here, OUTSIDE
|
|
278
|
-
// any catch, so the abort cannot be lost to a swallowed exception (C3).
|
|
279
|
-
const decision = await auditInstalledStashForDangerousKeys({
|
|
280
|
-
installedStashRoot,
|
|
281
|
-
ref,
|
|
282
|
-
allowDangerousKeys,
|
|
283
|
-
rollbackTarget,
|
|
284
|
-
isTTY: process.stdin.isTTY === true,
|
|
285
|
-
});
|
|
286
|
-
if (decision.blocked) {
|
|
287
|
-
process.exit(decision.exitCode);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
243
|
output("add", result);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const result = await akmAdd({
|
|
247
|
+
ref,
|
|
248
|
+
name: args.name,
|
|
249
|
+
overrideType: args.type,
|
|
250
|
+
options: Object.keys(websiteOptions).length > 0 ? websiteOptions : undefined,
|
|
251
|
+
writable: args.writable,
|
|
252
|
+
});
|
|
253
|
+
appendEvent({
|
|
254
|
+
eventType: "add",
|
|
255
|
+
metadata: {
|
|
256
|
+
target: ref,
|
|
257
|
+
name: args.name ?? null,
|
|
258
|
+
overrideType: args.type ?? null,
|
|
259
|
+
writable: args.writable === true,
|
|
260
|
+
},
|
|
291
261
|
});
|
|
262
|
+
// ── Post-install env key audit ──────────────────────────────────────────
|
|
263
|
+
// Resolve the stash root from the install result and scan any env files
|
|
264
|
+
// for dangerous env var keys. When findings are present the install is
|
|
265
|
+
// gated: TTY → interactive confirmation prompt; non-TTY without
|
|
266
|
+
// --allow-insecure → hard failure (exit 1). Pass
|
|
267
|
+
// --allow-insecure to skip the prompt non-interactively.
|
|
268
|
+
const installedStashRoot = result.installed?.stashRoot ??
|
|
269
|
+
(result.sourceAdded && "stashRoot" in result.sourceAdded ? result.sourceAdded.stashRoot : undefined);
|
|
270
|
+
if (installedStashRoot) {
|
|
271
|
+
// Use the canonical installed id (most reliably resolved by akmRemove) rather
|
|
272
|
+
// than the raw user-supplied ref which may not match after URL normalisation.
|
|
273
|
+
const rollbackTarget = result.installed?.id ?? result.sourceAdded?.stashRoot ?? ref;
|
|
274
|
+
// The audit RETURNS its decision; we decide `process.exit` here, OUTSIDE
|
|
275
|
+
// any catch, so the abort cannot be lost to a swallowed exception (C3).
|
|
276
|
+
const decision = await auditInstalledStashForDangerousKeys({
|
|
277
|
+
installedStashRoot,
|
|
278
|
+
ref,
|
|
279
|
+
allowDangerousKeys,
|
|
280
|
+
rollbackTarget,
|
|
281
|
+
isTTY: process.stdin.isTTY === true,
|
|
282
|
+
});
|
|
283
|
+
if (decision.blocked) {
|
|
284
|
+
process.exit(decision.exitCode);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
output("add", result);
|
|
292
288
|
},
|
|
293
289
|
});
|
|
@@ -22,7 +22,7 @@ import { readEvents } from "../../core/events.js";
|
|
|
22
22
|
import { isoToSqlite, parseSinceToIso } from "../../core/time.js";
|
|
23
23
|
import { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
|
|
24
24
|
import { getUsageEvents } from "../../indexer/usage/usage-events.js";
|
|
25
|
-
import { listProposals } from "../proposal/
|
|
25
|
+
import { listProposals } from "../proposal/repository.js";
|
|
26
26
|
// Proposal lifecycle event types emitted by the proposal substrate (#225).
|
|
27
27
|
const PROPOSAL_EVENT_TYPES = new Set(["promoted", "rejected"]);
|
|
28
28
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -23,7 +23,7 @@ import { resolveStandardsContext } from "../../core/standards/resolve-standards-
|
|
|
23
23
|
import { info } from "../../core/warn.js";
|
|
24
24
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
25
25
|
import { chatCompletion, parseEmbeddedJsonResponse } from "../../llm/client.js";
|
|
26
|
-
import { createProposal, isProposalSkipped } from "../proposal/
|
|
26
|
+
import { createProposal, isProposalSkipped } from "../proposal/repository.js";
|
|
27
27
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
28
28
|
/** Minimum gap between schema-repair attempts on the same asset. */
|
|
29
29
|
const SCHEMA_REPAIR_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
@@ -23,7 +23,7 @@ import { loadConfig } from "../../core/config/config.js";
|
|
|
23
23
|
import { UsageError } from "../../core/errors.js";
|
|
24
24
|
import { appendEvent } from "../../core/events.js";
|
|
25
25
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
26
|
-
import {
|
|
26
|
+
import { parseFlagValue } from "../../output/context.js";
|
|
27
27
|
import { resolveWritableOverride, saveGitStash } from "../../sources/providers/git.js";
|
|
28
28
|
import { pkgVersion } from "../../version.js";
|
|
29
29
|
import { akmHistory } from "./history.js";
|
|
@@ -125,8 +125,8 @@ export const upgradeCommand = defineJsonCommand({
|
|
|
125
125
|
output("upgrade", check);
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
|
-
const skipChecksum =
|
|
129
|
-
const skipPostUpgrade =
|
|
128
|
+
const skipChecksum = args["skip-checksum"];
|
|
129
|
+
const skipPostUpgrade = args["skip-post-upgrade"];
|
|
130
130
|
const result = await performUpgrade(check, { force: args.force, skipChecksum, skipPostUpgrade });
|
|
131
131
|
output("upgrade", result);
|
|
132
132
|
},
|
|
@@ -60,7 +60,7 @@ export const initCommand = defineJsonCommand({
|
|
|
60
60
|
const legacyDir = parseFlagValue(process.argv, "--stashDir") ?? parseFlagValue(process.argv, "--stash-dir");
|
|
61
61
|
const result = await akmInit({
|
|
62
62
|
dir: args.dir ?? legacyDir,
|
|
63
|
-
setDefault:
|
|
63
|
+
setDefault: args["set-default"],
|
|
64
64
|
});
|
|
65
65
|
output("init", result);
|
|
66
66
|
},
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import { defineCommand } from "citty";
|
|
16
16
|
import { parsePositiveIntFlag } from "../../cli/parse-args.js";
|
|
17
17
|
import { defineGroupCommand, defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
18
|
-
import { getHyphenatedArg } from "../../output/context.js";
|
|
19
18
|
import { detectServerDefault, registerDefaultTasks } from "./default-tasks.js";
|
|
20
19
|
import { akmTasksAdd, akmTasksDoctor, akmTasksHistory, akmTasksList, akmTasksRemove, akmTasksRun, akmTasksSetEnabled, akmTasksShow, akmTasksSync, parseTaskRef, } from "./tasks.js";
|
|
21
20
|
const tasksAddCommand = defineJsonCommand({
|
|
@@ -51,7 +50,7 @@ const tasksAddCommand = defineJsonCommand({
|
|
|
51
50
|
profile: args.profile,
|
|
52
51
|
params: args.params,
|
|
53
52
|
name: args.name,
|
|
54
|
-
when_to_use:
|
|
53
|
+
when_to_use: args["when-to-use"],
|
|
55
54
|
description: args.description,
|
|
56
55
|
tags: args.tags
|
|
57
56
|
? args.tags
|
|
@@ -16,7 +16,6 @@ import { defineGroupCommand, defineJsonCommand, output, runWithJsonErrors } from
|
|
|
16
16
|
import { resolveStashDir } from "../core/common.js";
|
|
17
17
|
import { loadConfig, resolveConfiguredSources } from "../core/config/config.js";
|
|
18
18
|
import { ConfigError, UsageError } from "../core/errors.js";
|
|
19
|
-
import { getHyphenatedArg, getHyphenatedBoolean } from "../output/context.js";
|
|
20
19
|
import { akmAgentDispatch } from "./agent/agent-dispatch.js";
|
|
21
20
|
import { readKnowledgeInput } from "./read/knowledge.js";
|
|
22
21
|
import { buildWebsiteOptions } from "./sources/add-cli.js";
|
|
@@ -108,7 +107,7 @@ const wikiRemoveCommand = defineJsonCommand({
|
|
|
108
107
|
process.stderr.write("Aborted.\n");
|
|
109
108
|
return;
|
|
110
109
|
}
|
|
111
|
-
const withSources =
|
|
110
|
+
const withSources = args["with-sources"];
|
|
112
111
|
const { removeWiki } = await import("../wiki/wiki.js");
|
|
113
112
|
const { akmIndex } = await import("../indexer/indexer.js");
|
|
114
113
|
const stashDir = resolveStashDir();
|
|
@@ -244,7 +243,7 @@ const wikiIngestCommand = defineJsonCommand({
|
|
|
244
243
|
if (!profileName) {
|
|
245
244
|
throw new UsageError("akm wiki ingest requires an agent profile. Pass --profile <name> or set defaults.agent in config.", "MISSING_REQUIRED_ARGUMENT", "Available profiles are listed under profiles.agent in your config. Run `akm config get profiles.agent` to inspect.");
|
|
246
245
|
}
|
|
247
|
-
const timeoutMs = parsePositiveIntFlag(
|
|
246
|
+
const timeoutMs = parsePositiveIntFlag(args["timeout-ms"], "--timeout-ms");
|
|
248
247
|
const model = getStringArg(args, "model");
|
|
249
248
|
const { getDefaultLlmConfig } = await import("../core/config/config.js");
|
|
250
249
|
const dispatchResult = await akmAgentDispatch({
|
package/dist/core/common.js
CHANGED
|
@@ -132,9 +132,9 @@ export function writeFileAtomic(target, content, mode) {
|
|
|
132
132
|
*
|
|
133
133
|
* Throws if no valid stash directory is found.
|
|
134
134
|
*/
|
|
135
|
-
export function resolveStashDir(_options) {
|
|
135
|
+
export function resolveStashDir(_options, env = process.env) {
|
|
136
136
|
// 1. Env var override (for CI, scripts, testing)
|
|
137
|
-
const envDir =
|
|
137
|
+
const envDir = env.AKM_STASH_DIR?.trim();
|
|
138
138
|
if (envDir) {
|
|
139
139
|
return validateStashDir(envDir);
|
|
140
140
|
}
|
|
@@ -143,7 +143,7 @@ export function resolveStashDir(_options) {
|
|
|
143
143
|
if (configStashDir)
|
|
144
144
|
return validateStashDir(configStashDir);
|
|
145
145
|
// 3. Platform default — use it if it exists
|
|
146
|
-
const defaultDir = getDefaultStashDir();
|
|
146
|
+
const defaultDir = getDefaultStashDir(env);
|
|
147
147
|
if (isValidDirectory(defaultDir)) {
|
|
148
148
|
return defaultDir;
|
|
149
149
|
}
|
|
@@ -846,6 +846,12 @@ export const AkmConfigShape = {
|
|
|
846
846
|
semanticSearchMode: z.enum(["off", "auto"]).default("auto"),
|
|
847
847
|
embedding: EmbeddingConnectionConfigSchema.optional(),
|
|
848
848
|
index: IndexConfigSchema.optional(),
|
|
849
|
+
// The `installed[]` shape is OWNED by the registry (`InstalledStashEntry`):
|
|
850
|
+
// its `source` is the 4-value `InstallKind` produced by the registry ref
|
|
851
|
+
// parser, and installed entries never carry the extra passthrough keys. The
|
|
852
|
+
// schema still validates entries at runtime, but its OUTPUT type is pinned to
|
|
853
|
+
// the domain type so config consumers get the registry `InstalledStashEntry`
|
|
854
|
+
// (not a looser schema-local mirror) — the single-source-of-truth boundary.
|
|
849
855
|
installed: z.array(InstalledStashEntrySchema).optional(),
|
|
850
856
|
registries: z.array(RegistryConfigEntrySchema).optional(),
|
|
851
857
|
sources: z.array(SourceConfigEntrySchema).optional(),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Generic recursive object merge, extracted from the setup wizard (it is not
|
|
6
|
+
* setup-specific). Plain objects merge key-by-key; arrays and scalars replace
|
|
7
|
+
* wholesale. Used to apply a partial `--file` config over the existing config
|
|
8
|
+
* without dropping sibling subkeys.
|
|
9
|
+
*/
|
|
10
|
+
/** True for non-null, non-array plain objects. */
|
|
11
|
+
export function isPlainObject(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Recursively merge `incoming` into `base`: plain objects merge key-by-key,
|
|
16
|
+
* while arrays and scalars replace wholesale. A partial input therefore only
|
|
17
|
+
* updates the keys it carries and never drops sibling subkeys (e.g. a file
|
|
18
|
+
* containing `{ output: { format: "text" } }` leaves `output.detail` intact).
|
|
19
|
+
*
|
|
20
|
+
* `base` is treated as immutable — a fresh object graph is returned.
|
|
21
|
+
*/
|
|
22
|
+
export function deepMergeConfig(base, incoming) {
|
|
23
|
+
if (!isPlainObject(incoming))
|
|
24
|
+
return incoming;
|
|
25
|
+
const baseObj = isPlainObject(base) ? base : {};
|
|
26
|
+
const out = { ...baseObj };
|
|
27
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
continue;
|
|
30
|
+
if (isPlainObject(value) && isPlainObject(baseObj[key])) {
|
|
31
|
+
out[key] = deepMergeConfig(baseObj[key], value);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
out[key] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
package/dist/core/events.js
CHANGED
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
* - `ts` is ISO-8601 (UTC, millisecond precision).
|
|
26
26
|
*/
|
|
27
27
|
import path from "node:path";
|
|
28
|
+
import { insertEvent, readStateEvents } from "../storage/repositories/events-repository.js";
|
|
28
29
|
import { rethrowIfTestIsolationError } from "./errors.js";
|
|
29
30
|
import { getDataDir } from "./paths.js";
|
|
30
|
-
import {
|
|
31
|
+
import { openStateDatabase, withStateDb } from "./state-db.js";
|
|
31
32
|
import { error } from "./warn.js";
|
|
32
33
|
/**
|
|
33
34
|
* Legacy events.jsonl path — used only by the migration script
|
package/dist/core/logs-db.js
CHANGED
|
@@ -37,11 +37,9 @@
|
|
|
37
37
|
*
|
|
38
38
|
* @module logs-db
|
|
39
39
|
*/
|
|
40
|
-
import fs from "node:fs";
|
|
41
40
|
import path from "node:path";
|
|
42
|
-
import { openDatabase } from "../storage/database.js";
|
|
43
41
|
import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
|
|
44
|
-
import {
|
|
42
|
+
import { openManagedDatabase } from "../storage/managed-db.js";
|
|
45
43
|
import { getDataDir } from "./paths.js";
|
|
46
44
|
// ── Path helper ──────────────────────────────────────────────────────────────
|
|
47
45
|
/**
|
|
@@ -72,16 +70,13 @@ export function getLogsDbPath() {
|
|
|
72
70
|
*/
|
|
73
71
|
export function openLogsDatabase(dbPath) {
|
|
74
72
|
const resolvedPath = dbPath ?? getLogsDbPath();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
applyStandardPragmas(db, { dataDir: dir, foreignKeys: false });
|
|
83
|
-
runMigrations(db);
|
|
84
|
-
return db;
|
|
73
|
+
// foreignKeys:false preserves this opener's historical behaviour — logs.db
|
|
74
|
+
// has never enforced foreign keys.
|
|
75
|
+
return openManagedDatabase({
|
|
76
|
+
path: resolvedPath,
|
|
77
|
+
pragmas: { dataDir: path.dirname(resolvedPath), foreignKeys: false },
|
|
78
|
+
init: runMigrations,
|
|
79
|
+
});
|
|
85
80
|
}
|
|
86
81
|
// ── Migrations ───────────────────────────────────────────────────────────────
|
|
87
82
|
/**
|
package/dist/core/paths.js
CHANGED
|
@@ -115,8 +115,8 @@ export function getConfigPath() {
|
|
|
115
115
|
return path.join(getConfigDir(), "config.json");
|
|
116
116
|
}
|
|
117
117
|
// ── Cache directory ──────────────────────────────────────────────────────────
|
|
118
|
-
export function getCacheDir() {
|
|
119
|
-
const override =
|
|
118
|
+
export function getCacheDir(env = process.env) {
|
|
119
|
+
const override = env.AKM_CACHE_DIR?.trim();
|
|
120
120
|
if (override)
|
|
121
121
|
return override;
|
|
122
122
|
// Explicit XDG/platform overrides win before the transient-stash isolation
|
|
@@ -125,13 +125,13 @@ export function getCacheDir() {
|
|
|
125
125
|
// as set, so the AKM_STASH_DIR transient rule does not silently move cache
|
|
126
126
|
// writes away from where they pointed them.
|
|
127
127
|
if (IS_WINDOWS) {
|
|
128
|
-
const localAppData =
|
|
128
|
+
const localAppData = env.LOCALAPPDATA?.trim();
|
|
129
129
|
if (localAppData)
|
|
130
130
|
return path.join(localAppData, "akm");
|
|
131
|
-
const userProfile =
|
|
131
|
+
const userProfile = env.USERPROFILE?.trim();
|
|
132
132
|
if (userProfile)
|
|
133
133
|
return path.join(userProfile, "AppData", "Local", "akm");
|
|
134
|
-
const appData =
|
|
134
|
+
const appData = env.APPDATA?.trim();
|
|
135
135
|
if (appData) {
|
|
136
136
|
// Heuristic fallback: APPDATA points to %APPDATA% (Roaming), so
|
|
137
137
|
// navigate to the sibling "Local" directory. This is typically
|
|
@@ -141,7 +141,7 @@ export function getCacheDir() {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
else {
|
|
144
|
-
const xdgCacheHome =
|
|
144
|
+
const xdgCacheHome = env.XDG_CACHE_HOME?.trim();
|
|
145
145
|
if (xdgCacheHome)
|
|
146
146
|
return path.join(xdgCacheHome, "akm");
|
|
147
147
|
}
|
|
@@ -150,7 +150,7 @@ export function getCacheDir() {
|
|
|
150
150
|
// into `${AKM_STASH_DIR}/.akm/cache` so that config backups, registry-index
|
|
151
151
|
// cache, and other regenerable artifacts do not pollute the user's host
|
|
152
152
|
// ~/.cache/akm directory.
|
|
153
|
-
const stashOverride =
|
|
153
|
+
const stashOverride = env.AKM_STASH_DIR?.trim();
|
|
154
154
|
if (stashOverride && isTransientStashPath(stashOverride)) {
|
|
155
155
|
return path.join(stashOverride, ".akm", "cache");
|
|
156
156
|
}
|
|
@@ -158,7 +158,7 @@ export function getCacheDir() {
|
|
|
158
158
|
// None of LOCALAPPDATA / USERPROFILE / APPDATA were set above.
|
|
159
159
|
throw new ConfigError("Unable to determine cache directory. Set LOCALAPPDATA, USERPROFILE, or APPDATA.", "CONFIG_DIR_UNRESOLVABLE");
|
|
160
160
|
}
|
|
161
|
-
const home =
|
|
161
|
+
const home = env.HOME?.trim();
|
|
162
162
|
if (!home)
|
|
163
163
|
return path.join("/tmp", "akm-cache");
|
|
164
164
|
return path.join(home, ".cache", "akm");
|
|
@@ -257,17 +257,17 @@ export function getTaskHistoryDir() {
|
|
|
257
257
|
return path.join(getCacheDir(), "tasks", "history");
|
|
258
258
|
}
|
|
259
259
|
// ── Default stash directory ──────────────────────────────────────────────────
|
|
260
|
-
export function getDefaultStashDir() {
|
|
261
|
-
const override =
|
|
260
|
+
export function getDefaultStashDir(env = process.env) {
|
|
261
|
+
const override = env.AKM_STASH_DIR?.trim();
|
|
262
262
|
if (override)
|
|
263
263
|
return override;
|
|
264
264
|
if (IS_WINDOWS) {
|
|
265
|
-
const userProfile =
|
|
265
|
+
const userProfile = env.USERPROFILE?.trim();
|
|
266
266
|
if (userProfile)
|
|
267
267
|
return path.join(userProfile, "Documents", "akm");
|
|
268
268
|
return path.join("C:\\", "akm");
|
|
269
269
|
}
|
|
270
|
-
const home =
|
|
270
|
+
const home = env.HOME?.trim();
|
|
271
271
|
if (!home) {
|
|
272
272
|
throw new ConfigError("Unable to determine default stash directory. Set HOME.", "STASH_DIR_NOT_FOUND");
|
|
273
273
|
}
|
|
@@ -294,7 +294,7 @@ export function getDefaultStashDir() {
|
|
|
294
294
|
* is fine even though `~/.local` is refused). This catches fat-finger
|
|
295
295
|
* `--dir /` or `--dir ~` without preventing legitimate nested use.
|
|
296
296
|
*/
|
|
297
|
-
export function assertSafeStashDir(stashDir) {
|
|
297
|
+
export function assertSafeStashDir(stashDir, env = process.env) {
|
|
298
298
|
const resolved = path.resolve(stashDir);
|
|
299
299
|
// Filesystem root — POSIX and Windows drive roots.
|
|
300
300
|
if (resolved === "/" || /^[A-Za-z]:[\\/]?$/.test(resolved)) {
|
|
@@ -334,7 +334,7 @@ export function assertSafeStashDir(stashDir) {
|
|
|
334
334
|
// under bun test (which isolates HOME to a tempdir while os.homedir()
|
|
335
335
|
// still returns the real user's home).
|
|
336
336
|
const candidateHomes = new Set();
|
|
337
|
-
const envHome = (
|
|
337
|
+
const envHome = (env.HOME ?? env.USERPROFILE)?.trim();
|
|
338
338
|
if (envHome)
|
|
339
339
|
candidateHomes.add(path.resolve(envHome));
|
|
340
340
|
try {
|