akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4
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 +756 -0
- package/dist/akm +94 -196
- package/dist/cli/shared.js +6 -2
- package/dist/cli.js +22 -9
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +2 -2
- package/dist/commands/health/checks.js +30 -75
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +0 -38
- package/dist/commands/health/improve-metrics.js +123 -562
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +11 -106
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -73
- package/dist/commands/health.js +122 -143
- package/dist/commands/improve/consolidate/chunking.js +25 -100
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +538 -1075
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1075
- package/dist/commands/improve/eligibility.js +126 -400
- package/dist/commands/improve/execution.js +3 -5
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +29 -166
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +12 -7
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +443 -1063
- package/dist/commands/improve/ledger.js +114 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +459 -1172
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +14 -118
- package/dist/commands/improve/memory/memory-improve.js +4 -3
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -10
- package/dist/commands/improve/preparation.js +851 -2339
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +621 -1367
- package/dist/commands/improve/salience.js +46 -232
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +323 -0
- package/dist/commands/proposal/drain.js +251 -644
- package/dist/commands/proposal/proposal-cli.js +3 -18
- package/dist/commands/proposal/proposal-types.js +20 -41
- package/dist/commands/proposal/proposal.js +1 -2
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +502 -1487
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +13 -89
- package/dist/commands/read/curate.js +63 -413
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +2 -13
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +7 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +2 -11
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +9 -8
- package/dist/commands/tasks/tasks.js +326 -930
- package/dist/commands/tasks/validate.js +42 -21
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +0 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -12
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +291 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +1 -33
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +163 -68
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +21 -95
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-lock.js +76 -173
- package/dist/core/logs-db.js +2 -2
- package/dist/core/paths.js +0 -27
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +108 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +293 -1012
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/graph-db.js +106 -46
- package/dist/indexer/ensure-index.js +44 -85
- package/dist/indexer/graph/graph-extraction.js +340 -562
- package/dist/indexer/graph/graph-related.js +130 -0
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +139 -151
- package/dist/indexer/indexer.js +524 -846
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +81 -90
- package/dist/indexer/passes/metadata.js +132 -200
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +1 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +181 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +2 -7
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +10 -15
- package/dist/llm/graph-extract.js +3 -12
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/metadata-enhance.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +2 -34
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +60 -23
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +1 -2
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +43937 -93313
- package/dist/scripts/akm-migrate.js +43697 -93071
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +0 -17
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +168 -0
- package/dist/storage/repositories/index-connection.js +34 -70
- package/dist/storage/repositories/index-entries-repository.js +69 -111
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +83 -269
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +192 -220
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +35 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +34 -27
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +129 -277
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +125 -745
- package/dist/tasks/backends/schtasks.js +101 -620
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/scheduler-binding.js +18 -238
- package/dist/tasks/scheduler-invocation.js +52 -52
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +363 -679
- package/dist/tasks/source/parse-task-source.js +160 -10
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +2 -2
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +41 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
- package/docs/reference/cli.md +182 -125
- package/docs/reference/configuration.md +49 -56
- package/docs/reference/data-and-telemetry.md +19 -20
- package/docs/reference/tasks.md +86 -38
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +87 -406
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -146
- package/dist/commands/improve/distill/promote-memory.js +0 -329
- package/dist/commands/improve/distill/quality-gate.js +0 -500
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -21
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/config/retired-experimental-keys-shim.js +0 -62
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -78
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Find graph files that share entities with the given file.
|
|
6
|
+
*
|
|
7
|
+
* Implementation: SQL self-join on graph_file_entities, scoped by stash_root,
|
|
8
|
+
* grouped by file_path, ordered by shared-entity count desc. Touches ~50-200
|
|
9
|
+
* rows instead of loading the entire snapshot into memory. Cold-call latency
|
|
10
|
+
* drops from ~30-60ms (full snapshot parse) to ~2-5ms on typical stashes.
|
|
11
|
+
*
|
|
12
|
+
* #624-P1: the graph tables are keyed on (stash_root, file_path, body_hash) —
|
|
13
|
+
* NOT entries.id — so candidates are identified by file_path (the unique index
|
|
14
|
+
* idx_graph_files_path guarantees one graph_files row per path).
|
|
15
|
+
*
|
|
16
|
+
* The returned `ref` field carries the canonical indexed `concept_id`, never a
|
|
17
|
+
* value re-derived from presentation fields. It is undefined
|
|
18
|
+
* when the graph row has no matching entry with current indexed provenance.
|
|
19
|
+
*/
|
|
20
|
+
export function listRelatedPathsForFile(stashRoot, filePath, limit = 5, db) {
|
|
21
|
+
if (!db) {
|
|
22
|
+
// Fallback: opening a transient DB here is not currently a use case (all
|
|
23
|
+
// callers pass a handle), so degrade to empty rather than reopening.
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
// Confirm the target file has a graph row; without it there is nothing to
|
|
27
|
+
// relate. (Identity is file_path within the stash — one row per path.)
|
|
28
|
+
const row = db
|
|
29
|
+
.prepare("SELECT 1 AS present FROM graph_files WHERE stash_root = ? AND file_path = ? LIMIT 1")
|
|
30
|
+
.get(stashRoot, filePath);
|
|
31
|
+
if (row === undefined)
|
|
32
|
+
return [];
|
|
33
|
+
const effectiveLimit = Math.max(1, limit);
|
|
34
|
+
// Shared-entity count per candidate file_path. The target's entities are the
|
|
35
|
+
// rows for `filePath`; candidates are any OTHER file_path in the stash that
|
|
36
|
+
// shares a normalized entity.
|
|
37
|
+
const candidateRows = db
|
|
38
|
+
.prepare(`SELECT gf.file_path AS file_path,
|
|
39
|
+
gf.file_type AS file_type,
|
|
40
|
+
COUNT(*) AS shared
|
|
41
|
+
FROM graph_file_entities target
|
|
42
|
+
JOIN graph_file_entities e
|
|
43
|
+
ON e.stash_root = target.stash_root
|
|
44
|
+
AND e.entity_norm = target.entity_norm
|
|
45
|
+
AND e.file_path != target.file_path
|
|
46
|
+
JOIN graph_files gf
|
|
47
|
+
ON gf.stash_root = e.stash_root
|
|
48
|
+
AND gf.file_path = e.file_path
|
|
49
|
+
AND gf.body_hash = e.body_hash
|
|
50
|
+
WHERE target.file_path = ?
|
|
51
|
+
AND target.stash_root = ?
|
|
52
|
+
GROUP BY gf.file_path
|
|
53
|
+
ORDER BY shared DESC, gf.file_path ASC
|
|
54
|
+
LIMIT ?`)
|
|
55
|
+
.all(filePath, stashRoot, effectiveLimit);
|
|
56
|
+
if (candidateRows.length === 0)
|
|
57
|
+
return [];
|
|
58
|
+
const candidatePaths = candidateRows.map((r) => r.file_path);
|
|
59
|
+
const placeholders = candidatePaths.map(() => "?").join(",");
|
|
60
|
+
// Pull the shared entity names (joined by normalized casing) for display, in
|
|
61
|
+
// a fixed order so identical calls return identical results.
|
|
62
|
+
const sharedRows = db
|
|
63
|
+
.prepare(`SELECT e.file_path AS file_path, e.entity AS entity
|
|
64
|
+
FROM graph_file_entities e
|
|
65
|
+
JOIN graph_file_entities target
|
|
66
|
+
ON target.stash_root = e.stash_root
|
|
67
|
+
AND target.entity_norm = e.entity_norm
|
|
68
|
+
WHERE e.file_path IN (${placeholders})
|
|
69
|
+
AND e.stash_root = ?
|
|
70
|
+
AND target.file_path = ?
|
|
71
|
+
AND target.stash_root = ?
|
|
72
|
+
ORDER BY e.file_path, e.entity`)
|
|
73
|
+
.all(...candidatePaths, stashRoot, filePath, stashRoot);
|
|
74
|
+
const sharedByPath = new Map();
|
|
75
|
+
for (const row of sharedRows) {
|
|
76
|
+
let bucket = sharedByPath.get(row.file_path);
|
|
77
|
+
if (!bucket) {
|
|
78
|
+
bucket = new Set();
|
|
79
|
+
sharedByPath.set(row.file_path, bucket);
|
|
80
|
+
}
|
|
81
|
+
bucket.add(row.entity);
|
|
82
|
+
}
|
|
83
|
+
// Relation count for each candidate (relations where either endpoint
|
|
84
|
+
// matches one of the shared entities).
|
|
85
|
+
const relationCountByPath = new Map();
|
|
86
|
+
const relationRows = db
|
|
87
|
+
.prepare(`SELECT file_path, from_entity, to_entity
|
|
88
|
+
FROM graph_file_relations
|
|
89
|
+
WHERE file_path IN (${placeholders})
|
|
90
|
+
AND stash_root = ?`)
|
|
91
|
+
.all(...candidatePaths, stashRoot);
|
|
92
|
+
for (const row of relationRows) {
|
|
93
|
+
const shared = sharedByPath.get(row.file_path);
|
|
94
|
+
if (!shared)
|
|
95
|
+
continue;
|
|
96
|
+
if (shared.has(row.from_entity) || shared.has(row.to_entity)) {
|
|
97
|
+
relationCountByPath.set(row.file_path, (relationCountByPath.get(row.file_path) ?? 0) + 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// This related list is scoped to one source root, so the user-facing ref is
|
|
101
|
+
// the indexed bundle-less conceptId.
|
|
102
|
+
const refByPath = new Map();
|
|
103
|
+
try {
|
|
104
|
+
const entryRows = db
|
|
105
|
+
.prepare(`SELECT file_path, concept_id FROM entries
|
|
106
|
+
WHERE file_path IN (${placeholders})
|
|
107
|
+
ORDER BY file_path, concept_id`)
|
|
108
|
+
.all(...candidatePaths);
|
|
109
|
+
// Several entries can index one file; the first concept id wins.
|
|
110
|
+
for (const row of entryRows) {
|
|
111
|
+
if (!refByPath.has(row.file_path))
|
|
112
|
+
refByPath.set(row.file_path, row.concept_id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* ignore — refs are best-effort */
|
|
117
|
+
}
|
|
118
|
+
return candidateRows.map((row) => {
|
|
119
|
+
const sharedSet = sharedByPath.get(row.file_path) ?? new Set();
|
|
120
|
+
const sharedEntities = [...sharedSet].sort((a, b) => a.localeCompare(b));
|
|
121
|
+
const ref = refByPath.get(row.file_path);
|
|
122
|
+
return {
|
|
123
|
+
...(ref ? { ref } : {}),
|
|
124
|
+
path: row.file_path,
|
|
125
|
+
type: row.file_type,
|
|
126
|
+
sharedEntities,
|
|
127
|
+
relationCount: relationCountByPath.get(row.file_path) ?? 0,
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
* no-stale-age-window rationale.
|
|
21
21
|
*/
|
|
22
22
|
import { releaseLock } from "../core/file-lock.js";
|
|
23
|
-
import { tryWithMaintenanceStartBarrier, withMaintenanceStartBarrier } from "../core/maintenance-barrier.js";
|
|
24
23
|
import { getIndexRebuildLockPath } from "../core/paths.js";
|
|
25
24
|
import { formatLockHolderPid, tryAcquireRunLock } from "../core/run-lock.js";
|
|
26
25
|
import { warn, warnVerbose } from "../core/warn.js";
|
|
@@ -49,21 +48,14 @@ export function tryAcquireIndexRebuildLock(skipIfLocked) {
|
|
|
49
48
|
`(${info.reason}); that process is no longer running.`);
|
|
50
49
|
},
|
|
51
50
|
});
|
|
51
|
+
const result = acquire();
|
|
52
|
+
if (result.state === "acquired")
|
|
53
|
+
return result;
|
|
52
54
|
if (skipIfLocked) {
|
|
53
|
-
const result = tryWithMaintenanceStartBarrier(acquire);
|
|
54
|
-
if (!result) {
|
|
55
|
-
warn("[index] maintenance barrier held; skipping (--skip-if-locked)");
|
|
56
|
-
return { state: "skipped", holder: { pid: null, startedAt: null, launcherPid: null } };
|
|
57
|
-
}
|
|
58
|
-
if (result.state === "acquired")
|
|
59
|
-
return result;
|
|
60
55
|
warn(`[index] another index run holds the lock (PID ${formatLockHolderPid(result.holder)}, started ${result.holder.startedAt}); ` +
|
|
61
56
|
"skipping (--skip-if-locked)");
|
|
62
57
|
return { state: "skipped", holder: result.holder };
|
|
63
58
|
}
|
|
64
|
-
const result = withMaintenanceStartBarrier(acquire);
|
|
65
|
-
if (result.state === "acquired")
|
|
66
|
-
return result;
|
|
67
59
|
warn(`[index] another index run is active (pid ${formatLockHolderPid(result.holder)}, started ${result.holder.startedAt}); ` +
|
|
68
60
|
"this run will contend with it — pass --skip-if-locked for scheduled runs");
|
|
69
61
|
return { state: "contended", holder: result.holder };
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
26
26
|
import fs from "node:fs";
|
|
27
27
|
import path from "node:path";
|
|
28
|
+
import { TransientError } from "../core/errors.js";
|
|
28
29
|
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../core/file-lock.js";
|
|
29
|
-
import { tryAcquireMaintenanceBarrier } from "../core/maintenance-barrier.js";
|
|
30
30
|
import { getDbPath, getIndexWriterLockPath } from "../core/paths.js";
|
|
31
31
|
import { warn } from "../core/warn.js";
|
|
32
32
|
import { sleepSync } from "../runtime.js";
|
|
@@ -92,21 +92,12 @@ function createLease(lockPath, ownership) {
|
|
|
92
92
|
}
|
|
93
93
|
function tryAcquireAssetMutationLease(lockPath, purpose) {
|
|
94
94
|
while (true) {
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
95
|
+
const ownership = tryAcquireLockSync(lockPath, buildPayload(purpose));
|
|
96
|
+
if (ownership)
|
|
97
|
+
return createLease(lockPath, ownership);
|
|
98
|
+
const probe = probeLock(lockPath);
|
|
99
|
+
if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
|
|
97
100
|
return undefined;
|
|
98
|
-
try {
|
|
99
|
-
const ownership = tryAcquireLockSync(lockPath, buildPayload(purpose));
|
|
100
|
-
if (ownership)
|
|
101
|
-
return createLease(lockPath, ownership);
|
|
102
|
-
// No `staleAfterMs`: only a verifiably dead holder is ever reclaimed.
|
|
103
|
-
const probe = probeLock(lockPath);
|
|
104
|
-
if (probe.state !== "stale" || !reclaimStaleLock(lockPath, probe))
|
|
105
|
-
return undefined;
|
|
106
|
-
}
|
|
107
|
-
finally {
|
|
108
|
-
releaseBarrier();
|
|
109
|
-
}
|
|
110
101
|
}
|
|
111
102
|
}
|
|
112
103
|
export async function acquireAssetMutationLease(options) {
|
|
@@ -129,7 +120,7 @@ export async function acquireAssetMutationLease(options) {
|
|
|
129
120
|
// attempt, so a caller with maxWaitMs:0 still gets one chance at a free lock
|
|
130
121
|
// instead of throwing before it ever tries.
|
|
131
122
|
if (maxWaitMs >= 0 && Date.now() - startedAt >= maxWaitMs) {
|
|
132
|
-
throw new
|
|
123
|
+
throw new TransientError(`timed out waiting for asset mutation lease for ${options.purpose}`, "ASSET_MUTATION_LEASE_HELD");
|
|
133
124
|
}
|
|
134
125
|
const waitedMs = Date.now() - startedAt;
|
|
135
126
|
if (waitedMs - lastWaitNoticeMs >= 15000) {
|
|
@@ -180,7 +171,7 @@ export function withAssetMutationLeaseSync(purpose, run) {
|
|
|
180
171
|
if (!lease) {
|
|
181
172
|
const waitedMs = Date.now() - startedAt;
|
|
182
173
|
if (waitedMs >= maxWaitMs) {
|
|
183
|
-
throw new
|
|
174
|
+
throw new TransientError(`timed out waiting for asset mutation lease for ${purpose}`, "ASSET_MUTATION_LEASE_HELD");
|
|
184
175
|
}
|
|
185
176
|
// #956: the sync path used to wait up to 10 minutes with zero
|
|
186
177
|
// progress feedback. Mirror the async path's 15s onWait cadence, but
|
|
@@ -33,9 +33,8 @@ import { closeDatabase, openExistingDatabase } from "../storage/repositories/ind
|
|
|
33
33
|
import { deleteEntriesByIds, getEntryCount, upsertEntry } from "../storage/repositories/index-entries-repository.js";
|
|
34
34
|
import { deriveEntryProvenance, deriveInstallations } from "./installations.js";
|
|
35
35
|
import { generateEmbeddingsForDb, publishTargetedEmbeddingMeta } from "./materialize-embeddings.js";
|
|
36
|
-
import {
|
|
36
|
+
import { withFileSize } from "./passes/metadata.js";
|
|
37
37
|
import { drainDirDocuments } from "./scan/drain-dir.js";
|
|
38
|
-
import { buildSearchText } from "./search/search-fields.js";
|
|
39
38
|
import { buildFileContext } from "./walk/file-context.js";
|
|
40
39
|
/**
|
|
41
40
|
* Busy-timeout (ms) for write-path index upserts. A real write — unlike the
|
|
@@ -66,161 +65,150 @@ export const WRITE_PATH_INDEX_BUSY_TIMEOUT_MS = 5_000;
|
|
|
66
65
|
*/
|
|
67
66
|
export async function indexWrittenAssets(stashDir, filePaths, options = {}) {
|
|
68
67
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
}
|
|
89
|
-
const dbPath = getDbPath();
|
|
90
|
-
// `true` here means "the index is in the state the caller expects" — and
|
|
91
|
-
// `acceptProposal` advances its journal to `index-finalized` on the
|
|
92
|
-
// strength of it. Only a genuinely ABSENT index earns that answer: an
|
|
93
|
-
// index we cannot read has NOT been updated, so it falls through to
|
|
94
|
-
// `openExistingDatabase` and surfaces as the honest `false` (#791).
|
|
95
|
-
if (isPathAbsent(dbPath))
|
|
96
|
-
return true;
|
|
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.
|
|
102
|
-
const files = filePaths.filter((f) => {
|
|
103
|
-
const rel = path.relative(stashDir, f);
|
|
104
|
-
return !rel.split(/[\\/]+/).some((segment) => segment.startsWith("."));
|
|
68
|
+
// #956: a live `akm index` rebuild holds index.db under one long
|
|
69
|
+
// transaction (persistDirRecords), which this fast path's own 5s
|
|
70
|
+
// busy_timeout would just contend with pointlessly. Interactive
|
|
71
|
+
// commands (remember, import, extract session assets) must never wait
|
|
72
|
+
// on it — skip the inline upsert/embedding entirely; the write itself
|
|
73
|
+
// (file + commit) has already succeeded by the time this runs, and the
|
|
74
|
+
// rebuild in progress will pick up the change on its own.
|
|
75
|
+
const rebuildProbe = probeLock(getIndexRebuildLockPath());
|
|
76
|
+
if (rebuildProbe.state === "held") {
|
|
77
|
+
// #956: name the launcher pid alongside the holder pid when known —
|
|
78
|
+
// every process listing and task log shows the launcher's pid, not
|
|
79
|
+
// the bun/node child's.
|
|
80
|
+
const holderLabel = formatLockHolderPid({
|
|
81
|
+
pid: rebuildProbe.holderPid,
|
|
82
|
+
launcherPid: rebuildProbe.launcherPid ?? null,
|
|
105
83
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
84
|
+
warn(`index rebuild in progress (pid ${holderLabel}); the next index pass will index ${filePaths.join(", ")}`);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
const dbPath = getDbPath();
|
|
88
|
+
// `true` here means "the index is in the state the caller expects" — and
|
|
89
|
+
// `acceptProposal` advances its journal to `index-finalized` on the
|
|
90
|
+
// strength of it. Only a genuinely ABSENT index earns that answer: an
|
|
91
|
+
// index we cannot read has NOT been updated, so it falls through to
|
|
92
|
+
// `openExistingDatabase` and surfaces as the honest `false` (#791).
|
|
93
|
+
if (isPathAbsent(dbPath))
|
|
94
|
+
return true;
|
|
95
|
+
// The full walk never descends into dot-directories (for example `.meta/`)
|
|
96
|
+
// — mirror that dot-segment skip here so this fast path indexes exactly
|
|
97
|
+
// what a full run would. Sensitive/
|
|
98
|
+
// infra abstention is the adapter's job now (see the `akmAdapter` note
|
|
99
|
+
// below), not a path pre-filter.
|
|
100
|
+
const files = filePaths.filter((f) => {
|
|
101
|
+
const rel = path.relative(stashDir, f);
|
|
102
|
+
return !rel.split(/[\\/]+/).some((segment) => segment.startsWith("."));
|
|
103
|
+
});
|
|
104
|
+
if (files.length === 0)
|
|
105
|
+
return true;
|
|
106
|
+
// Generate metadata BEFORE opening the DB so the write window stays
|
|
107
|
+
// short. One drain call per file keeps the entry↔path pairing exact and
|
|
108
|
+
// reuses the full-index recognize engine (F4a M-core-2 item 5): broken
|
|
109
|
+
// workflows drop, valid workflow docs are cached for the side-table upsert.
|
|
110
|
+
const component = deriveInstallations([
|
|
111
|
+
{ path: stashDir, writable: true, ...(options.bundleId ? { registryId: options.bundleId } : {}) },
|
|
112
|
+
])[0]?.components[0];
|
|
113
|
+
if (!component)
|
|
114
|
+
throw new Error(`Could not derive bundle provenance for ${stashDir}`);
|
|
115
|
+
const pairs = [];
|
|
116
|
+
const unindexable = new Set();
|
|
117
|
+
const rejectedConceptIds = new Set();
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
if (!fs.existsSync(file)) {
|
|
120
|
+
let authoredDanglingSymlink = false;
|
|
121
|
+
try {
|
|
122
|
+
authoredDanglingSymlink = fs.lstatSync(file).isSymbolicLink();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// A genuinely absent path has no source identity to preflight.
|
|
133
126
|
}
|
|
134
|
-
|
|
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
|
|
127
|
+
if (!authoredDanglingSymlink) {
|
|
152
128
|
unindexable.add(file);
|
|
153
|
-
|
|
154
|
-
const db = openExistingDatabase(dbPath);
|
|
155
|
-
try {
|
|
156
|
-
db.exec(`PRAGMA busy_timeout = ${WRITE_PATH_INDEX_BUSY_TIMEOUT_MS}`);
|
|
157
|
-
if (getEntryCount(db) === 0)
|
|
158
|
-
return true;
|
|
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) {
|
|
214
|
-
const config = loadConfig();
|
|
215
|
-
await generateEmbeddingsForDb(db, config, () => { }, undefined, [...targetEntryIds]);
|
|
216
|
-
publishTargetedEmbeddingMeta(db, config);
|
|
129
|
+
continue;
|
|
217
130
|
}
|
|
218
131
|
}
|
|
219
|
-
|
|
220
|
-
|
|
132
|
+
const ctx = buildFileContext(stashDir, file);
|
|
133
|
+
// Hardcoded `akmAdapter` on purpose (owner ruling 2026-07-21): this
|
|
134
|
+
// write-path fast path only ever runs for assets a first-class akm
|
|
135
|
+
// mutation command just wrote into a managed akm source, so the akm
|
|
136
|
+
// adapter is always the right recognizer here — no per-component
|
|
137
|
+
// dispatch needed.
|
|
138
|
+
const drained = drainDirDocuments(akmAdapter, component, [ctx]);
|
|
139
|
+
for (const rejectedPath of drained.rejectedPaths)
|
|
140
|
+
unindexable.add(rejectedPath);
|
|
141
|
+
for (const conceptId of drained.rejectedConceptIds)
|
|
142
|
+
rejectedConceptIds.add(conceptId);
|
|
143
|
+
const entry = drained.entries[0];
|
|
144
|
+
// A broken workflow drains to zero entries and is treated as
|
|
145
|
+
// unindexable; valid peer sources have already compiled to source IR.
|
|
146
|
+
const conceptId = drained.conceptIdByFile.get(ctx.absPath);
|
|
147
|
+
if (entry && conceptId)
|
|
148
|
+
pairs.push({ file, entry, conceptId, contentHash: drained.hashByFile.get(ctx.absPath) });
|
|
149
|
+
else
|
|
150
|
+
unindexable.add(file);
|
|
151
|
+
}
|
|
152
|
+
const db = openExistingDatabase(dbPath);
|
|
153
|
+
try {
|
|
154
|
+
db.exec(`PRAGMA busy_timeout = ${WRITE_PATH_INDEX_BUSY_TIMEOUT_MS}`);
|
|
155
|
+
if (getEntryCount(db) === 0)
|
|
156
|
+
return true;
|
|
157
|
+
const targetEntryIds = new Set();
|
|
158
|
+
let mutated = false;
|
|
159
|
+
db.transaction(() => {
|
|
160
|
+
const unindexableEntryIds = new Set();
|
|
161
|
+
for (const file of unindexable) {
|
|
162
|
+
const rows = db
|
|
163
|
+
.prepare(`SELECT id FROM entries
|
|
164
|
+
WHERE file_path = ?
|
|
165
|
+
AND bundle_id = ?
|
|
166
|
+
AND adapter_id = ?`)
|
|
167
|
+
.all(file, component.id, component.adapter);
|
|
168
|
+
for (const row of rows)
|
|
169
|
+
unindexableEntryIds.add(row.id);
|
|
170
|
+
}
|
|
171
|
+
for (const conceptId of rejectedConceptIds) {
|
|
172
|
+
const itemRef = `${component.id}//${conceptId}`;
|
|
173
|
+
const rows = db
|
|
174
|
+
.prepare(`SELECT id FROM entries
|
|
175
|
+
WHERE bundle_id = ? AND adapter_id = ?
|
|
176
|
+
AND type = 'workflow'
|
|
177
|
+
AND (concept_id = ? OR item_ref = ?)`)
|
|
178
|
+
.all(component.id, component.adapter, conceptId, itemRef);
|
|
179
|
+
for (const row of rows)
|
|
180
|
+
unindexableEntryIds.add(row.id);
|
|
181
|
+
}
|
|
182
|
+
deleteEntriesByIds(db, [...unindexableEntryIds]);
|
|
183
|
+
mutated ||= unindexableEntryIds.size > 0;
|
|
184
|
+
for (const { file, entry, conceptId, contentHash } of pairs) {
|
|
185
|
+
const entryWithSize = withFileSize(entry, file);
|
|
186
|
+
// Real provenance (F4a M-core-2 item 5): populate item_ref/content_hash
|
|
187
|
+
// via the SAME derivation the full-index writer uses, so a write-path
|
|
188
|
+
// row is never a NULL-item_ref straggler.
|
|
189
|
+
const provenance = deriveEntryProvenance({ bundleId: component.id, componentId: component.id, adapterId: component.adapter }, entry.type, entry.name, conceptId);
|
|
190
|
+
// A materialized file has one current owner. If a targeted write is
|
|
191
|
+
// the ownership handoff (for example an explicitly named bundle
|
|
192
|
+
// replacing an earlier path-derived bootstrap identity), remove the
|
|
193
|
+
// superseded physical-path row before publishing the canonical ref.
|
|
194
|
+
const supersededIds = db
|
|
195
|
+
.prepare("SELECT id FROM entries WHERE file_path = ? AND item_ref <> ?")
|
|
196
|
+
.all(file, provenance.itemRef);
|
|
197
|
+
deleteEntriesByIds(db, supersededIds.map((row) => row.id));
|
|
198
|
+
targetEntryIds.add(upsertEntry(db, file, entryWithSize, provenance, contentHash));
|
|
199
|
+
mutated = true;
|
|
200
|
+
}
|
|
201
|
+
})();
|
|
202
|
+
if (mutated) {
|
|
203
|
+
const config = loadConfig();
|
|
204
|
+
await generateEmbeddingsForDb(db, config, () => { }, undefined, [...targetEntryIds]);
|
|
205
|
+
publishTargetedEmbeddingMeta(db, config);
|
|
221
206
|
}
|
|
222
|
-
|
|
223
|
-
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
closeDatabase(db);
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
224
212
|
}
|
|
225
213
|
catch (error) {
|
|
226
214
|
// A permission fault is the one failure the next full index will NOT heal,
|