akm-cli 0.8.0-rc2 → 0.8.0
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} +191 -3
- package/README.md +22 -6
- 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 +93 -3
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2141 -1268
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +20 -12
- package/dist/commands/agent-support.js +11 -5
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +129 -517
- package/dist/commands/consolidate.js +1533 -144
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +5 -3
- package/dist/commands/distill.js +906 -100
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +3 -0
- package/dist/commands/events.js +3 -0
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +260 -5
- package/dist/commands/health.js +977 -51
- package/dist/commands/help/help-accept.md +6 -3
- package/dist/commands/help/help-improve.md +36 -8
- package/dist/commands/help/help-proposals.md +7 -4
- package/dist/commands/help/help-reject.md +5 -2
- package/dist/commands/history.js +51 -16
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +236 -0
- package/dist/commands/improve-profiles.js +184 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +1725 -332
- package/dist/commands/info.js +3 -0
- package/dist/commands/init.js +49 -1
- package/dist/commands/installed-stashes.js +6 -23
- package/dist/commands/knowledge.js +3 -0
- package/dist/commands/lint/agent-linter.js +3 -0
- package/dist/commands/lint/base-linter.js +199 -5
- package/dist/commands/lint/command-linter.js +3 -0
- package/dist/commands/lint/default-linter.js +3 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +92 -3
- package/dist/commands/lint/knowledge-linter.js +3 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +3 -0
- package/dist/commands/lint/registry.js +3 -0
- package/dist/commands/lint/skill-linter.js +3 -0
- package/dist/commands/lint/task-linter.js +15 -12
- package/dist/commands/lint/types.js +3 -0
- package/dist/commands/lint/workflow-linter.js +3 -0
- package/dist/commands/lint.js +3 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal-drain-policies.js +128 -0
- package/dist/commands/proposal-drain.js +477 -0
- package/dist/commands/proposal.js +60 -6
- package/dist/commands/propose.js +24 -19
- package/dist/commands/reflect.js +1004 -94
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +3 -0
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +15 -6
- package/dist/commands/schema-repair.js +88 -15
- package/dist/commands/search.js +99 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +32 -13
- package/dist/commands/source-add.js +7 -35
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +3 -0
- package/dist/commands/tasks.js +161 -95
- package/dist/commands/url-checker.js +3 -0
- package/dist/core/action-contributors.js +3 -0
- package/dist/core/asset-ref.js +13 -2
- package/dist/core/asset-registry.js +9 -2
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +61 -5
- package/dist/core/common.js +93 -5
- package/dist/core/concurrent.js +3 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +558 -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 +366 -1077
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +31 -25
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -10
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +3 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +142 -14
- package/dist/core/parse.js +3 -0
- package/dist/core/paths.js +218 -50
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +11 -3
- package/dist/core/proposals.js +464 -5
- package/dist/core/state-db.js +349 -56
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +3 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +7 -2
- package/dist/core/write-source.js +12 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +136 -28
- package/dist/indexer/db.js +661 -166
- package/dist/indexer/ensure-index.js +3 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +162 -40
- package/dist/indexer/graph-db.js +241 -51
- package/dist/indexer/graph-dedup.js +3 -7
- package/dist/indexer/graph-extraction.js +242 -149
- package/dist/indexer/index-context.js +3 -9
- package/dist/indexer/indexer.js +84 -14
- package/dist/indexer/llm-cache.js +24 -19
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +184 -11
- package/dist/indexer/memory-inference.js +94 -50
- package/dist/indexer/metadata-contributors.js +3 -0
- package/dist/indexer/metadata.js +110 -50
- package/dist/indexer/path-resolver.js +3 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +134 -7
- package/dist/indexer/ranking.js +8 -1
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +91 -2
- package/dist/indexer/search-source.js +20 -1
- package/dist/indexer/semantic-status.js +4 -1
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +3 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +121 -401
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +6 -14
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +3 -0
- package/dist/integrations/agent/prompts.js +137 -8
- package/dist/integrations/agent/runner.js +208 -0
- package/dist/integrations/agent/sdk-runner.js +8 -2
- package/dist/integrations/agent/spawn.js +54 -14
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +22 -51
- package/dist/integrations/session-logs/index.js +4 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +226 -0
- package/dist/integrations/session-logs/providers/opencode.js +231 -25
- package/dist/integrations/session-logs/types.js +3 -0
- package/dist/llm/call-ai.js +14 -26
- package/dist/llm/client.js +16 -2
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +92 -56
- package/dist/llm/graph-extract.js +401 -30
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +30 -2
- package/dist/llm/metadata-enhance.js +3 -7
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +24 -1
- package/dist/output/cli-hints-full.md +60 -32
- package/dist/output/cli-hints-short.md +10 -7
- package/dist/output/cli-hints.js +5 -2
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +170 -194
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +105 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -549
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1059 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +12 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1329
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +11 -2
- package/dist/registry/providers/static-index.js +10 -1
- 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 +17767 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -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 +306 -67
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +3 -11
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +171 -21
- 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 +3 -0
- package/dist/tasks/backends/cron.js +3 -0
- package/dist/tasks/backends/exec-utils.js +3 -0
- package/dist/tasks/backends/index.js +3 -11
- package/dist/tasks/backends/launchd.js +3 -0
- package/dist/tasks/backends/schtasks.js +3 -0
- package/dist/tasks/parser.js +51 -38
- package/dist/tasks/resolveAkmBin.js +3 -0
- package/dist/tasks/runner.js +35 -9
- package/dist/tasks/schedule.js +20 -1
- package/dist/tasks/schema.js +5 -3
- package/dist/tasks/validator.js +6 -3
- package/dist/version.js +3 -0
- package/dist/wiki/wiki-templates.js +3 -0
- package/dist/wiki/wiki.js +3 -0
- package/dist/workflows/authoring.js +3 -0
- 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 +3 -0
- package/dist/workflows/runs.js +18 -1
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +5 -9
- package/docs/README.md +7 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +57 -5
- package/docs/migration/v0.7-to-v0.8.md +1378 -0
- package/package.json +28 -11
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -385
- package/dist/commands/vault.js +0 -310
- package/dist/indexer/match-contributors.js +0 -141
- package/dist/integrations/agent/pipeline.js +0 -39
- package/dist/integrations/agent/runners.js +0 -31
package/dist/indexer/indexer.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
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 fs from "node:fs";
|
|
2
5
|
import path from "node:path";
|
|
6
|
+
import { SCRIPT_EXTENSIONS } from "../core/asset-spec";
|
|
3
7
|
import { isHttpUrl, resolveStashDir, toErrorMessage } from "../core/common";
|
|
4
8
|
import { concurrentMap } from "../core/concurrent";
|
|
5
9
|
import { getDbPath } from "../core/paths";
|
|
6
10
|
import { isVerbose, warn, warnVerbose } from "../core/warn";
|
|
7
11
|
import { resolveIndexPassLLM } from "../llm/index-passes";
|
|
8
12
|
import { takeWorkflowDocument } from "../workflows/document-cache";
|
|
9
|
-
import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddingCount, getEntriesByDir, getEntryCount, getIndexDirState, getMeta, isVecAvailable, openDatabase, openExistingDatabase, rebuildFts, relinkUsageEvents, setMeta, upsertEmbedding, upsertEntry, upsertIndexDirState, upsertUtilityScore, upsertWorkflowDocument, warnIfVecMissing, } from "./db";
|
|
13
|
+
import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByIds, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddingCount, getEntriesByDir, getEntryCount, getIndexDirState, getMeta, isVecAvailable, openDatabase, openExistingDatabase, rebuildFts, relinkUsageEvents, setMeta, upsertEmbedding, upsertEntry, upsertIndexDirState, upsertUtilityScore, upsertWorkflowDocument, warnIfVecMissing, } from "./db";
|
|
14
|
+
import { deleteStoredGraph } from "./graph-db";
|
|
10
15
|
import { applyCuratedFrontmatter, applyWikiFrontmatter, generateMetadataFlat, isEnrichmentComplete, isWorkflowSkipWarning, loadStashFile, shouldIndexStashFile, } from "./metadata";
|
|
11
16
|
import { buildSearchText } from "./search-fields";
|
|
12
17
|
import { classifySemanticFailure, clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "./semantic-status";
|
|
@@ -64,6 +69,7 @@ async function runSourceCachePhase(ctx) {
|
|
|
64
69
|
ctx.hadRemovedSources = true;
|
|
65
70
|
deleteEntriesByStashDir(db, dir);
|
|
66
71
|
deleteIndexDirStatesByStashDir(db, dir);
|
|
72
|
+
deleteStoredGraph(db, dir);
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
}
|
|
@@ -193,6 +199,29 @@ async function runFinalizePhase(ctx) {
|
|
|
193
199
|
// suppress unused warning — sources was previously used inline
|
|
194
200
|
void sources;
|
|
195
201
|
}
|
|
202
|
+
// ── Clean pass ───────────────────────────────────────────────────────────────
|
|
203
|
+
/**
|
|
204
|
+
* Post-index clean pass: scan the `entries` table for rows whose source file
|
|
205
|
+
* no longer exists on disk and remove them (unless `dryRun` is true).
|
|
206
|
+
*
|
|
207
|
+
* Only rows with a non-empty `file_path` are checked — remote/virtual entries
|
|
208
|
+
* that have no local path are always skipped.
|
|
209
|
+
*/
|
|
210
|
+
function runCleanPass(db, dryRun) {
|
|
211
|
+
const allEntries = db.prepare("SELECT id, entry_key AS ref, file_path AS path FROM entries").all();
|
|
212
|
+
// Only check entries that have a non-empty local path (skip remote/virtual).
|
|
213
|
+
const localEntries = allEntries.filter((e) => typeof e.path === "string" && e.path.trim() !== "");
|
|
214
|
+
const missing = localEntries.filter((e) => !fs.existsSync(e.path));
|
|
215
|
+
if (!dryRun && missing.length > 0) {
|
|
216
|
+
deleteEntriesByIds(db, missing.map((e) => e.id));
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
checked: localEntries.length,
|
|
220
|
+
removed: dryRun ? 0 : missing.length,
|
|
221
|
+
removedRefs: missing.map((e) => e.ref),
|
|
222
|
+
dryRun,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
196
225
|
// ── Indexer ──────────────────────────────────────────────────────────────────
|
|
197
226
|
export async function akmIndex(options) {
|
|
198
227
|
const stashDir = options?.stashDir || resolveStashDir();
|
|
@@ -200,6 +229,8 @@ export async function akmIndex(options) {
|
|
|
200
229
|
const signal = options?.signal;
|
|
201
230
|
const reEnrich = options?.reEnrich === true;
|
|
202
231
|
const full = options?.full === true;
|
|
232
|
+
const clean = options?.clean === true;
|
|
233
|
+
const dryRun = options?.dryRun === true;
|
|
203
234
|
// Load config and resolve all stash sources
|
|
204
235
|
const { loadConfig } = await import("../core/config.js");
|
|
205
236
|
const config = loadConfig();
|
|
@@ -269,6 +300,14 @@ export async function akmIndex(options) {
|
|
|
269
300
|
// ────────────────────────────────────────────────────────────────────────
|
|
270
301
|
const { _verification: verification, _totalEntries: totalEntries } = ctx;
|
|
271
302
|
const { timing } = ctx;
|
|
303
|
+
// ── Clean pass ───────────────────────────────────────────────────────────
|
|
304
|
+
// After the normal index completes, remove entries whose source files no
|
|
305
|
+
// longer exist on disk. Remote entries (empty file_path) are skipped.
|
|
306
|
+
let cleanResult;
|
|
307
|
+
if (clean) {
|
|
308
|
+
cleanResult = runCleanPass(db, dryRun);
|
|
309
|
+
}
|
|
310
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
272
311
|
return {
|
|
273
312
|
stashDir,
|
|
274
313
|
totalEntries,
|
|
@@ -286,6 +325,7 @@ export async function akmIndex(options) {
|
|
|
286
325
|
embedMs: timing.tEmbedEnd - timing.tFtsEnd,
|
|
287
326
|
ftsMs: timing.tFtsEnd - timing.tLlmEnd,
|
|
288
327
|
},
|
|
328
|
+
...(cleanResult !== undefined ? { clean: cleanResult } : {}),
|
|
289
329
|
};
|
|
290
330
|
}
|
|
291
331
|
finally {
|
|
@@ -567,7 +607,20 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
|
|
|
567
607
|
reason: persistedReason,
|
|
568
608
|
});
|
|
569
609
|
if (persistedRows === 0) {
|
|
570
|
-
|
|
610
|
+
// Warn only when the dir had files that *could* produce entries (.md or
|
|
611
|
+
// known script extensions). Dirs with only non-indexable types (.json,
|
|
612
|
+
// .yaml, .conf, .env, .gitkeep) or deduped-only rows are expected and
|
|
613
|
+
// not actionable at normal log level.
|
|
614
|
+
const hasIndexableExtension = files.some((f) => {
|
|
615
|
+
const ext = path.extname(f).toLowerCase();
|
|
616
|
+
return ext === ".md" || SCRIPT_EXTENSIONS.has(ext);
|
|
617
|
+
});
|
|
618
|
+
if (persistedReason !== "deduped-zero-row" && hasIndexableExtension) {
|
|
619
|
+
warn(`[index] zero-row ${dirPath}: ${persistedReason}`);
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
warnVerbose(`[index] zero-row ${dirPath}: ${persistedReason}`);
|
|
623
|
+
}
|
|
571
624
|
}
|
|
572
625
|
}
|
|
573
626
|
});
|
|
@@ -884,14 +937,24 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal) {
|
|
|
884
937
|
throwIfAborted(signal);
|
|
885
938
|
// Wrap all embedding upserts in a single transaction so partial
|
|
886
939
|
// state is rolled back on failure rather than leaving the table half-filled.
|
|
940
|
+
let storedCount = 0;
|
|
941
|
+
let skippedCount = 0;
|
|
887
942
|
db.transaction(() => {
|
|
888
943
|
for (let i = 0; i < allEntries.length; i++) {
|
|
889
|
-
upsertEmbedding(db, allEntries[i].id, embeddings[i])
|
|
944
|
+
if (upsertEmbedding(db, allEntries[i].id, embeddings[i])) {
|
|
945
|
+
storedCount++;
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
skippedCount++;
|
|
949
|
+
}
|
|
890
950
|
}
|
|
891
951
|
})();
|
|
952
|
+
if (skippedCount > 0) {
|
|
953
|
+
warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
|
|
954
|
+
}
|
|
892
955
|
onProgress({
|
|
893
956
|
phase: "embeddings",
|
|
894
|
-
message: `Stored ${
|
|
957
|
+
message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"}.`,
|
|
895
958
|
});
|
|
896
959
|
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
897
960
|
return { success: true };
|
|
@@ -1258,18 +1321,25 @@ export function recomputeUtilityScores(db) {
|
|
|
1258
1321
|
const emaDecay = EMA_DECAY ** elapsedDays;
|
|
1259
1322
|
const emaNew = 1 - emaDecay; // complement so weights still sum to 1
|
|
1260
1323
|
// Single aggregate query instead of N+1 per-entry queries.
|
|
1261
|
-
// Only processes entries that actually have usage events
|
|
1324
|
+
// Only processes entries that actually have usage events AND still exist
|
|
1325
|
+
// in `entries`. The latter check is critical: usage_events has no FK to
|
|
1326
|
+
// entries, so its entry_id can become stale (entry deleted, re-keyed,
|
|
1327
|
+
// moved between sources). Without the JOIN, writing the derived row to
|
|
1328
|
+
// utility_scores (which DOES have an FK) raises "FOREIGN KEY constraint
|
|
1329
|
+
// failed" and rolls back the whole finalize transaction — failing every
|
|
1330
|
+
// index run.
|
|
1262
1331
|
const usageRows = db
|
|
1263
1332
|
.prepare(`
|
|
1264
|
-
SELECT entry_id,
|
|
1265
|
-
SUM(CASE WHEN event_type = 'search' THEN 1 ELSE 0 END) AS search_count,
|
|
1266
|
-
SUM(CASE WHEN event_type = 'show' THEN 1 ELSE 0 END) AS show_count,
|
|
1267
|
-
SUM(CASE WHEN event_type = 'feedback' AND signal = 'positive' THEN 1 ELSE 0 END) AS positive_feedback_count,
|
|
1268
|
-
SUM(CASE WHEN event_type = 'feedback' AND signal = 'negative' THEN 1 ELSE 0 END) AS negative_feedback_count,
|
|
1269
|
-
MAX(created_at) AS last_used_at
|
|
1270
|
-
FROM usage_events
|
|
1271
|
-
|
|
1272
|
-
|
|
1333
|
+
SELECT u.entry_id,
|
|
1334
|
+
SUM(CASE WHEN u.event_type = 'search' THEN 1 ELSE 0 END) AS search_count,
|
|
1335
|
+
SUM(CASE WHEN u.event_type = 'show' THEN 1 ELSE 0 END) AS show_count,
|
|
1336
|
+
SUM(CASE WHEN u.event_type = 'feedback' AND u.signal = 'positive' THEN 1 ELSE 0 END) AS positive_feedback_count,
|
|
1337
|
+
SUM(CASE WHEN u.event_type = 'feedback' AND u.signal = 'negative' THEN 1 ELSE 0 END) AS negative_feedback_count,
|
|
1338
|
+
MAX(u.created_at) AS last_used_at
|
|
1339
|
+
FROM usage_events u
|
|
1340
|
+
JOIN entries e ON e.id = u.entry_id
|
|
1341
|
+
WHERE u.entry_id IS NOT NULL
|
|
1342
|
+
GROUP BY u.entry_id
|
|
1273
1343
|
`)
|
|
1274
1344
|
.all();
|
|
1275
1345
|
if (usageRows.length === 0) {
|
|
@@ -1,33 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Each pass that calls an LLM and wants to skip re-processing unchanged
|
|
5
|
-
* content can delegate the cache check/write to `withLlmCache` instead of
|
|
6
|
-
* duplicating the hash-compute → lookup → write pattern inline.
|
|
7
|
-
*/
|
|
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/.
|
|
8
4
|
import { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry } from "./db";
|
|
9
5
|
/**
|
|
10
6
|
* Generic LLM cache wrapper. Returns cached result if body unchanged,
|
|
11
7
|
* otherwise calls llmFn(), caches the result, and returns it.
|
|
12
8
|
* Returns undefined if llmFn() returns undefined or throws.
|
|
13
9
|
*
|
|
14
|
-
* @param db
|
|
15
|
-
* @param cacheKey
|
|
16
|
-
* @param body
|
|
17
|
-
* @param reEnrich
|
|
18
|
-
* @param llmFn
|
|
19
|
-
* @param validate
|
|
20
|
-
*
|
|
10
|
+
* @param db - SQLite database holding the LLM result cache.
|
|
11
|
+
* @param cacheKey - Stable identifier for this asset (typically its absolute path).
|
|
12
|
+
* @param body - The content being processed; its hash determines cache validity.
|
|
13
|
+
* @param reEnrich - When true the cache is bypassed and llmFn() is always called.
|
|
14
|
+
* @param llmFn - Async function that performs the actual LLM call.
|
|
15
|
+
* @param validate - Converts the raw parsed JSON back into the pass-specific type;
|
|
16
|
+
* returns undefined when the cached data is unusable.
|
|
17
|
+
* @param precomputedHash - Optional precomputed SHA-256 of `body`. When provided,
|
|
18
|
+
* the wrapper skips its internal hashing — callers that
|
|
19
|
+
* already hashed the body (e.g. to reuse it elsewhere)
|
|
20
|
+
* should pass this to avoid the redundant work.
|
|
21
|
+
* @param cacheVariant - Namespace token for the cache row (e.g. staleness-detect
|
|
22
|
+
* sets one so its rows do not collide with memory-inference).
|
|
23
|
+
* @param hooks - Optional event sink for telemetry (see {@link WithLlmCacheHooks}).
|
|
21
24
|
*/
|
|
22
|
-
export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate) {
|
|
23
|
-
const bodyHash = computeBodyHash(body);
|
|
25
|
+
export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant = "", hooks) {
|
|
26
|
+
const bodyHash = precomputedHash ?? computeBodyHash(body);
|
|
24
27
|
if (!reEnrich) {
|
|
25
28
|
try {
|
|
26
|
-
const cached = getLlmCacheEntry(db, cacheKey, bodyHash);
|
|
29
|
+
const cached = getLlmCacheEntry(db, cacheKey, bodyHash, cacheVariant);
|
|
27
30
|
if (cached) {
|
|
28
31
|
const result = validate(JSON.parse(cached.resultJson));
|
|
29
|
-
if (result !== undefined)
|
|
32
|
+
if (result !== undefined) {
|
|
33
|
+
hooks?.onCacheHit?.();
|
|
30
34
|
return result;
|
|
35
|
+
}
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
catch {
|
|
@@ -37,7 +42,7 @@ export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate
|
|
|
37
42
|
const result = await llmFn();
|
|
38
43
|
if (result !== undefined) {
|
|
39
44
|
try {
|
|
40
|
-
upsertLlmCacheEntry(db, cacheKey, bodyHash, JSON.stringify(result));
|
|
45
|
+
upsertLlmCacheEntry(db, cacheKey, bodyHash, JSON.stringify(result), cacheVariant);
|
|
41
46
|
}
|
|
42
47
|
catch {
|
|
43
48
|
// Cache write failure is non-fatal
|
package/dist/indexer/manifest.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
|
* Manifest: compact asset listing for cheap capability discovery.
|
|
3
6
|
*
|
package/dist/indexer/matchers.js
CHANGED
|
@@ -1,15 +1,185 @@
|
|
|
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
|
* Built-in asset matchers for the akm file classification system.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* Each private `classifyBy*` function encapsulates the classification logic for
|
|
8
|
+
* one heuristic. The public `*Matcher` exports compose those facts into the
|
|
9
|
+
* `MatchResult` shape expected by the rest of the indexer.
|
|
7
10
|
*/
|
|
8
11
|
import { defaultRendererRegistry } from "../core/asset-registry";
|
|
12
|
+
import { SCRIPT_EXTENSIONS } from "../core/asset-spec";
|
|
13
|
+
import { looksLikeWorkflow } from "../workflows/parser";
|
|
9
14
|
import { registerMatcher } from "./file-context";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Private data
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const DIR_TYPE_MAP = [
|
|
19
|
+
{
|
|
20
|
+
dir: "scripts",
|
|
21
|
+
type: "script",
|
|
22
|
+
test: (ext) => SCRIPT_EXTENSIONS.has(ext),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
dir: "commands",
|
|
26
|
+
type: "command",
|
|
27
|
+
test: (ext) => ext === ".md",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
dir: "agents",
|
|
31
|
+
type: "agent",
|
|
32
|
+
test: (ext) => ext === ".md",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
dir: "knowledge",
|
|
36
|
+
type: "knowledge",
|
|
37
|
+
test: (ext) => ext === ".md",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
dir: "workflows",
|
|
41
|
+
type: "workflow",
|
|
42
|
+
test: (ext) => ext === ".md",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
dir: "memories",
|
|
46
|
+
type: "memory",
|
|
47
|
+
test: (ext) => ext === ".md",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
dir: "lessons",
|
|
51
|
+
type: "lesson",
|
|
52
|
+
test: (ext) => ext === ".md",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
dir: "env",
|
|
56
|
+
type: "env",
|
|
57
|
+
test: (_, fileName) => fileName === ".env" || fileName.endsWith(".env"),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
dir: "vaults",
|
|
61
|
+
type: "vault",
|
|
62
|
+
test: (_, fileName) => fileName === ".env" || fileName.endsWith(".env"),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
dir: "secrets",
|
|
66
|
+
type: "secret",
|
|
67
|
+
// Any regular file under secrets/ is a secret value, except the lock and
|
|
68
|
+
// sensitive-marker sidecars. The whole file is the value (no extension or
|
|
69
|
+
// body parsing — see the secret-file renderer + indexer guards).
|
|
70
|
+
test: (_, fileName) => !fileName.endsWith(".lock") && !fileName.endsWith(".sensitive"),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
dir: "tasks",
|
|
74
|
+
type: "task",
|
|
75
|
+
test: (ext) => ext === ".md",
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
const COMMAND_PLACEHOLDER_RE = /\$ARGUMENTS|\$[123]\b/;
|
|
79
|
+
// Files that should never be treated as the typed asset for the surrounding
|
|
80
|
+
// directory (e.g. `workflows/README.md` is documentation, not a workflow).
|
|
81
|
+
// Lower-cased and matched case-insensitively against `ctx.fileName`. They are
|
|
82
|
+
// still indexable — falling through to `classifyBySmartMd` typically routes
|
|
83
|
+
// them to the generic `knowledge` type.
|
|
84
|
+
const TYPED_DIR_DOC_FILES = new Set(["readme.md"]);
|
|
85
|
+
function isTypedDirDocFile(fileName) {
|
|
86
|
+
return TYPED_DIR_DOC_FILES.has(fileName.toLowerCase());
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Private helpers
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
function matchDirectoryHint(dirName, ctx, specificity) {
|
|
92
|
+
if (dirName === "skills" && ctx.fileName === "SKILL.md") {
|
|
93
|
+
return { type: "skill", specificity };
|
|
94
|
+
}
|
|
95
|
+
for (const rule of DIR_TYPE_MAP) {
|
|
96
|
+
if (rule.dir === dirName && rule.test(ctx.ext, ctx.fileName)) {
|
|
97
|
+
// Skip `README.md` (case-insensitive) so `workflows/README.md`,
|
|
98
|
+
// `agents/README.md`, etc. are not parsed as the typed asset and don't
|
|
99
|
+
// trip the workflow/agent metadata validators. They still get indexed
|
|
100
|
+
// as `knowledge` via the smart-md matcher.
|
|
101
|
+
if (isTypedDirDocFile(ctx.fileName))
|
|
102
|
+
return null;
|
|
103
|
+
return { type: rule.type, specificity };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
function classifyByExtension(ctx) {
|
|
109
|
+
if (ctx.fileName === "SKILL.md" && !ctx.ancestorDirs.includes("wikis")) {
|
|
110
|
+
return { type: "skill", specificity: 25 };
|
|
111
|
+
}
|
|
112
|
+
if (SCRIPT_EXTENSIONS.has(ctx.ext)) {
|
|
113
|
+
return { type: "script", specificity: 3 };
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function classifyByDirectory(ctx) {
|
|
118
|
+
for (const dir of ctx.ancestorDirs) {
|
|
119
|
+
const result = matchDirectoryHint(dir, ctx, 10);
|
|
120
|
+
if (result)
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function classifyByParentDirHint(ctx) {
|
|
126
|
+
const { parentDir, ext, fileName } = ctx;
|
|
127
|
+
if (parentDir === "skills" && (fileName === "SKILL.md" || ext === ".md")) {
|
|
128
|
+
return { type: "skill", specificity: 15 };
|
|
129
|
+
}
|
|
130
|
+
return matchDirectoryHint(parentDir, ctx, 15);
|
|
131
|
+
}
|
|
132
|
+
function classifyBySmartMd(ctx) {
|
|
133
|
+
if (ctx.ext !== ".md")
|
|
134
|
+
return null;
|
|
135
|
+
// Never read the body of a file under secrets/ — the whole file is the
|
|
136
|
+
// secret value. The directory matcher classifies it as `secret` without
|
|
137
|
+
// touching content; bailing here keeps classifyBySmartMd from calling
|
|
138
|
+
// ctx.content()/frontmatter() on secret material.
|
|
139
|
+
if (ctx.ancestorDirs.includes("secrets"))
|
|
140
|
+
return null;
|
|
141
|
+
// README.md is documentation, never a workflow/agent/command even when the
|
|
142
|
+
// body shape would otherwise classify (e.g. step-list inside a project
|
|
143
|
+
// README under workflows/). Fall straight through to `knowledge`.
|
|
144
|
+
if (isTypedDirDocFile(ctx.fileName)) {
|
|
145
|
+
return { type: "knowledge", specificity: 5 };
|
|
146
|
+
}
|
|
147
|
+
const body = ctx.content();
|
|
148
|
+
if (looksLikeWorkflow(body)) {
|
|
149
|
+
return { type: "workflow", specificity: 19 };
|
|
150
|
+
}
|
|
151
|
+
const fm = ctx.frontmatter();
|
|
152
|
+
if (fm) {
|
|
153
|
+
if ("toolPolicy" in fm || "tools" in fm) {
|
|
154
|
+
return { type: "agent", specificity: 20 };
|
|
155
|
+
}
|
|
156
|
+
if ("agent" in fm) {
|
|
157
|
+
return { type: "command", specificity: 18 };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (COMMAND_PLACEHOLDER_RE.test(body)) {
|
|
161
|
+
return { type: "command", specificity: 18 };
|
|
162
|
+
}
|
|
163
|
+
if (fm && "model" in fm) {
|
|
164
|
+
return { type: "agent", specificity: 8 };
|
|
165
|
+
}
|
|
166
|
+
return { type: "knowledge", specificity: 5 };
|
|
167
|
+
}
|
|
168
|
+
function classifyByWiki(ctx) {
|
|
169
|
+
if (ctx.ext !== ".md")
|
|
170
|
+
return null;
|
|
171
|
+
const idx = ctx.ancestorDirs.indexOf("wikis");
|
|
172
|
+
if (idx < 0)
|
|
173
|
+
return null;
|
|
174
|
+
if (idx + 1 >= ctx.ancestorDirs.length)
|
|
175
|
+
return null;
|
|
176
|
+
return { type: "wiki", specificity: 20 };
|
|
177
|
+
}
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Adapter: MatchFact → MatchResult
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
function toMatchResult(ctx, classify) {
|
|
182
|
+
const fact = classify(ctx);
|
|
13
183
|
if (!fact)
|
|
14
184
|
return null;
|
|
15
185
|
const renderer = defaultRendererRegistry.rendererNameFor(fact.type);
|
|
@@ -22,20 +192,23 @@ function toMatchResult(ctx, contributor) {
|
|
|
22
192
|
...(fact.meta ? { meta: fact.meta } : {}),
|
|
23
193
|
};
|
|
24
194
|
}
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Public matchers (API unchanged)
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
25
198
|
export function extensionMatcher(ctx) {
|
|
26
|
-
return toMatchResult(ctx,
|
|
199
|
+
return toMatchResult(ctx, classifyByExtension);
|
|
27
200
|
}
|
|
28
201
|
export function directoryMatcher(ctx) {
|
|
29
|
-
return toMatchResult(ctx,
|
|
202
|
+
return toMatchResult(ctx, classifyByDirectory);
|
|
30
203
|
}
|
|
31
204
|
export function parentDirHintMatcher(ctx) {
|
|
32
|
-
return toMatchResult(ctx,
|
|
205
|
+
return toMatchResult(ctx, classifyByParentDirHint);
|
|
33
206
|
}
|
|
34
207
|
export function smartMdMatcher(ctx) {
|
|
35
|
-
return toMatchResult(ctx,
|
|
208
|
+
return toMatchResult(ctx, classifyBySmartMd);
|
|
36
209
|
}
|
|
37
210
|
export function wikiMatcher(ctx) {
|
|
38
|
-
return toMatchResult(ctx,
|
|
211
|
+
return toMatchResult(ctx, classifyByWiki);
|
|
39
212
|
}
|
|
40
213
|
const builtinMatchers = [
|
|
41
214
|
extensionMatcher,
|