akm-cli 0.9.1 → 0.9.2-alpha.2
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 +103 -28
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +5 -6
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract-prompt.js +64 -22
- package/dist/commands/improve/extract.js +608 -360
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +53 -22
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +901 -177
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +85 -35
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +23 -26
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/index-config.js +0 -27
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +78 -39
- package/dist/indexer/indexer.js +471 -432
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/materialize-embeddings.js +155 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/passes/metadata.js +263 -118
- package/dist/indexer/scan/doc-to-entry.js +7 -10
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +156 -50
- package/dist/indexer/search/fts-query.js +40 -40
- package/dist/indexer/search/ranking.js +36 -1
- package/dist/indexer/search/search-attribution.js +3 -1
- package/dist/indexer/search/search-fields.js +23 -14
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +31 -33
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19334 -52269
- package/dist/scripts/akm-migrate.js +19270 -51612
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +203 -229
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +64 -71
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +74 -350
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +56 -7
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +159 -36
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -224
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
|
@@ -113,6 +113,12 @@ export function deriveInstallations(sources) {
|
|
|
113
113
|
}
|
|
114
114
|
return installations;
|
|
115
115
|
}
|
|
116
|
+
/** Canonical bundle ids whose configured component policy permits mutation. */
|
|
117
|
+
export function deriveWritableBundleIds(sources) {
|
|
118
|
+
return new Set(deriveInstallations(sources)
|
|
119
|
+
.filter((installation) => installation.components.some((component) => component.writable === true))
|
|
120
|
+
.map((installation) => installation.id));
|
|
121
|
+
}
|
|
116
122
|
/**
|
|
117
123
|
* Derive the durable `EntryProvenance` for an indexed entry (Chunk-5 flip
|
|
118
124
|
* §14.4): `conceptId` is the D-R2 qualified `<stash-subdir>/<name>` spelling
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { adapterForId } from "../../core/adapter/registry.js";
|
|
7
|
+
import { isWithin } from "../../core/common.js";
|
|
8
|
+
import { UsageError } from "../../core/errors.js";
|
|
9
|
+
import { canonicalizeWorkflowName } from "../../core/recognition-util.js";
|
|
10
|
+
import { resolveUniqueWorkflowSource, workflowNameForConceptId, } from "../../workflows/source-files.js";
|
|
11
|
+
import { buildFileContext } from "../walk/file-context.js";
|
|
12
|
+
const CONTENT_READ_REQUIRED = Symbol("adapter ownership probe requires content");
|
|
13
|
+
const OWNER_SCAN_MAX_DIRECTORIES = 4_096;
|
|
14
|
+
const OWNER_SCAN_MAX_FILES = 16_384;
|
|
15
|
+
const OWNER_SCAN_SKIP_DIRECTORIES = new Set([".git", "node_modules", "bin", ".cache"]);
|
|
16
|
+
const OWNER_SCAN_SKIP_FILES = new Set([".stash.json", ".gitignore", ".gitattributes"]);
|
|
17
|
+
export class AdapterConceptOwnershipError extends UsageError {
|
|
18
|
+
}
|
|
19
|
+
export class AdapterConceptCollisionError extends AdapterConceptOwnershipError {
|
|
20
|
+
constructor(adapterId, conceptId, paths) {
|
|
21
|
+
const sorted = [...paths].sort(comparePaths);
|
|
22
|
+
super(`Adapter "${adapterId}" has multiple physical owners for "${conceptId}": ${sorted.join(", ")}.`, "RESOURCE_ALREADY_EXISTS");
|
|
23
|
+
this.name = "AdapterConceptCollisionError";
|
|
24
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class AdapterConceptScanError extends AdapterConceptOwnershipError {
|
|
28
|
+
constructor(adapterId, sourcePath, detail) {
|
|
29
|
+
super(`Adapter "${adapterId}" could not safely enumerate ${sourcePath}: ${detail}.`, "INVALID_FLAG_VALUE");
|
|
30
|
+
this.name = "AdapterConceptScanError";
|
|
31
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function normalizedConceptId(conceptId) {
|
|
35
|
+
const normalized = conceptId.replaceAll("\\", "/");
|
|
36
|
+
if (!normalized ||
|
|
37
|
+
path.posix.isAbsolute(normalized) ||
|
|
38
|
+
normalized === ".." ||
|
|
39
|
+
normalized.startsWith("../") ||
|
|
40
|
+
path.posix.normalize(normalized) !== normalized) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
function componentFor(sourcePath, adapterId) {
|
|
46
|
+
return { id: adapterId, adapter: adapterId, root: sourcePath, writable: false };
|
|
47
|
+
}
|
|
48
|
+
function lexicallyWithin(candidate, root) {
|
|
49
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
50
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
51
|
+
}
|
|
52
|
+
/** Add case-only suffix spellings beside an adapter-authored read candidate. */
|
|
53
|
+
function candidateSpellings(candidate, sourcePath, realRoot) {
|
|
54
|
+
const authored = path.resolve(candidate.path);
|
|
55
|
+
if (!lexicallyWithin(authored, sourcePath))
|
|
56
|
+
return [{ ...candidate, path: authored }];
|
|
57
|
+
const parent = path.dirname(authored);
|
|
58
|
+
let realParent;
|
|
59
|
+
let entries;
|
|
60
|
+
try {
|
|
61
|
+
realParent = fs.realpathSync(parent);
|
|
62
|
+
entries = fs.readdirSync(parent, { withFileTypes: true });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [{ ...candidate, path: authored }];
|
|
66
|
+
}
|
|
67
|
+
if (!isWithin(realParent, realRoot))
|
|
68
|
+
return [{ ...candidate, path: authored }];
|
|
69
|
+
const extension = path.extname(authored);
|
|
70
|
+
if (!extension)
|
|
71
|
+
return [{ ...candidate, path: authored }];
|
|
72
|
+
const stem = path.basename(authored, extension);
|
|
73
|
+
return entries
|
|
74
|
+
.filter((entry) => {
|
|
75
|
+
const entryExtension = path.extname(entry.name);
|
|
76
|
+
return (path.basename(entry.name, entryExtension) === stem && entryExtension.toLowerCase() === extension.toLowerCase());
|
|
77
|
+
})
|
|
78
|
+
.map((entry) => ({ ...candidate, path: path.join(parent, entry.name) }));
|
|
79
|
+
}
|
|
80
|
+
function inspectCandidate(sourcePath, realRoot, adapterId, candidate) {
|
|
81
|
+
const authoredPath = path.resolve(candidate.path);
|
|
82
|
+
if (!lexicallyWithin(authoredPath, sourcePath))
|
|
83
|
+
return undefined;
|
|
84
|
+
let authoredStat;
|
|
85
|
+
try {
|
|
86
|
+
authoredStat = fs.lstatSync(authoredPath);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (!authoredStat.isFile() && !authoredStat.isSymbolicLink())
|
|
92
|
+
return undefined;
|
|
93
|
+
let realPath;
|
|
94
|
+
try {
|
|
95
|
+
realPath = fs.realpathSync(authoredPath);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (!isWithin(realPath, realRoot))
|
|
101
|
+
return undefined;
|
|
102
|
+
try {
|
|
103
|
+
if (!fs.statSync(realPath).isFile())
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return { path: authoredPath, realPath, conceptId: candidate.conceptId, adapterId };
|
|
110
|
+
}
|
|
111
|
+
function claimsWithoutContent(adapter, component, owner) {
|
|
112
|
+
const file = buildFileContext(component.root, owner.path);
|
|
113
|
+
let requestedBytes = false;
|
|
114
|
+
const denyBytes = () => {
|
|
115
|
+
requestedBytes = true;
|
|
116
|
+
throw CONTENT_READ_REQUIRED;
|
|
117
|
+
};
|
|
118
|
+
const noContentFile = { ...file, content: denyBytes, frontmatter: denyBytes };
|
|
119
|
+
try {
|
|
120
|
+
const document = adapter.recognize(component, noContentFile);
|
|
121
|
+
if (requestedBytes)
|
|
122
|
+
return true;
|
|
123
|
+
return document?.conceptId === owner.conceptId;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error === CONTENT_READ_REQUIRED)
|
|
127
|
+
return true;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function adapterPathContext(root, authoredPath) {
|
|
132
|
+
const file = buildFileContext(root, authoredPath);
|
|
133
|
+
return {
|
|
134
|
+
absPath: file.absPath,
|
|
135
|
+
relPath: file.relPath,
|
|
136
|
+
ext: file.ext,
|
|
137
|
+
fileName: file.fileName,
|
|
138
|
+
parentDir: file.parentDir,
|
|
139
|
+
parentDirAbs: file.parentDirAbs,
|
|
140
|
+
ancestorDirs: file.ancestorDirs,
|
|
141
|
+
stashRoot: file.stashRoot,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function scanRegularAuthoredPaths(sourcePath, realRoot, adapterId) {
|
|
145
|
+
const paths = [];
|
|
146
|
+
const stack = [path.resolve(sourcePath)];
|
|
147
|
+
let directories = 0;
|
|
148
|
+
let files = 0;
|
|
149
|
+
while (stack.length > 0) {
|
|
150
|
+
const current = stack.pop();
|
|
151
|
+
if (!current)
|
|
152
|
+
continue;
|
|
153
|
+
directories++;
|
|
154
|
+
if (directories > OWNER_SCAN_MAX_DIRECTORIES) {
|
|
155
|
+
throw new AdapterConceptScanError(adapterId, sourcePath, `directory limit ${OWNER_SCAN_MAX_DIRECTORIES} exceeded`);
|
|
156
|
+
}
|
|
157
|
+
let entries;
|
|
158
|
+
try {
|
|
159
|
+
entries = fs
|
|
160
|
+
.readdirSync(current, { withFileTypes: true })
|
|
161
|
+
.sort((left, right) => comparePaths(left.name, right.name));
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
throw new AdapterConceptScanError(adapterId, sourcePath, `cannot read ${current}: ${String(error)}`);
|
|
165
|
+
}
|
|
166
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
167
|
+
const entry = entries[index];
|
|
168
|
+
if (!entry)
|
|
169
|
+
continue;
|
|
170
|
+
const authoredPath = path.join(current, entry.name);
|
|
171
|
+
if (entry.isSymbolicLink())
|
|
172
|
+
continue;
|
|
173
|
+
if (entry.isDirectory()) {
|
|
174
|
+
if (OWNER_SCAN_SKIP_DIRECTORIES.has(entry.name) || entry.name.startsWith("."))
|
|
175
|
+
continue;
|
|
176
|
+
stack.push(authoredPath);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (!entry.isFile() || OWNER_SCAN_SKIP_FILES.has(entry.name))
|
|
180
|
+
continue;
|
|
181
|
+
files++;
|
|
182
|
+
if (files > OWNER_SCAN_MAX_FILES) {
|
|
183
|
+
throw new AdapterConceptScanError(adapterId, sourcePath, `file limit ${OWNER_SCAN_MAX_FILES} exceeded`);
|
|
184
|
+
}
|
|
185
|
+
let realPath;
|
|
186
|
+
try {
|
|
187
|
+
realPath = fs.realpathSync(authoredPath);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (isWithin(realPath, realRoot))
|
|
193
|
+
paths.push(authoredPath);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return paths.sort(comparePaths);
|
|
197
|
+
}
|
|
198
|
+
function scannedReadCandidates(adapter, component, sourcePath, realRoot, conceptId) {
|
|
199
|
+
const recognizePathCandidates = adapter.recognizePathCandidates;
|
|
200
|
+
if (!recognizePathCandidates)
|
|
201
|
+
return [];
|
|
202
|
+
return scanRegularAuthoredPaths(sourcePath, realRoot, adapter.id).flatMap((authoredPath) => recognizePathCandidates(component, adapterPathContext(sourcePath, authoredPath)).flatMap((candidateConceptId) => candidateConceptId === conceptId ? [{ path: authoredPath, conceptId: candidateConceptId }] : []));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Resolve one adapter/component's exact physical concept owner without reading
|
|
206
|
+
* authored bytes. Native workflow arbitration is reused verbatim; every other
|
|
207
|
+
* adapter supplies its own read placements and is probed with a byte-denying
|
|
208
|
+
* FileContext so path-level abstention remains authoritative.
|
|
209
|
+
*/
|
|
210
|
+
export function resolveAdapterConceptOwner(sourcePath, adapterId, conceptId) {
|
|
211
|
+
const adapter = adapterForId(adapterId);
|
|
212
|
+
const normalized = normalizedConceptId(conceptId);
|
|
213
|
+
if (!adapter || !normalized)
|
|
214
|
+
return undefined;
|
|
215
|
+
let realRoot;
|
|
216
|
+
try {
|
|
217
|
+
realRoot = fs.realpathSync(sourcePath);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
const component = componentFor(sourcePath, adapterId);
|
|
223
|
+
const ownersByIdentity = new Map();
|
|
224
|
+
const workflowName = workflowNameForConceptId(adapterId, normalized);
|
|
225
|
+
const resolutionConceptId = workflowName === undefined
|
|
226
|
+
? normalized
|
|
227
|
+
: adapterId === "akm"
|
|
228
|
+
? `workflows/${canonicalizeWorkflowName(workflowName)}`
|
|
229
|
+
: canonicalizeWorkflowName(workflowName);
|
|
230
|
+
if (workflowName !== undefined) {
|
|
231
|
+
const workflowSource = resolveUniqueWorkflowSource(sourcePath, adapterId, workflowName);
|
|
232
|
+
if (workflowSource) {
|
|
233
|
+
const workflowOwner = {
|
|
234
|
+
path: workflowSource.path,
|
|
235
|
+
realPath: workflowSource.realPath,
|
|
236
|
+
conceptId: resolutionConceptId,
|
|
237
|
+
adapterId,
|
|
238
|
+
workflowSource,
|
|
239
|
+
};
|
|
240
|
+
ownersByIdentity.set(`${path.resolve(workflowOwner.path)}\0${resolutionConceptId}`, workflowOwner);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const directSpellings = (adapter.readCandidates?.(component, resolutionConceptId) ?? []).flatMap((candidate) => candidateSpellings(candidate, sourcePath, realRoot));
|
|
244
|
+
const spellings = [
|
|
245
|
+
...directSpellings,
|
|
246
|
+
...scannedReadCandidates(adapter, component, sourcePath, realRoot, resolutionConceptId),
|
|
247
|
+
];
|
|
248
|
+
const inspected = [
|
|
249
|
+
...new Map(spellings.map((candidate) => [`${path.resolve(candidate.path)}\0${candidate.conceptId}`, candidate])).values(),
|
|
250
|
+
]
|
|
251
|
+
.sort((left, right) => comparePaths(left.path, right.path))
|
|
252
|
+
.flatMap((candidate) => {
|
|
253
|
+
const owner = inspectCandidate(sourcePath, realRoot, adapterId, candidate);
|
|
254
|
+
return owner ? [owner] : [];
|
|
255
|
+
});
|
|
256
|
+
for (const candidate of inspected) {
|
|
257
|
+
if (candidate.conceptId !== resolutionConceptId)
|
|
258
|
+
continue;
|
|
259
|
+
const identity = `${path.resolve(candidate.path)}\0${candidate.conceptId}`;
|
|
260
|
+
if (ownersByIdentity.has(identity))
|
|
261
|
+
continue;
|
|
262
|
+
if (claimsWithoutContent(adapter, component, candidate))
|
|
263
|
+
ownersByIdentity.set(identity, candidate);
|
|
264
|
+
}
|
|
265
|
+
const owners = [...ownersByIdentity.values()].filter((owner) => owner.conceptId === resolutionConceptId);
|
|
266
|
+
if (owners.length > 1) {
|
|
267
|
+
throw new AdapterConceptCollisionError(adapterId, resolutionConceptId, owners.map((owner) => path.relative(sourcePath, owner.path).replaceAll("\\", "/")));
|
|
268
|
+
}
|
|
269
|
+
return owners[0];
|
|
270
|
+
}
|
|
271
|
+
export function indexedPathMatchesOwner(indexedPath, owner) {
|
|
272
|
+
if (path.resolve(indexedPath) !== path.resolve(owner.path))
|
|
273
|
+
return false;
|
|
274
|
+
try {
|
|
275
|
+
return path.resolve(fs.realpathSync(indexedPath)) === path.resolve(owner.realPath);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function comparePaths(left, right) {
|
|
282
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
283
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { isVerbose, warn, warnVerbose } from "../core/warn.js";
|
|
5
|
+
import { embedBatch } from "../llm/embedder.js";
|
|
6
|
+
import { estimateTokenCount } from "../llm/embedders/remote.js";
|
|
7
|
+
import { getEmbeddableEntryCount } from "../storage/repositories/index-entries-repository.js";
|
|
8
|
+
import { deleteMeta, getMeta, setMeta } from "../storage/repositories/index-meta-repository.js";
|
|
9
|
+
import { getAllEntriesForEmbedding, getEmbeddingCount, isVecAvailable, isVecFastPathComplete, isVecFastPathReady, purgeEmbeddings, setVecFastPathReady, upsertEmbedding, } from "../storage/repositories/index-vec-repository.js";
|
|
10
|
+
import { classifySemanticFailure, clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "./search/semantic-status.js";
|
|
11
|
+
function throwIfAborted(signal) {
|
|
12
|
+
if (signal?.aborted) {
|
|
13
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("index interrupted");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function generateEmbeddingsForDb(db, config, onProgress, signal, entryIds) {
|
|
17
|
+
throwIfAborted(signal);
|
|
18
|
+
if (config.semanticSearchMode === "off") {
|
|
19
|
+
onProgress({ phase: "embeddings", message: "Semantic search disabled; skipping embeddings." });
|
|
20
|
+
return { success: false, reason: "index-missing", message: "Semantic search is disabled." };
|
|
21
|
+
}
|
|
22
|
+
// A targeted call starts from an already-published generation. Preserve its
|
|
23
|
+
// trust decision in O(1): successful writes for the changed IDs keep a
|
|
24
|
+
// healthy fast path healthy, but can never promote a generation already
|
|
25
|
+
// marked degraded. Global runs can afford to verify the entire derived set.
|
|
26
|
+
const vecFastPathWasReady = isVecFastPathReady(db);
|
|
27
|
+
const currentFingerprint = deriveSemanticProviderFingerprint(config.embedding);
|
|
28
|
+
const storedFingerprint = getMeta(db, "embeddingFingerprint");
|
|
29
|
+
let targetEntryIds = entryIds;
|
|
30
|
+
if (storedFingerprint && storedFingerprint !== currentFingerprint) {
|
|
31
|
+
purgeEmbeddings(db, { dropVecTable: true });
|
|
32
|
+
deleteMeta(db, "embeddingDim");
|
|
33
|
+
// A provider/model change invalidates the entire vector generation, even
|
|
34
|
+
// when a targeted write happened to discover it first.
|
|
35
|
+
targetEntryIds = undefined;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
throwIfAborted(signal);
|
|
39
|
+
const allEntries = getAllEntriesForEmbedding(db, targetEntryIds);
|
|
40
|
+
if (allEntries.length === 0) {
|
|
41
|
+
onProgress({ phase: "embeddings", message: "Embeddings already up to date." });
|
|
42
|
+
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
43
|
+
return { success: true };
|
|
44
|
+
}
|
|
45
|
+
onProgress({
|
|
46
|
+
phase: "embeddings",
|
|
47
|
+
message: `Generating embeddings for ${allEntries.length} entr${allEntries.length === 1 ? "y" : "ies"}.`,
|
|
48
|
+
});
|
|
49
|
+
const texts = allEntries.map((entry) => entry.searchText);
|
|
50
|
+
if (isVerbose()) {
|
|
51
|
+
const EMBED_BATCH_SIZE = 100;
|
|
52
|
+
const totalBatches = Math.ceil(texts.length / EMBED_BATCH_SIZE);
|
|
53
|
+
for (const [i, entry] of allEntries.entries()) {
|
|
54
|
+
const batchNum = Math.floor(i / EMBED_BATCH_SIZE) + 1;
|
|
55
|
+
const chars = entry.searchText.length;
|
|
56
|
+
const tokens = estimateTokenCount(entry.searchText);
|
|
57
|
+
const ref = entry.itemRef;
|
|
58
|
+
warnVerbose(`[embed] ${ref} (${chars} chars, est. ${tokens} tokens) → batch ${batchNum}/${totalBatches}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let heartbeatTimer;
|
|
62
|
+
try {
|
|
63
|
+
heartbeatTimer = setInterval(() => {
|
|
64
|
+
onProgress({
|
|
65
|
+
phase: "embeddings",
|
|
66
|
+
message: `Still generating embeddings for ${allEntries.length} entr${allEntries.length === 1 ? "y" : "ies"}; waiting on embedding provider.`,
|
|
67
|
+
});
|
|
68
|
+
}, 15000);
|
|
69
|
+
const embeddings = await embedBatch(texts, config.embedding, signal);
|
|
70
|
+
throwIfAborted(signal);
|
|
71
|
+
let storedCount = 0;
|
|
72
|
+
let skippedCount = 0;
|
|
73
|
+
let vecFailedCount = 0;
|
|
74
|
+
let vecUnavailableCount = 0;
|
|
75
|
+
db.transaction(() => {
|
|
76
|
+
for (const [i, entry] of allEntries.entries()) {
|
|
77
|
+
const embedding = embeddings[i];
|
|
78
|
+
if (!embedding)
|
|
79
|
+
throw new Error(`Embedding provider returned no vector for ${entry.itemRef}.`);
|
|
80
|
+
const result = upsertEmbedding(db, entry.id, embedding);
|
|
81
|
+
if (result.stored)
|
|
82
|
+
storedCount++;
|
|
83
|
+
else
|
|
84
|
+
skippedCount++;
|
|
85
|
+
if (result.vec === "failed")
|
|
86
|
+
vecFailedCount++;
|
|
87
|
+
if (result.vec === "unavailable")
|
|
88
|
+
vecUnavailableCount++;
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
if (skippedCount > 0) {
|
|
92
|
+
warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
|
|
93
|
+
}
|
|
94
|
+
const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
|
|
95
|
+
setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
|
|
96
|
+
if (vecFailedCount > 0) {
|
|
97
|
+
warn(`[embed] ${vecFailedCount} sqlite-vec fast-path insert${vecFailedCount === 1 ? "" : "s"} failed — ` +
|
|
98
|
+
"semantic search will use the slower JS-cosine fallback over stored embeddings. " +
|
|
99
|
+
"Rebuild with 'akm index --full' after resolving the vec table (often a vector-dimension mismatch).");
|
|
100
|
+
}
|
|
101
|
+
onProgress({
|
|
102
|
+
phase: "embeddings",
|
|
103
|
+
message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"}.`,
|
|
104
|
+
});
|
|
105
|
+
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
106
|
+
return { success: true, vecInsertFailures: vecFailedCount };
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
if (heartbeatTimer)
|
|
110
|
+
clearInterval(heartbeatTimer);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
+
warn("Embedding generation failed, continuing without:", message);
|
|
116
|
+
onProgress({ phase: "embeddings", message: `Embedding generation failed: ${message}` });
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
reason: classifySemanticFailure(message),
|
|
120
|
+
message: `Semantic search verification failed: ${message}`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Publish the canonical semantic health snapshot after a targeted mutation. */
|
|
125
|
+
export function publishTargetedSemanticStatus(db, config, result) {
|
|
126
|
+
if (config.semanticSearchMode === "off") {
|
|
127
|
+
clearSemanticStatus();
|
|
128
|
+
setMeta(db, "hasEmbeddings", "0");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const entryCount = getEmbeddableEntryCount(db);
|
|
132
|
+
const embeddingCount = getEmbeddingCount(db);
|
|
133
|
+
let status;
|
|
134
|
+
if (entryCount === 0)
|
|
135
|
+
status = "pending";
|
|
136
|
+
else if (embeddingCount >= entryCount)
|
|
137
|
+
status = isVecAvailable(db) && isVecFastPathReady(db) ? "ready-vec" : "ready-js";
|
|
138
|
+
else
|
|
139
|
+
status = "blocked";
|
|
140
|
+
setMeta(db, "hasEmbeddings", status === "ready-js" || status === "ready-vec" ? "1" : "0");
|
|
141
|
+
writeSemanticStatus({
|
|
142
|
+
status,
|
|
143
|
+
...(status === "blocked" ? { reason: result.reason ?? "index-failed" } : {}),
|
|
144
|
+
...(status === "blocked"
|
|
145
|
+
? {
|
|
146
|
+
message: result.message ??
|
|
147
|
+
`Semantic search verification failed (${embeddingCount}/${entryCount} embeddings available).`,
|
|
148
|
+
}
|
|
149
|
+
: {}),
|
|
150
|
+
providerFingerprint: deriveSemanticProviderFingerprint(config.embedding),
|
|
151
|
+
lastCheckedAt: new Date().toISOString(),
|
|
152
|
+
entryCount,
|
|
153
|
+
embeddingCount,
|
|
154
|
+
});
|
|
155
|
+
}
|