akm-cli 0.9.0-beta.6 → 0.9.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +663 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +11 -3
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -14
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +220 -227
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +153 -147
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1083
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +1313 -1278
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +243 -599
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +55 -4
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +160 -7
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +32 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +510 -2537
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +212 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +7 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -882
- package/dist/commands/read/curate.js +419 -103
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +29 -41
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +15 -1
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +21 -4
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +389 -58
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +21 -18
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +770 -0
- package/dist/core/state-db.js +142 -1091
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +356 -780
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +103 -24
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +106 -0
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +291 -310
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +39 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +7 -2
- package/dist/integrations/agent/profiles.js +7 -1
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +69 -67
- package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
- package/dist/integrations/harnesses/claude/index.js +2 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +2 -0
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +98 -17
- package/dist/integrations/harnesses/types.js +1 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +57 -15
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +85 -14
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/text/helpers.js +29 -1
- package/dist/output/text/workflow.js +1 -0
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2718 -2354
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +891 -597
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +15 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/cli.js +1 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +43 -118
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +5 -4
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
* Shared `entries`-row projection + mapper, extracted from indexer/db/db.ts.
|
|
6
|
+
*
|
|
7
|
+
* Centralizes the one canonical `entries` SELECT column list and the
|
|
8
|
+
* JSON-parse-guarded row → {@link DbIndexedEntry} mapping that several queries
|
|
9
|
+
* used to reimplement. Corrupt `entry_json` rows are skipped (warn once) rather
|
|
10
|
+
* than crashing the caller.
|
|
11
|
+
*/
|
|
12
|
+
import { warn } from "../../core/warn.js";
|
|
13
|
+
/**
|
|
14
|
+
* Canonical column list for reading a full indexed entry from the `entries`
|
|
15
|
+
* table, in the order {@link rowToIndexedEntry} expects.
|
|
16
|
+
*/
|
|
17
|
+
export const ENTRY_COLUMNS = "id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text";
|
|
18
|
+
/**
|
|
19
|
+
* Map one raw `entries` row to a {@link DbIndexedEntry}, parsing `entry_json`.
|
|
20
|
+
* Returns `null` (and warns, tagged with `context`) when the JSON is corrupt so
|
|
21
|
+
* callers can skip the row instead of crashing.
|
|
22
|
+
*/
|
|
23
|
+
export function rowToIndexedEntry(row, context) {
|
|
24
|
+
let entry;
|
|
25
|
+
try {
|
|
26
|
+
entry = JSON.parse(row.entry_json);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
warn(`[db] ${context}: skipping entry id=${row.id} — corrupt entry_json`);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
id: row.id,
|
|
34
|
+
entryKey: row.entry_key,
|
|
35
|
+
dirPath: row.dir_path,
|
|
36
|
+
filePath: row.file_path,
|
|
37
|
+
stashDir: row.stash_dir,
|
|
38
|
+
entry,
|
|
39
|
+
searchText: row.search_text,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { rethrowIfTestIsolationError } from "../../core/errors.js";
|
|
6
6
|
import { getDbPath } from "../../core/paths.js";
|
|
7
|
-
import { warn } from "../../core/warn.js";
|
|
8
7
|
import { closeDatabase, openExistingDatabase } from "./db.js";
|
|
9
8
|
function withReadableGraphDb(db, fn) {
|
|
10
9
|
if (db)
|
|
@@ -26,38 +25,16 @@ function uniqueSorted(values) {
|
|
|
26
25
|
function normalizeEntity(value) {
|
|
27
26
|
return value.trim().toLowerCase();
|
|
28
27
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Resolve a file_path within a stash to its entries.id. Returns null when the
|
|
31
|
-
* path has no indexed entry (orphan graph row).
|
|
32
|
-
*/
|
|
33
|
-
export function resolveEntryIdForPath(db, stashRoot, filePath) {
|
|
34
|
-
try {
|
|
35
|
-
const row = db
|
|
36
|
-
.prepare("SELECT id FROM entries WHERE stash_dir = ? AND file_path = ? LIMIT 1")
|
|
37
|
-
.get(stashRoot, filePath);
|
|
38
|
-
if (row)
|
|
39
|
-
return row.id;
|
|
40
|
-
// Fall back to file_path-only match (legacy callers may pass a stash root
|
|
41
|
-
// that doesn't exactly match entries.stash_dir, e.g. trailing-slash diffs).
|
|
42
|
-
const fallback = db.prepare("SELECT id FROM entries WHERE file_path = ? LIMIT 1").get(filePath);
|
|
43
|
-
return fallback?.id ?? null;
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
28
|
/**
|
|
50
29
|
* Persist (or update) a graph snapshot for a stash root.
|
|
51
30
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* Orphan files (no entries row resolvable) are skipped and counted in a
|
|
60
|
-
* single warn() so the caller sees the magnitude without log spam.
|
|
31
|
+
* #624-P1: keyed on (stash_root, file_path, body_hash) — NOT entries.id. Graph
|
|
32
|
+
* rows are self-keyed by path, so they survive an entries delete + reinsert
|
|
33
|
+
* (a reindex) when body_hash is unchanged. Unchanged files (matching body_hash)
|
|
34
|
+
* only have their file-meta refreshed; files whose body_hash changed have their
|
|
35
|
+
* old row + child rows deleted and the new content inserted; files in DB but
|
|
36
|
+
* absent from the new snapshot are deleted. There is no entry_id resolution and
|
|
37
|
+
* no orphan-skip — a graph file no longer needs a matching entries row.
|
|
61
38
|
*/
|
|
62
39
|
export function replaceStoredGraph(db, graph) {
|
|
63
40
|
const upsertMeta = db.prepare(`INSERT INTO graph_meta (
|
|
@@ -98,87 +75,146 @@ export function replaceStoredGraph(db, graph) {
|
|
|
98
75
|
cache_misses = excluded.cache_misses,
|
|
99
76
|
truncation_count = excluded.truncation_count,
|
|
100
77
|
failure_count = excluded.failure_count`);
|
|
101
|
-
const selectExisting = db.prepare("SELECT
|
|
102
|
-
const deleteFile = db.prepare("DELETE FROM graph_files WHERE
|
|
103
|
-
const deleteEntities = db.prepare("DELETE FROM graph_file_entities WHERE
|
|
104
|
-
const deleteRelations = db.prepare("DELETE FROM graph_file_relations WHERE
|
|
78
|
+
const selectExisting = db.prepare("SELECT file_path, body_hash, file_order FROM graph_files WHERE stash_root = ?");
|
|
79
|
+
const deleteFile = db.prepare("DELETE FROM graph_files WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
80
|
+
const deleteEntities = db.prepare("DELETE FROM graph_file_entities WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
81
|
+
const deleteRelations = db.prepare("DELETE FROM graph_file_relations WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
|
|
105
82
|
const insertFile = db.prepare(`INSERT INTO graph_files (
|
|
106
|
-
|
|
107
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
83
|
+
stash_root, file_path, file_order, file_type, body_hash, confidence, status, reason, extraction_run_id
|
|
84
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
108
85
|
const updateFileMeta = db.prepare(`UPDATE graph_files
|
|
109
86
|
SET file_order = ?, file_type = ?, confidence = ?, status = ?, reason = ?, extraction_run_id = ?
|
|
110
|
-
WHERE
|
|
111
|
-
const insertEntity = db.prepare(`INSERT INTO graph_file_entities (
|
|
112
|
-
VALUES (?, ?, ?, ?, ?)`);
|
|
87
|
+
WHERE stash_root = ? AND file_path = ? AND body_hash = ?`);
|
|
88
|
+
const insertEntity = db.prepare(`INSERT INTO graph_file_entities (stash_root, file_path, body_hash, entity_order, entity_norm, entity)
|
|
89
|
+
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
113
90
|
const insertRelation = db.prepare(`INSERT INTO graph_file_relations (
|
|
114
|
-
|
|
115
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
91
|
+
stash_root, file_path, body_hash, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence
|
|
92
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
116
93
|
const quality = graph.quality;
|
|
117
94
|
const telemetry = graph.telemetry;
|
|
118
95
|
db.transaction(() => {
|
|
119
96
|
upsertMeta.run(graph.stashRoot, graph.schemaVersion, graph.generatedAt, quality?.consideredFiles ?? graph.files.length, quality?.extractedFiles ?? graph.files.length, quality?.entityCount ?? graph.entities?.length ?? 0, quality?.relationCount ?? graph.relations?.length ?? 0, quality?.extractionCoverage ?? 0, quality?.density ?? 0, telemetry?.extractorId ?? null, telemetry?.extractionRunId ?? null, telemetry?.model ?? null, telemetry?.promptVersion ?? null, telemetry?.batchSize ?? null, telemetry?.cacheHits ?? 0, telemetry?.cacheMisses ?? 0, telemetry?.truncationCount ?? 0, telemetry?.failureCount ?? 0);
|
|
120
|
-
// Build a snapshot of existing rows for incremental compare.
|
|
97
|
+
// Build a snapshot of existing rows for incremental compare. The unique
|
|
98
|
+
// index idx_graph_files_path guarantees at most one row per file_path.
|
|
121
99
|
const existingRows = selectExisting.all(graph.stashRoot);
|
|
122
100
|
const existingByPath = new Map();
|
|
123
101
|
for (const row of existingRows)
|
|
124
102
|
existingByPath.set(row.file_path, row);
|
|
125
|
-
|
|
126
|
-
const presentEntryIds = new Set();
|
|
103
|
+
const presentPaths = new Set();
|
|
127
104
|
for (const [fileOrder, node] of graph.files.entries()) {
|
|
128
|
-
// body_hash is
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
105
|
+
// body_hash is part of the PK; default to a sentinel for inputs (test
|
|
106
|
+
// fixtures, legacy imports) that don't supply one. The sentinel never
|
|
107
|
+
// equals a real hash so subsequent staleness checks always re-extract —
|
|
108
|
+
// correct behaviour for "unknown" bodies. Distinct files in one stash
|
|
109
|
+
// are still keyed apart by file_path, so the empty sentinel is safe.
|
|
132
110
|
const bodyHash = node.bodyHash && node.bodyHash.length > 0 ? node.bodyHash : "";
|
|
133
|
-
|
|
134
|
-
if (entryId == null) {
|
|
135
|
-
orphanCount += 1;
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
presentEntryIds.add(entryId);
|
|
111
|
+
presentPaths.add(node.path);
|
|
139
112
|
const existing = existingByPath.get(node.path);
|
|
140
|
-
if (existing && existing.
|
|
113
|
+
if (existing && existing.body_hash === bodyHash) {
|
|
141
114
|
// Body unchanged — only fix up file_order/confidence in case they drifted.
|
|
142
|
-
updateFileMeta.run(fileOrder, node.type, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null,
|
|
115
|
+
updateFileMeta.run(fileOrder, node.type, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null, graph.stashRoot, node.path, bodyHash);
|
|
143
116
|
continue;
|
|
144
117
|
}
|
|
145
118
|
if (existing) {
|
|
146
|
-
// Stale row (different body_hash
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
deleteEntities.run(existing.
|
|
150
|
-
deleteRelations.run(existing.
|
|
151
|
-
deleteFile.run(existing.
|
|
119
|
+
// Stale row (different body_hash for this path). Delete the old row by
|
|
120
|
+
// its OLD body_hash; child rows cascade, but explicit DELETE keeps the
|
|
121
|
+
// order deterministic and is safe regardless of the FK pragma.
|
|
122
|
+
deleteEntities.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
123
|
+
deleteRelations.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
124
|
+
deleteFile.run(graph.stashRoot, existing.file_path, existing.body_hash);
|
|
152
125
|
}
|
|
153
|
-
insertFile.run(
|
|
126
|
+
insertFile.run(graph.stashRoot, node.path, fileOrder, node.type, bodyHash, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null);
|
|
154
127
|
for (const [entityOrder, entity] of node.entities.entries()) {
|
|
155
|
-
insertEntity.run(
|
|
128
|
+
insertEntity.run(graph.stashRoot, node.path, bodyHash, entityOrder, normalizeEntity(entity), entity);
|
|
156
129
|
}
|
|
157
130
|
for (const [relationOrder, relation] of node.relations.entries()) {
|
|
158
|
-
insertRelation.run(
|
|
131
|
+
insertRelation.run(graph.stashRoot, node.path, bodyHash, relationOrder, normalizeEntity(relation.from), relation.from, normalizeEntity(relation.to), relation.to, relation.type ?? null, relation.confidence ?? null);
|
|
159
132
|
}
|
|
160
133
|
}
|
|
161
134
|
// Delete files present in DB but absent from the new snapshot. Child
|
|
162
|
-
// tables CASCADE on
|
|
135
|
+
// tables CASCADE on the composite key; explicit DELETE keeps it determinstic.
|
|
163
136
|
for (const row of existingRows) {
|
|
164
|
-
if (!
|
|
165
|
-
deleteEntities.run(row.
|
|
166
|
-
deleteRelations.run(row.
|
|
167
|
-
deleteFile.run(row.
|
|
137
|
+
if (!presentPaths.has(row.file_path)) {
|
|
138
|
+
deleteEntities.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
139
|
+
deleteRelations.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
140
|
+
deleteFile.run(graph.stashRoot, row.file_path, row.body_hash);
|
|
168
141
|
}
|
|
169
142
|
}
|
|
170
|
-
if (orphanCount > 0) {
|
|
171
|
-
warn(`[graph] replaceStoredGraph: skipped ${orphanCount} file(s) with no resolvable entry under ${graph.stashRoot}.`);
|
|
172
|
-
}
|
|
173
143
|
})();
|
|
174
144
|
}
|
|
175
145
|
export function deleteStoredGraph(db, stashPath) {
|
|
176
146
|
db.transaction(() => {
|
|
177
|
-
// Child rows cascade via
|
|
147
|
+
// Child rows cascade via the composite (stash_root, file_path, body_hash)
|
|
148
|
+
// FK; deleting graph_files clears them. This is the explicit full-clear
|
|
149
|
+
// path for a stash (entries-delete no longer wipes graph data — see #624-P1).
|
|
178
150
|
db.prepare("DELETE FROM graph_files WHERE stash_root = ?").run(stashPath);
|
|
179
151
|
db.prepare("DELETE FROM graph_meta WHERE stash_root = ?").run(stashPath);
|
|
180
152
|
})();
|
|
181
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* #624-P1 — does any graph data exist for a file_path under a stash root?
|
|
156
|
+
* Consumed by show/curate flows (P3) but defined here so the schema and its
|
|
157
|
+
* accessors land together.
|
|
158
|
+
*/
|
|
159
|
+
export function hasGraphData(db, stashRoot, filePath) {
|
|
160
|
+
try {
|
|
161
|
+
const row = db
|
|
162
|
+
.prepare("SELECT 1 AS present FROM graph_files WHERE stash_root = ? AND file_path = ? LIMIT 1")
|
|
163
|
+
.get(stashRoot, filePath);
|
|
164
|
+
return row !== undefined;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* #624-P3 — enqueue a file for lazy graph extraction. Idempotent on the
|
|
172
|
+
* (stash_root, file_path) PK: a second enqueue refreshes body_hash + queued_at
|
|
173
|
+
* and keeps the HIGHER priority. Non-blocking, no LLM call — the queued row is
|
|
174
|
+
* drained later by the graph-extraction pass. Tolerant of a missing table /
|
|
175
|
+
* db error (best-effort), but never masks the bun-test isolation guard.
|
|
176
|
+
*/
|
|
177
|
+
export function enqueueGraphExtraction(db, stashRoot, filePath, bodyHash, priority = 0) {
|
|
178
|
+
try {
|
|
179
|
+
db.prepare(`INSERT INTO graph_extraction_queue (stash_root, file_path, body_hash, priority)
|
|
180
|
+
VALUES (?, ?, ?, ?)
|
|
181
|
+
ON CONFLICT(stash_root, file_path) DO UPDATE SET
|
|
182
|
+
body_hash = excluded.body_hash,
|
|
183
|
+
priority = MAX(graph_extraction_queue.priority, excluded.priority),
|
|
184
|
+
queued_at = datetime('now')`).run(stashRoot, filePath, bodyHash, priority);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
rethrowIfTestIsolationError(err);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* #624-P3 — drain up to `limit` queued files for a stash, highest-priority
|
|
192
|
+
* first (then oldest queued_at). The returned rows are DELETED from the queue
|
|
193
|
+
* in the SAME transaction (SELECT-then-DELETE-by-PK), so a drain is exactly
|
|
194
|
+
* once. Tolerant of a missing table / db error (returns []), but never masks
|
|
195
|
+
* the bun-test isolation guard.
|
|
196
|
+
*/
|
|
197
|
+
export function drainExtractionQueue(db, stashRoot, limit) {
|
|
198
|
+
try {
|
|
199
|
+
return db.transaction(() => {
|
|
200
|
+
const rows = db
|
|
201
|
+
.prepare(`SELECT file_path, body_hash, priority
|
|
202
|
+
FROM graph_extraction_queue
|
|
203
|
+
WHERE stash_root = ?
|
|
204
|
+
ORDER BY priority DESC, queued_at ASC
|
|
205
|
+
LIMIT ?`)
|
|
206
|
+
.all(stashRoot, limit);
|
|
207
|
+
const del = db.prepare("DELETE FROM graph_extraction_queue WHERE stash_root = ? AND file_path = ?");
|
|
208
|
+
for (const row of rows)
|
|
209
|
+
del.run(stashRoot, row.file_path);
|
|
210
|
+
return rows.map((row) => ({ filePath: row.file_path, bodyHash: row.body_hash, priority: row.priority }));
|
|
211
|
+
})();
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
rethrowIfTestIsolationError(err);
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
182
218
|
/**
|
|
183
219
|
* Scoped loader — only the graph_meta row for a stash. Used by callers that
|
|
184
220
|
* only need summary numbers (e.g. `akm graph summary`).
|
|
@@ -195,13 +231,12 @@ export function loadGraphFilesOnly(stashPath, db) {
|
|
|
195
231
|
return withReadableGraphDb(db, (readDb) => {
|
|
196
232
|
try {
|
|
197
233
|
const rows = readDb
|
|
198
|
-
.prepare(`SELECT
|
|
234
|
+
.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason
|
|
199
235
|
FROM graph_files
|
|
200
236
|
WHERE stash_root = ?
|
|
201
237
|
ORDER BY file_order`)
|
|
202
238
|
.all(stashPath);
|
|
203
239
|
return rows.map((row) => ({
|
|
204
|
-
entryId: row.entry_id,
|
|
205
240
|
path: row.file_path,
|
|
206
241
|
type: row.file_type,
|
|
207
242
|
bodyHash: row.body_hash,
|
|
@@ -222,13 +257,16 @@ export function loadGraphFilesOnly(stashPath, db) {
|
|
|
222
257
|
}
|
|
223
258
|
}
|
|
224
259
|
/**
|
|
225
|
-
* Scoped loader — entities for a single
|
|
260
|
+
* Scoped loader — entities for a single file, keyed on the #624-P1 composite
|
|
261
|
+
* (stash_root, file_path, body_hash). Used by per-asset show/curate lookups.
|
|
226
262
|
*/
|
|
227
|
-
export function
|
|
263
|
+
export function loadGraphEntitiesByPath(db, stashRoot, filePath, bodyHash) {
|
|
228
264
|
try {
|
|
229
265
|
const rows = db
|
|
230
|
-
.prepare(
|
|
231
|
-
|
|
266
|
+
.prepare(`SELECT entity FROM graph_file_entities
|
|
267
|
+
WHERE stash_root = ? AND file_path = ? AND body_hash = ?
|
|
268
|
+
ORDER BY entity_order`)
|
|
269
|
+
.all(stashRoot, filePath, bodyHash);
|
|
232
270
|
return rows.map((r) => r.entity);
|
|
233
271
|
}
|
|
234
272
|
catch {
|
|
@@ -314,27 +352,32 @@ export function loadStoredGraphSnapshot(stashPath, db) {
|
|
|
314
352
|
return null;
|
|
315
353
|
try {
|
|
316
354
|
const fileRows = readDb
|
|
317
|
-
.prepare(`SELECT
|
|
355
|
+
.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason, extraction_run_id
|
|
318
356
|
FROM graph_files
|
|
319
357
|
WHERE stash_root = ?
|
|
320
358
|
ORDER BY file_order`)
|
|
321
359
|
.all(stashPath);
|
|
322
360
|
const entityRows = readDb
|
|
323
|
-
.prepare(`SELECT
|
|
361
|
+
.prepare(`SELECT gf.file_path AS file_path, gfe.entity AS entity
|
|
324
362
|
FROM graph_file_entities gfe
|
|
325
|
-
JOIN graph_files gf
|
|
363
|
+
JOIN graph_files gf
|
|
364
|
+
ON gf.stash_root = gfe.stash_root
|
|
365
|
+
AND gf.file_path = gfe.file_path
|
|
366
|
+
AND gf.body_hash = gfe.body_hash
|
|
326
367
|
WHERE gf.stash_root = ?
|
|
327
368
|
ORDER BY gf.file_order, gfe.entity_order`)
|
|
328
369
|
.all(stashPath);
|
|
329
370
|
const relationRows = readDb
|
|
330
|
-
.prepare(`SELECT
|
|
331
|
-
gf.file_path AS file_path,
|
|
371
|
+
.prepare(`SELECT gf.file_path AS file_path,
|
|
332
372
|
gfr.from_entity AS from_entity,
|
|
333
373
|
gfr.to_entity AS to_entity,
|
|
334
374
|
gfr.relation_type AS relation_type,
|
|
335
375
|
gfr.confidence AS confidence
|
|
336
376
|
FROM graph_file_relations gfr
|
|
337
|
-
JOIN graph_files gf
|
|
377
|
+
JOIN graph_files gf
|
|
378
|
+
ON gf.stash_root = gfr.stash_root
|
|
379
|
+
AND gf.file_path = gfr.file_path
|
|
380
|
+
AND gf.body_hash = gfr.body_hash
|
|
338
381
|
WHERE gf.stash_root = ?
|
|
339
382
|
ORDER BY gf.file_order, gfr.relation_order`)
|
|
340
383
|
.all(stashPath);
|
|
@@ -26,8 +26,8 @@ import { computeBodyHash, getLlmCacheEntry, upsertLlmCacheEntry } from "./db.js"
|
|
|
26
26
|
* the wrapper skips its internal hashing — callers that
|
|
27
27
|
* already hashed the body (e.g. to reuse it elsewhere)
|
|
28
28
|
* should pass this to avoid the redundant work.
|
|
29
|
-
* @param cacheVariant - Namespace token for the cache row
|
|
30
|
-
*
|
|
29
|
+
* @param cacheVariant - Namespace token for the cache row so different passes'
|
|
30
|
+
* rows do not collide (e.g. memory-inference vs graph).
|
|
31
31
|
* @param hooks - Optional event sink for telemetry (see {@link WithLlmCacheHooks}).
|
|
32
32
|
*/
|
|
33
33
|
export async function withLlmCache(db, cacheKey, body, reEnrich, llmFn, validate, precomputedHash, cacheVariant = "", hooks) {
|