akm-cli 0.9.0-beta.6 → 0.9.0-rc.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/CHANGELOG.md +663 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- 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/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- 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 +11 -3
- 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 +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -14
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +220 -227
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +153 -147
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1083
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -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 +1313 -1278
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +243 -599
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +55 -4
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +160 -7
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +32 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +510 -2537
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +212 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -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/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +7 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -882
- package/dist/commands/read/curate.js +419 -103
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +29 -41
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +15 -1
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +21 -4
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +389 -58
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +21 -18
- 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 +770 -0
- package/dist/core/state-db.js +142 -1091
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +356 -780
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +103 -24
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +106 -0
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +291 -310
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +39 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +7 -2
- package/dist/integrations/agent/profiles.js +7 -1
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +69 -67
- package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
- package/dist/integrations/harnesses/claude/index.js +2 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +2 -0
- 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 +98 -17
- package/dist/integrations/harnesses/types.js +1 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +57 -15
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- 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 +85 -14
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/text/helpers.js +29 -1
- package/dist/output/text/workflow.js +1 -0
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2718 -2354
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +891 -597
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- 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/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- 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 +15 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/cli.js +1 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +43 -118
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +5 -4
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
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";
|
|
@@ -116,7 +118,7 @@ async function runWalkPhase(ctx) {
|
|
|
116
118
|
ctx.timing.tWalkEnd = Date.now();
|
|
117
119
|
throwIfAborted(signal);
|
|
118
120
|
// LLM enrichment for directories that need it
|
|
119
|
-
await enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal,
|
|
121
|
+
await enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, reEnrich);
|
|
120
122
|
onProgress({
|
|
121
123
|
phase: "llm",
|
|
122
124
|
message: resolveIndexPassLLM("enrichment", config)
|
|
@@ -141,6 +143,7 @@ async function runEmbeddingPhase(ctx) {
|
|
|
141
143
|
*/
|
|
142
144
|
async function runFinalizePhase(ctx) {
|
|
143
145
|
const { db, config, sources, sourceDirs, isIncremental, stashDir, signal, onProgress } = ctx;
|
|
146
|
+
ctx.timing.tFinalizeStart = Date.now();
|
|
144
147
|
// Rebuild FTS after all inserts. Use incremental mode when this whole
|
|
145
148
|
// index run is incremental — only entries touched by `upsertEntry`
|
|
146
149
|
// since the last rebuild are re-indexed.
|
|
@@ -151,10 +154,13 @@ async function runFinalizePhase(ctx) {
|
|
|
151
154
|
});
|
|
152
155
|
ctx.timing.tFtsEnd = Date.now();
|
|
153
156
|
// Re-link detached usage_events and recompute utility scores.
|
|
157
|
+
onProgress({ phase: "finalize", message: "Relinking usage events." });
|
|
154
158
|
relinkUsageEvents(db);
|
|
159
|
+
onProgress({ phase: "finalize", message: "Recomputing utility scores." });
|
|
155
160
|
recomputeUtilityScores(db);
|
|
156
161
|
// Purge LLM cache entries for assets that no longer exist in the index.
|
|
157
162
|
try {
|
|
163
|
+
onProgress({ phase: "finalize", message: "Clearing stale LLM cache entries." });
|
|
158
164
|
clearStaleCacheEntries(db);
|
|
159
165
|
}
|
|
160
166
|
catch {
|
|
@@ -162,6 +168,7 @@ async function runFinalizePhase(ctx) {
|
|
|
162
168
|
}
|
|
163
169
|
// Regenerate each wiki's index.md from its pages' frontmatter. Best-effort.
|
|
164
170
|
try {
|
|
171
|
+
onProgress({ phase: "finalize", message: "Regenerating wiki indexes." });
|
|
165
172
|
const { regenerateAllWikiIndexes } = await import("../wiki/wiki.js");
|
|
166
173
|
regenerateAllWikiIndexes(stashDir);
|
|
167
174
|
}
|
|
@@ -178,6 +185,7 @@ async function runFinalizePhase(ctx) {
|
|
|
178
185
|
warnIfVecMissing(db);
|
|
179
186
|
const totalEntries = getEntryCount(db);
|
|
180
187
|
const semanticEntryCount = getEmbeddableEntryCount(db);
|
|
188
|
+
onProgress({ phase: "finalize", message: "Verifying semantic search state." });
|
|
181
189
|
const verification = verifyIndexState(db, config, semanticEntryCount, embeddingResult);
|
|
182
190
|
if (config.semanticSearchMode === "off") {
|
|
183
191
|
clearSemanticStatus();
|
|
@@ -195,8 +203,9 @@ async function runFinalizePhase(ctx) {
|
|
|
195
203
|
}
|
|
196
204
|
onProgress({ phase: "verify", message: verification.message });
|
|
197
205
|
// Store verification result and totalEntries on ctx for the caller to use
|
|
198
|
-
ctx.
|
|
199
|
-
ctx.
|
|
206
|
+
ctx.verification = verification;
|
|
207
|
+
ctx.totalEntries = totalEntries;
|
|
208
|
+
ctx.timing.tFinalizeEnd = Date.now();
|
|
200
209
|
// suppress unused warning — sources was previously used inline
|
|
201
210
|
void sources;
|
|
202
211
|
}
|
|
@@ -224,129 +233,186 @@ function runCleanPass(db, dryRun) {
|
|
|
224
233
|
};
|
|
225
234
|
}
|
|
226
235
|
// ── Indexer ──────────────────────────────────────────────────────────────────
|
|
236
|
+
// ── Test seam ────────────────────────────────────────────────────────────────
|
|
237
|
+
// Swap-and-restore override. Inert in production; only tests call the setter.
|
|
238
|
+
let akmIndexOverride;
|
|
239
|
+
/** TEST-ONLY. Swap the implementation of `akmIndex`; pass undefined to restore. */
|
|
240
|
+
export function _setAkmIndexForTests(fake) {
|
|
241
|
+
akmIndexOverride = fake;
|
|
242
|
+
}
|
|
227
243
|
export async function akmIndex(options) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
//
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
t0,
|
|
274
|
-
tWalkStart: t0,
|
|
275
|
-
tWalkEnd: t0,
|
|
276
|
-
tLlmEnd: t0,
|
|
277
|
-
tFtsEnd: t0,
|
|
278
|
-
tEmbedEnd: t0,
|
|
279
|
-
},
|
|
280
|
-
isIncremental,
|
|
281
|
-
builtAtMs,
|
|
282
|
-
hadRemovedSources: false,
|
|
283
|
-
scannedDirs: 0,
|
|
284
|
-
skippedDirs: 0,
|
|
285
|
-
generatedCount: 0,
|
|
286
|
-
walkWarnings: [],
|
|
287
|
-
dirsNeedingLlm: [],
|
|
288
|
-
embeddingResult: null,
|
|
289
|
-
graphExtractionResult: null,
|
|
290
|
-
};
|
|
244
|
+
if (akmIndexOverride)
|
|
245
|
+
return akmIndexOverride(options);
|
|
246
|
+
return akmIndexReal(options);
|
|
247
|
+
}
|
|
248
|
+
async function akmIndexReal(options) {
|
|
249
|
+
const requestedAt = Date.now();
|
|
250
|
+
let acquiredAt = requestedAt;
|
|
251
|
+
return withIndexWriterLease({
|
|
252
|
+
purpose: "akm-index",
|
|
253
|
+
signal: options?.signal,
|
|
254
|
+
onWait: ({ waitedMs }) => {
|
|
255
|
+
options?.onProgress?.({
|
|
256
|
+
phase: "preflight",
|
|
257
|
+
message: `Waiting for index writer lease (${Math.round(waitedMs / 1000)}s elapsed).`,
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
onAcquired: ({ waitedMs }) => {
|
|
261
|
+
acquiredAt = requestedAt + waitedMs;
|
|
262
|
+
},
|
|
263
|
+
}, async () => {
|
|
264
|
+
const stashDir = options?.stashDir || resolveStashDir();
|
|
265
|
+
const onProgress = options?.onProgress ?? (() => { });
|
|
266
|
+
const signal = options?.signal;
|
|
267
|
+
const reEnrich = options?.reEnrich === true;
|
|
268
|
+
const full = options?.full === true;
|
|
269
|
+
const clean = options?.clean === true;
|
|
270
|
+
const dryRun = options?.dryRun === true;
|
|
271
|
+
// Load config and resolve all stash sources
|
|
272
|
+
const { loadConfig } = await import("../core/config/config.js");
|
|
273
|
+
const config = loadConfig();
|
|
274
|
+
// One-time, read-only guard: warn if the writable stash still holds an
|
|
275
|
+
// un-migrated `vaults/` directory. In 0.9.0 the indexer skips `vaults/`
|
|
276
|
+
// entirely, so an unmigrated vault's `.env` data would silently never be
|
|
277
|
+
// indexed. Non-destructive — only stats, never reads/writes/deletes.
|
|
278
|
+
const { warnOnUnmigratedVaults } = await import("./usage/unmigrated-vaults-guard.js");
|
|
279
|
+
warnOnUnmigratedVaults(stashDir);
|
|
280
|
+
// Ensure git stash caches are extracted before resolving stash dirs,
|
|
281
|
+
// so their content directories exist on disk for the walker to discover.
|
|
282
|
+
const sourceCacheStart = Date.now();
|
|
283
|
+
onProgress({ phase: "preflight", message: "Hydrating source caches." });
|
|
284
|
+
const { ensureSourceCaches, resolveSourceEntries } = await import("./search/search-source.js");
|
|
285
|
+
await ensureSourceCaches(config, { force: full });
|
|
286
|
+
const sourceCacheEnd = Date.now();
|
|
287
|
+
const allSourceEntries = resolveSourceEntries(stashDir, config);
|
|
288
|
+
const allSourceDirs = allSourceEntries.map((s) => s.path);
|
|
291
289
|
onProgress({
|
|
292
|
-
phase: "
|
|
293
|
-
message:
|
|
294
|
-
mode: isIncremental ? "incremental" : "full",
|
|
295
|
-
sourcesCount: allSourceDirs.length,
|
|
296
|
-
semanticSearchMode: config.semanticSearchMode,
|
|
297
|
-
embeddingProvider: getEmbeddingProvider(config.embedding),
|
|
298
|
-
llmEnabled: !!resolveIndexPassLLM("enrichment", config),
|
|
299
|
-
vecAvailable: isVecAvailable(db),
|
|
300
|
-
}),
|
|
290
|
+
phase: "preflight",
|
|
291
|
+
message: `Resolved ${allSourceDirs.length} stash source${allSourceDirs.length === 1 ? "" : "s"}.`,
|
|
301
292
|
});
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
293
|
+
const t0 = Date.now();
|
|
294
|
+
// Open database — pass embedding dimension from config if available
|
|
295
|
+
const dbPath = getDbPath();
|
|
296
|
+
const embeddingDim = config.embedding?.dimension;
|
|
297
|
+
const db = openIndexDatabase(dbPath, embeddingDim ? { embeddingDim } : undefined);
|
|
298
|
+
try {
|
|
299
|
+
// Determine incremental vs full mode
|
|
300
|
+
const prevStashDir = getMeta(db, "stashDir");
|
|
301
|
+
const prevBuiltAt = getMeta(db, "builtAt");
|
|
302
|
+
const isIncremental = !full && prevStashDir === stashDir && !!prevBuiltAt;
|
|
303
|
+
const builtAtMs = isIncremental && prevBuiltAt ? new Date(prevBuiltAt).getTime() : 0;
|
|
304
|
+
// Assemble the run context
|
|
305
|
+
const ctx = {
|
|
306
|
+
db,
|
|
307
|
+
config,
|
|
308
|
+
sources: allSourceEntries,
|
|
309
|
+
sourceDirs: allSourceDirs,
|
|
310
|
+
full,
|
|
311
|
+
reEnrich,
|
|
312
|
+
stashDir,
|
|
313
|
+
onProgress,
|
|
314
|
+
signal,
|
|
315
|
+
timing: {
|
|
316
|
+
t0,
|
|
317
|
+
tWalkStart: t0,
|
|
318
|
+
tWalkEnd: t0,
|
|
319
|
+
tLlmEnd: t0,
|
|
320
|
+
tFtsEnd: t0,
|
|
321
|
+
tEmbedEnd: t0,
|
|
322
|
+
tFinalizeStart: t0,
|
|
323
|
+
tFinalizeEnd: t0,
|
|
324
|
+
},
|
|
325
|
+
isIncremental,
|
|
326
|
+
builtAtMs,
|
|
327
|
+
hadRemovedSources: false,
|
|
328
|
+
scannedDirs: 0,
|
|
329
|
+
skippedDirs: 0,
|
|
330
|
+
generatedCount: 0,
|
|
331
|
+
walkWarnings: [],
|
|
332
|
+
dirsNeedingLlm: [],
|
|
333
|
+
embeddingResult: null,
|
|
334
|
+
};
|
|
335
|
+
onProgress({
|
|
336
|
+
phase: "summary",
|
|
337
|
+
message: buildIndexSummaryMessage({
|
|
338
|
+
mode: isIncremental ? "incremental" : "full",
|
|
339
|
+
sourcesCount: allSourceDirs.length,
|
|
340
|
+
semanticSearchMode: config.semanticSearchMode,
|
|
341
|
+
embeddingProvider: getEmbeddingProvider(config.embedding),
|
|
342
|
+
llmEnabled: !!resolveIndexPassLLM("enrichment", config),
|
|
343
|
+
vecAvailable: isVecAvailable(db),
|
|
344
|
+
}),
|
|
345
|
+
});
|
|
346
|
+
// ── Phase sequence ───────────────────────────────────────────────────────
|
|
347
|
+
await runSourceCachePhase(ctx);
|
|
348
|
+
await runWalkPhase(ctx);
|
|
349
|
+
await runEmbeddingPhase(ctx);
|
|
350
|
+
await runFinalizePhase(ctx);
|
|
351
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
352
|
+
// runFinalizePhase always populates these before returning.
|
|
353
|
+
const verification = ctx.verification;
|
|
354
|
+
const totalEntries = ctx.totalEntries;
|
|
355
|
+
const { timing } = ctx;
|
|
356
|
+
// ── Clean pass ───────────────────────────────────────────────────────────
|
|
357
|
+
// After the normal index completes, remove entries whose source files no
|
|
358
|
+
// longer exist on disk. Remote entries (empty file_path) are skipped.
|
|
359
|
+
let cleanResult;
|
|
360
|
+
const cleanStart = Date.now();
|
|
361
|
+
if (clean) {
|
|
362
|
+
onProgress({
|
|
363
|
+
phase: "finalize",
|
|
364
|
+
message: dryRun ? "Scanning for stale index entries (dry run)." : "Removing stale index entries.",
|
|
365
|
+
});
|
|
366
|
+
cleanResult = runCleanPass(db, dryRun);
|
|
367
|
+
}
|
|
368
|
+
const cleanEnd = Date.now();
|
|
369
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
370
|
+
return {
|
|
371
|
+
stashDir,
|
|
372
|
+
totalEntries,
|
|
373
|
+
generatedMetadata: ctx.generatedCount,
|
|
374
|
+
indexPath: dbPath,
|
|
375
|
+
mode: isIncremental ? "incremental" : "full",
|
|
376
|
+
directoriesScanned: ctx.scannedDirs,
|
|
377
|
+
directoriesSkipped: ctx.skippedDirs,
|
|
378
|
+
...(ctx.walkWarnings.length > 0 ? { warnings: ctx.walkWarnings } : {}),
|
|
379
|
+
verification,
|
|
380
|
+
timing: {
|
|
381
|
+
totalMs: Date.now() - timing.t0,
|
|
382
|
+
walkMs: timing.tWalkEnd - timing.tWalkStart,
|
|
383
|
+
llmMs: timing.tLlmEnd - timing.tWalkEnd,
|
|
384
|
+
embedMs: timing.tEmbedEnd - timing.tLlmEnd,
|
|
385
|
+
ftsMs: timing.tFtsEnd - timing.tEmbedEnd,
|
|
386
|
+
finalizeMs: timing.tFinalizeEnd - timing.tFinalizeStart,
|
|
387
|
+
cleanMs: clean ? cleanEnd - cleanStart : 0,
|
|
388
|
+
preflightMs: timing.t0 - requestedAt,
|
|
389
|
+
leaseWaitMs: acquiredAt - requestedAt,
|
|
390
|
+
sourceCacheMs: sourceCacheEnd - sourceCacheStart,
|
|
391
|
+
endToEndMs: Date.now() - requestedAt,
|
|
392
|
+
},
|
|
393
|
+
...(cleanResult !== undefined ? { clean: cleanResult } : {}),
|
|
394
|
+
};
|
|
316
395
|
}
|
|
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
|
-
}
|
|
396
|
+
finally {
|
|
397
|
+
closeDatabase(db);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
341
400
|
}
|
|
342
|
-
|
|
343
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Phase 1 (async): walk every source directory and pre-generate all metadata
|
|
403
|
+
* outside any transaction, producing the per-directory scan records that
|
|
404
|
+
* {@link persistDirRecords} later writes.
|
|
405
|
+
*
|
|
406
|
+
* generateMetadataFlat is async (uses dynamic import for the matcher/renderer
|
|
407
|
+
* registry), so it cannot run inside a db.transaction() callback — hence the
|
|
408
|
+
* split into a pure-ish scan pass and a synchronous persist pass.
|
|
409
|
+
*/
|
|
410
|
+
async function scanSourceDirs(db, allSourceEntries, isIncremental, builtAtMs, hadRemovedSources, onProgress) {
|
|
344
411
|
let scannedDirs = 0;
|
|
345
412
|
let skippedDirs = 0;
|
|
346
413
|
let generatedCount = 0;
|
|
347
414
|
const warnings = [];
|
|
348
415
|
const seenPaths = new Set();
|
|
349
|
-
const dirsNeedingLlm = [];
|
|
350
416
|
const dirRecords = [];
|
|
351
417
|
let processedDirs = 0;
|
|
352
418
|
let priorDirsChanged = hadRemovedSources;
|
|
@@ -366,6 +432,53 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
|
|
|
366
432
|
reportScanProgress(`${kind === "scan" ? "Rescanning" : "Skipping"} ${path.relative(currentStashDir, dirPath) || "."} ` +
|
|
367
433
|
`from ${currentStashDir}: ${reason.kind}${detail}${rowInfo}`);
|
|
368
434
|
};
|
|
435
|
+
// Duplicate-directory guard shared by the wiki-root and normal branches. A
|
|
436
|
+
// dir may surface via multiple stash roots; only the first occurrence is
|
|
437
|
+
// indexed, and the later ones are recorded as skips. Returns true when the
|
|
438
|
+
// dir was already seen (caller should skip further processing).
|
|
439
|
+
const markSeenOrSkipDuplicate = (dirPath, currentStashDir, files) => {
|
|
440
|
+
const resolved = path.resolve(dirPath);
|
|
441
|
+
if (seenPaths.has(resolved)) {
|
|
442
|
+
const reason = { kind: "duplicate-dir" };
|
|
443
|
+
dirRecords.push({ dirPath, currentStashDir, files, stash: null, skip: true, reason });
|
|
444
|
+
reportDirDecision("skip", dirPath, currentStashDir, reason);
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
seenPaths.add(resolved);
|
|
448
|
+
return false;
|
|
449
|
+
};
|
|
450
|
+
// Incremental freshness gate shared by both branches: consult the persisted
|
|
451
|
+
// dir state and record either a skip (unchanged + eligible for incremental
|
|
452
|
+
// skip) or a scan record carrying the candidate stash.
|
|
453
|
+
const recordFreshnessDecision = (dirPath, currentStashDir, stateFiles, stash) => {
|
|
454
|
+
const previousState = getDirIndexState(db, dirPath, stateFiles, builtAtMs);
|
|
455
|
+
if (isIncremental && !previousState.stale && canUseIncrementalSkip(previousState, priorDirsChanged)) {
|
|
456
|
+
skippedDirs++;
|
|
457
|
+
dirRecords.push({
|
|
458
|
+
dirPath,
|
|
459
|
+
currentStashDir,
|
|
460
|
+
files: stateFiles,
|
|
461
|
+
stash: null,
|
|
462
|
+
skip: true,
|
|
463
|
+
reason: previousState.reason,
|
|
464
|
+
});
|
|
465
|
+
reportDirDecision("skip", dirPath, currentStashDir, previousState.reason, previousState.persistedRowCount);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
scannedDirs++;
|
|
469
|
+
priorDirsChanged = true;
|
|
470
|
+
const reason = isIncremental ? previousState.reason : { kind: "full-rebuild" };
|
|
471
|
+
dirRecords.push({
|
|
472
|
+
dirPath,
|
|
473
|
+
currentStashDir,
|
|
474
|
+
files: stateFiles,
|
|
475
|
+
stash,
|
|
476
|
+
skip: false,
|
|
477
|
+
reason,
|
|
478
|
+
persistedRowCount: previousState.persistedRowCount,
|
|
479
|
+
});
|
|
480
|
+
reportDirDecision("scan", dirPath, currentStashDir, reason, previousState.persistedRowCount);
|
|
481
|
+
};
|
|
369
482
|
for (const sourceAdded of allSourceEntries) {
|
|
370
483
|
const currentStashDir = sourceAdded.path;
|
|
371
484
|
const fileContexts = walkStashFlat(currentStashDir);
|
|
@@ -403,33 +516,9 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
|
|
|
403
516
|
}
|
|
404
517
|
}
|
|
405
518
|
for (const [dirPath, { files, entries }] of wikiDirGroups) {
|
|
406
|
-
if (
|
|
407
|
-
const reason = { kind: "duplicate-dir" };
|
|
408
|
-
dirRecords.push({ dirPath, currentStashDir, files, stash: null, skip: true, reason });
|
|
409
|
-
reportDirDecision("skip", dirPath, currentStashDir, reason);
|
|
519
|
+
if (markSeenOrSkipDuplicate(dirPath, currentStashDir, files))
|
|
410
520
|
continue;
|
|
411
|
-
}
|
|
412
|
-
seenPaths.add(path.resolve(dirPath));
|
|
413
|
-
const previousState = getDirIndexState(db, dirPath, files, builtAtMs);
|
|
414
|
-
if (isIncremental && !previousState.stale && canUseIncrementalSkip(previousState, priorDirsChanged)) {
|
|
415
|
-
skippedDirs++;
|
|
416
|
-
dirRecords.push({ dirPath, currentStashDir, files, stash: null, skip: true, reason: previousState.reason });
|
|
417
|
-
reportDirDecision("skip", dirPath, currentStashDir, previousState.reason, previousState.persistedRowCount);
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
scannedDirs++;
|
|
421
|
-
priorDirsChanged = true;
|
|
422
|
-
const reason = isIncremental ? previousState.reason : { kind: "full-rebuild" };
|
|
423
|
-
dirRecords.push({
|
|
424
|
-
dirPath,
|
|
425
|
-
currentStashDir,
|
|
426
|
-
files,
|
|
427
|
-
stash: { entries },
|
|
428
|
-
skip: false,
|
|
429
|
-
reason,
|
|
430
|
-
persistedRowCount: previousState.persistedRowCount,
|
|
431
|
-
});
|
|
432
|
-
reportDirDecision("scan", dirPath, currentStashDir, reason, previousState.persistedRowCount);
|
|
521
|
+
recordFreshnessDecision(dirPath, currentStashDir, files, { entries });
|
|
433
522
|
}
|
|
434
523
|
continue;
|
|
435
524
|
}
|
|
@@ -444,13 +533,8 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
|
|
|
444
533
|
}
|
|
445
534
|
for (const [dirPath, files] of dirGroups) {
|
|
446
535
|
const indexableFiles = files.filter((file) => shouldIndexStashFile(currentStashDir, file));
|
|
447
|
-
if (
|
|
448
|
-
const reason = { kind: "duplicate-dir" };
|
|
449
|
-
dirRecords.push({ dirPath, currentStashDir, files: indexableFiles, stash: null, skip: true, reason });
|
|
450
|
-
reportDirDecision("skip", dirPath, currentStashDir, reason);
|
|
536
|
+
if (markSeenOrSkipDuplicate(dirPath, currentStashDir, indexableFiles))
|
|
451
537
|
continue;
|
|
452
|
-
}
|
|
453
|
-
seenPaths.add(path.resolve(dirPath));
|
|
454
538
|
if (indexableFiles.length === 0) {
|
|
455
539
|
skippedDirs++;
|
|
456
540
|
const reason = { kind: "no-indexable-files" };
|
|
@@ -480,37 +564,17 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
|
|
|
480
564
|
if (generated.entries.length > 0) {
|
|
481
565
|
generatedCount += generated.entries.length;
|
|
482
566
|
}
|
|
483
|
-
|
|
484
|
-
if (isIncremental && !previousState.stale && canUseIncrementalSkip(previousState, priorDirsChanged)) {
|
|
485
|
-
skippedDirs++;
|
|
486
|
-
dirRecords.push({
|
|
487
|
-
dirPath,
|
|
488
|
-
currentStashDir,
|
|
489
|
-
files: staleFiles,
|
|
490
|
-
stash: null,
|
|
491
|
-
skip: true,
|
|
492
|
-
reason: previousState.reason,
|
|
493
|
-
});
|
|
494
|
-
reportDirDecision("skip", dirPath, currentStashDir, previousState.reason, previousState.persistedRowCount);
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
scannedDirs++;
|
|
498
|
-
priorDirsChanged = true;
|
|
499
|
-
const reason = isIncremental ? previousState.reason : { kind: "full-rebuild" };
|
|
500
|
-
dirRecords.push({
|
|
501
|
-
dirPath,
|
|
502
|
-
currentStashDir,
|
|
503
|
-
files: staleFiles,
|
|
504
|
-
stash,
|
|
505
|
-
skip: false,
|
|
506
|
-
reason,
|
|
507
|
-
persistedRowCount: previousState.persistedRowCount,
|
|
508
|
-
});
|
|
509
|
-
reportDirDecision("scan", dirPath, currentStashDir, reason, previousState.persistedRowCount);
|
|
567
|
+
recordFreshnessDecision(dirPath, currentStashDir, staleFiles, stash);
|
|
510
568
|
}
|
|
511
569
|
}
|
|
512
|
-
|
|
513
|
-
|
|
570
|
+
return { dirRecords, scannedDirs, skippedDirs, generatedCount, warnings };
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Phase 2 (sync): write all pre-generated scan records inside a single
|
|
574
|
+
* transaction, returning the directories that still need LLM enrichment.
|
|
575
|
+
*/
|
|
576
|
+
function persistDirRecords(db, dirRecords, doFullDelete, warnings) {
|
|
577
|
+
const dirsNeedingLlm = [];
|
|
514
578
|
// Cross-stash dedup: track indexed assets by content identity
|
|
515
579
|
// (type + filename + description) so the same asset from a lower-priority
|
|
516
580
|
// stash root is skipped when a higher-priority root already covers it.
|
|
@@ -632,101 +696,17 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
|
|
|
632
696
|
}
|
|
633
697
|
});
|
|
634
698
|
insertTransaction();
|
|
635
|
-
return {
|
|
636
|
-
}
|
|
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;
|
|
699
|
+
return { dirsNeedingLlm };
|
|
718
700
|
}
|
|
719
|
-
function
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
return "empty-generated-set";
|
|
727
|
-
return `zero-row:${priorReason?.kind ?? "unknown"}`;
|
|
701
|
+
async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadRemovedSources, doFullDelete = false, onProgress) {
|
|
702
|
+
// Phase 1 (async): walk directories and pre-generate all metadata outside the
|
|
703
|
+
// transaction.
|
|
704
|
+
const { dirRecords, scannedDirs, skippedDirs, generatedCount, warnings } = await scanSourceDirs(db, allSourceEntries, isIncremental, builtAtMs, hadRemovedSources, onProgress);
|
|
705
|
+
// Phase 2 (sync): write all pre-generated metadata inside a single transaction.
|
|
706
|
+
const { dirsNeedingLlm } = persistDirRecords(db, dirRecords, doFullDelete, warnings);
|
|
707
|
+
return { scannedDirs, skippedDirs, generatedCount, warnings, dirsNeedingLlm };
|
|
728
708
|
}
|
|
729
|
-
async function enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal,
|
|
709
|
+
async function enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, reEnrich = false) {
|
|
730
710
|
// Resolve per-pass LLM config via the unified shim. Returns undefined when
|
|
731
711
|
// either no `akm.llm` is configured or the user opted this pass out via
|
|
732
712
|
// `index.enrichment.llm = false`. (#208)
|
|
@@ -896,21 +876,9 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal) {
|
|
|
896
876
|
const currentFingerprint = deriveSemanticProviderFingerprint(config.embedding);
|
|
897
877
|
const storedFingerprint = getMeta(db, "embeddingFingerprint");
|
|
898
878
|
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");
|
|
879
|
+
// Model/provider changed → stored vectors are incompatible. Clear them
|
|
880
|
+
// (same dimension, so keep the vec table); re-embedded by this index run.
|
|
881
|
+
purgeEmbeddings(db);
|
|
914
882
|
}
|
|
915
883
|
try {
|
|
916
884
|
const { embedBatch } = await import("../llm/embedder.js");
|
|
@@ -940,31 +908,44 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal) {
|
|
|
940
908
|
warnVerbose(`[embed] ${ref} (${chars} chars, est. ${tokens} tokens) → batch ${batchNum}/${totalBatches}`);
|
|
941
909
|
}
|
|
942
910
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
911
|
+
let heartbeatTimer;
|
|
912
|
+
try {
|
|
913
|
+
heartbeatTimer = setInterval(() => {
|
|
914
|
+
onProgress({
|
|
915
|
+
phase: "embeddings",
|
|
916
|
+
message: `Still generating embeddings for ${allEntries.length} entr${allEntries.length === 1 ? "y" : "ies"}; waiting on embedding provider.`,
|
|
917
|
+
});
|
|
918
|
+
}, 15000);
|
|
919
|
+
const embeddings = await embedBatch(texts, config.embedding, signal);
|
|
920
|
+
throwIfAborted(signal);
|
|
921
|
+
// Wrap all embedding upserts in a single transaction so partial
|
|
922
|
+
// state is rolled back on failure rather than leaving the table half-filled.
|
|
923
|
+
let storedCount = 0;
|
|
924
|
+
let skippedCount = 0;
|
|
925
|
+
db.transaction(() => {
|
|
926
|
+
for (let i = 0; i < allEntries.length; i++) {
|
|
927
|
+
if (upsertEmbedding(db, allEntries[i].id, embeddings[i])) {
|
|
928
|
+
storedCount++;
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
skippedCount++;
|
|
932
|
+
}
|
|
956
933
|
}
|
|
934
|
+
})();
|
|
935
|
+
if (skippedCount > 0) {
|
|
936
|
+
warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
|
|
957
937
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
938
|
+
onProgress({
|
|
939
|
+
phase: "embeddings",
|
|
940
|
+
message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"}.`,
|
|
941
|
+
});
|
|
942
|
+
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
943
|
+
return { success: true };
|
|
944
|
+
}
|
|
945
|
+
finally {
|
|
946
|
+
if (heartbeatTimer)
|
|
947
|
+
clearInterval(heartbeatTimer);
|
|
961
948
|
}
|
|
962
|
-
onProgress({
|
|
963
|
-
phase: "embeddings",
|
|
964
|
-
message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"}.`,
|
|
965
|
-
});
|
|
966
|
-
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
967
|
-
return { success: true };
|
|
968
949
|
}
|
|
969
950
|
catch (error) {
|
|
970
951
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1075,7 +1056,7 @@ function resolveIndexedFiles(dirPath, files, stash) {
|
|
|
1075
1056
|
for (const entry of stash.entries) {
|
|
1076
1057
|
const entryPath = entry.filename
|
|
1077
1058
|
? path.join(dirPath, entry.filename)
|
|
1078
|
-
: matchEntryToFile(entry.name, fileBasenameMap
|
|
1059
|
+
: matchEntryToFile(entry.name, fileBasenameMap);
|
|
1079
1060
|
if (entryPath)
|
|
1080
1061
|
resolved.add(entryPath);
|
|
1081
1062
|
}
|
|
@@ -1194,7 +1175,7 @@ export function buildFileBasenameMap(files) {
|
|
|
1194
1175
|
* try matching the last segment
|
|
1195
1176
|
* 3. No implicit file fallback: ambiguous legacy entries are skipped
|
|
1196
1177
|
*/
|
|
1197
|
-
export function matchEntryToFile(entryName, fileMap
|
|
1178
|
+
export function matchEntryToFile(entryName, fileMap) {
|
|
1198
1179
|
// Exact match on entry name
|
|
1199
1180
|
const exact = fileMap.get(entryName);
|
|
1200
1181
|
if (exact)
|