akm-cli 0.9.16-alpha.1 → 0.9.16-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 +40 -132
- package/dist/assets/hints/cli-hints-full.md +13 -6
- package/dist/assets/tasks/core/index-refresh.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
- package/dist/cli/retired-commands.js +0 -4
- package/dist/cli/unknown-flags.js +3 -36
- package/dist/commands/env/env-binding.js +4 -4
- package/dist/commands/env/env-cli.js +3 -3
- package/dist/commands/improve/collapse-detector.js +2 -2
- package/dist/commands/improve/consolidate.js +4 -6
- package/dist/commands/improve/improve-cli.js +20 -15
- package/dist/commands/improve/reflect.js +23 -2
- package/dist/commands/lint/base-linter.js +9 -0
- package/dist/commands/lint/env-key-rules.js +2 -2
- package/dist/commands/proposal/propose.js +15 -1
- package/dist/commands/proposal/repository.js +3 -12
- package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
- package/dist/commands/proposal/validators/proposal-validators.js +5 -4
- package/dist/commands/read/curate.js +44 -34
- package/dist/commands/read/search.js +35 -54
- package/dist/commands/read/show.js +21 -2
- package/dist/commands/registry-cli.js +5 -5
- package/dist/commands/sources/add-cli.js +59 -16
- package/dist/commands/sources/bundle-cli.js +35 -11
- package/dist/commands/sources/bundle-config-ops.js +30 -0
- package/dist/commands/sources/dangerous-env-audit.js +4 -4
- package/dist/commands/sources/info.js +8 -8
- package/dist/commands/sources/installed-stashes.js +55 -61
- package/dist/commands/sources/source-add.js +39 -38
- package/dist/commands/sources/source-manage.js +34 -12
- package/dist/commands/sources/stash-cli.js +111 -119
- package/dist/commands/sources/stash-skeleton.js +6 -3
- package/dist/commands/tasks/explain.js +4 -1
- package/dist/commands/tasks/tasks-cli.js +31 -9
- package/dist/commands/tasks/tasks.js +239 -194
- package/dist/commands/tasks/validate.js +20 -32
- package/dist/core/activation-policy.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +8 -35
- package/dist/core/adapter/adapters/akm-metadata.js +1 -11
- package/dist/core/adapter/execution-source.js +10 -29
- package/dist/core/asset/asset-placement.js +0 -35
- package/dist/core/config/config-schema.js +64 -8
- package/dist/core/config/config-sources.js +96 -2
- package/dist/core/config/config.js +190 -24
- package/dist/core/config/legacy-source-shape-shim.js +9 -0
- package/dist/core/config/schema/embedding.js +30 -7
- package/dist/core/config/schema/execution.js +23 -0
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/scheduler.js +20 -0
- package/dist/core/config/schema/search.js +10 -12
- package/dist/core/config/schema/sources-bundles.js +32 -1
- package/dist/core/content-safety.js +52 -0
- package/dist/core/errors.js +2 -5
- package/dist/core/maintenance-barrier.js +11 -13
- package/dist/core/paths.js +11 -0
- package/dist/core/run-lock.js +2 -5
- package/dist/core/state/migrations.js +1 -26
- package/dist/core/state-db.js +27 -63
- package/dist/core/type-presentation.js +1 -1
- package/dist/core/write-source.js +13 -8
- package/dist/indexer/bundle-identity-guard.js +45 -8
- package/dist/indexer/ensure-index.js +0 -5
- package/dist/indexer/index-db-contention.js +56 -0
- package/dist/indexer/index-rebuild-lock.js +73 -0
- package/dist/indexer/index-written-assets.js +171 -133
- package/dist/indexer/indexer.js +1621 -458
- package/dist/indexer/lookup/adapter-concept-owner.js +5 -19
- package/dist/indexer/materialize-embeddings.js +785 -0
- package/dist/indexer/passes/dir-staleness.js +161 -0
- package/dist/indexer/passes/metadata.js +1 -18
- package/dist/indexer/scan/drain-dir.js +70 -27
- package/dist/indexer/search/db-search.js +89 -373
- package/dist/indexer/search/ranking-contributors.js +16 -21
- package/dist/indexer/search/ranking.js +57 -135
- package/dist/indexer/search/search-source.js +29 -11
- package/dist/integrations/agent/execution-lowering.js +3 -2
- package/dist/integrations/agent/execution-preparation.js +32 -1
- package/dist/integrations/agent/prompts.js +1 -1
- package/dist/integrations/agent/request-lowering.js +3 -2
- package/dist/llm/client.js +3 -11
- package/dist/llm/embedder.js +3 -10
- package/dist/llm/embedders/remote.js +104 -133
- package/dist/llm/feature-gate.js +2 -4
- package/dist/llm/rerank-client.js +3 -3
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/text/command-format.js +13 -19
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/index.js +2 -5
- package/dist/registry/resolve.js +37 -10
- package/dist/scripts/akm-migrate-node.js +15197 -11351
- package/dist/scripts/akm-migrate.js +15514 -11668
- package/dist/setup/semantic-assets.js +2 -2
- package/dist/setup/setup.js +3 -3
- package/dist/setup/steps/connection.js +2 -3
- package/dist/setup/steps/tasks.js +29 -36
- package/dist/sources/providers/git-install.js +17 -11
- package/dist/sources/providers/git-provider.js +12 -5
- package/dist/sources/providers/git-stash.js +38 -16
- package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
- package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
- package/dist/storage/repositories/index-connection.js +3 -1
- package/dist/storage/repositories/index-entries-repository.js +68 -77
- package/dist/storage/repositories/index-entry-schema.js +25 -16
- package/dist/storage/repositories/index-fts-repository.js +263 -29
- package/dist/storage/repositories/index-meta-repository.js +29 -0
- package/dist/storage/repositories/index-schema.js +122 -115
- package/dist/storage/repositories/index-utility-repository.js +1 -1
- package/dist/storage/repositories/index-vec-repository.js +435 -22
- package/dist/tasks/activation-config.js +90 -0
- package/dist/tasks/backends/cron.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -0
- package/dist/tasks/backends/schtasks.js +2 -0
- package/dist/tasks/embedded.js +4 -5
- package/dist/tasks/scheduler-binding.js +2 -2
- package/dist/tasks/scheduler-sync-preview.js +8 -1
- package/dist/tasks/scheduler-sync.js +19 -10
- package/dist/tasks/source/parse-task-source.js +10 -113
- package/dist/tasks/source/project-v4.js +2 -2
- package/dist/tasks/source/task-source-v4.js +4 -12
- package/dist/tasks/source/task-to-v3.js +4 -12
- package/dist/tasks/source/task-to-v4.js +40 -7
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.15.md +36 -34
- package/docs/migration/release-notes/0.9.16.md +60 -98
- package/docs/migration/release-notes/README.md +0 -5
- package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
- package/docs/reference/cli.md +124 -122
- package/docs/reference/configuration.md +137 -133
- package/docs/reference/data-and-telemetry.md +1 -2
- package/docs/reference/tasks.md +34 -29
- package/package.json +1 -1
- package/schemas/akm-config.json +170 -6
- package/schemas/akm-task.json +1 -2
- package/dist/commands/sources/index-status.js +0 -99
- package/dist/core/hash.js +0 -18
- package/dist/indexer/drain.js +0 -306
- package/dist/indexer/embedding-identity.js +0 -20
- package/dist/indexer/enrich.js +0 -260
- package/dist/indexer/reconcile.js +0 -890
- package/dist/indexer/scan/parse-file.js +0 -66
- package/dist/indexer/units/unit.js +0 -159
- package/dist/llm/embedders/provider-limits.js +0 -288
- package/dist/storage/repositories/files-repository.js +0 -181
- package/dist/storage/repositories/units-repository.js +0 -510
|
@@ -2,130 +2,90 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
* Write-path indexing:
|
|
6
|
-
* the same call as the write. `remember` / `writeMarkdownAsset`, extract's
|
|
7
|
-
* session assets, `source clone`, and proposal acceptance all call this
|
|
8
|
-
* right after committing their file write so the asset is searchable
|
|
9
|
-
* immediately, without a background reindex.
|
|
5
|
+
* Write-path indexing: targeted single-file index updates for asset writers.
|
|
10
6
|
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
7
|
+
* The index is maintained eagerly by every first-class mutation command
|
|
8
|
+
* (`source add`, `wiki`, `workflow`, `setup` all run `akmIndex()` after
|
|
9
|
+
* writing). The memory write paths — `akm remember` / `writeMarkdownAsset`
|
|
10
|
+
* and extract's session assets — historically did not, which is why reads
|
|
11
|
+
* used to compensate with stale-triggered background reindexes (the
|
|
12
|
+
* lock-contention footgun removed alongside this module's introduction, per
|
|
13
|
+
* the 2026-07 read-path reindex-contention findings §7).
|
|
18
14
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
15
|
+
* This is NOT a general reindex. It upserts exactly the files the caller just
|
|
16
|
+
* wrote: frontmatter/metadata via the shared matcher pipeline, the canonical
|
|
17
|
+
* row with its transactionally owned FTS projection, and vectors for changed
|
|
18
|
+
* entry IDs when semantic search is enabled. Index-time LLM passes, graph
|
|
19
|
+
* extraction, `builtAt`, and the per-dir walk cache remain full-index
|
|
20
|
+
* responsibilities.
|
|
24
21
|
*/
|
|
22
|
+
import fs from "node:fs";
|
|
25
23
|
import path from "node:path";
|
|
24
|
+
import { akmAdapter } from "../core/adapter/adapters/akm-adapter.js";
|
|
26
25
|
import { loadConfig } from "../core/config/config.js";
|
|
27
26
|
import { isDataDirUnreadableError } from "../core/errors.js";
|
|
27
|
+
import { probeLock } from "../core/file-lock.js";
|
|
28
28
|
import { isPathAbsent } from "../core/path-access.js";
|
|
29
|
-
import { getDbPath } from "../core/paths.js";
|
|
29
|
+
import { getDbPath, getIndexRebuildLockPath } from "../core/paths.js";
|
|
30
|
+
import { formatLockHolderPid } from "../core/run-lock.js";
|
|
30
31
|
import { warn, warnVerbose } from "../core/warn.js";
|
|
31
32
|
import { closeDatabase, openExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
32
|
-
import { getEntryCount } from "../storage/repositories/index-entries-repository.js";
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
33
|
+
import { deleteEntriesByIds, getEntryCount, upsertEntry } from "../storage/repositories/index-entries-repository.js";
|
|
34
|
+
import { deriveEntryProvenance, deriveInstallations } from "./installations.js";
|
|
35
|
+
import { generateEmbeddingsForDb, publishTargetedEmbeddingMeta } from "./materialize-embeddings.js";
|
|
36
|
+
import { getMarkdownFragmentContent, hasMarkdownFragmentContent, setMarkdownFragmentContent, } from "./passes/metadata.js";
|
|
37
|
+
import { drainDirDocuments } from "./scan/drain-dir.js";
|
|
38
|
+
import { buildSearchText } from "./search/search-fields.js";
|
|
39
|
+
import { buildFileContext } from "./walk/file-context.js";
|
|
38
40
|
/**
|
|
39
|
-
* Busy-timeout (ms) for write-path index upserts.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* write waits behind another process's short reconcile/drain transaction.
|
|
41
|
+
* Busy-timeout (ms) for write-path index upserts. A real write — unlike the
|
|
42
|
+
* 250ms telemetry inserts — but it must not hang `akm remember` for the full
|
|
43
|
+
* default 30s behind a running full reindex. When it times out, the upsert is
|
|
44
|
+
* skipped and the asset becomes searchable after that reindex instead.
|
|
44
45
|
*/
|
|
45
46
|
export const WRITE_PATH_INDEX_BUSY_TIMEOUT_MS = 5_000;
|
|
46
|
-
/**
|
|
47
|
-
* The distinct unit hashes reachable from an entry at one of `files`, via the
|
|
48
|
-
* `entry_units` mapping `reconcilePaths` just wrote — i.e. exactly the units
|
|
49
|
-
* this write's reconcile pass added or touched, the scope B4's drain should
|
|
50
|
-
* be asked to embed (module B2, docs/plans/index-redesign-contract.md:
|
|
51
|
-
* "Compute 'the units this write added' from the mapping:
|
|
52
|
-
* entries.file_path IN (paths) → entry_units → unit_hash"). A gone/unindexable
|
|
53
|
-
* path has no `entries` row any more and so contributes nothing here, which
|
|
54
|
-
* is correct — there is nothing left to embed for it.
|
|
55
|
-
*/
|
|
56
|
-
function unitHashesForFiles(db, files) {
|
|
57
|
-
const hashes = new Set();
|
|
58
|
-
for (let offset = 0; offset < files.length; offset += SQLITE_CHUNK_SIZE) {
|
|
59
|
-
const chunk = files.slice(offset, offset + SQLITE_CHUNK_SIZE);
|
|
60
|
-
const placeholders = chunk.map(() => "?").join(",");
|
|
61
|
-
const rows = db
|
|
62
|
-
.prepare(`SELECT DISTINCT eu.unit_hash AS unitHash
|
|
63
|
-
FROM entries e
|
|
64
|
-
JOIN entry_units eu ON eu.entry_id = e.id
|
|
65
|
-
WHERE e.file_path IN (${placeholders})`)
|
|
66
|
-
.all(...chunk);
|
|
67
|
-
for (const row of rows)
|
|
68
|
-
hashes.add(row.unitHash);
|
|
69
|
-
}
|
|
70
|
-
return [...hashes];
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* The bundle id a fresh write into `stashDir` should be recorded under, when
|
|
74
|
-
* the caller did not already know it (`options.bundleId`).
|
|
75
|
-
*
|
|
76
|
-
* `stashDir` is matched against the CONFIGURED sources first, the way every
|
|
77
|
-
* other write-path caller resolves its bundle id (`knowledge.ts`'s demoted-file
|
|
78
|
-
* loop: `resolveSourceEntries` + `deriveInstallations`, matched by resolved
|
|
79
|
-
* root). A stash that is ALSO a configured bundle (`akm bundle add <dir>
|
|
80
|
-
* --name x`) must produce that bundle's id here, not a fresh slug of the raw
|
|
81
|
-
* path — a mismatch leaves an undeletable duplicate row once a full `akm
|
|
82
|
-
* index` reconciles the same path under its real, configured id (issue found
|
|
83
|
-
* integrating write-path W1). Only a stash that is genuinely unconfigured (an
|
|
84
|
-
* ad hoc write target) falls back to deriving an id from the raw path alone,
|
|
85
|
-
* the way a fresh install would.
|
|
86
|
-
*/
|
|
87
|
-
function resolveWrittenAssetBundleId(stashDir) {
|
|
88
|
-
const resolvedStashDir = path.resolve(stashDir);
|
|
89
|
-
const sourceEntries = resolveSourceEntries();
|
|
90
|
-
const index = sourceEntries.findIndex((entry) => path.resolve(entry.path) === resolvedStashDir);
|
|
91
|
-
if (index !== -1)
|
|
92
|
-
return deriveInstallations(sourceEntries)[index]?.id;
|
|
93
|
-
return deriveInstallations([{ path: stashDir, writable: true }])[0]?.id;
|
|
94
|
-
}
|
|
95
47
|
/**
|
|
96
48
|
* Index the given just-written asset files into the existing local index.
|
|
97
49
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
50
|
+
* FAIL-OPEN at every step: any error (index.db absent, empty, locked past the
|
|
51
|
+
* busy timeout, unparseable file) is reduced to a verbose-only warning and the
|
|
52
|
+
* write command succeeds untouched. The degraded outcome is exactly the
|
|
53
|
+
* pre-write-path-indexing behavior: the asset appears after the next full
|
|
54
|
+
* `akm index` / improve-cron run.
|
|
103
55
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* - An index with zero entries: same reasoning — nothing has bootstrapped
|
|
108
|
-
* it yet.
|
|
109
|
-
* - Every given path living under a dot-segment directory (e.g. `.meta/`):
|
|
110
|
-
* the full reconcile never descends into those either.
|
|
56
|
+
* An absent or empty index is skipped on purpose — bootstrap belongs to the
|
|
57
|
+
* first read (`ensureIndex`) or an explicit `akm index`, which also cover
|
|
58
|
+
* embeddings and the other passes this fast path skips.
|
|
111
59
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* already succeeded and the asset appears after the next reconcile.
|
|
119
|
-
*
|
|
120
|
-
* The embedding drain that follows a successful reconcile is best-effort: a
|
|
121
|
-
* failure there (a provider outage, a tripped circuit breaker) does NOT turn
|
|
122
|
-
* this call into a `false` — the asset is already lexically searchable, and
|
|
123
|
-
* the embedding queue is durable (module B4): any later drain, including the
|
|
124
|
-
* next write, computes the same "no vector yet" query and picks it up.
|
|
60
|
+
* A live rebuild holding the rebuild lock is the SAME kind of skip, not a
|
|
61
|
+
* failure (#956 fix): a live `akm index` run owns bringing the index to the
|
|
62
|
+
* expected state on its own, so this call returns `true` on that skip
|
|
63
|
+
* exactly like the absent-index and empty-index cases above. Callers that
|
|
64
|
+
* gate their own success on this boolean (`acceptProposal`, `source clone`)
|
|
65
|
+
* must never fail or warn just because a concurrent rebuild is in progress.
|
|
125
66
|
*/
|
|
126
67
|
export async function indexWrittenAssets(stashDir, filePaths, options = {}) {
|
|
127
68
|
try {
|
|
128
69
|
return await (async () => {
|
|
70
|
+
// #956: a live `akm index` rebuild holds index.db under one long
|
|
71
|
+
// transaction (persistDirRecords), which this fast path's own 5s
|
|
72
|
+
// busy_timeout would just contend with pointlessly. Interactive
|
|
73
|
+
// commands (remember, import, extract session assets) must never wait
|
|
74
|
+
// on it — skip the inline upsert/embedding entirely; the write itself
|
|
75
|
+
// (file + commit) has already succeeded by the time this runs, and the
|
|
76
|
+
// rebuild in progress will pick up the change on its own.
|
|
77
|
+
const rebuildProbe = probeLock(getIndexRebuildLockPath());
|
|
78
|
+
if (rebuildProbe.state === "held") {
|
|
79
|
+
// #956: name the launcher pid alongside the holder pid when known —
|
|
80
|
+
// every process listing and task log shows the launcher's pid, not
|
|
81
|
+
// the bun/node child's.
|
|
82
|
+
const holderLabel = formatLockHolderPid({
|
|
83
|
+
pid: rebuildProbe.holderPid,
|
|
84
|
+
launcherPid: rebuildProbe.launcherPid ?? null,
|
|
85
|
+
});
|
|
86
|
+
warn(`index rebuild in progress (pid ${holderLabel}); the next index pass will index ${filePaths.join(", ")}`);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
129
89
|
const dbPath = getDbPath();
|
|
130
90
|
// `true` here means "the index is in the state the caller expects" — and
|
|
131
91
|
// `acceptProposal` advances its journal to `index-finalized` on the
|
|
@@ -134,54 +94,132 @@ export async function indexWrittenAssets(stashDir, filePaths, options = {}) {
|
|
|
134
94
|
// `openExistingDatabase` and surfaces as the honest `false` (#791).
|
|
135
95
|
if (isPathAbsent(dbPath))
|
|
136
96
|
return true;
|
|
137
|
-
// The full
|
|
138
|
-
//
|
|
139
|
-
//
|
|
97
|
+
// The full walk never descends into dot-directories (for example `.meta/`)
|
|
98
|
+
// — mirror that dot-segment skip here so this fast path indexes exactly
|
|
99
|
+
// what a full run would. Sensitive/
|
|
100
|
+
// infra abstention is the adapter's job now (see the `akmAdapter` note
|
|
101
|
+
// below), not a path pre-filter.
|
|
140
102
|
const files = filePaths.filter((f) => {
|
|
141
103
|
const rel = path.relative(stashDir, f);
|
|
142
104
|
return !rel.split(/[\\/]+/).some((segment) => segment.startsWith("."));
|
|
143
105
|
});
|
|
144
106
|
if (files.length === 0)
|
|
145
107
|
return true;
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
108
|
+
// Generate metadata BEFORE opening the DB so the write window stays
|
|
109
|
+
// short. One drain call per file keeps the entry↔path pairing exact and
|
|
110
|
+
// reuses the full-index recognize engine (F4a M-core-2 item 5): broken
|
|
111
|
+
// workflows drop, valid workflow docs are cached for the side-table upsert.
|
|
112
|
+
const component = deriveInstallations([
|
|
113
|
+
{ path: stashDir, writable: true, ...(options.bundleId ? { registryId: options.bundleId } : {}) },
|
|
114
|
+
])[0]?.components[0];
|
|
115
|
+
if (!component)
|
|
151
116
|
throw new Error(`Could not derive bundle provenance for ${stashDir}`);
|
|
117
|
+
const pairs = [];
|
|
118
|
+
const unindexable = new Set();
|
|
119
|
+
const rejectedConceptIds = new Set();
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
if (!fs.existsSync(file)) {
|
|
122
|
+
let authoredDanglingSymlink = false;
|
|
123
|
+
try {
|
|
124
|
+
authoredDanglingSymlink = fs.lstatSync(file).isSymbolicLink();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// A genuinely absent path has no source identity to preflight.
|
|
128
|
+
}
|
|
129
|
+
if (!authoredDanglingSymlink) {
|
|
130
|
+
unindexable.add(file);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const ctx = buildFileContext(stashDir, file);
|
|
135
|
+
// Hardcoded `akmAdapter` on purpose (owner ruling 2026-07-21): this
|
|
136
|
+
// write-path fast path only ever runs for assets a first-class akm
|
|
137
|
+
// mutation command just wrote into a managed akm source, so the akm
|
|
138
|
+
// adapter is always the right recognizer here — no per-component
|
|
139
|
+
// dispatch needed.
|
|
140
|
+
const drained = drainDirDocuments(akmAdapter, component, [ctx]);
|
|
141
|
+
for (const rejectedPath of drained.rejectedPaths)
|
|
142
|
+
unindexable.add(rejectedPath);
|
|
143
|
+
for (const conceptId of drained.rejectedConceptIds)
|
|
144
|
+
rejectedConceptIds.add(conceptId);
|
|
145
|
+
const entry = drained.entries[0];
|
|
146
|
+
// A broken workflow drains to zero entries and is treated as
|
|
147
|
+
// unindexable; valid peer sources have already compiled to source IR.
|
|
148
|
+
const conceptId = drained.conceptIdByFile.get(ctx.absPath);
|
|
149
|
+
if (entry && conceptId)
|
|
150
|
+
pairs.push({ file, entry, conceptId, contentHash: drained.hashByFile.get(ctx.absPath) });
|
|
151
|
+
else
|
|
152
|
+
unindexable.add(file);
|
|
153
|
+
}
|
|
152
154
|
const db = openExistingDatabase(dbPath);
|
|
153
155
|
try {
|
|
154
156
|
db.exec(`PRAGMA busy_timeout = ${WRITE_PATH_INDEX_BUSY_TIMEOUT_MS}`);
|
|
155
|
-
// An absent or empty index is skipped on purpose — bootstrap belongs
|
|
156
|
-
// to the first read (`ensureIndex`) or an explicit `akm index`.
|
|
157
157
|
if (getEntryCount(db) === 0)
|
|
158
158
|
return true;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
159
|
+
const targetEntryIds = new Set();
|
|
160
|
+
let mutated = false;
|
|
161
|
+
db.transaction(() => {
|
|
162
|
+
const unindexableEntryIds = new Set();
|
|
163
|
+
for (const file of unindexable) {
|
|
164
|
+
const rows = db
|
|
165
|
+
.prepare(`SELECT id FROM entries
|
|
166
|
+
WHERE file_path = ?
|
|
167
|
+
AND bundle_id = ?
|
|
168
|
+
AND adapter_id = ?`)
|
|
169
|
+
.all(file, component.id, component.adapter);
|
|
170
|
+
for (const row of rows)
|
|
171
|
+
unindexableEntryIds.add(row.id);
|
|
172
|
+
}
|
|
173
|
+
for (const conceptId of rejectedConceptIds) {
|
|
174
|
+
const itemRef = `${component.id}//${conceptId}`;
|
|
175
|
+
const rows = db
|
|
176
|
+
.prepare(`SELECT id FROM entries
|
|
177
|
+
WHERE bundle_id = ? AND adapter_id = ?
|
|
178
|
+
AND type = 'workflow'
|
|
179
|
+
AND (concept_id = ? OR item_ref = ?)`)
|
|
180
|
+
.all(component.id, component.adapter, conceptId, itemRef);
|
|
181
|
+
for (const row of rows)
|
|
182
|
+
unindexableEntryIds.add(row.id);
|
|
183
|
+
}
|
|
184
|
+
deleteEntriesByIds(db, [...unindexableEntryIds]);
|
|
185
|
+
mutated ||= unindexableEntryIds.size > 0;
|
|
186
|
+
for (const { file, entry, conceptId, contentHash } of pairs) {
|
|
187
|
+
let entryWithSize = entry;
|
|
188
|
+
try {
|
|
189
|
+
entryWithSize = { ...entry, fileSize: fs.statSync(file).size };
|
|
190
|
+
if (hasMarkdownFragmentContent(entry)) {
|
|
191
|
+
setMarkdownFragmentContent(entryWithSize, getMarkdownFragmentContent(entry));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// stat raced a delete — index without the size, like the full walk does.
|
|
196
|
+
}
|
|
197
|
+
// Real provenance (F4a M-core-2 item 5): populate item_ref/content_hash
|
|
198
|
+
// via the SAME derivation the full-index writer uses, so a write-path
|
|
199
|
+
// row is never a NULL-item_ref straggler.
|
|
200
|
+
const provenance = deriveEntryProvenance({ bundleId: component.id, componentId: component.id, adapterId: component.adapter }, entry.type, entry.name, conceptId);
|
|
201
|
+
// A materialized file has one current owner. If a targeted write is
|
|
202
|
+
// the ownership handoff (for example an explicitly named bundle
|
|
203
|
+
// replacing an earlier path-derived bootstrap identity), remove the
|
|
204
|
+
// superseded physical-path row before publishing the canonical ref.
|
|
205
|
+
const supersededIds = db
|
|
206
|
+
.prepare("SELECT id FROM entries WHERE file_path = ? AND item_ref <> ?")
|
|
207
|
+
.all(file, provenance.itemRef);
|
|
208
|
+
deleteEntriesByIds(db, supersededIds.map((row) => row.id));
|
|
209
|
+
targetEntryIds.add(upsertEntry(db, file, entryWithSize, buildSearchText(entry), provenance, contentHash));
|
|
210
|
+
mutated = true;
|
|
211
|
+
}
|
|
212
|
+
})();
|
|
213
|
+
if (mutated) {
|
|
169
214
|
const config = loadConfig();
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
catch (drainError) {
|
|
174
|
-
// Best-effort: the write is already lexically searchable via
|
|
175
|
-
// reconcilePaths above. The embedding queue is durable, so a
|
|
176
|
-
// failed drain here is not a failure of this call — see the
|
|
177
|
-
// return-contract note above.
|
|
178
|
-
warnVerbose("Write-path embedding drain skipped (vectors appear after the next drain):", drainError instanceof Error ? drainError.message : String(drainError));
|
|
215
|
+
await generateEmbeddingsForDb(db, config, () => { }, undefined, [...targetEntryIds]);
|
|
216
|
+
publishTargetedEmbeddingMeta(db, config);
|
|
179
217
|
}
|
|
180
|
-
return true;
|
|
181
218
|
}
|
|
182
219
|
finally {
|
|
183
220
|
closeDatabase(db);
|
|
184
221
|
}
|
|
222
|
+
return true;
|
|
185
223
|
})();
|
|
186
224
|
}
|
|
187
225
|
catch (error) {
|