akm-cli 0.9.0-beta.5 → 0.9.0-beta.51
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/CHANGELOG.md +711 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +6 -2
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +38 -10
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +27 -11
- package/dist/commands/agent/agent-dispatch.js +2 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +17 -4
- package/dist/commands/config-cli.js +18 -2
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +33 -26
- package/dist/commands/env/secret-cli.js +36 -22
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/checks.js +49 -1
- package/dist/commands/health/html-report.js +422 -80
- package/dist/commands/health.js +386 -9
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +81 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +635 -660
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +159 -69
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +124 -2
- package/dist/commands/improve/extract-prompt.js +39 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +389 -40
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +138 -7
- package/dist/commands/improve/improve-cli.js +36 -61
- package/dist/commands/improve/improve-profiles.js +14 -0
- package/dist/commands/improve/improve-result-file.js +14 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2498
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1083 -0
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/preparation.js +1966 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +418 -0
- package/dist/commands/improve/recombine.js +850 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +183 -40
- package/dist/commands/improve/salience.js +438 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain-policies.js +5 -0
- package/dist/commands/proposal/drain.js +43 -50
- package/dist/commands/proposal/proposal-cli.js +21 -31
- package/dist/commands/proposal/proposal.js +5 -0
- package/dist/commands/proposal/propose.js +7 -2
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +189 -63
- package/dist/commands/read/curate.js +414 -94
- package/dist/commands/read/knowledge.js +6 -3
- package/dist/commands/read/search-cli.js +9 -4
- package/dist/commands/read/search.js +10 -6
- package/dist/commands/read/show.js +86 -7
- package/dist/commands/sources/init.js +49 -17
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +43 -45
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/stash-cli.js +28 -40
- package/dist/commands/sources/stash-skeleton.js +23 -8
- package/dist/commands/tasks/tasks-cli.js +19 -27
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +21 -35
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +18 -2
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +0 -5
- package/dist/core/config/config-migration.js +12 -11
- package/dist/core/config/config-schema.js +340 -56
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +28 -7
- package/dist/core/events.js +3 -7
- package/dist/core/improve-types.js +11 -8
- package/dist/core/logs-db.js +10 -66
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +3 -0
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +714 -0
- package/dist/core/state-db.js +525 -474
- package/dist/indexer/db/db.js +439 -247
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +218 -4
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +123 -221
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +19 -6
- package/dist/indexer/search/ranking-contributors.js +22 -0
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/indexer/search/search-source.js +17 -18
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/integrations/agent/config.js +6 -53
- package/dist/integrations/agent/index.js +2 -18
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +45 -15
- package/dist/llm/embedder.js +42 -3
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/feature-gate.js +8 -4
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +74 -2
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/text/helpers.js +16 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +22 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2617 -1961
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
- package/dist/setup/setup.js +29 -8
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/registry-cache.js +92 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +5 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +37 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +1 -117
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +3 -2
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +13 -11
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
package/dist/indexer/indexer.js
CHANGED
|
@@ -10,8 +10,10 @@ import { getDbPath } from "../core/paths.js";
|
|
|
10
10
|
import { isVerbose, warn, warnVerbose } from "../core/warn.js";
|
|
11
11
|
import { resolveIndexPassLLM } from "../llm/index-passes.js";
|
|
12
12
|
import { takeWorkflowDocument } from "../workflows/runtime/document-cache.js";
|
|
13
|
-
import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByIds, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddableEntryCount, getEmbeddingCount,
|
|
13
|
+
import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByIds, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddableEntryCount, getEmbeddingCount, getEntryCount, getMeta, isVecAvailable, openExistingDatabase, openIndexDatabase, purgeEmbeddings, rebuildFts, relinkUsageEvents, setMeta, upsertEmbedding, upsertEntry, upsertIndexDirState, upsertUtilityScore, upsertWorkflowDocument, warnIfVecMissing, } from "./db/db.js";
|
|
14
14
|
import { deleteStoredGraph } from "./db/graph-db.js";
|
|
15
|
+
import { withIndexWriterLease } from "./index-writer-lock.js";
|
|
16
|
+
import { canUseIncrementalSkip, computeDirFingerprint, getCachedZeroRowDirState, getDirIndexState, inferZeroRowReason, } from "./passes/dir-staleness.js";
|
|
15
17
|
import { applyCuratedFrontmatter, applyWikiFrontmatter, generateMetadataFlat, isEnrichmentComplete, isWorkflowSkipWarning, loadStashFile, shouldIndexStashFile, } from "./passes/metadata.js";
|
|
16
18
|
import { buildSearchText } from "./search/search-fields.js";
|
|
17
19
|
import { classifySemanticFailure, clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "./search/semantic-status.js";
|
|
@@ -195,8 +197,8 @@ async function runFinalizePhase(ctx) {
|
|
|
195
197
|
}
|
|
196
198
|
onProgress({ phase: "verify", message: verification.message });
|
|
197
199
|
// Store verification result and totalEntries on ctx for the caller to use
|
|
198
|
-
ctx.
|
|
199
|
-
ctx.
|
|
200
|
+
ctx.verification = verification;
|
|
201
|
+
ctx.totalEntries = totalEntries;
|
|
200
202
|
// suppress unused warning — sources was previously used inline
|
|
201
203
|
void sources;
|
|
202
204
|
}
|
|
@@ -225,119 +227,123 @@ function runCleanPass(db, dryRun) {
|
|
|
225
227
|
}
|
|
226
228
|
// ── Indexer ──────────────────────────────────────────────────────────────────
|
|
227
229
|
export async function akmIndex(options) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
230
|
+
return withIndexWriterLease({ purpose: "akm-index", signal: options?.signal }, async () => {
|
|
231
|
+
const stashDir = options?.stashDir || resolveStashDir();
|
|
232
|
+
const onProgress = options?.onProgress ?? (() => { });
|
|
233
|
+
const signal = options?.signal;
|
|
234
|
+
const reEnrich = options?.reEnrich === true;
|
|
235
|
+
const full = options?.full === true;
|
|
236
|
+
const clean = options?.clean === true;
|
|
237
|
+
const dryRun = options?.dryRun === true;
|
|
238
|
+
// Load config and resolve all stash sources
|
|
239
|
+
const { loadConfig } = await import("../core/config/config.js");
|
|
240
|
+
const config = loadConfig();
|
|
241
|
+
// One-time, read-only guard: warn if the writable stash still holds an
|
|
242
|
+
// un-migrated `vaults/` directory. In 0.9.0 the indexer skips `vaults/`
|
|
243
|
+
// entirely, so an unmigrated vault's `.env` data would silently never be
|
|
244
|
+
// indexed. Non-destructive — only stats, never reads/writes/deletes.
|
|
245
|
+
const { warnOnUnmigratedVaults } = await import("./usage/unmigrated-vaults-guard.js");
|
|
246
|
+
warnOnUnmigratedVaults(stashDir);
|
|
247
|
+
// Ensure git stash caches are extracted before resolving stash dirs,
|
|
248
|
+
// so their content directories exist on disk for the walker to discover.
|
|
249
|
+
const { ensureSourceCaches, resolveSourceEntries } = await import("./search/search-source.js");
|
|
250
|
+
await ensureSourceCaches(config, { force: full });
|
|
251
|
+
const allSourceEntries = resolveSourceEntries(stashDir, config);
|
|
252
|
+
const allSourceDirs = allSourceEntries.map((s) => s.path);
|
|
253
|
+
const t0 = Date.now();
|
|
254
|
+
// Open database — pass embedding dimension from config if available
|
|
255
|
+
const dbPath = getDbPath();
|
|
256
|
+
const embeddingDim = config.embedding?.dimension;
|
|
257
|
+
const db = openIndexDatabase(dbPath, embeddingDim ? { embeddingDim } : undefined);
|
|
258
|
+
try {
|
|
259
|
+
// Determine incremental vs full mode
|
|
260
|
+
const prevStashDir = getMeta(db, "stashDir");
|
|
261
|
+
const prevBuiltAt = getMeta(db, "builtAt");
|
|
262
|
+
const isIncremental = !full && prevStashDir === stashDir && !!prevBuiltAt;
|
|
263
|
+
const builtAtMs = isIncremental && prevBuiltAt ? new Date(prevBuiltAt).getTime() : 0;
|
|
264
|
+
// Assemble the run context
|
|
265
|
+
const ctx = {
|
|
266
|
+
db,
|
|
267
|
+
config,
|
|
268
|
+
sources: allSourceEntries,
|
|
269
|
+
sourceDirs: allSourceDirs,
|
|
270
|
+
full,
|
|
271
|
+
reEnrich,
|
|
272
|
+
stashDir,
|
|
273
|
+
onProgress,
|
|
274
|
+
signal,
|
|
275
|
+
timing: {
|
|
276
|
+
t0,
|
|
277
|
+
tWalkStart: t0,
|
|
278
|
+
tWalkEnd: t0,
|
|
279
|
+
tLlmEnd: t0,
|
|
280
|
+
tFtsEnd: t0,
|
|
281
|
+
tEmbedEnd: t0,
|
|
282
|
+
},
|
|
283
|
+
isIncremental,
|
|
284
|
+
builtAtMs,
|
|
285
|
+
hadRemovedSources: false,
|
|
286
|
+
scannedDirs: 0,
|
|
287
|
+
skippedDirs: 0,
|
|
288
|
+
generatedCount: 0,
|
|
289
|
+
walkWarnings: [],
|
|
290
|
+
dirsNeedingLlm: [],
|
|
291
|
+
embeddingResult: null,
|
|
292
|
+
graphExtractionResult: null,
|
|
293
|
+
};
|
|
294
|
+
onProgress({
|
|
295
|
+
phase: "summary",
|
|
296
|
+
message: buildIndexSummaryMessage({
|
|
297
|
+
mode: isIncremental ? "incremental" : "full",
|
|
298
|
+
sourcesCount: allSourceDirs.length,
|
|
299
|
+
semanticSearchMode: config.semanticSearchMode,
|
|
300
|
+
embeddingProvider: getEmbeddingProvider(config.embedding),
|
|
301
|
+
llmEnabled: !!resolveIndexPassLLM("enrichment", config),
|
|
302
|
+
vecAvailable: isVecAvailable(db),
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
// ── Phase sequence ───────────────────────────────────────────────────────
|
|
306
|
+
await runSourceCachePhase(ctx);
|
|
307
|
+
await runWalkPhase(ctx);
|
|
308
|
+
await runEmbeddingPhase(ctx);
|
|
309
|
+
await runFinalizePhase(ctx);
|
|
310
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
311
|
+
// runFinalizePhase always populates these before returning.
|
|
312
|
+
const verification = ctx.verification;
|
|
313
|
+
const totalEntries = ctx.totalEntries;
|
|
314
|
+
const { timing } = ctx;
|
|
315
|
+
// ── Clean pass ───────────────────────────────────────────────────────────
|
|
316
|
+
// After the normal index completes, remove entries whose source files no
|
|
317
|
+
// longer exist on disk. Remote entries (empty file_path) are skipped.
|
|
318
|
+
let cleanResult;
|
|
319
|
+
if (clean) {
|
|
320
|
+
cleanResult = runCleanPass(db, dryRun);
|
|
321
|
+
}
|
|
322
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
323
|
+
return {
|
|
324
|
+
stashDir,
|
|
325
|
+
totalEntries,
|
|
326
|
+
generatedMetadata: ctx.generatedCount,
|
|
327
|
+
indexPath: dbPath,
|
|
294
328
|
mode: isIncremental ? "incremental" : "full",
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const { _verification: verification, _totalEntries: totalEntries } = ctx;
|
|
309
|
-
const { timing } = ctx;
|
|
310
|
-
// ── Clean pass ───────────────────────────────────────────────────────────
|
|
311
|
-
// After the normal index completes, remove entries whose source files no
|
|
312
|
-
// longer exist on disk. Remote entries (empty file_path) are skipped.
|
|
313
|
-
let cleanResult;
|
|
314
|
-
if (clean) {
|
|
315
|
-
cleanResult = runCleanPass(db, dryRun);
|
|
329
|
+
directoriesScanned: ctx.scannedDirs,
|
|
330
|
+
directoriesSkipped: ctx.skippedDirs,
|
|
331
|
+
...(ctx.walkWarnings.length > 0 ? { warnings: ctx.walkWarnings } : {}),
|
|
332
|
+
verification,
|
|
333
|
+
timing: {
|
|
334
|
+
totalMs: Date.now() - timing.t0,
|
|
335
|
+
walkMs: timing.tWalkEnd - timing.tWalkStart,
|
|
336
|
+
llmMs: timing.tLlmEnd - timing.tWalkEnd,
|
|
337
|
+
embedMs: timing.tEmbedEnd - timing.tLlmEnd,
|
|
338
|
+
ftsMs: timing.tFtsEnd - timing.tEmbedEnd,
|
|
339
|
+
},
|
|
340
|
+
...(cleanResult !== undefined ? { clean: cleanResult } : {}),
|
|
341
|
+
};
|
|
316
342
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
generatedMetadata: ctx.generatedCount,
|
|
322
|
-
indexPath: dbPath,
|
|
323
|
-
mode: isIncremental ? "incremental" : "full",
|
|
324
|
-
directoriesScanned: ctx.scannedDirs,
|
|
325
|
-
directoriesSkipped: ctx.skippedDirs,
|
|
326
|
-
...(ctx.walkWarnings.length > 0 ? { warnings: ctx.walkWarnings } : {}),
|
|
327
|
-
verification,
|
|
328
|
-
timing: {
|
|
329
|
-
totalMs: Date.now() - timing.t0,
|
|
330
|
-
walkMs: timing.tWalkEnd - timing.tWalkStart,
|
|
331
|
-
llmMs: timing.tLlmEnd - timing.tWalkEnd,
|
|
332
|
-
embedMs: timing.tEmbedEnd - timing.tLlmEnd,
|
|
333
|
-
ftsMs: timing.tFtsEnd - timing.tEmbedEnd,
|
|
334
|
-
},
|
|
335
|
-
...(cleanResult !== undefined ? { clean: cleanResult } : {}),
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
finally {
|
|
339
|
-
closeDatabase(db);
|
|
340
|
-
}
|
|
343
|
+
finally {
|
|
344
|
+
closeDatabase(db);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
341
347
|
}
|
|
342
348
|
// ── Extracted helpers for indexing ────────────────────────────────────────────
|
|
343
349
|
async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadRemovedSources, doFullDelete = false, onProgress) {
|
|
@@ -634,98 +640,6 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
|
|
|
634
640
|
insertTransaction();
|
|
635
641
|
return { scannedDirs, skippedDirs, generatedCount, warnings, dirsNeedingLlm };
|
|
636
642
|
}
|
|
637
|
-
function getDirIndexState(db, dirPath, files, builtAtMs) {
|
|
638
|
-
const prevEntries = getEntriesByDir(db, dirPath);
|
|
639
|
-
const fingerprint = computeDirFingerprint(dirPath, files);
|
|
640
|
-
if (prevEntries.length > 0) {
|
|
641
|
-
const staleReason = getDirStaleReason(dirPath, files, prevEntries, builtAtMs);
|
|
642
|
-
if (!staleReason) {
|
|
643
|
-
return { stale: false, reason: { kind: "unchanged" }, persistedRowCount: prevEntries.length };
|
|
644
|
-
}
|
|
645
|
-
return { stale: true, reason: staleReason, persistedRowCount: prevEntries.length };
|
|
646
|
-
}
|
|
647
|
-
const cachedState = getIndexDirState(db, dirPath);
|
|
648
|
-
if (cachedState &&
|
|
649
|
-
cachedState.fileSetHash === fingerprint.fileSetHash &&
|
|
650
|
-
cachedState.fileMtimeMaxMs === fingerprint.fileMtimeMaxMs) {
|
|
651
|
-
return {
|
|
652
|
-
stale: false,
|
|
653
|
-
reason: { kind: "cached-zero-row-state", detail: cachedState.reason },
|
|
654
|
-
persistedRowCount: 0,
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
return {
|
|
658
|
-
stale: true,
|
|
659
|
-
reason: { kind: "no-previous-rows", detail: cachedState ? `cached=${cachedState.reason}` : undefined },
|
|
660
|
-
persistedRowCount: 0,
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
function getCachedZeroRowDirState(db, dirPath, files, builtAtMs, priorDirsChanged) {
|
|
664
|
-
const state = getDirIndexState(db, dirPath, files, builtAtMs);
|
|
665
|
-
if (state.stale || state.reason.kind !== "cached-zero-row-state")
|
|
666
|
-
return undefined;
|
|
667
|
-
if (!canUseIncrementalSkip(state, priorDirsChanged))
|
|
668
|
-
return undefined;
|
|
669
|
-
return state;
|
|
670
|
-
}
|
|
671
|
-
function canUseIncrementalSkip(state, priorDirsChanged) {
|
|
672
|
-
return !(priorDirsChanged &&
|
|
673
|
-
state.reason.kind === "cached-zero-row-state" &&
|
|
674
|
-
state.reason.detail === "deduped-zero-row");
|
|
675
|
-
}
|
|
676
|
-
function computeDirFingerprint(_dirPath, files) {
|
|
677
|
-
const normalizedFiles = [...new Set(files.map((file) => path.basename(file)))].sort();
|
|
678
|
-
let fileMtimeMaxMs = 0;
|
|
679
|
-
for (const file of files) {
|
|
680
|
-
try {
|
|
681
|
-
fileMtimeMaxMs = Math.max(fileMtimeMaxMs, fs.statSync(file).mtimeMs);
|
|
682
|
-
}
|
|
683
|
-
catch {
|
|
684
|
-
fileMtimeMaxMs = Number.POSITIVE_INFINITY;
|
|
685
|
-
break;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
return {
|
|
689
|
-
fileSetHash: normalizedFiles.join("\0"),
|
|
690
|
-
fileMtimeMaxMs,
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
function getDirStaleReason(_dirPath, currentFiles, previousEntries, builtAtMs) {
|
|
694
|
-
const prevFileNames = new Set(previousEntries
|
|
695
|
-
.map((ie) => {
|
|
696
|
-
const fromPath = path.basename(ie.filePath);
|
|
697
|
-
return fromPath || ie.entry.filename;
|
|
698
|
-
})
|
|
699
|
-
.filter((e) => !!e));
|
|
700
|
-
const currFileNames = new Set(currentFiles.map((f) => path.basename(f)));
|
|
701
|
-
if (prevFileNames.size !== currFileNames.size) {
|
|
702
|
-
return { kind: "file-set-changed", detail: `${prevFileNames.size} -> ${currFileNames.size} files` };
|
|
703
|
-
}
|
|
704
|
-
for (const name of currFileNames) {
|
|
705
|
-
if (!prevFileNames.has(name))
|
|
706
|
-
return { kind: "file-set-changed", detail: name };
|
|
707
|
-
}
|
|
708
|
-
for (const file of currentFiles) {
|
|
709
|
-
try {
|
|
710
|
-
if (fs.statSync(file).mtimeMs > builtAtMs)
|
|
711
|
-
return { kind: "mtime-changed", detail: path.basename(file) };
|
|
712
|
-
}
|
|
713
|
-
catch {
|
|
714
|
-
return { kind: "missing-file", detail: path.basename(file) };
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
return undefined;
|
|
718
|
-
}
|
|
719
|
-
function inferZeroRowReason(stash, priorReason, warnings, dirPath, dedupedRows) {
|
|
720
|
-
if (dedupedRows > 0)
|
|
721
|
-
return "deduped-zero-row";
|
|
722
|
-
const workflowNoise = warnings.some((warning) => warning.startsWith("Skipped workflow ") && warning.includes(dirPath));
|
|
723
|
-
if (workflowNoise)
|
|
724
|
-
return "workflow-noise";
|
|
725
|
-
if (!stash || stash.entries.length === 0)
|
|
726
|
-
return "empty-generated-set";
|
|
727
|
-
return `zero-row:${priorReason?.kind ?? "unknown"}`;
|
|
728
|
-
}
|
|
729
643
|
async function enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, _enrich = false, reEnrich = false) {
|
|
730
644
|
// Resolve per-pass LLM config via the unified shim. Returns undefined when
|
|
731
645
|
// either no `akm.llm` is configured or the user opted this pass out via
|
|
@@ -896,21 +810,9 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal) {
|
|
|
896
810
|
const currentFingerprint = deriveSemanticProviderFingerprint(config.embedding);
|
|
897
811
|
const storedFingerprint = getMeta(db, "embeddingFingerprint");
|
|
898
812
|
if (storedFingerprint && storedFingerprint !== currentFingerprint) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
catch {
|
|
903
|
-
/* ignore */
|
|
904
|
-
}
|
|
905
|
-
if (isVecAvailable(db)) {
|
|
906
|
-
try {
|
|
907
|
-
db.exec("DELETE FROM entries_vec");
|
|
908
|
-
}
|
|
909
|
-
catch {
|
|
910
|
-
/* ignore */
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
setMeta(db, "hasEmbeddings", "0");
|
|
813
|
+
// Model/provider changed → stored vectors are incompatible. Clear them
|
|
814
|
+
// (same dimension, so keep the vec table); re-embedded by this index run.
|
|
815
|
+
purgeEmbeddings(db);
|
|
914
816
|
}
|
|
915
817
|
try {
|
|
916
818
|
const { embedBatch } = await import("../llm/embedder.js");
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
+
* Incremental dir-staleness engine.
|
|
6
|
+
*
|
|
7
|
+
* Decides, per stash directory, whether the directory's indexed rows are still
|
|
8
|
+
* fresh relative to what is on disk — so an incremental `akm index` run can
|
|
9
|
+
* skip unchanged directories instead of regenerating their metadata.
|
|
10
|
+
*
|
|
11
|
+
* Two persisted signals back the decision:
|
|
12
|
+
* 1. The `entries` rows already indexed for the directory (`getEntriesByDir`).
|
|
13
|
+
* 2. The `index_dir_state` fingerprint row (`getIndexDirState`), which caches
|
|
14
|
+
* the file-set hash + max mtime for directories that legitimately produced
|
|
15
|
+
* zero rows, so they are not rescanned every run.
|
|
16
|
+
*
|
|
17
|
+
* `computeDirFingerprint` derives the fingerprint (basename set + max mtime)
|
|
18
|
+
* that both the freshness check and the persisted `index_dir_state` row use.
|
|
19
|
+
*/
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { getEntriesByDir, getIndexDirState } from "../db/db.js";
|
|
23
|
+
export function getDirIndexState(db, dirPath, files, builtAtMs) {
|
|
24
|
+
const prevEntries = getEntriesByDir(db, dirPath);
|
|
25
|
+
const fingerprint = computeDirFingerprint(dirPath, files);
|
|
26
|
+
if (prevEntries.length > 0) {
|
|
27
|
+
const staleReason = getDirStaleReason(dirPath, files, prevEntries, builtAtMs);
|
|
28
|
+
if (!staleReason) {
|
|
29
|
+
return { stale: false, reason: { kind: "unchanged" }, persistedRowCount: prevEntries.length };
|
|
30
|
+
}
|
|
31
|
+
return { stale: true, reason: staleReason, persistedRowCount: prevEntries.length };
|
|
32
|
+
}
|
|
33
|
+
const cachedState = getIndexDirState(db, dirPath);
|
|
34
|
+
if (cachedState &&
|
|
35
|
+
cachedState.fileSetHash === fingerprint.fileSetHash &&
|
|
36
|
+
cachedState.fileMtimeMaxMs === fingerprint.fileMtimeMaxMs) {
|
|
37
|
+
return {
|
|
38
|
+
stale: false,
|
|
39
|
+
reason: { kind: "cached-zero-row-state", detail: cachedState.reason },
|
|
40
|
+
persistedRowCount: 0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
stale: true,
|
|
45
|
+
reason: { kind: "no-previous-rows", detail: cachedState ? `cached=${cachedState.reason}` : undefined },
|
|
46
|
+
persistedRowCount: 0,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function getCachedZeroRowDirState(db, dirPath, files, builtAtMs, priorDirsChanged) {
|
|
50
|
+
const state = getDirIndexState(db, dirPath, files, builtAtMs);
|
|
51
|
+
if (state.stale || state.reason.kind !== "cached-zero-row-state")
|
|
52
|
+
return undefined;
|
|
53
|
+
if (!canUseIncrementalSkip(state, priorDirsChanged))
|
|
54
|
+
return undefined;
|
|
55
|
+
return state;
|
|
56
|
+
}
|
|
57
|
+
export function canUseIncrementalSkip(state, priorDirsChanged) {
|
|
58
|
+
return !(priorDirsChanged &&
|
|
59
|
+
state.reason.kind === "cached-zero-row-state" &&
|
|
60
|
+
state.reason.detail === "deduped-zero-row");
|
|
61
|
+
}
|
|
62
|
+
export function computeDirFingerprint(_dirPath, files) {
|
|
63
|
+
const normalizedFiles = [...new Set(files.map((file) => path.basename(file)))].sort();
|
|
64
|
+
let fileMtimeMaxMs = 0;
|
|
65
|
+
for (const file of files) {
|
|
66
|
+
try {
|
|
67
|
+
fileMtimeMaxMs = Math.max(fileMtimeMaxMs, fs.statSync(file).mtimeMs);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
fileMtimeMaxMs = Number.POSITIVE_INFINITY;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
fileSetHash: normalizedFiles.join("\0"),
|
|
76
|
+
fileMtimeMaxMs,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function getDirStaleReason(_dirPath, currentFiles, previousEntries, builtAtMs) {
|
|
80
|
+
const prevFileNames = new Set(previousEntries
|
|
81
|
+
.map((ie) => {
|
|
82
|
+
const fromPath = path.basename(ie.filePath);
|
|
83
|
+
return fromPath || ie.entry.filename;
|
|
84
|
+
})
|
|
85
|
+
.filter((e) => !!e));
|
|
86
|
+
const currFileNames = new Set(currentFiles.map((f) => path.basename(f)));
|
|
87
|
+
if (prevFileNames.size !== currFileNames.size) {
|
|
88
|
+
return { kind: "file-set-changed", detail: `${prevFileNames.size} -> ${currFileNames.size} files` };
|
|
89
|
+
}
|
|
90
|
+
for (const name of currFileNames) {
|
|
91
|
+
if (!prevFileNames.has(name))
|
|
92
|
+
return { kind: "file-set-changed", detail: name };
|
|
93
|
+
}
|
|
94
|
+
for (const file of currentFiles) {
|
|
95
|
+
try {
|
|
96
|
+
if (fs.statSync(file).mtimeMs > builtAtMs)
|
|
97
|
+
return { kind: "mtime-changed", detail: path.basename(file) };
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return { kind: "missing-file", detail: path.basename(file) };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
export function inferZeroRowReason(stash, priorReason, warnings, dirPath, dedupedRows) {
|
|
106
|
+
if (dedupedRows > 0)
|
|
107
|
+
return "deduped-zero-row";
|
|
108
|
+
const workflowNoise = warnings.some((warning) => warning.startsWith("Skipped workflow ") && warning.includes(dirPath));
|
|
109
|
+
if (workflowNoise)
|
|
110
|
+
return "workflow-noise";
|
|
111
|
+
if (!stash || stash.entries.length === 0)
|
|
112
|
+
return "empty-generated-set";
|
|
113
|
+
return `zero-row:${priorReason?.kind ?? "unknown"}`;
|
|
114
|
+
}
|
|
@@ -72,6 +72,7 @@ const FM_CAPTURE_MODE = "captureMode";
|
|
|
72
72
|
*/
|
|
73
73
|
export async function runMemoryInferencePass(ctx) {
|
|
74
74
|
const { config, sources, signal, db, reEnrich, onProgress, options = {} } = ctx;
|
|
75
|
+
const compressMemoryToDerivedMemory = options.compressMemoryToDerivedMemory ?? memoryInfer.compressMemoryToDerivedMemory;
|
|
75
76
|
const result = {
|
|
76
77
|
considered: 0,
|
|
77
78
|
cacheHits: 0,
|
|
@@ -172,14 +173,14 @@ export async function runMemoryInferencePass(ctx) {
|
|
|
172
173
|
retryAttempts += 1;
|
|
173
174
|
};
|
|
174
175
|
const derived = db
|
|
175
|
-
? await withLlmCache(db, record.filePath, record.body, reEnrich ?? false, () =>
|
|
176
|
+
? await withLlmCache(db, record.filePath, record.body, reEnrich ?? false, () => compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
|
|
176
177
|
warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
|
|
177
178
|
}, inferTelemetry, onRetryAttempt), validate, undefined, "", {
|
|
178
179
|
onCacheHit: () => {
|
|
179
180
|
fromCache = true;
|
|
180
181
|
},
|
|
181
182
|
})
|
|
182
|
-
: await
|
|
183
|
+
: await compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
|
|
183
184
|
warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
|
|
184
185
|
}, inferTelemetry, onRetryAttempt);
|
|
185
186
|
if (!derived) {
|
|
@@ -425,10 +426,17 @@ function markParentProcessed(parent) {
|
|
|
425
426
|
warn(`memory inference: failed to re-read parent ${parent.filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
426
427
|
return;
|
|
427
428
|
}
|
|
428
|
-
const updatedFm = { ...parent.data, [FM_INFERENCE_PROCESSED]: true };
|
|
429
429
|
const block = parseFrontmatterBlock(raw);
|
|
430
|
-
|
|
431
|
-
|
|
430
|
+
if (!block) {
|
|
431
|
+
// Cannot safely rewrite malformed frontmatter — skip marking so the memory
|
|
432
|
+
// is retried on the next run once the frontmatter is repaired. Writing with
|
|
433
|
+
// `body = raw` would wrap the entire file (including the bad frontmatter)
|
|
434
|
+
// in a new block, producing a duplicate-frontmatter corruption.
|
|
435
|
+
warn(`memory inference: skipping markParentProcessed for ${parent.filePath} — could not parse frontmatter block`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const updatedFm = { ...parent.data, [FM_INFERENCE_PROCESSED]: true };
|
|
439
|
+
const next = assembleAsset(updatedFm, block.content);
|
|
432
440
|
try {
|
|
433
441
|
fs.writeFileSync(parent.filePath, next, "utf8");
|
|
434
442
|
}
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
import { createHash } from "node:crypto";
|
|
45
45
|
import fs from "node:fs";
|
|
46
46
|
import path from "node:path";
|
|
47
|
+
import stalenessDetectSystemPrompt from "../../assets/prompts/staleness-detect-system.md" with { type: "text" };
|
|
47
48
|
import { assembleAsset } from "../../core/asset/asset-serialize.js";
|
|
48
49
|
import { parseFrontmatter, parseFrontmatterBlock } from "../../core/asset/frontmatter.js";
|
|
49
50
|
import { concurrentMap } from "../../core/concurrent.js";
|
|
@@ -319,11 +320,7 @@ function pickSimilar(candidate, all) {
|
|
|
319
320
|
return scored.slice(0, TOP_K_SIMILAR).map((s) => s.snap);
|
|
320
321
|
}
|
|
321
322
|
// ── LLM dispatch ────────────────────────────────────────────────────────────
|
|
322
|
-
const SYSTEM_PROMPT =
|
|
323
|
-
"Respond on the first line with exactly YES or NO.\n" +
|
|
324
|
-
"If YES, the second line MUST be of the form `SUPERSEDED_BY: <ref>` where <ref> is the exact ref of the superseding memory from the list provided. Do NOT invent refs.\n" +
|
|
325
|
-
"If NO, do not include any additional lines.\n" +
|
|
326
|
-
"No prose, no preamble, no markdown.";
|
|
323
|
+
const SYSTEM_PROMPT = stalenessDetectSystemPrompt;
|
|
327
324
|
async function askValidator(connection, candidate, allMemories, signal, timeoutMs) {
|
|
328
325
|
const similar = pickSimilar(candidate, allMemories);
|
|
329
326
|
if (similar.length === 0) {
|
|
@@ -65,6 +65,9 @@ export async function searchLocal(input) {
|
|
|
65
65
|
const includeProposed = input.includeProposed === true;
|
|
66
66
|
const beliefFilter = input.beliefFilter ?? "all";
|
|
67
67
|
const restrictToSources = input.restrictToSources === true;
|
|
68
|
+
const includeExcludedTypes = input.includeExcludedTypes === true;
|
|
69
|
+
const disableProjectContext = input.disableProjectContext === true;
|
|
70
|
+
const disableScopedUtility = input.disableScopedUtility === true;
|
|
68
71
|
const rendererRegistry = input.rendererRegistry ?? defaultRendererRegistry;
|
|
69
72
|
const allSourceDirs = sources.map((s) => s.path);
|
|
70
73
|
const rawStatus = readSemanticStatus();
|
|
@@ -114,7 +117,7 @@ export async function searchLocal(input) {
|
|
|
114
117
|
mode: "keyword",
|
|
115
118
|
};
|
|
116
119
|
}
|
|
117
|
-
const { hits, embedMs, rankMs } = await searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry, filters, includeProposed, beliefFilter, restrictToSources);
|
|
120
|
+
const { hits, embedMs, rankMs } = await searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry, filters, includeProposed, beliefFilter, restrictToSources, includeExcludedTypes, disableProjectContext, disableScopedUtility);
|
|
118
121
|
return {
|
|
119
122
|
hits,
|
|
120
123
|
tip: hits.length === 0
|
|
@@ -131,14 +134,19 @@ export async function searchLocal(input) {
|
|
|
131
134
|
}
|
|
132
135
|
}
|
|
133
136
|
// ── Database search ─────────────────────────────────────────────────────────
|
|
134
|
-
async function searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry = defaultRendererRegistry, filters, includeProposed = false, beliefFilter = "all", restrictToSources = false) {
|
|
137
|
+
async function searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry = defaultRendererRegistry, filters, includeProposed = false, beliefFilter = "all", restrictToSources = false, includeExcludedTypes = false, disableProjectContext = false, disableScopedUtility = false) {
|
|
135
138
|
const hasSearchableTokens = query.length > 0 && sanitizeFtsQuery(query).length > 0;
|
|
139
|
+
// #627 — resolve the default type-exclusion policy. It applies ONLY on the
|
|
140
|
+
// untyped ('any') path and only when the caller did not opt back in via
|
|
141
|
+
// `includeExcludedTypes`. When the config key is ABSENT a built-in default of
|
|
142
|
+
// ['session'] is applied; an explicit empty list disables exclusion.
|
|
143
|
+
const defaultExcludes = searchType === "any" && !includeExcludedTypes ? (config.search?.defaultExcludeTypes ?? ["session"]) : [];
|
|
136
144
|
// Empty queries — including ones that sanitize down to no searchable FTS
|
|
137
145
|
// tokens such as "." — should enumerate matching entries instead of
|
|
138
146
|
// returning an empty result set from FTS.
|
|
139
147
|
if (!hasSearchableTokens) {
|
|
140
148
|
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
141
|
-
const allEntries = getAllEntries(db, typeFilter);
|
|
149
|
+
const allEntries = getAllEntries(db, typeFilter, defaultExcludes);
|
|
142
150
|
// Deduplicate by file path — multiple entries can share the same file
|
|
143
151
|
const seenFilePaths = new Set();
|
|
144
152
|
const uniqueEntries = allEntries.filter((ie) => {
|
|
@@ -187,7 +195,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
187
195
|
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
188
196
|
const tEmbed0 = Date.now();
|
|
189
197
|
const embeddingPromise = tryVecScores(db, query, limit * 3, config);
|
|
190
|
-
const ftsResults = searchFts(db, query, limit * 3, typeFilter);
|
|
198
|
+
const ftsResults = searchFts(db, query, limit * 3, typeFilter, defaultExcludes);
|
|
191
199
|
const embeddingScores = await embeddingPromise;
|
|
192
200
|
const embedMs = Date.now() - tEmbed0;
|
|
193
201
|
const tRank0 = Date.now();
|
|
@@ -208,6 +216,11 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
208
216
|
embedScoreMap,
|
|
209
217
|
getEntryById: (id) => getEntryById(db, id) ?? undefined,
|
|
210
218
|
typeFilter,
|
|
219
|
+
// #627 — also exclude default-hidden types from the vector-only branch so a
|
|
220
|
+
// session asset that is a top-k vector neighbor (but not an FTS match) does
|
|
221
|
+
// not leak into default ('any') results. defaultExcludes is already []
|
|
222
|
+
// unless this is the untyped path without includeExcludedTypes.
|
|
223
|
+
excludeTypes: defaultExcludes,
|
|
211
224
|
});
|
|
212
225
|
// ── Scoring Phase ──────────────────────────────────────────────────────
|
|
213
226
|
// Apply boosts as multiplicative factors (all boosts in a single phase
|
|
@@ -236,7 +249,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
236
249
|
// Resolve project-context tokens from the current working directory once
|
|
237
250
|
// per search invocation. Returns null when running from home dir / /tmp,
|
|
238
251
|
// or when the caller has set AKM_DISABLE_PROJECT_CONTEXT=1.
|
|
239
|
-
const projectContext =
|
|
252
|
+
const projectContext = disableProjectContext ? null : resolveProjectContext(process.cwd());
|
|
240
253
|
// Phase 2A / Rec 5: resolve forgetting-curve config and skip the feedback
|
|
241
254
|
// count query when the boost cannot make a difference (default ≤ 1.0 means
|
|
242
255
|
// boost^count == 1 — zero overhead for the common case).
|
|
@@ -256,7 +269,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
256
269
|
// AKM_DISABLE_SCOPED_UTILITY=1 opts out (e.g. for registry searches or tests).
|
|
257
270
|
let scopeKey;
|
|
258
271
|
try {
|
|
259
|
-
scopeKey =
|
|
272
|
+
scopeKey = disableScopedUtility ? undefined : getCurrentWorkflowScopeKey();
|
|
260
273
|
}
|
|
261
274
|
catch {
|
|
262
275
|
// Non-fatal — ranking proceeds without scoped utility on any error.
|