akm-cli 0.7.5 → 0.8.0-rc.6
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/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
- package/README.md +20 -4
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli.js +1995 -551
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +1531 -0
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +990 -75
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +5 -23
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +400 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +77 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-profiles.js +146 -0
- package/dist/commands/improve-result-file.js +103 -0
- package/dist/commands/improve.js +2175 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/index.js +183 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/vault-key-rules.js +139 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal.js +66 -12
- package/dist/commands/propose.js +86 -31
- package/dist/commands/reflect.js +1119 -73
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember.js +69 -6
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +144 -25
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +438 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/commands/vault.js +130 -77
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +7 -0
- package/dist/core/asset-registry.js +7 -16
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +22 -0
- package/dist/core/common.js +157 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +625 -0
- package/dist/core/config-schema.js +501 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +327 -987
- package/dist/core/errors.js +40 -19
- package/dist/core/events.js +91 -138
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +3 -6
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +326 -14
- package/dist/core/proposal-quality-validators.js +364 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +498 -42
- package/dist/core/state-db.js +927 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/warn.js +62 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +152 -253
- package/dist/indexer/db.js +933 -103
- package/dist/indexer/ensure-index.js +64 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -124
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +506 -291
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +148 -160
- package/dist/indexer/memory-inference.js +99 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +255 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +5 -16
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +150 -74
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +118 -23
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +68 -0
- package/dist/integrations/session-logs/providers/claude-code.js +59 -0
- package/dist/integrations/session-logs/providers/opencode.js +55 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +72 -124
- package/dist/llm/embedder.js +3 -19
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +3 -0
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +89 -48
- package/dist/llm/graph-extract.js +676 -70
- package/dist/llm/index-passes.js +9 -23
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +281 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +5 -318
- package/dist/output/context.js +3 -0
- package/dist/output/renderers.js +223 -256
- package/dist/output/shapes.js +150 -105
- package/dist/output/text.js +318 -30
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +3 -0
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +70 -49
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17307 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +7 -5
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +211 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +140 -10
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +62 -91
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +9 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +20 -8
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -381
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -1,5 +1,8 @@
|
|
|
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/.
|
|
1
4
|
import { toErrorMessage } from "../core/common";
|
|
2
|
-
import { DEFAULT_CONFIG
|
|
5
|
+
import { DEFAULT_CONFIG } from "../core/config";
|
|
3
6
|
import { warn } from "../core/warn";
|
|
4
7
|
import { resolveProviderFactory } from "../registry/factory";
|
|
5
8
|
// ── Eagerly import providers to trigger self-registration ───────────────────
|
|
@@ -133,7 +136,7 @@ export function resolveRegistries(configRegistries) {
|
|
|
133
136
|
}
|
|
134
137
|
return entries;
|
|
135
138
|
}
|
|
136
|
-
const registries = configRegistries ??
|
|
139
|
+
const registries = configRegistries ?? DEFAULT_CONFIG.registries ?? [];
|
|
137
140
|
return registries.filter((r) => r.enabled !== false);
|
|
138
141
|
}
|
|
139
142
|
// ── Provider resolution ─────────────────────────────────────────────────────
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* Memory-specific helpers for `akm remember`.
|
|
3
6
|
*
|
|
@@ -5,12 +8,13 @@
|
|
|
5
8
|
* heuristic derivation, LLM enrichment) is testable in isolation and the
|
|
6
9
|
* CLI entry point stays focused on argument parsing + output routing.
|
|
7
10
|
*/
|
|
8
|
-
import {
|
|
11
|
+
import { serializeFrontmatter } from "../core/asset-serialize";
|
|
9
12
|
import { toErrorMessage, tryReadStdinText } from "../core/common";
|
|
10
|
-
import { loadConfig } from "../core/config";
|
|
13
|
+
import { getDefaultLlmConfig, loadConfig } from "../core/config";
|
|
11
14
|
import { UsageError } from "../core/errors";
|
|
12
15
|
import { warn } from "../core/warn";
|
|
13
16
|
import { SCOPE_KEYS } from "../indexer/metadata";
|
|
17
|
+
import { parseFlagValue } from "../output/context";
|
|
14
18
|
/**
|
|
15
19
|
* Parse a shorthand duration string to a number of milliseconds.
|
|
16
20
|
* Supports: `30d` (days), `12h` (hours), `6m` (months, approximated as 30d).
|
|
@@ -53,6 +57,12 @@ export function buildMemoryFrontmatter(fields) {
|
|
|
53
57
|
obj.expires = fields.expires;
|
|
54
58
|
if (fields.subjective)
|
|
55
59
|
obj.subjective = true;
|
|
60
|
+
if (fields.captureMode === "hot" || fields.captureMode === "background") {
|
|
61
|
+
obj.captureMode = fields.captureMode;
|
|
62
|
+
}
|
|
63
|
+
if (typeof fields.beliefState === "string" && fields.beliefState.trim()) {
|
|
64
|
+
obj.beliefState = fields.beliefState.trim();
|
|
65
|
+
}
|
|
56
66
|
// Scope keys are emitted as flat top-level keys (`scope_user`, …) so the
|
|
57
67
|
// existing one-level frontmatter parser can read them without nesting.
|
|
58
68
|
// A scope object with no populated values is dropped.
|
|
@@ -68,7 +78,7 @@ export function buildMemoryFrontmatter(fields) {
|
|
|
68
78
|
// produce `---\n{}\n---` (the YAML serializer's empty-object form).
|
|
69
79
|
if (Object.keys(obj).length === 0)
|
|
70
80
|
return "---\n---";
|
|
71
|
-
const serialized =
|
|
81
|
+
const serialized = serializeFrontmatter(obj);
|
|
72
82
|
return `---\n${serialized}\n---`;
|
|
73
83
|
}
|
|
74
84
|
/**
|
|
@@ -133,11 +143,11 @@ const LLM_ENRICH_TIMEOUT_MS = 10_000;
|
|
|
133
143
|
*/
|
|
134
144
|
export async function runLlmEnrich(body) {
|
|
135
145
|
const config = loadConfig();
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
const llmConfig = getDefaultLlmConfig(config);
|
|
147
|
+
if (!llmConfig) {
|
|
148
|
+
warn("Warning: --enrich requires an LLM to be configured. Run `akm setup` to configure one.");
|
|
138
149
|
return { tags: [] };
|
|
139
150
|
}
|
|
140
|
-
const llmConfig = config.llm;
|
|
141
151
|
const { chatCompletion, parseEmbeddedJsonResponse: parseJsonResponse } = await import("../llm/client");
|
|
142
152
|
const prompt = `You are a memory tagger for a developer knowledge base.
|
|
143
153
|
Given the memory text below, return ONLY a JSON object with these fields:
|
|
@@ -188,3 +198,56 @@ Return ONLY the JSON object, no prose, no markdown fences.`;
|
|
|
188
198
|
return { tags: [] };
|
|
189
199
|
}
|
|
190
200
|
}
|
|
201
|
+
// ── Content-arg disambiguation ───────────────────────────────────────────────
|
|
202
|
+
/**
|
|
203
|
+
* Guard against citty consuming a global flag value as the `content` positional.
|
|
204
|
+
*
|
|
205
|
+
* When the user runs `akm remember --format json` without a content argument,
|
|
206
|
+
* citty may assign `"json"` to the `content` positional because of how it
|
|
207
|
+
* handles flag order. This helper detects that case and returns `undefined`
|
|
208
|
+
* so `readMemoryContent` falls through to stdin.
|
|
209
|
+
*/
|
|
210
|
+
export function resolveRememberContentArg(content) {
|
|
211
|
+
if (content === undefined)
|
|
212
|
+
return undefined;
|
|
213
|
+
const parsedFormat = parseFlagValue(process.argv, "--format");
|
|
214
|
+
if (parsedFormat !== undefined &&
|
|
215
|
+
content === parsedFormat &&
|
|
216
|
+
wasRememberFlagValueConsumedAsContent(content, parsedFormat, "--format")) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const parsedDetail = parseFlagValue(process.argv, "--detail");
|
|
220
|
+
if (parsedDetail !== undefined &&
|
|
221
|
+
content === parsedDetail &&
|
|
222
|
+
wasRememberFlagValueConsumedAsContent(content, parsedDetail, "--detail")) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return content;
|
|
226
|
+
}
|
|
227
|
+
function wasRememberFlagValueConsumedAsContent(content, flagValue, flagName) {
|
|
228
|
+
const argv = process.argv.slice(2);
|
|
229
|
+
const rememberIndex = argv.indexOf("remember");
|
|
230
|
+
const tokens = rememberIndex >= 0 ? argv.slice(rememberIndex + 1) : argv;
|
|
231
|
+
let flagIndex = -1;
|
|
232
|
+
let flagConsumesNextToken = false;
|
|
233
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
234
|
+
const token = tokens[i];
|
|
235
|
+
if (token === flagName) {
|
|
236
|
+
flagIndex = i;
|
|
237
|
+
flagConsumesNextToken = true;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
if (token === `${flagName}=${flagValue}`) {
|
|
241
|
+
flagIndex = i;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (flagIndex === -1)
|
|
246
|
+
return false;
|
|
247
|
+
if (tokens.slice(0, flagIndex).includes(content))
|
|
248
|
+
return false;
|
|
249
|
+
const firstTokenAfterFlag = flagIndex + (flagConsumesNextToken ? 2 : 1);
|
|
250
|
+
if (tokens.slice(firstTokenAfterFlag).includes(content))
|
|
251
|
+
return false;
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
* Schema-repair pass for `akm improve`.
|
|
6
|
+
*
|
|
7
|
+
* Attempts to patch missing frontmatter fields (`description`, `when_to_use`)
|
|
8
|
+
* on assets that failed schema validation, using a single bounded in-tree LLM
|
|
9
|
+
* call per asset. Results are recorded as `schema_repair_invoked` events.
|
|
10
|
+
*
|
|
11
|
+
* This module is extracted from `improve.ts` to make the repair logic
|
|
12
|
+
* independently testable and to use the `tryLlmFeature` seam rather than raw
|
|
13
|
+
* `chatCompletion`.
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { parseAssetRef } from "../core/asset-ref";
|
|
18
|
+
import { assembleAsset } from "../core/asset-serialize";
|
|
19
|
+
import { appendEvent, readEvents } from "../core/events";
|
|
20
|
+
import { parseFrontmatter } from "../core/frontmatter";
|
|
21
|
+
import { createProposal, isProposalSkipped } from "../core/proposals";
|
|
22
|
+
import { info, warn } from "../core/warn";
|
|
23
|
+
import { resolveAssetPath } from "../indexer/path-resolver";
|
|
24
|
+
import { chatCompletion, parseEmbeddedJsonResponse } from "../llm/client";
|
|
25
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
26
|
+
/** Minimum gap between schema-repair attempts on the same asset. */
|
|
27
|
+
const SCHEMA_REPAIR_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
28
|
+
/**
|
|
29
|
+
* Per-ref attempt cap (O-6 / #379): maximum number of schema-repair attempts
|
|
30
|
+
* allowed within SCHEMA_REPAIR_WINDOW_MS. Prevents indefinite nightly re-repair
|
|
31
|
+
* of assets whose source content is genuinely ambiguous or inconsistently
|
|
32
|
+
* structured. After cap, the asset is skipped until the window rolls over.
|
|
33
|
+
* Self-Refine arXiv:2303.17651 — iteration must be bounded.
|
|
34
|
+
*/
|
|
35
|
+
const SCHEMA_REPAIR_MAX_ATTEMPTS = 3;
|
|
36
|
+
const SCHEMA_REPAIR_WINDOW_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
37
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Run the schema-repair loop for a batch of validation failures.
|
|
40
|
+
* Returns a list of per-asset outcome records and the set of refs that were
|
|
41
|
+
* successfully repaired (so the caller can exclude them from skip logic).
|
|
42
|
+
*/
|
|
43
|
+
export async function runSchemaRepairPass(failures, options) {
|
|
44
|
+
const repairs = [];
|
|
45
|
+
const repairedRefs = new Set();
|
|
46
|
+
const { startMs, budgetMs, llmConfig, stashDir, findFilePath = defaultFindFilePath, isLessonCandidateFn = defaultIsLessonCandidate, chatFn = chatCompletion, } = options;
|
|
47
|
+
for (const failure of failures) {
|
|
48
|
+
if (Date.now() - startMs >= budgetMs)
|
|
49
|
+
break;
|
|
50
|
+
// Cooldown: skip repair if we ran it successfully recently.
|
|
51
|
+
const recentRepairs = readEvents({ type: "schema_repair_invoked", ref: failure.ref });
|
|
52
|
+
const lastRepair = recentRepairs.events
|
|
53
|
+
.filter((e) => e.metadata?.outcome === "written")
|
|
54
|
+
.sort((a, b) => new Date(b.ts ?? 0).getTime() - new Date(a.ts ?? 0).getTime())[0];
|
|
55
|
+
if (lastRepair?.ts && Date.now() - new Date(lastRepair.ts).getTime() < SCHEMA_REPAIR_COOLDOWN_MS) {
|
|
56
|
+
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// O-6 / #379: Cap total attempts at SCHEMA_REPAIR_MAX_ATTEMPTS per SCHEMA_REPAIR_WINDOW_MS.
|
|
60
|
+
// Prevents indefinite nightly re-repair of assets whose source is genuinely ambiguous.
|
|
61
|
+
// After the cap is reached, the asset is skipped until the window rolls over.
|
|
62
|
+
const windowStart = Date.now() - SCHEMA_REPAIR_WINDOW_MS;
|
|
63
|
+
const attemptsInWindow = recentRepairs.events.filter((e) => e.ts !== undefined && new Date(e.ts).getTime() >= windowStart).length;
|
|
64
|
+
if (attemptsInWindow >= SCHEMA_REPAIR_MAX_ATTEMPTS) {
|
|
65
|
+
repairs.push({
|
|
66
|
+
ref: failure.ref,
|
|
67
|
+
reason: failure.reason,
|
|
68
|
+
outcome: "skipped",
|
|
69
|
+
error: `schema-repair attempt cap reached (${attemptsInWindow}/${SCHEMA_REPAIR_MAX_ATTEMPTS} in 30d window)`,
|
|
70
|
+
});
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const filePath = await findFilePath(failure.ref, stashDir);
|
|
74
|
+
if (!filePath) {
|
|
75
|
+
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (path.extname(filePath).toLowerCase() !== ".md") {
|
|
79
|
+
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
84
|
+
const fm = parseFrontmatter(raw);
|
|
85
|
+
const missingFields = [];
|
|
86
|
+
if (!fm.data.description)
|
|
87
|
+
missingFields.push("description");
|
|
88
|
+
if (isLessonCandidateFn(failure.ref) && !fm.data.when_to_use)
|
|
89
|
+
missingFields.push("when_to_use");
|
|
90
|
+
if (missingFields.length === 0) {
|
|
91
|
+
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const fieldList = missingFields.join(" and ");
|
|
95
|
+
info(`[improve] schema-repair ${failure.ref} (${fieldList})`);
|
|
96
|
+
const bodyPreview = (fm.content ?? raw).slice(0, 2000);
|
|
97
|
+
const llmResponse = await chatFn(llmConfig, [
|
|
98
|
+
{
|
|
99
|
+
role: "system",
|
|
100
|
+
content: `You generate concise asset frontmatter fields. Respond with a JSON object containing only the missing fields. No prose, no markdown fences.`,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
role: "user",
|
|
104
|
+
content: `Generate the missing frontmatter fields (${fieldList}) for this ${parseAssetRef(failure.ref).type} asset. Return ONLY valid JSON like {"description": "...", "when_to_use": "..."}\n\n${bodyPreview}`,
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
const parsed = parseEmbeddedJsonResponse(llmResponse.trim());
|
|
108
|
+
if (!parsed) {
|
|
109
|
+
repairs.push({
|
|
110
|
+
ref: failure.ref,
|
|
111
|
+
reason: failure.reason,
|
|
112
|
+
outcome: "error",
|
|
113
|
+
error: "LLM returned unparseable JSON for schema repair",
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const newFm = { ...fm.data };
|
|
118
|
+
if (parsed.description)
|
|
119
|
+
newFm.description = parsed.description;
|
|
120
|
+
if (parsed.when_to_use)
|
|
121
|
+
newFm.when_to_use = parsed.when_to_use;
|
|
122
|
+
const newContent = assembleAsset(newFm, fm.content);
|
|
123
|
+
// M-3 / #387: Route through proposal queue instead of writing directly to
|
|
124
|
+
// disk. This restores akm's safety invariant — the proposal queue is the
|
|
125
|
+
// only path to a committed asset write. LLM-generated `description` /
|
|
126
|
+
// `when_to_use` fields can be incorrect; routing through the queue makes
|
|
127
|
+
// them human-reviewable before they affect search ranking and curate hints.
|
|
128
|
+
// mem0 open gaps (arXiv:2504.19413) — any LLM write to a memory field
|
|
129
|
+
// should be human-reviewable.
|
|
130
|
+
if (stashDir) {
|
|
131
|
+
const proposalResult = createProposal(stashDir, {
|
|
132
|
+
ref: failure.ref,
|
|
133
|
+
source: "schema-repair",
|
|
134
|
+
payload: {
|
|
135
|
+
content: newContent,
|
|
136
|
+
...(Object.keys(newFm).length > 0 ? { frontmatter: newFm } : {}),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
if (isProposalSkipped(proposalResult)) {
|
|
140
|
+
info(`[improve] schema-repair proposal skipped for ${failure.ref}: ${proposalResult.message}`);
|
|
141
|
+
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
info(`[improve] schema-repair queued: ${failure.ref} (proposal id: ${proposalResult.id})`);
|
|
145
|
+
appendEvent({
|
|
146
|
+
eventType: "schema_repair_invoked",
|
|
147
|
+
ref: failure.ref,
|
|
148
|
+
metadata: { outcome: "queued", reason: failure.reason, proposalId: proposalResult.id },
|
|
149
|
+
});
|
|
150
|
+
repairs.push({
|
|
151
|
+
ref: failure.ref,
|
|
152
|
+
reason: failure.reason,
|
|
153
|
+
outcome: "queued",
|
|
154
|
+
proposalId: proposalResult.id,
|
|
155
|
+
});
|
|
156
|
+
// Mark as repaired so the caller removes it from the validation-failure set.
|
|
157
|
+
repairedRefs.add(failure.ref);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Fallback: no stash dir available — write directly (legacy path).
|
|
161
|
+
// This should not occur in production; stashDir is always provided by
|
|
162
|
+
// `runSchemaRepairPass` callers in improve.ts.
|
|
163
|
+
warn(`[improve] schema-repair: no stashDir available for ${failure.ref}, falling back to direct write`);
|
|
164
|
+
fs.writeFileSync(filePath, newContent, "utf8");
|
|
165
|
+
info(`[improve] schema-repair written: ${failure.ref}`);
|
|
166
|
+
appendEvent({
|
|
167
|
+
eventType: "schema_repair_invoked",
|
|
168
|
+
ref: failure.ref,
|
|
169
|
+
metadata: { outcome: "written", reason: failure.reason },
|
|
170
|
+
});
|
|
171
|
+
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "written" });
|
|
172
|
+
repairedRefs.add(failure.ref);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
appendEvent({
|
|
177
|
+
eventType: "schema_repair_invoked",
|
|
178
|
+
ref: failure.ref,
|
|
179
|
+
metadata: { outcome: "error", reason: failure.reason, error: String(e) },
|
|
180
|
+
});
|
|
181
|
+
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "error", error: String(e) });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { repairs, repairedRefs };
|
|
185
|
+
}
|
|
186
|
+
// ── Default seam implementations ─────────────────────────────────────────────
|
|
187
|
+
function defaultIsLessonCandidate(ref) {
|
|
188
|
+
try {
|
|
189
|
+
const parsed = parseAssetRef(ref);
|
|
190
|
+
return parsed.type === "lesson";
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function defaultFindFilePath(ref, stashDir) {
|
|
197
|
+
return resolveAssetPath(ref, {
|
|
198
|
+
stashDir,
|
|
199
|
+
mode: "index-first",
|
|
200
|
+
directoryIndexNames: ["SKILL.md", "index.md", "README.md"],
|
|
201
|
+
preserveDirectNameFallback: true,
|
|
202
|
+
});
|
|
203
|
+
}
|
package/dist/commands/search.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
/**
|
|
2
5
|
* `akm search` — entry point.
|
|
3
6
|
*
|
|
@@ -9,11 +12,13 @@
|
|
|
9
12
|
* Provider `search()` methods do not exist.
|
|
10
13
|
*/
|
|
11
14
|
import { loadConfig } from "../core/config";
|
|
12
|
-
import { UsageError } from "../core/errors";
|
|
15
|
+
import { rethrowIfTestIsolationError, UsageError } from "../core/errors";
|
|
13
16
|
import { appendEvent } from "../core/events";
|
|
14
|
-
import {
|
|
17
|
+
import { isTransientStashPath } from "../core/paths";
|
|
18
|
+
import { bumpUtilityScoresBatch, closeDatabase, openExistingDatabase } from "../indexer/db";
|
|
15
19
|
import { searchLocal } from "../indexer/db-search";
|
|
16
20
|
import { resolveSourceEntries } from "../indexer/search-source";
|
|
21
|
+
import { getCurrentWorkflowScopeKey } from "../workflows/scope-key";
|
|
17
22
|
// Eagerly import source providers to trigger self-registration before the
|
|
18
23
|
// indexer or path-resolution code runs.
|
|
19
24
|
import "../sources/providers/index";
|
|
@@ -26,10 +31,42 @@ export async function akmSearch(input) {
|
|
|
26
31
|
const normalizedQuery = query.toLowerCase();
|
|
27
32
|
const searchType = input.type ?? "any";
|
|
28
33
|
const limit = normalizeLimit(input.limit);
|
|
29
|
-
const
|
|
34
|
+
const parsedSource = parseSearchSource(input.source ?? "stash");
|
|
30
35
|
const config = loadConfig();
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
// Named-source filter: when --source is not a standard enum value, treat it
|
|
37
|
+
// as a named source from config.sources[].name. Validate early (before
|
|
38
|
+
// resolveSourceEntries, which can throw STASH_DIR_NOT_FOUND) so that a bad
|
|
39
|
+
// --source name always produces INVALID_SOURCE_VALUE regardless of stash state.
|
|
40
|
+
let namedSourceName;
|
|
41
|
+
let source;
|
|
42
|
+
if (parsedSource !== "stash" && parsedSource !== "registry" && parsedSource !== "both") {
|
|
43
|
+
namedSourceName = parsedSource;
|
|
44
|
+
// Check that the named source exists in the config before touching the stash.
|
|
45
|
+
const configSources = config.sources ?? [];
|
|
46
|
+
const foundInConfig = configSources.some((s) => s.name === namedSourceName) || configSources.some((s) => s.path === namedSourceName);
|
|
47
|
+
if (!foundInConfig) {
|
|
48
|
+
const validNames = configSources.map((s) => s.name).filter((n) => Boolean(n));
|
|
49
|
+
const hint = validNames.length > 0
|
|
50
|
+
? `Known source names: ${validNames.join(", ")}`
|
|
51
|
+
: "No named sources are configured. Run `akm list` to see installed stashes.";
|
|
52
|
+
throw new UsageError(`Unknown source name: "${namedSourceName}". ${hint}`, "INVALID_SOURCE_VALUE");
|
|
53
|
+
}
|
|
54
|
+
source = "stash";
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
source = parsedSource;
|
|
58
|
+
}
|
|
59
|
+
let allSources = resolveSourceEntries(undefined, config);
|
|
60
|
+
// When a named source was requested, narrow the sources list to just that entry.
|
|
61
|
+
// `resolveSourceEntries` sets `registryId` to `entry.name` for each config source.
|
|
62
|
+
if (namedSourceName !== undefined) {
|
|
63
|
+
const ns = namedSourceName;
|
|
64
|
+
allSources = allSources.filter((s) => s.registryId === ns || s.path === ns);
|
|
65
|
+
// allSources may still be empty if the configured source dir doesn't exist on
|
|
66
|
+
// disk (resolveSourceEntries skips non-existent dirs). Fall through to the
|
|
67
|
+
// zero-sources guard below which emits a friendly warning.
|
|
68
|
+
}
|
|
69
|
+
if (allSources.length === 0) {
|
|
33
70
|
// stashDir: "" is a safe sentinel here — the response carries zero hits
|
|
34
71
|
// and a warning, so no downstream code will try to use the empty path.
|
|
35
72
|
const response = {
|
|
@@ -40,14 +77,18 @@ export async function akmSearch(input) {
|
|
|
40
77
|
warnings: ["No stashes configured. Run `akm init` to create your working stash."],
|
|
41
78
|
timing: { totalMs: Date.now() - t0 },
|
|
42
79
|
};
|
|
43
|
-
|
|
80
|
+
if (!input.skipLogging)
|
|
81
|
+
logSearchEvent(query, response, undefined, undefined, input.eventSource);
|
|
44
82
|
return response;
|
|
45
83
|
}
|
|
46
84
|
// Primary stash directory — used for DB path lookups and as the default
|
|
47
85
|
// stash root. Safe because the empty-sources case is handled above.
|
|
48
|
-
const stashDir =
|
|
86
|
+
const stashDir = allSources[0].path;
|
|
87
|
+
// Expose the filtered source list to downstream search calls.
|
|
88
|
+
const sources = allSources;
|
|
49
89
|
const filters = normalizeScopeFilters(input.filters);
|
|
50
90
|
const includeProposed = input.includeProposed === true;
|
|
91
|
+
const belief = input.belief ?? "all";
|
|
51
92
|
const localResult = source === "registry"
|
|
52
93
|
? undefined
|
|
53
94
|
: await searchLocal({
|
|
@@ -59,6 +100,13 @@ export async function akmSearch(input) {
|
|
|
59
100
|
config,
|
|
60
101
|
filters,
|
|
61
102
|
includeProposed,
|
|
103
|
+
beliefFilter: belief,
|
|
104
|
+
// When `--source <name>` narrowed the source list above, propagate
|
|
105
|
+
// that intent down to the database layer so FTS/vector hits from
|
|
106
|
+
// sources outside the narrowed set are filtered out post-ranking.
|
|
107
|
+
// Without this, the index (which spans every configured source)
|
|
108
|
+
// would leak hits from sources the caller did not request.
|
|
109
|
+
restrictToSources: namedSourceName !== undefined,
|
|
62
110
|
});
|
|
63
111
|
const registryResult = source === "stash" ? undefined : await searchRegistry(query, { limit, registries: config.registries });
|
|
64
112
|
if (source === "stash") {
|
|
@@ -73,7 +121,8 @@ export async function akmSearch(input) {
|
|
|
73
121
|
warnings: localResult?.warnings?.length ? localResult.warnings : undefined,
|
|
74
122
|
timing: { totalMs: Date.now() - t0, rankMs: localResult?.rankMs, embedMs: localResult?.embedMs },
|
|
75
123
|
};
|
|
76
|
-
|
|
124
|
+
if (!input.skipLogging)
|
|
125
|
+
logSearchEvent(query, response, undefined, localResult?.mode ?? "keyword", input.eventSource);
|
|
77
126
|
return response;
|
|
78
127
|
}
|
|
79
128
|
const registryHits = (registryResult?.hits ?? []).map((hit) => {
|
|
@@ -107,7 +156,8 @@ export async function akmSearch(input) {
|
|
|
107
156
|
warnings: registryResult?.warnings.length ? registryResult.warnings : undefined,
|
|
108
157
|
timing: { totalMs: Date.now() - t0 },
|
|
109
158
|
};
|
|
110
|
-
|
|
159
|
+
if (!input.skipLogging)
|
|
160
|
+
logSearchEvent(query, response, undefined, undefined, input.eventSource);
|
|
111
161
|
return response;
|
|
112
162
|
}
|
|
113
163
|
// source === "both"
|
|
@@ -124,7 +174,8 @@ export async function akmSearch(input) {
|
|
|
124
174
|
warnings: warnings.length ? warnings : undefined,
|
|
125
175
|
timing: { totalMs: Date.now() - t0 },
|
|
126
176
|
};
|
|
127
|
-
|
|
177
|
+
if (!input.skipLogging)
|
|
178
|
+
logSearchEvent(query, response, undefined, undefined, input.eventSource);
|
|
128
179
|
return response;
|
|
129
180
|
}
|
|
130
181
|
/**
|
|
@@ -160,13 +211,16 @@ function resolveEntryIds(db, hits) {
|
|
|
160
211
|
* Per-entry events are recorded only for stash hits because registry hits
|
|
161
212
|
* have no local entry_id to reference.
|
|
162
213
|
*/
|
|
163
|
-
function logSearchEvent(query, response, existingDb) {
|
|
214
|
+
function logSearchEvent(query, response, existingDb, mode = "keyword", eventSource = "user") {
|
|
164
215
|
// Emit a structured event to events.jsonl so workflow-trace consumers
|
|
165
216
|
// detect akm search invocations without relying on stdout scraping.
|
|
166
217
|
const stashHits = response.hits.filter((h) => h.type !== "registry");
|
|
218
|
+
// D8: include registry hit refs so a show following a registry-only search generates a select event
|
|
219
|
+
const registryHitRefs = (response.registryHits ?? []).map((h) => `registry:${h.id}`);
|
|
220
|
+
const allResultRefs = [...stashHits.map((h) => h.ref), ...registryHitRefs];
|
|
167
221
|
appendEvent({
|
|
168
222
|
eventType: "search",
|
|
169
|
-
metadata: { query, hitCount: stashHits.length, resultRefs:
|
|
223
|
+
metadata: { query, hitCount: stashHits.length, resultRefs: allResultRefs, mode },
|
|
170
224
|
});
|
|
171
225
|
try {
|
|
172
226
|
const db = existingDb ?? openExistingDatabase();
|
|
@@ -178,8 +232,24 @@ function logSearchEvent(query, response, existingDb) {
|
|
|
178
232
|
query,
|
|
179
233
|
entry_id: entryId,
|
|
180
234
|
entry_ref: ref,
|
|
235
|
+
source: eventSource,
|
|
181
236
|
});
|
|
182
237
|
}
|
|
238
|
+
// Bump utility scores for all resolved entries (MemRL retrieval signal).
|
|
239
|
+
// The indexer overwrites these at next reindex; bumps are temporary hints.
|
|
240
|
+
const resolvedIds = resolved.map((r) => r.entryId).filter((id) => id !== undefined);
|
|
241
|
+
if (resolvedIds.length > 0) {
|
|
242
|
+
let scopeKey;
|
|
243
|
+
try {
|
|
244
|
+
const stashPath = response.stashDir;
|
|
245
|
+
const disabled = process.env.AKM_DISABLE_SCOPED_UTILITY === "1" || (stashPath && isTransientStashPath(stashPath));
|
|
246
|
+
scopeKey = disabled ? undefined : getCurrentWorkflowScopeKey();
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// Non-fatal — fall back to global-only bumps on any error.
|
|
250
|
+
}
|
|
251
|
+
bumpUtilityScoresBatch(db, resolvedIds, 1.0, 0.1, scopeKey);
|
|
252
|
+
}
|
|
183
253
|
// Count registry hits separately so registry-only searches record a
|
|
184
254
|
// non-zero resultCount. response.hits is always [] when source="registry".
|
|
185
255
|
const stashHitCount = response.hits.length;
|
|
@@ -192,7 +262,9 @@ function logSearchEvent(query, response, existingDb) {
|
|
|
192
262
|
stashHitCount,
|
|
193
263
|
registryHitCount,
|
|
194
264
|
resolvedCount: resolved.length,
|
|
265
|
+
mode,
|
|
195
266
|
}),
|
|
267
|
+
source: eventSource,
|
|
196
268
|
});
|
|
197
269
|
}
|
|
198
270
|
finally {
|
|
@@ -200,7 +272,8 @@ function logSearchEvent(query, response, existingDb) {
|
|
|
200
272
|
closeDatabase(db);
|
|
201
273
|
}
|
|
202
274
|
}
|
|
203
|
-
catch {
|
|
275
|
+
catch (err) {
|
|
276
|
+
rethrowIfTestIsolationError(err);
|
|
204
277
|
/* fire-and-forget */
|
|
205
278
|
}
|
|
206
279
|
}
|
|
@@ -211,6 +284,24 @@ function normalizeLimit(limit) {
|
|
|
211
284
|
}
|
|
212
285
|
return Math.min(Math.floor(limit), 200);
|
|
213
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Parse the `--source` flag value.
|
|
289
|
+
*
|
|
290
|
+
* Accepts:
|
|
291
|
+
* - `stash` (default) — search the local stash index only
|
|
292
|
+
* - `registry` — search remote registries only
|
|
293
|
+
* - `both` — search stash and registries
|
|
294
|
+
* - `local` — alias for `stash`
|
|
295
|
+
* - Any named source from `config.sources[].name` — filters stash results to
|
|
296
|
+
* that single source only. The named-source path is detected and resolved
|
|
297
|
+
* inside `akmSearch`; this function returns the raw name so the caller can
|
|
298
|
+
* pass it through to `akmSearch` which accepts `SearchSource | string`.
|
|
299
|
+
*
|
|
300
|
+
* Unknown values that are not a known enum AND not a named source will still
|
|
301
|
+
* produce an error inside `akmSearch` when the config lookup finds nothing.
|
|
302
|
+
* This allows the CLI to accept named sources without requiring config access
|
|
303
|
+
* at parse time.
|
|
304
|
+
*/
|
|
214
305
|
export function parseSearchSource(source) {
|
|
215
306
|
if (source === "stash" || source === "registry" || source === "both")
|
|
216
307
|
return source;
|
|
@@ -219,7 +310,17 @@ export function parseSearchSource(source) {
|
|
|
219
310
|
return "stash";
|
|
220
311
|
if (typeof source === "undefined")
|
|
221
312
|
return "stash";
|
|
222
|
-
|
|
313
|
+
// Pass through unknown strings — they may be valid named sources.
|
|
314
|
+
// `akmSearch` will validate against config.sources and throw a UsageError
|
|
315
|
+
// with a helpful message if the name isn't found.
|
|
316
|
+
return source;
|
|
317
|
+
}
|
|
318
|
+
export function parseBeliefFilterMode(value) {
|
|
319
|
+
if (value === undefined || value === "all")
|
|
320
|
+
return "all";
|
|
321
|
+
if (value === "current" || value === "historical")
|
|
322
|
+
return value;
|
|
323
|
+
throw new UsageError(`Invalid value for --belief: ${String(value)}. Expected one of: all|current|historical`, "INVALID_FLAG_VALUE");
|
|
223
324
|
}
|
|
224
325
|
/**
|
|
225
326
|
* Strip empty / non-string values from a scope filter object. Returns
|
|
@@ -1,3 +1,6 @@
|
|
|
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/.
|
|
1
4
|
import * as childProcess from "node:child_process";
|
|
2
5
|
import { createHash } from "node:crypto";
|
|
3
6
|
import fs from "node:fs";
|