akm-cli 0.8.0-rc1 → 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 +2162 -1258
- 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 +233 -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 +17 -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 +662 -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 +114 -48
- 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 -307
- package/dist/indexer/match-contributors.js +0 -141
- package/dist/integrations/agent/pipeline.js +0 -39
- package/dist/integrations/agent/runners.js +0 -31
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Extracted from source-search.ts to break the circular import:
|
|
5
|
-
* source-search.ts → sources/providers/filesystem.ts → db-search.ts (no cycle)
|
|
6
|
-
*
|
|
7
|
-
* source-search.ts imports this module for the `searchLocal` export.
|
|
8
|
-
* sources/providers/filesystem.ts also imports `searchLocal` from here.
|
|
9
|
-
*
|
|
10
|
-
* Renamed from `local-search.ts` to signal that this is the DB-layer search
|
|
11
|
-
* implementation, not a "local vs. remote" distinction.
|
|
12
|
-
*/
|
|
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/.
|
|
13
4
|
import fs from "node:fs";
|
|
14
5
|
import { buildActionFromContributors, defaultActionContributors } from "../core/action-contributors";
|
|
15
6
|
import { makeAssetRef } from "../core/asset-ref";
|
|
16
7
|
import { defaultRendererRegistry } from "../core/asset-registry";
|
|
17
8
|
import { getDbPath } from "../core/paths";
|
|
18
9
|
import { warn } from "../core/warn";
|
|
19
|
-
import {
|
|
10
|
+
import { getCurrentWorkflowScopeKey } from "../workflows/scope-key";
|
|
11
|
+
import { closeDatabase, getAllEntries, getEntryById, getEntryCount, getMeta, getPositiveFeedbackCountsByIds, openExistingDatabase, sanitizeFtsQuery, searchFts, searchVec, } from "./db";
|
|
20
12
|
import { ensureIndex } from "./ensure-index";
|
|
21
|
-
import { collectGraphRelatedHit, loadGraphBoostContext } from "./graph-boost";
|
|
13
|
+
import { collectGraphRelatedHit, computeGraphBoost, loadGraphBoostContext, } from "./graph-boost";
|
|
22
14
|
import { isProposedQuality } from "./metadata";
|
|
15
|
+
import { resolveProjectContext } from "./project-context";
|
|
23
16
|
import { applyRankingRules, combineSearchScores, normalizeFtsScores } from "./ranking";
|
|
24
17
|
import { enrichSearchHit } from "./search-hit-enrichers";
|
|
25
18
|
import { buildEditHint, findSourceForPath, isEditable } from "./search-source";
|
|
@@ -36,12 +29,30 @@ function resolveSearchHitRef(entry, refName, source) {
|
|
|
36
29
|
function resolveSearchHitOrigin(source) {
|
|
37
30
|
return source?.wikiName ? null : (source?.registryId ?? null);
|
|
38
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Phase 2A / Rec 5: gate for the per-search `getPositiveFeedbackCountsByIds`
|
|
34
|
+
* lookup. Returns `true` only when the user has explicitly opted into
|
|
35
|
+
* `improve.utilityDecay` AND configured a `feedbackStabilityBoost > 1.0`.
|
|
36
|
+
* Either condition being false makes the DB query pure overhead (the ranking
|
|
37
|
+
* contributor ignores `positiveFeedbackCounts` when `utilityDecayConfig` is
|
|
38
|
+
* absent, and `1.0^count == 1` collapses the boost into a no-op).
|
|
39
|
+
*
|
|
40
|
+
* Exported for unit testing — keeps the gate decision pinned so a future edit
|
|
41
|
+
* can't quietly broaden the hot path.
|
|
42
|
+
*/
|
|
43
|
+
export function shouldQueryPositiveFeedbackCounts(utilityDecayRaw) {
|
|
44
|
+
if (utilityDecayRaw === undefined)
|
|
45
|
+
return false;
|
|
46
|
+
const boost = utilityDecayRaw.feedbackStabilityBoost ?? 1.5;
|
|
47
|
+
return boost > 1.0;
|
|
48
|
+
}
|
|
39
49
|
// ── Main search entrypoint ───────────────────────────────────────────────────
|
|
40
50
|
export async function searchLocal(input) {
|
|
41
51
|
const { query, searchType, limit, stashDir, sources, config } = input;
|
|
42
52
|
const filters = input.filters;
|
|
43
53
|
const includeProposed = input.includeProposed === true;
|
|
44
54
|
const beliefFilter = input.beliefFilter ?? "all";
|
|
55
|
+
const restrictToSources = input.restrictToSources === true;
|
|
45
56
|
const rendererRegistry = input.rendererRegistry ?? defaultRendererRegistry;
|
|
46
57
|
const allSourceDirs = sources.map((s) => s.path);
|
|
47
58
|
const rawStatus = readSemanticStatus();
|
|
@@ -52,8 +63,18 @@ export async function searchLocal(input) {
|
|
|
52
63
|
if (rawStatus && rawStatus.providerFingerprint !== currentFingerprint) {
|
|
53
64
|
warnings.push("Embedding config changed. Run 'akm index --full' to rebuild the semantic index with the new provider.");
|
|
54
65
|
}
|
|
66
|
+
else if (!config.embedding?.endpoint || !config.embedding?.model) {
|
|
67
|
+
// #480: when semantic mode is `auto` but no embedding provider is
|
|
68
|
+
// configured (e.g. `akm setup --yes` ran without picking one), telling
|
|
69
|
+
// the user to "run akm setup" is misleading — they just did. Surface
|
|
70
|
+
// the actual remediation: configure an embedding endpoint OR switch
|
|
71
|
+
// semanticSearchMode to `off` to silence the warning.
|
|
72
|
+
warnings.push("Semantic search is enabled (semanticSearchMode='auto') but no embedding provider is configured. " +
|
|
73
|
+
'Either: (a) `akm config set embedding \'{"endpoint":"...","model":"..."}\'`, or ' +
|
|
74
|
+
"(b) `akm config set semanticSearchMode off` to use keyword-only search.");
|
|
75
|
+
}
|
|
55
76
|
else {
|
|
56
|
-
warnings.push("Semantic search is pending verification. Run 'akm
|
|
77
|
+
warnings.push("Semantic search is pending verification. Run 'akm index --full' to build the semantic index now, or wait for the next background index pass.");
|
|
57
78
|
}
|
|
58
79
|
}
|
|
59
80
|
if (config.semanticSearchMode === "auto" && semanticStatus === "blocked") {
|
|
@@ -81,7 +102,7 @@ export async function searchLocal(input) {
|
|
|
81
102
|
mode: "keyword",
|
|
82
103
|
};
|
|
83
104
|
}
|
|
84
|
-
const { hits, embedMs, rankMs } = await searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry, filters, includeProposed, beliefFilter);
|
|
105
|
+
const { hits, embedMs, rankMs } = await searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry, filters, includeProposed, beliefFilter, restrictToSources);
|
|
85
106
|
return {
|
|
86
107
|
hits,
|
|
87
108
|
tip: hits.length === 0
|
|
@@ -98,7 +119,7 @@ export async function searchLocal(input) {
|
|
|
98
119
|
}
|
|
99
120
|
}
|
|
100
121
|
// ── Database search ─────────────────────────────────────────────────────────
|
|
101
|
-
async function searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry = defaultRendererRegistry, filters, includeProposed = false, beliefFilter = "all") {
|
|
122
|
+
async function searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry = defaultRendererRegistry, filters, includeProposed = false, beliefFilter = "all", restrictToSources = false) {
|
|
102
123
|
const hasSearchableTokens = query.length > 0 && sanitizeFtsQuery(query).length > 0;
|
|
103
124
|
// Empty queries — including ones that sanitize down to no searchable FTS
|
|
104
125
|
// tokens such as "." — should enumerate matching entries instead of
|
|
@@ -114,12 +135,19 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
114
135
|
seenFilePaths.add(ie.filePath);
|
|
115
136
|
return true;
|
|
116
137
|
});
|
|
138
|
+
// Source filter: when the caller narrowed `sources` via `--source <name>`,
|
|
139
|
+
// drop entries whose filePath does not live under any of the requested
|
|
140
|
+
// sources. The FTS index spans every configured source, so without this
|
|
141
|
+
// filter a narrowed --source request would still leak results.
|
|
142
|
+
const sourceFiltered = restrictToSources
|
|
143
|
+
? uniqueEntries.filter((ie) => findSourceForPath(ie.filePath, sources) !== undefined)
|
|
144
|
+
: uniqueEntries;
|
|
117
145
|
// Scope filter: drop entries whose stored scope does not satisfy every
|
|
118
146
|
// supplied scope key. Filtering happens BEFORE the limit slice so a
|
|
119
147
|
// restrictive filter still returns up to `limit` results.
|
|
120
148
|
const scopeFiltered = filters
|
|
121
|
-
?
|
|
122
|
-
:
|
|
149
|
+
? sourceFiltered.filter((ie) => entryMatchesScope(ie.entry.scope, filters))
|
|
150
|
+
: sourceFiltered;
|
|
123
151
|
// Proposed-quality filter (v1 spec §4.2): exclude entries with
|
|
124
152
|
// `quality: "proposed"` unless the caller explicitly opts in.
|
|
125
153
|
const qualityFiltered = includeProposed
|
|
@@ -138,6 +166,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
138
166
|
sources,
|
|
139
167
|
config,
|
|
140
168
|
rendererRegistry,
|
|
169
|
+
db,
|
|
141
170
|
})));
|
|
142
171
|
return { hits };
|
|
143
172
|
}
|
|
@@ -192,24 +221,84 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
192
221
|
return null;
|
|
193
222
|
return loadGraphBoostContext(allSourceDirs, query, config, db);
|
|
194
223
|
})();
|
|
195
|
-
|
|
224
|
+
// Resolve project-context tokens from the current working directory once
|
|
225
|
+
// per search invocation. Returns null when running from home dir / /tmp,
|
|
226
|
+
// or when the caller has set AKM_DISABLE_PROJECT_CONTEXT=1.
|
|
227
|
+
const projectContext = process.env.AKM_DISABLE_PROJECT_CONTEXT === "1" ? null : resolveProjectContext(process.cwd());
|
|
228
|
+
// Phase 2A / Rec 5: resolve forgetting-curve config and skip the feedback
|
|
229
|
+
// count query when the boost cannot make a difference (default ≤ 1.0 means
|
|
230
|
+
// boost^count == 1 — zero overhead for the common case).
|
|
231
|
+
const utilityDecayRaw = config.improve?.utilityDecay;
|
|
232
|
+
const halfLifeDays = utilityDecayRaw?.halfLifeDays ?? 30;
|
|
233
|
+
const feedbackStabilityBoost = utilityDecayRaw?.feedbackStabilityBoost ?? 1.5;
|
|
234
|
+
const utilityDecayConfig = utilityDecayRaw !== undefined ? { halfLifeDays, feedbackStabilityBoost } : undefined;
|
|
235
|
+
// Gate the feedback-count query on the user having explicitly opted into
|
|
236
|
+
// utilityDecay. Without an opt-in, `utilityDecayConfig` is undefined and the
|
|
237
|
+
// ranking contributor ignores `positiveFeedbackCounts` — so running the DB
|
|
238
|
+
// query here would be pure overhead. The boost > 1.0 sub-gate then skips the
|
|
239
|
+
// query when the configured boost is a no-op (1.5^count when boost==1 is 1).
|
|
240
|
+
const positiveFeedbackCounts = shouldQueryPositiveFeedbackCounts(utilityDecayRaw)
|
|
241
|
+
? getPositiveFeedbackCountsByIds(db, scored.map((item) => item.id))
|
|
242
|
+
: undefined;
|
|
243
|
+
// Resolve per-project scope key for scoped utility scoring.
|
|
244
|
+
// AKM_DISABLE_SCOPED_UTILITY=1 opts out (e.g. for registry searches or tests).
|
|
245
|
+
let scopeKey;
|
|
246
|
+
try {
|
|
247
|
+
scopeKey = process.env.AKM_DISABLE_SCOPED_UTILITY === "1" ? undefined : getCurrentWorkflowScopeKey();
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Non-fatal — ranking proceeds without scoped utility on any error.
|
|
251
|
+
}
|
|
252
|
+
applyRankingRules({
|
|
253
|
+
db,
|
|
254
|
+
query,
|
|
255
|
+
items: scored,
|
|
256
|
+
graphContext,
|
|
257
|
+
projectContext,
|
|
258
|
+
utilityDecayConfig,
|
|
259
|
+
positiveFeedbackCounts,
|
|
260
|
+
scopeKey,
|
|
261
|
+
});
|
|
196
262
|
// ── minScore floor ──────────────────────────────────────────────────────
|
|
197
263
|
// Drop semantic-only hits (cosine-only, no FTS match) whose score falls
|
|
198
264
|
// below the configured floor. FTS hits and hybrid hits are always kept.
|
|
199
265
|
// Default floor: 0.2. Set search.minScore = 0 in config to disable.
|
|
200
266
|
const minScore = config.search?.minScore ?? 0.2;
|
|
201
267
|
const preFilter = minScore > 0 ? scored.filter((item) => item.rankingMode !== "semantic" || item.score >= minScore) : scored;
|
|
202
|
-
// Deterministic tiebreaker on equal scores
|
|
203
|
-
|
|
268
|
+
// Deterministic tiebreaker on equal scores.
|
|
269
|
+
//
|
|
270
|
+
// CRITICAL: sort on the SAME clamped+rounded value the user sees (see the
|
|
271
|
+
// `finalScore`/round-to-4dp logic below at buildDbHit), NOT the raw pre-clamp
|
|
272
|
+
// `item.score`. The boost loop can push scores above 1.0 (utility, graph,
|
|
273
|
+
// project boosts) and carries ~15 significant digits. Two entries that DISPLAY
|
|
274
|
+
// an identical score (e.g. both clamp to 1.0000) can still differ in their raw
|
|
275
|
+
// pre-clamp score by a timing-dependent epsilon — utility recency uses
|
|
276
|
+
// `Date.now()` and `last_used_at`, so the same query run twice in one process
|
|
277
|
+
// can yield raw scores that diverge at the 6th decimal. Sorting on the raw
|
|
278
|
+
// value lets that invisible epsilon decide the order, so the visible name
|
|
279
|
+
// tiebreaker never engages and the order flips run-to-run (Issue #14). Quantize
|
|
280
|
+
// to the display value first; only then does `localeCompare` break true ties.
|
|
281
|
+
const displayScore = (s) => Math.round(Math.min(1, Math.max(0, s)) * 10000) / 10000;
|
|
282
|
+
preFilter.sort((a, b) => displayScore(b.score) - displayScore(a.score) || a.entry.name.localeCompare(b.entry.name));
|
|
204
283
|
// Deduplicate by file path — keep only the highest-scored entry per file.
|
|
205
284
|
// Multiple .stash.json entries can map to the same file (e.g. entries without
|
|
206
285
|
// a filename field all collapse to files[0]). Showing the same path/ref
|
|
207
286
|
// multiple times clutters results.
|
|
208
287
|
const deduped = deduplicateByPath(preFilter);
|
|
288
|
+
// Source filter: when the caller narrowed `sources` via `--source <name>`,
|
|
289
|
+
// drop hits whose filePath does not live under any of the requested
|
|
290
|
+
// sources. The FTS/vector index spans every configured source, so without
|
|
291
|
+
// this filter a narrowed --source request would still leak results from
|
|
292
|
+
// other sources that happened to match the query text.
|
|
293
|
+
const sourceFiltered = restrictToSources
|
|
294
|
+
? deduped.filter((item) => findSourceForPath(item.filePath, sources) !== undefined)
|
|
295
|
+
: deduped;
|
|
209
296
|
// Scope filter: drop hits whose stored scope does not satisfy every supplied
|
|
210
297
|
// key. Applied AFTER ranking — filtering narrows the result set without
|
|
211
298
|
// touching the single FTS5+boosts scoring pipeline.
|
|
212
|
-
const scopeFiltered = filters
|
|
299
|
+
const scopeFiltered = filters
|
|
300
|
+
? sourceFiltered.filter((item) => entryMatchesScope(item.entry.scope, filters))
|
|
301
|
+
: sourceFiltered;
|
|
213
302
|
// Proposed-quality filter (v1 spec §4.2): exclude entries with
|
|
214
303
|
// `quality: "proposed"` unless the caller passed `--include-proposed`.
|
|
215
304
|
// Applied AFTER ranking for the same reason as scope filtering.
|
|
@@ -239,6 +328,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
239
328
|
utilityBoosted,
|
|
240
329
|
graphContext,
|
|
241
330
|
rendererRegistry,
|
|
331
|
+
db,
|
|
242
332
|
});
|
|
243
333
|
}));
|
|
244
334
|
return { embedMs, rankMs, hits };
|
|
@@ -248,9 +338,16 @@ function matchBeliefFilter(type, beliefState, filter) {
|
|
|
248
338
|
return true;
|
|
249
339
|
if (type !== "memory")
|
|
250
340
|
return true;
|
|
251
|
-
if (filter === "current")
|
|
252
|
-
|
|
253
|
-
|
|
341
|
+
if (filter === "current") {
|
|
342
|
+
// Phase 1A: `asserted` is a "current" state (stronger authority than `active`);
|
|
343
|
+
// `deprecated` is excluded from current results.
|
|
344
|
+
return beliefState === undefined || beliefState === "active" || beliefState === "asserted";
|
|
345
|
+
}
|
|
346
|
+
// historical
|
|
347
|
+
return (beliefState === "contradicted" ||
|
|
348
|
+
beliefState === "superseded" ||
|
|
349
|
+
beliefState === "deprecated" ||
|
|
350
|
+
beliefState === "archived");
|
|
254
351
|
}
|
|
255
352
|
// ── Vector scorer ───────────────────────────────────────────────────────────
|
|
256
353
|
async function tryVecScores(db, query, k, config) {
|
|
@@ -292,7 +389,8 @@ export async function buildDbHit(input) {
|
|
|
292
389
|
const confidenceBoost = typeof input.entry.confidence === "number" ? Math.min(0.05, Math.max(0, input.entry.confidence) * 0.05) : 0;
|
|
293
390
|
// Round to 4 decimal places, no boost multiplication
|
|
294
391
|
const score = Math.round(input.score * 10000) / 10000;
|
|
295
|
-
const
|
|
392
|
+
const graphBoost = input.graphContext ? computeGraphBoost(input.graphContext, input.path) : 0;
|
|
393
|
+
const whyMatched = buildWhyMatched(input.entry, input.query, input.rankingMode, qualityBoost, confidenceBoost, input.utilityBoosted, graphBoost);
|
|
296
394
|
const graphHit = input.graphContext ? collectGraphRelatedHit(input.graphContext, input.path) : null;
|
|
297
395
|
const source = findSourceForPath(input.path, input.sources);
|
|
298
396
|
const ref = resolveSearchHitRef(input.entry, input.entry.name, source);
|
|
@@ -326,12 +424,13 @@ export async function buildDbHit(input) {
|
|
|
326
424
|
type: input.entry.type,
|
|
327
425
|
stashDir: entryStashDir,
|
|
328
426
|
rendererRegistry,
|
|
427
|
+
db: input.db,
|
|
329
428
|
});
|
|
330
429
|
return hit;
|
|
331
430
|
}
|
|
332
431
|
export function buildWhyMatched(entry, query,
|
|
333
432
|
// "hybrid" ranking mode
|
|
334
|
-
rankingMode, qualityBoost, confidenceBoost, utilityBoosted) {
|
|
433
|
+
rankingMode, qualityBoost, confidenceBoost, utilityBoosted, graphBoost) {
|
|
335
434
|
const reasons = [
|
|
336
435
|
rankingMode === "hybrid"
|
|
337
436
|
? "hybrid (fts + semantic)"
|
|
@@ -375,12 +474,21 @@ rankingMode, qualityBoost, confidenceBoost, utilityBoosted) {
|
|
|
375
474
|
reasons.push("metadata confidence boost");
|
|
376
475
|
if (entry.beliefState === "active")
|
|
377
476
|
reasons.push("active belief state");
|
|
477
|
+
if (entry.beliefState === "asserted")
|
|
478
|
+
reasons.push("asserted belief state");
|
|
378
479
|
if (entry.beliefState === "contradicted")
|
|
379
480
|
reasons.push("contradicted belief state");
|
|
380
481
|
if (entry.beliefState === "superseded")
|
|
381
482
|
reasons.push("superseded belief state");
|
|
483
|
+
if (entry.beliefState === "deprecated")
|
|
484
|
+
reasons.push("deprecated belief state");
|
|
485
|
+
if (entry.beliefState === "archived")
|
|
486
|
+
reasons.push("archived belief state");
|
|
382
487
|
if (utilityBoosted)
|
|
383
488
|
reasons.push("usage history boost");
|
|
489
|
+
if (typeof graphBoost === "number" && graphBoost > 0) {
|
|
490
|
+
reasons.push(`graph boost +${graphBoost.toFixed(2)}`);
|
|
491
|
+
}
|
|
384
492
|
return reasons;
|
|
385
493
|
}
|
|
386
494
|
// ── Utilities ────────────────────────────────────────────────────────────────
|