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
|
@@ -0,0 +1,192 @@
|
|
|
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
|
+
* Project-context resolution for search ranking.
|
|
6
|
+
*
|
|
7
|
+
* Extracts meaningful identifier tokens from the current working directory
|
|
8
|
+
* so the ranking pipeline can boost assets that are relevant to the active
|
|
9
|
+
* project. Token extraction tries, in order:
|
|
10
|
+
*
|
|
11
|
+
* 1. `.git/config` remote-origin URL basename (strips `.git` extension)
|
|
12
|
+
* 2. `package.json` `name` field (last `/`-separated segment, minus common
|
|
13
|
+
* framework suffixes)
|
|
14
|
+
* 3. Basename of the directory returned by `resolveWorkflowScopeAnchor`
|
|
15
|
+
* 4. `null` — no meaningful project context (home dir, /tmp, etc.)
|
|
16
|
+
*
|
|
17
|
+
* Tokens are lowercased, split on `[-_/]`, then filtered through a noise-word
|
|
18
|
+
* blocklist. A maximum of 5 tokens is kept.
|
|
19
|
+
*/
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import os from "node:os";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { resolveWorkflowScopeAnchor } from "../workflows/scope-key.js";
|
|
24
|
+
// Words that appear in almost every project name and carry no discriminating
|
|
25
|
+
// signal for ranking. Filtered out after token splitting.
|
|
26
|
+
const TOKEN_BLOCKLIST = new Set([
|
|
27
|
+
"my",
|
|
28
|
+
"the",
|
|
29
|
+
"app",
|
|
30
|
+
"lib",
|
|
31
|
+
"sdk",
|
|
32
|
+
"api",
|
|
33
|
+
"cli",
|
|
34
|
+
"tool",
|
|
35
|
+
"kit",
|
|
36
|
+
"core",
|
|
37
|
+
"main",
|
|
38
|
+
"index",
|
|
39
|
+
"src",
|
|
40
|
+
"test",
|
|
41
|
+
]);
|
|
42
|
+
// Common suffixes stripped from package.json `name` before tokenisation so
|
|
43
|
+
// that e.g. `akm-cli` contributes `akm` rather than `akm` + `cli` (which is
|
|
44
|
+
// in the blocklist anyway, but explicit stripping keeps the raw name shorter).
|
|
45
|
+
const STRIP_SUFFIXES = ["-cli", "-app", "-lib", "-sdk", "-plugin"];
|
|
46
|
+
const MAX_TOKENS = 5;
|
|
47
|
+
// Paths that are definitely NOT a meaningful project root (home dir, tmp).
|
|
48
|
+
// When `resolveWorkflowScopeAnchor` returns one of these we return `null`.
|
|
49
|
+
function isNoiseRoot(dir) {
|
|
50
|
+
const homedir = os.homedir();
|
|
51
|
+
const normalized = dir.replace(/\/+$/, "");
|
|
52
|
+
if (normalized === homedir || normalized === homedir.replace(/\/+$/, ""))
|
|
53
|
+
return true;
|
|
54
|
+
// /tmp or /tmp/… (any depth)
|
|
55
|
+
if (normalized === "/tmp" || normalized.startsWith("/tmp/"))
|
|
56
|
+
return true;
|
|
57
|
+
// Windows-style temp
|
|
58
|
+
const tmpdir = os.tmpdir();
|
|
59
|
+
if (normalized === tmpdir || normalized.startsWith(tmpdir + path.sep))
|
|
60
|
+
return true;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the project context for the given working directory.
|
|
65
|
+
*
|
|
66
|
+
* @param cwd - Directory to inspect. Defaults to `process.cwd()`.
|
|
67
|
+
* @param fsOverride - Optional FS override for unit testing.
|
|
68
|
+
* @returns `ProjectContext` with a non-empty `tokens` set, or `null` when no
|
|
69
|
+
* meaningful context can be derived (home dir, /tmp, extraction failed).
|
|
70
|
+
*/
|
|
71
|
+
export function resolveProjectContext(cwd, fsOverride) {
|
|
72
|
+
const effectiveCwd = cwd ?? process.cwd();
|
|
73
|
+
const readFile = fsOverride?.readFileSync ?? ((p, enc) => fs.readFileSync(p, enc));
|
|
74
|
+
// Attempt 1 — git remote-origin URL
|
|
75
|
+
const gitConfigPath = path.join(effectiveCwd, ".git", "config");
|
|
76
|
+
const gitTokens = tryExtractGitTokens(gitConfigPath, readFile);
|
|
77
|
+
if (gitTokens !== null && gitTokens.size > 0) {
|
|
78
|
+
return { tokens: gitTokens };
|
|
79
|
+
}
|
|
80
|
+
// Attempt 2 — package.json name
|
|
81
|
+
const pkgJsonPath = path.join(effectiveCwd, "package.json");
|
|
82
|
+
const pkgTokens = tryExtractPackageJsonTokens(pkgJsonPath, readFile);
|
|
83
|
+
if (pkgTokens !== null && pkgTokens.size > 0) {
|
|
84
|
+
return { tokens: pkgTokens };
|
|
85
|
+
}
|
|
86
|
+
// Attempt 3 — workflow scope anchor basename
|
|
87
|
+
try {
|
|
88
|
+
const anchor = resolveWorkflowScopeAnchor(effectiveCwd);
|
|
89
|
+
if (isNoiseRoot(anchor))
|
|
90
|
+
return null;
|
|
91
|
+
const baseName = path.basename(anchor);
|
|
92
|
+
const tokens = tokenize(baseName);
|
|
93
|
+
if (tokens.size > 0) {
|
|
94
|
+
return { tokens };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore errors from scope anchor resolution (e.g. during testing).
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// ── Private helpers ──────────────────────────────────────────────────────────
|
|
103
|
+
/**
|
|
104
|
+
* Parse `.git/config` for the `[remote "origin"]` section and extract the
|
|
105
|
+
* repo name from the `url =` line.
|
|
106
|
+
*
|
|
107
|
+
* url = git@github.com:itlackey/akm.git → "akm"
|
|
108
|
+
* url = https://github.com/itlackey/akm → "akm"
|
|
109
|
+
*/
|
|
110
|
+
function tryExtractGitTokens(gitConfigPath, readFile) {
|
|
111
|
+
try {
|
|
112
|
+
const content = readFile(gitConfigPath, "utf-8");
|
|
113
|
+
const urlMatch = extractRemoteOriginUrl(content);
|
|
114
|
+
if (!urlMatch)
|
|
115
|
+
return null;
|
|
116
|
+
// Strip .git extension, then take the last path segment.
|
|
117
|
+
const withoutGit = urlMatch.replace(/\.git$/, "");
|
|
118
|
+
const segments = withoutGit.replace(/\/$/, "").split(/[/:]/).filter(Boolean);
|
|
119
|
+
const repoName = segments[segments.length - 1] ?? "";
|
|
120
|
+
const tokens = tokenize(repoName);
|
|
121
|
+
return tokens.size > 0 ? tokens : null;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extracts the `url =` value from the `[remote "origin"]` section of a git
|
|
129
|
+
* config file. Returns `null` when the section or key is absent.
|
|
130
|
+
*/
|
|
131
|
+
function extractRemoteOriginUrl(content) {
|
|
132
|
+
// Split into sections delimited by `[...]` headers.
|
|
133
|
+
const lines = content.split(/\r?\n/);
|
|
134
|
+
let inOrigin = false;
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
const trimmed = line.trim();
|
|
137
|
+
if (trimmed.startsWith("[")) {
|
|
138
|
+
// New section header
|
|
139
|
+
inOrigin = /^\[remote\s+"origin"\]$/i.test(trimmed);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (inOrigin) {
|
|
143
|
+
const m = trimmed.match(/^url\s*=\s*(.+)$/i);
|
|
144
|
+
if (m)
|
|
145
|
+
return m[1].trim();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Read `package.json` and extract the `name` field as tokens.
|
|
152
|
+
*/
|
|
153
|
+
function tryExtractPackageJsonTokens(pkgPath, readFile) {
|
|
154
|
+
try {
|
|
155
|
+
const content = readFile(pkgPath, "utf-8");
|
|
156
|
+
const parsed = JSON.parse(content);
|
|
157
|
+
if (typeof parsed.name !== "string" || !parsed.name)
|
|
158
|
+
return null;
|
|
159
|
+
// For scoped packages (@org/pkg-name) take the last `/`-separated segment.
|
|
160
|
+
const rawName = parsed.name.split("/").pop() ?? parsed.name;
|
|
161
|
+
// Strip common framework suffixes before tokenising.
|
|
162
|
+
let strippedName = rawName;
|
|
163
|
+
for (const suffix of STRIP_SUFFIXES) {
|
|
164
|
+
if (strippedName.endsWith(suffix)) {
|
|
165
|
+
strippedName = strippedName.slice(0, -suffix.length);
|
|
166
|
+
break; // only strip one suffix
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const tokens = tokenize(strippedName);
|
|
170
|
+
return tokens.size > 0 ? tokens : null;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Split a raw name string into lowercase tokens, then filter through the
|
|
178
|
+
* blocklist and cap at `MAX_TOKENS`.
|
|
179
|
+
*
|
|
180
|
+
* "akm-cli" → Set { "akm" }
|
|
181
|
+
* "my-app" → Set {} (all tokens blocked)
|
|
182
|
+
* "openpalm" → Set { "openpalm" }
|
|
183
|
+
*/
|
|
184
|
+
function tokenize(raw) {
|
|
185
|
+
const parts = raw
|
|
186
|
+
.toLowerCase()
|
|
187
|
+
.split(/[-_/]+/)
|
|
188
|
+
.filter(Boolean)
|
|
189
|
+
.filter((t) => !TOKEN_BLOCKLIST.has(t))
|
|
190
|
+
.slice(0, MAX_TOKENS);
|
|
191
|
+
return new Set(parts);
|
|
192
|
+
}
|
|
@@ -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 { computeGraphBoost } from "./graph-boost";
|
|
2
5
|
const TYPE_BOOST = {
|
|
3
6
|
skill: 0.4,
|
|
@@ -11,17 +14,36 @@ const TYPE_BOOST = {
|
|
|
11
14
|
const MAX_BOOST_SUM = 3.0;
|
|
12
15
|
const UTILITY_WEIGHT = 0.5;
|
|
13
16
|
const UTILITY_MAX_BOOST = 1.5;
|
|
14
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Phase 2A / Rec 5: default recency half-life (days) used when no
|
|
19
|
+
* `utilityDecayConfig` is supplied to the ranking pipeline. Matches the
|
|
20
|
+
* pre-2A hardcoded `RECENCY_DECAY_DAYS = 30` constant — the formula is
|
|
21
|
+
* default-safe and collapses to `exp(-days / 30)` when no overrides apply.
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_RECENCY_HALF_LIFE_DAYS = 30;
|
|
24
|
+
/**
|
|
25
|
+
* Cap on the effective half-life after applying the feedback stability
|
|
26
|
+
* boost — prevents indefinite half-life inflation for memories with many
|
|
27
|
+
* positive feedback events. `effectiveHalfLife = min(halfLife * boost^count, halfLife * 4)`.
|
|
28
|
+
*/
|
|
29
|
+
const FEEDBACK_HALF_LIFE_CAP_MULTIPLIER = 4;
|
|
15
30
|
function beliefStateBoost(item) {
|
|
16
31
|
const entry = item.entry;
|
|
17
32
|
if (entry.type !== "memory")
|
|
18
33
|
return 0;
|
|
34
|
+
// Phase 1A: `asserted` and `deprecated` are first-class states.
|
|
35
|
+
// `asserted` carries stronger user-explicit authority than `active`.
|
|
36
|
+
// `deprecated` is a frozen historical state — penalized but milder than `superseded`.
|
|
19
37
|
if (entry.beliefState === "contradicted")
|
|
20
38
|
return -0.45;
|
|
21
39
|
if (entry.beliefState === "superseded")
|
|
22
40
|
return -0.25;
|
|
23
41
|
if (entry.beliefState === "archived")
|
|
24
42
|
return -0.6;
|
|
43
|
+
if (entry.beliefState === "deprecated")
|
|
44
|
+
return -0.15;
|
|
45
|
+
if (entry.beliefState === "asserted")
|
|
46
|
+
return 0.08;
|
|
25
47
|
if (entry.beliefState === "active")
|
|
26
48
|
return 0.06;
|
|
27
49
|
return 0;
|
|
@@ -151,29 +173,131 @@ const graphRankingContributor = {
|
|
|
151
173
|
return ctx.graphContext ? computeGraphBoost(ctx.graphContext, item.filePath) : 0;
|
|
152
174
|
},
|
|
153
175
|
};
|
|
176
|
+
/**
|
|
177
|
+
* Capture-mode boost — Phase 1B / Rec 7.
|
|
178
|
+
*
|
|
179
|
+
* Memories captured via the hot path (`akm remember`) get a modest additive
|
|
180
|
+
* boost so they outrank otherwise-equal background-derived memories. Memories
|
|
181
|
+
* without `captureMode` (legacy) return 0 and rank exactly as before.
|
|
182
|
+
*/
|
|
183
|
+
const captureModeRankingContributor = {
|
|
184
|
+
name: "capture-mode-ranking",
|
|
185
|
+
appliesTo(item) {
|
|
186
|
+
return item.entry.type === "memory" && item.entry.captureMode === "hot";
|
|
187
|
+
},
|
|
188
|
+
adjust() {
|
|
189
|
+
return 0.2;
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Lesson strength boost — Phase 7A / Advantage D4b.
|
|
194
|
+
*
|
|
195
|
+
* Each ref that has credited a lesson via `akm feedback --applied-to` adds
|
|
196
|
+
* 0.06 to the boost (capped at 0.3 ≈ five credits). Lessons without a
|
|
197
|
+
* `lessonStrength` array (or a number) return 0.
|
|
198
|
+
*/
|
|
199
|
+
const lessonStrengthContributor = {
|
|
200
|
+
name: "lesson-strength-ranking",
|
|
201
|
+
appliesTo(item) {
|
|
202
|
+
return (item.entry.type === "lesson" && typeof item.entry.lessonStrength === "number" && item.entry.lessonStrength > 0);
|
|
203
|
+
},
|
|
204
|
+
adjust(item) {
|
|
205
|
+
const strength = item.entry.lessonStrength ?? 0;
|
|
206
|
+
return Math.min(0.3, 0.06 * strength);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Blend ratio for scoped vs. global utility signals.
|
|
211
|
+
*
|
|
212
|
+
* When a scoped row exists: `effectiveUtility = scoped * 0.7 + global * 0.3`
|
|
213
|
+
* This ensures the in-project signal strongly dominates while the global
|
|
214
|
+
* cold-start signal still helps when scoped history is sparse.
|
|
215
|
+
*/
|
|
216
|
+
const SCOPED_UTILITY_BLEND_SCOPED = 0.7;
|
|
217
|
+
const SCOPED_UTILITY_BLEND_GLOBAL = 1 - SCOPED_UTILITY_BLEND_SCOPED;
|
|
154
218
|
const utilityRankingContributor = {
|
|
155
219
|
name: "utility-ranking",
|
|
156
220
|
appliesTo(item, ctx) {
|
|
157
221
|
const utilScore = ctx.utilityScores.get(item.id);
|
|
158
|
-
|
|
222
|
+
const scopedScore = ctx.scopedUtilityScores?.get(item.id);
|
|
223
|
+
return Boolean((utilScore && utilScore.utility > 0) || (scopedScore && scopedScore.utility > 0));
|
|
159
224
|
},
|
|
160
225
|
apply(item, ctx) {
|
|
161
226
|
const utilScore = ctx.utilityScores.get(item.id);
|
|
162
|
-
|
|
227
|
+
const scopedScore = ctx.scopedUtilityScores?.get(item.id);
|
|
228
|
+
// Determine effective utility: prefer scoped when present, blend with global.
|
|
229
|
+
const globalUtility = utilScore?.utility ?? 0;
|
|
230
|
+
const scopedUtility = scopedScore?.utility ?? 0;
|
|
231
|
+
const effectiveUtility = scopedUtility > 0
|
|
232
|
+
? scopedUtility * SCOPED_UTILITY_BLEND_SCOPED + globalUtility * SCOPED_UTILITY_BLEND_GLOBAL
|
|
233
|
+
: globalUtility;
|
|
234
|
+
if (effectiveUtility <= 0)
|
|
163
235
|
return;
|
|
236
|
+
// Recency decay: use the global lastUsedAt for the decay factor (it's an
|
|
237
|
+
// ISO string with full resolution), falling back to scoped lastUsedAt (ms).
|
|
164
238
|
let recencyFactor = 1;
|
|
165
|
-
|
|
166
|
-
|
|
239
|
+
const lastUsedRaw = utilScore?.lastUsedAt ?? (scopedScore ? new Date(scopedScore.lastUsedAt).toISOString() : undefined);
|
|
240
|
+
if (lastUsedRaw) {
|
|
241
|
+
const lastUsedMs = new Date(lastUsedRaw).getTime();
|
|
167
242
|
const daysSinceLastUse = Number.isNaN(lastUsedMs)
|
|
168
243
|
? Infinity
|
|
169
244
|
: Math.max(0, (Date.now() - lastUsedMs) / (1000 * 60 * 60 * 24));
|
|
170
|
-
|
|
245
|
+
// Phase 2A / Rec 5: configurable forgetting curve with optional
|
|
246
|
+
// feedback-stability boost. Absent config + absent positive feedback
|
|
247
|
+
// collapses to `exp(-days / 30)` — pre-2A default-safe.
|
|
248
|
+
const halfLifeDays = ctx.utilityDecayConfig?.halfLifeDays ?? DEFAULT_RECENCY_HALF_LIFE_DAYS;
|
|
249
|
+
const stabilityBoost = ctx.utilityDecayConfig?.feedbackStabilityBoost ?? 1.5;
|
|
250
|
+
const positiveCount = ctx.positiveFeedbackCounts?.get(item.id) ?? 0;
|
|
251
|
+
// `boost^count` is 1 when count is 0 OR when boost is 1.0, so neither
|
|
252
|
+
// a missing feedback count nor a "no boost" config widens the half-life.
|
|
253
|
+
let stabilizedHalfLife = halfLifeDays * stabilityBoost ** positiveCount;
|
|
254
|
+
stabilizedHalfLife = Math.min(stabilizedHalfLife, halfLifeDays * FEEDBACK_HALF_LIFE_CAP_MULTIPLIER);
|
|
255
|
+
// Defensive: half-life must stay positive to avoid div-by-zero / Infinity.
|
|
256
|
+
const safeHalfLife = Math.max(0.0001, stabilizedHalfLife);
|
|
257
|
+
recencyFactor = Math.exp(-daysSinceLastUse / safeHalfLife);
|
|
171
258
|
}
|
|
172
|
-
const rawBoost = 1 +
|
|
259
|
+
const rawBoost = 1 + effectiveUtility * recencyFactor * UTILITY_WEIGHT;
|
|
173
260
|
item.score *= Math.min(rawBoost, UTILITY_MAX_BOOST);
|
|
174
261
|
item.utilityBoosted = true;
|
|
175
262
|
},
|
|
176
263
|
};
|
|
264
|
+
/**
|
|
265
|
+
* Project-context boost.
|
|
266
|
+
*
|
|
267
|
+
* Auto-boosts assets whose name, tags, aliases, or search hints contain tokens
|
|
268
|
+
* derived from the current working directory's project name. For example, when
|
|
269
|
+
* running `akm search` from the `akm` git repo, assets tagged `akm` or named
|
|
270
|
+
* `akm-*` receive an additive boost.
|
|
271
|
+
*
|
|
272
|
+
* The boost is capped at 0.5 so it can never overpower an exact-name match
|
|
273
|
+
* (which contributes 2.0). Each matching token adds 0.2 up to the cap.
|
|
274
|
+
*
|
|
275
|
+
* Skipped entirely when `projectContext` is absent or has no tokens (e.g.
|
|
276
|
+
* when running from home dir, /tmp, or when disabled via
|
|
277
|
+
* `--no-project-context` / `AKM_DISABLE_PROJECT_CONTEXT=1`).
|
|
278
|
+
*/
|
|
279
|
+
const projectContextRankingContributor = {
|
|
280
|
+
name: "project-context-ranking",
|
|
281
|
+
appliesTo(_item, ctx) {
|
|
282
|
+
return ctx.projectContext != null && ctx.projectContext.tokens.size > 0;
|
|
283
|
+
},
|
|
284
|
+
adjust(item, ctx) {
|
|
285
|
+
if (!ctx.projectContext)
|
|
286
|
+
return 0;
|
|
287
|
+
const fields = [
|
|
288
|
+
item.entry.name ?? "",
|
|
289
|
+
...(item.entry.tags ?? []),
|
|
290
|
+
...(item.entry.aliases ?? []),
|
|
291
|
+
...(item.entry.searchHints ?? []),
|
|
292
|
+
].map((s) => s.toLowerCase());
|
|
293
|
+
let hits = 0;
|
|
294
|
+
for (const token of ctx.projectContext.tokens) {
|
|
295
|
+
if (fields.some((f) => f.includes(token)))
|
|
296
|
+
hits++;
|
|
297
|
+
}
|
|
298
|
+
return Math.min(0.5, hits * 0.2);
|
|
299
|
+
},
|
|
300
|
+
};
|
|
177
301
|
export const defaultRankingContributors = [
|
|
178
302
|
exactNameRankingContributor,
|
|
179
303
|
typeRankingContributor,
|
|
@@ -184,6 +308,9 @@ export const defaultRankingContributors = [
|
|
|
184
308
|
descriptionRankingContributor,
|
|
185
309
|
metadataRankingContributor,
|
|
186
310
|
graphRankingContributor,
|
|
311
|
+
captureModeRankingContributor,
|
|
312
|
+
lessonStrengthContributor,
|
|
313
|
+
projectContextRankingContributor,
|
|
187
314
|
];
|
|
188
315
|
export const defaultUtilityRankingContributors = [utilityRankingContributor];
|
|
189
316
|
export function applyScoreContributors(item, ctx, contributors = defaultRankingContributors) {
|
package/dist/indexer/ranking.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
|
import { getUtilityScoresByIds } from "./db";
|
|
2
5
|
import { applyScoreContributors, applyUtilityContributors } from "./ranking-contributors";
|
|
3
6
|
export function normalizeFtsScores(results) {
|
|
@@ -58,14 +61,18 @@ export function applyRankingRules(options) {
|
|
|
58
61
|
queryLower,
|
|
59
62
|
queryTokens,
|
|
60
63
|
graphContext: options.graphContext,
|
|
64
|
+
projectContext: options.projectContext,
|
|
61
65
|
};
|
|
62
66
|
for (const item of options.items) {
|
|
63
67
|
applyScoreContributors(item, rankingContext);
|
|
64
68
|
}
|
|
65
|
-
const utilScoresMap = getUtilityScoresByIds(options.db, options.items.map((item) => item.id));
|
|
69
|
+
const { global: utilScoresMap, scoped: scopedUtilScoresMap } = getUtilityScoresByIds(options.db, options.items.map((item) => item.id), options.scopeKey);
|
|
66
70
|
const utilityContext = {
|
|
67
71
|
...rankingContext,
|
|
68
72
|
utilityScores: utilScoresMap,
|
|
73
|
+
scopedUtilityScores: scopedUtilScoresMap,
|
|
74
|
+
utilityDecayConfig: options.utilityDecayConfig,
|
|
75
|
+
positiveFeedbackCounts: options.positiveFeedbackCounts,
|
|
69
76
|
};
|
|
70
77
|
for (const item of options.items) {
|
|
71
78
|
applyUtilityContributors(item, utilityContext);
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Extracted from indexer.ts to break the circular dependency:
|
|
5
|
-
* db.ts -> indexer.ts -> db.ts
|
|
6
|
-
*
|
|
7
|
-
* This module imports only from metadata.ts (for the StashEntry type),
|
|
8
|
-
* so it can be safely imported by both db.ts and indexer.ts.
|
|
9
|
-
*/
|
|
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/.
|
|
10
4
|
/**
|
|
11
5
|
* Return per-field search text for multi-column FTS5 indexing.
|
|
12
6
|
*
|
|
@@ -45,6 +39,8 @@ export function buildSearchFields(entry) {
|
|
|
45
39
|
hintParts.push(entry.xrefs.join(" "));
|
|
46
40
|
if (entry.pageKind)
|
|
47
41
|
hintParts.push(entry.pageKind);
|
|
42
|
+
if (entry.whenToUse)
|
|
43
|
+
hintParts.push(entry.whenToUse);
|
|
48
44
|
const hints = hintParts.join(" ").toLowerCase();
|
|
49
45
|
const contentParts = [];
|
|
50
46
|
if (entry.toc) {
|
|
@@ -1,3 +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/.
|
|
4
|
+
import { makeAssetRef } from "../core/asset-ref";
|
|
5
|
+
import { getDerivedForParent } from "./db";
|
|
1
6
|
import { getRenderer } from "./file-context";
|
|
2
7
|
const rendererSearchHitEnricher = {
|
|
3
8
|
name: "renderer-search-hit-enricher",
|
|
@@ -12,8 +17,92 @@ const rendererSearchHitEnricher = {
|
|
|
12
17
|
renderer?.enrichSearchHit?.(hit, ctx.stashDir);
|
|
13
18
|
},
|
|
14
19
|
};
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Phase 5A / Advantage D5 — derived-memory enricher.
|
|
22
|
+
*
|
|
23
|
+
* When a parent memory has a `.derived` child indexed (the LLM-distilled
|
|
24
|
+
* lesson surface), this enricher rewrites the parent hit to surface the
|
|
25
|
+
* derived child's description / searchHints / tags AND sets `expandTo` to
|
|
26
|
+
* the derived child's ref so callers can fetch it via `akm show <ref>`.
|
|
27
|
+
*
|
|
28
|
+
* The parent ref is preserved on the hit — only the surface text is
|
|
29
|
+
* swapped, so links and provenance still point at the canonical parent.
|
|
30
|
+
*
|
|
31
|
+
* Skipped for:
|
|
32
|
+
* - non-memory hits
|
|
33
|
+
* - memory hits that are themselves derived children (name ends with
|
|
34
|
+
* `.derived`) — we never recurse parent→child→grandchild
|
|
35
|
+
* - contexts without an open DB connection
|
|
36
|
+
*/
|
|
37
|
+
export const derivedMemoryEnricher = {
|
|
38
|
+
name: "derived-memory-enricher",
|
|
39
|
+
appliesTo(ctx) {
|
|
40
|
+
return ctx.type === "memory" && ctx.db !== undefined;
|
|
41
|
+
},
|
|
42
|
+
enrich(hit, ctx) {
|
|
43
|
+
if (!ctx.db)
|
|
44
|
+
return;
|
|
45
|
+
// Never recurse: a `.derived` hit is itself the child surface; leaving
|
|
46
|
+
// it untouched also avoids `<parent>.derived.derived` chains.
|
|
47
|
+
if (hit.name.toLowerCase().endsWith(".derived"))
|
|
48
|
+
return;
|
|
49
|
+
// Parent ref shape: `memory:<name>`. Re-build from the entry's name
|
|
50
|
+
// so we don't depend on whatever wiki/registry prefix `hit.ref` carries.
|
|
51
|
+
const parentRef = makeAssetRef("memory", hit.name);
|
|
52
|
+
const derived = getDerivedForParent(ctx.db, parentRef);
|
|
53
|
+
if (!derived)
|
|
54
|
+
return;
|
|
55
|
+
// Swap description / searchHints / tags from the derived child.
|
|
56
|
+
// The parent ref itself is preserved — only the surface text is swapped.
|
|
57
|
+
if (typeof derived.entry.description === "string" && derived.entry.description.length > 0) {
|
|
58
|
+
hit.description = derived.entry.description;
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(derived.entry.searchHints) && derived.entry.searchHints.length > 0) {
|
|
61
|
+
// We don't have a `searchHints` field on SourceSearchHit today — it's
|
|
62
|
+
// only used inside ranking. The plan says to swap when present; we
|
|
63
|
+
// record it onto the hit only if a future renderer surfaces it. For
|
|
64
|
+
// now, treat as advisory (no-op when SearchHit lacks the field).
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(derived.entry.tags) && derived.entry.tags.length > 0) {
|
|
67
|
+
hit.tags = derived.entry.tags;
|
|
68
|
+
}
|
|
69
|
+
hit.expandTo = makeAssetRef("memory", derived.entry.name);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Registry of additional enrichers — populated by
|
|
74
|
+
* {@link registerSearchHitEnricher} and consumed in addition to
|
|
75
|
+
* {@link defaultSearchHitEnrichers} when `enrichSearchHit` is invoked
|
|
76
|
+
* without an explicit enricher list.
|
|
77
|
+
*
|
|
78
|
+
* Kept module-local so callers must use `registerSearchHitEnricher` rather
|
|
79
|
+
* than mutating the array directly.
|
|
80
|
+
*/
|
|
81
|
+
const additionalEnrichers = [];
|
|
82
|
+
export const defaultSearchHitEnrichers = [rendererSearchHitEnricher, derivedMemoryEnricher];
|
|
83
|
+
/**
|
|
84
|
+
* Register an additional enricher to be applied alongside the defaults.
|
|
85
|
+
*
|
|
86
|
+
* Idempotent on `name`: subsequent calls with the same name replace the
|
|
87
|
+
* previously-registered enricher (so tests can re-register cleanly without
|
|
88
|
+
* stacking duplicates).
|
|
89
|
+
*/
|
|
90
|
+
export function registerSearchHitEnricher(enricher) {
|
|
91
|
+
const existingIndex = additionalEnrichers.findIndex((e) => e.name === enricher.name);
|
|
92
|
+
if (existingIndex >= 0) {
|
|
93
|
+
additionalEnrichers[existingIndex] = enricher;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
additionalEnrichers.push(enricher);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Test-only: clear the registered-enrichers list. Not part of the public API.
|
|
101
|
+
*/
|
|
102
|
+
export function _resetRegisteredSearchHitEnrichers() {
|
|
103
|
+
additionalEnrichers.length = 0;
|
|
104
|
+
}
|
|
105
|
+
export async function enrichSearchHit(hit, ctx, enrichers = [...defaultSearchHitEnrichers, ...additionalEnrichers]) {
|
|
17
106
|
for (const enricher of enrichers) {
|
|
18
107
|
if (!enricher.appliesTo(ctx))
|
|
19
108
|
continue;
|
|
@@ -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 fs from "node:fs";
|
|
2
5
|
import path from "node:path";
|
|
3
6
|
import { resolveStashDir } from "../core/common";
|
|
@@ -37,8 +40,24 @@ export function resolveSourceEntries(overrideStashDir, existingConfig) {
|
|
|
37
40
|
const seen = new Set([path.resolve(stashDir)]);
|
|
38
41
|
const addSource = (dir, registryId, wikiName, writable) => {
|
|
39
42
|
const resolved = path.resolve(dir);
|
|
40
|
-
if (seen.has(resolved))
|
|
43
|
+
if (seen.has(resolved)) {
|
|
44
|
+
// Already in the source list — typically the primary stash injected at
|
|
45
|
+
// sources[0] before this loop. Enrich that entry with whatever metadata
|
|
46
|
+
// the matching config source carries so `--source <config-name>` can
|
|
47
|
+
// find it via registryId. Without this, the primary stash entry stays
|
|
48
|
+
// identity-less and a user-named primary source ("name": "my-stash")
|
|
49
|
+
// would validate but match zero entries when filtering.
|
|
50
|
+
const existing = sources.find((s) => s.path === resolved);
|
|
51
|
+
if (existing) {
|
|
52
|
+
if (registryId && !existing.registryId)
|
|
53
|
+
existing.registryId = registryId;
|
|
54
|
+
if (wikiName && !existing.wikiName)
|
|
55
|
+
existing.wikiName = wikiName;
|
|
56
|
+
if (writable && !existing.writable)
|
|
57
|
+
existing.writable = true;
|
|
58
|
+
}
|
|
41
59
|
return;
|
|
60
|
+
}
|
|
42
61
|
seen.add(resolved);
|
|
43
62
|
if (isSuspiciousStashRoot(dir)) {
|
|
44
63
|
warn(`Warning: stash root "${dir}" appears to be a system directory. This may be unintentional.`);
|
|
@@ -1,7 +1,10 @@
|
|
|
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 { writeFileAtomic } from "../core/common";
|
|
3
6
|
import { getCacheDir, getSemanticStatusPath } from "../core/paths";
|
|
4
|
-
import { DEFAULT_LOCAL_MODEL } from "../llm/
|
|
7
|
+
import { DEFAULT_LOCAL_MODEL } from "../llm/embedders/local";
|
|
5
8
|
export function deriveSemanticProviderFingerprint(embedding) {
|
|
6
9
|
if (embedding?.endpoint) {
|
|
7
10
|
return `remote:${embedding.endpoint}|${embedding.model}|${embedding.dimension ?? "default"}`;
|