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
|
@@ -2,21 +2,29 @@
|
|
|
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
|
-
* Auto-index: silently
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `akm index
|
|
5
|
+
* Auto-index bootstrap: silently build the local index inline when it cannot
|
|
6
|
+
* serve the caller's stash at all (missing DB, no `entries` table, zero rows,
|
|
7
|
+
* or built for a different stash), so `search`, `show`, and `feedback` work
|
|
8
|
+
* on first use without a manual `akm index`.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Content FRESHNESS is intentionally not this module's job on the read path.
|
|
11
|
+
* Writers maintain the index (`indexWrittenAssets` for `remember`/extract
|
|
12
|
+
* session assets; the mutation commands run `akmIndex()` themselves), and the
|
|
13
|
+
* improve cron / explicit `akm index` do full refreshes. Reads serve whatever
|
|
14
|
+
* populated index exists. The previous design — a staleness walk plus a
|
|
15
|
+
* detached background reindex per read — made every read on an actively
|
|
16
|
+
* written stash spawn a writer that the read's own telemetry then queued
|
|
17
|
+
* behind (see docs/design/read-path-reindex-contention-findings.md).
|
|
18
|
+
*
|
|
19
|
+
* `mode: "blocking"` (improve) still checks staleness and rebuilds inline,
|
|
20
|
+
* because its planning logic needs a current `entries` table in-process.
|
|
13
21
|
*/
|
|
14
22
|
import fs from "node:fs";
|
|
15
23
|
import path from "node:path";
|
|
16
24
|
import { ASSET_SPECS, TYPE_DIRS } from "../core/asset/asset-spec.js";
|
|
17
25
|
import { getDbPath } from "../core/paths.js";
|
|
18
26
|
import { warn } from "../core/warn.js";
|
|
19
|
-
import { closeDatabase, getEntryCount, getMeta, openExistingDatabase } from "./db/db.js";
|
|
27
|
+
import { closeDatabase, getEntryCount, getIndexedFilePaths, getMeta, openExistingDatabase } from "./db/db.js";
|
|
20
28
|
function getIndexableFiles(root, spec) {
|
|
21
29
|
if (!fs.existsSync(root))
|
|
22
30
|
return [];
|
|
@@ -52,16 +60,34 @@ function getIndexableFiles(root, spec) {
|
|
|
52
60
|
}
|
|
53
61
|
return files;
|
|
54
62
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Whether any indexable file under `stashDir` is newer than the last build, or
|
|
65
|
+
* has never been indexed at all.
|
|
66
|
+
*
|
|
67
|
+
* Two independent signals, because neither alone is sufficient:
|
|
68
|
+
* 1. **mtime > builtAt** — catches in-place *edits* of already-indexed files.
|
|
69
|
+
* 2. **path not in `indexedPaths`** — catches *newly added* files. This is
|
|
70
|
+
* clock-independent on purpose: a freshly-written file can have a
|
|
71
|
+
* filesystem mtime that compares as *older* than the wall-clock `builtAt`
|
|
72
|
+
* (the two clocks are not perfectly synchronized and `builtAt` is
|
|
73
|
+
* millisecond-truncated), so the mtime test alone silently misses
|
|
74
|
+
* additions made within ~a millisecond of the previous build.
|
|
75
|
+
*
|
|
76
|
+
* `getIndexableFiles` applies each asset type's own relevance filter, so
|
|
77
|
+
* non-indexed companion files (e.g. `package.json` next to a knowledge doc) are
|
|
78
|
+
* never considered and do not produce false "new file" positives.
|
|
79
|
+
*/
|
|
80
|
+
function hasNewerIndexableFiles(stashDir, builtAt, indexedPaths) {
|
|
81
|
+
const builtAtMs = builtAt ? new Date(builtAt).getTime() : Number.NaN;
|
|
82
|
+
const builtAtUsable = Number.isFinite(builtAtMs);
|
|
61
83
|
for (const [type, spec] of Object.entries(ASSET_SPECS)) {
|
|
62
84
|
const typeRoot = path.join(stashDir, TYPE_DIRS[type] ?? spec.stashDir);
|
|
63
85
|
const files = getIndexableFiles(typeRoot, spec);
|
|
64
86
|
for (const file of files) {
|
|
87
|
+
if (!indexedPaths.has(file))
|
|
88
|
+
return true;
|
|
89
|
+
if (!builtAtUsable)
|
|
90
|
+
return true;
|
|
65
91
|
try {
|
|
66
92
|
if (fs.statSync(file).mtimeMs > builtAtMs)
|
|
67
93
|
return true;
|
|
@@ -89,7 +115,7 @@ export function isIndexStale(stashDir) {
|
|
|
89
115
|
if (entryCount === 0)
|
|
90
116
|
return true;
|
|
91
117
|
const builtAt = getMeta(db, "builtAt");
|
|
92
|
-
if (hasNewerIndexableFiles(stashDir, builtAt))
|
|
118
|
+
if (hasNewerIndexableFiles(stashDir, builtAt, getIndexedFilePaths(db)))
|
|
93
119
|
return true;
|
|
94
120
|
const storedStashDir = getMeta(db, "stashDir");
|
|
95
121
|
if (storedStashDir !== stashDir) {
|
|
@@ -114,16 +140,44 @@ export function isIndexStale(stashDir) {
|
|
|
114
140
|
}
|
|
115
141
|
}
|
|
116
142
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
143
|
+
* Whether the existing index can serve queries for `stashDir` *right now* —
|
|
144
|
+
* i.e. the DB file exists, the `entries` table holds rows, and those rows were
|
|
145
|
+
* built for this stash (it is the stored primary stash or appears in the
|
|
146
|
+
* stored `stashDirs` set). When this is true the index is at worst
|
|
147
|
+
* content-stale, so read paths serve it as-is. When it is false the existing
|
|
148
|
+
* index has nothing relevant to return (no DB, no `entries` table, zero rows,
|
|
149
|
+
* or built for a different stash), so those cases must rebuild inline.
|
|
123
150
|
*/
|
|
124
|
-
|
|
125
|
-
|
|
151
|
+
function indexCanServeStash(stashDir) {
|
|
152
|
+
const dbPath = getDbPath();
|
|
153
|
+
if (!fs.existsSync(dbPath))
|
|
154
|
+
return false;
|
|
155
|
+
let db;
|
|
156
|
+
try {
|
|
157
|
+
db = openExistingDatabase(dbPath);
|
|
158
|
+
if (getEntryCount(db) === 0)
|
|
159
|
+
return false;
|
|
160
|
+
const storedStashDir = getMeta(db, "stashDir");
|
|
161
|
+
if (storedStashDir === stashDir)
|
|
162
|
+
return true;
|
|
163
|
+
try {
|
|
164
|
+
const storedDirs = JSON.parse(getMeta(db, "stashDirs") ?? "[]");
|
|
165
|
+
return storedDirs.includes(stashDir);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// No `entries` table (or otherwise unreadable) — cannot serve.
|
|
126
173
|
return false;
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
if (db)
|
|
177
|
+
closeDatabase(db);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function runInlineReindex(stashDir) {
|
|
127
181
|
try {
|
|
128
182
|
const { akmIndex } = await import("./indexer.js");
|
|
129
183
|
await akmIndex({ stashDir });
|
|
@@ -131,6 +185,31 @@ export async function ensureIndex(stashDir) {
|
|
|
131
185
|
}
|
|
132
186
|
catch (error) {
|
|
133
187
|
warn("Auto-index failed, proceeding with existing index:", error instanceof Error ? error.message : String(error));
|
|
134
|
-
return
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Ensure the local index exists and can serve the caller.
|
|
193
|
+
*
|
|
194
|
+
* Default mode is `background` — the read-path contract (`search`, `show`,
|
|
195
|
+
* `feedback`): a populated index built for this stash is served as-is (its
|
|
196
|
+
* freshness is the writers' job, see module doc); an unusable index rebuilds
|
|
197
|
+
* inline, since there is nothing to proceed against.
|
|
198
|
+
*
|
|
199
|
+
* `mode: "blocking"` additionally treats content-staleness as a rebuild
|
|
200
|
+
* trigger and waits for it. Use this for callers like `improve` whose
|
|
201
|
+
* planning logic depends on a current `entries` table in the same process.
|
|
202
|
+
*
|
|
203
|
+
* Returns `true` only when an inline index run succeeds.
|
|
204
|
+
* A rebuild attempt that fails (throws) resolves to `false`.
|
|
205
|
+
*/
|
|
206
|
+
export async function ensureIndex(stashDir, options = {}) {
|
|
207
|
+
if (options.mode === "blocking") {
|
|
208
|
+
if (!isIndexStale(stashDir))
|
|
209
|
+
return false;
|
|
210
|
+
return runInlineReindex(stashDir);
|
|
135
211
|
}
|
|
212
|
+
if (indexCanServeStash(stashDir))
|
|
213
|
+
return false;
|
|
214
|
+
return runInlineReindex(stashDir);
|
|
136
215
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
* MemRL feedback → utility policy, extracted from indexer/db/db.ts.
|
|
6
|
+
*
|
|
7
|
+
* This is the domain/policy math (arXiv:2601.03192) that decides how a batch of
|
|
8
|
+
* positive/negative feedback signals moves an asset's utility score. It is pure
|
|
9
|
+
* — no database access — so the bounded-step behaviour is unit-testable in
|
|
10
|
+
* isolation; the DB read/write stays with `applyFeedbackToUtilityScore` in db.ts.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* MemRL learning rate for feedback-driven utility updates (F-5 / #386).
|
|
14
|
+
*
|
|
15
|
+
* Follows the bounded-step formula from MemRL (arXiv:2601.03192):
|
|
16
|
+
* next = clamp(current + lr × (reward − current), 0, 1)
|
|
17
|
+
*
|
|
18
|
+
* This replaces the unbounded `-0.03 × negativeCount` delta that could
|
|
19
|
+
* silently remove high-utility assets from the improvement loop.
|
|
20
|
+
*/
|
|
21
|
+
export const FEEDBACK_LR = 0.1;
|
|
22
|
+
/**
|
|
23
|
+
* Positive reward signal for a single positive feedback event.
|
|
24
|
+
* Reward 1.0 means "fully correct / helpful".
|
|
25
|
+
*/
|
|
26
|
+
const FEEDBACK_REWARD_POSITIVE = 1.0;
|
|
27
|
+
/**
|
|
28
|
+
* Negative reward signal for a single negative feedback event.
|
|
29
|
+
* Reward 0.0 means "not helpful" (lowest MemRL signal).
|
|
30
|
+
*/
|
|
31
|
+
const FEEDBACK_REWARD_NEGATIVE = 0.0;
|
|
32
|
+
/**
|
|
33
|
+
* Utility threshold below which a review-needed escalation is triggered.
|
|
34
|
+
* When a previously high-utility asset (≥ HIGH_UTILITY_THRESHOLD) drops
|
|
35
|
+
* below this value, the caller should create an escalation proposal.
|
|
36
|
+
*/
|
|
37
|
+
export const UTILITY_REVIEW_THRESHOLD = 0.5;
|
|
38
|
+
/**
|
|
39
|
+
* Utility level considered "high" — assets above this are tracked for
|
|
40
|
+
* threshold-crossing escalation.
|
|
41
|
+
*/
|
|
42
|
+
export const HIGH_UTILITY_THRESHOLD = 0.5;
|
|
43
|
+
/**
|
|
44
|
+
* Compute the next utility from accumulated feedback counts using the MemRL
|
|
45
|
+
* bounded-step EMA formula (F-5 / #386, arXiv:2601.03192):
|
|
46
|
+
*
|
|
47
|
+
* reward = weighted average of positive and negative signals
|
|
48
|
+
* nextUtil = clamp(currentUtil + lr × (reward − currentUtil), 0, 1)
|
|
49
|
+
*
|
|
50
|
+
* The step is inherently bounded: reward ∈ [0, 1] and currentUtil ∈ [0, 1], so
|
|
51
|
+
* a single call moves utility by at most {@link FEEDBACK_LR} in either
|
|
52
|
+
* direction. `reward` is a proportion of the counts, not their magnitude, so
|
|
53
|
+
* with no positive signals the number of negatives is irrelevant (reward is 0
|
|
54
|
+
* whether there is 1 negative or 100). Mixing in positives shifts reward and so
|
|
55
|
+
* the step, but never past the learning-rate bound.
|
|
56
|
+
*
|
|
57
|
+
* Pure: no DB access. When both counts are zero, utility is unchanged.
|
|
58
|
+
*/
|
|
59
|
+
export function computeNextUtility(previousUtility, positiveCount, negativeCount) {
|
|
60
|
+
if (positiveCount === 0 && negativeCount === 0) {
|
|
61
|
+
return { previousUtility, nextUtility: previousUtility, crossedReviewThreshold: false };
|
|
62
|
+
}
|
|
63
|
+
const total = positiveCount + negativeCount;
|
|
64
|
+
// Weighted reward: proportion of positive signals.
|
|
65
|
+
const reward = positiveCount > 0 && negativeCount === 0
|
|
66
|
+
? FEEDBACK_REWARD_POSITIVE
|
|
67
|
+
: negativeCount > 0 && positiveCount === 0
|
|
68
|
+
? FEEDBACK_REWARD_NEGATIVE
|
|
69
|
+
: (positiveCount * FEEDBACK_REWARD_POSITIVE + negativeCount * FEEDBACK_REWARD_NEGATIVE) / total;
|
|
70
|
+
// MemRL bounded-step EMA: lr × (reward − current). |delta| ≤ FEEDBACK_LR.
|
|
71
|
+
const delta = FEEDBACK_LR * (reward - previousUtility);
|
|
72
|
+
const nextUtility = Math.max(0, Math.min(1, previousUtility + delta));
|
|
73
|
+
const crossedReviewThreshold = previousUtility >= HIGH_UTILITY_THRESHOLD && nextUtility < UTILITY_REVIEW_THRESHOLD;
|
|
74
|
+
return { previousUtility, nextUtility, crossedReviewThreshold };
|
|
75
|
+
}
|
|
@@ -241,13 +241,18 @@ export function collectGraphRelatedHit(context, filePath) {
|
|
|
241
241
|
* Find graph files that share entities with the given file.
|
|
242
242
|
*
|
|
243
243
|
* Implementation: SQL self-join on graph_file_entities, scoped by stash_root,
|
|
244
|
-
* grouped by
|
|
244
|
+
* grouped by file_path, ordered by shared-entity count desc. Touches ~50-200
|
|
245
245
|
* rows instead of loading the entire snapshot into memory. Cold-call latency
|
|
246
246
|
* drops from ~30-60ms (full snapshot parse) to ~2-5ms on typical stashes.
|
|
247
247
|
*
|
|
248
|
+
* #624-P1: the graph tables are keyed on (stash_root, file_path, body_hash) —
|
|
249
|
+
* NOT entries.id — so candidates are identified by file_path (the unique index
|
|
250
|
+
* idx_graph_files_path guarantees one graph_files row per path).
|
|
251
|
+
*
|
|
248
252
|
* The returned `ref` field carries the canonical asset ref (`type:name`)
|
|
249
|
-
* resolved from entries.entry_key when the
|
|
250
|
-
* fall back to formatting `path` when `ref` is undefined (
|
|
253
|
+
* resolved from entries.entry_key when the file is indexed. Callers should
|
|
254
|
+
* fall back to formatting `path` when `ref` is undefined (graph row with no
|
|
255
|
+
* matching entries row).
|
|
251
256
|
*/
|
|
252
257
|
export function listRelatedPathsForFile(stashRoot, filePath, limit = 5, db) {
|
|
253
258
|
if (!db) {
|
|
@@ -255,113 +260,118 @@ export function listRelatedPathsForFile(stashRoot, filePath, limit = 5, db) {
|
|
|
255
260
|
// callers pass a handle), so degrade to empty rather than reopening.
|
|
256
261
|
return [];
|
|
257
262
|
}
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
let targetEntryId;
|
|
263
|
+
// Confirm the target file has a graph row; without it there is nothing to
|
|
264
|
+
// relate. (Identity is file_path within the stash — one row per path.)
|
|
261
265
|
try {
|
|
262
266
|
const row = db
|
|
263
|
-
.prepare("SELECT
|
|
267
|
+
.prepare("SELECT 1 AS present FROM graph_files WHERE stash_root = ? AND file_path = ? LIMIT 1")
|
|
264
268
|
.get(stashRoot, filePath);
|
|
265
|
-
|
|
269
|
+
if (row === undefined)
|
|
270
|
+
return [];
|
|
266
271
|
}
|
|
267
272
|
catch {
|
|
268
273
|
return [];
|
|
269
274
|
}
|
|
270
|
-
if (targetEntryId == null)
|
|
271
|
-
return [];
|
|
272
275
|
const effectiveLimit = Math.max(1, limit);
|
|
273
|
-
// Shared-entity count per candidate
|
|
276
|
+
// Shared-entity count per candidate file_path. The target's entities are the
|
|
277
|
+
// rows for `filePath`; candidates are any OTHER file_path in the stash that
|
|
278
|
+
// shares a normalized entity.
|
|
274
279
|
let candidateRows;
|
|
275
280
|
try {
|
|
276
281
|
candidateRows = db
|
|
277
|
-
.prepare(`SELECT gf.
|
|
278
|
-
gf.file_path AS file_path,
|
|
282
|
+
.prepare(`SELECT gf.file_path AS file_path,
|
|
279
283
|
gf.file_type AS file_type,
|
|
280
284
|
COUNT(*) AS shared
|
|
281
285
|
FROM graph_file_entities target
|
|
282
286
|
JOIN graph_file_entities e
|
|
283
287
|
ON e.stash_root = target.stash_root
|
|
284
288
|
AND e.entity_norm = target.entity_norm
|
|
285
|
-
AND e.
|
|
289
|
+
AND e.file_path != target.file_path
|
|
286
290
|
JOIN graph_files gf
|
|
287
|
-
ON gf.
|
|
288
|
-
|
|
291
|
+
ON gf.stash_root = e.stash_root
|
|
292
|
+
AND gf.file_path = e.file_path
|
|
293
|
+
AND gf.body_hash = e.body_hash
|
|
294
|
+
WHERE target.file_path = ?
|
|
289
295
|
AND target.stash_root = ?
|
|
290
|
-
GROUP BY gf.
|
|
296
|
+
GROUP BY gf.file_path
|
|
291
297
|
ORDER BY shared DESC, gf.file_path ASC
|
|
292
298
|
LIMIT ?`)
|
|
293
|
-
.all(
|
|
299
|
+
.all(filePath, stashRoot, effectiveLimit);
|
|
294
300
|
}
|
|
295
301
|
catch {
|
|
296
302
|
return [];
|
|
297
303
|
}
|
|
298
304
|
if (candidateRows.length === 0)
|
|
299
305
|
return [];
|
|
300
|
-
const
|
|
301
|
-
const placeholders =
|
|
306
|
+
const candidatePaths = candidateRows.map((r) => r.file_path);
|
|
307
|
+
const placeholders = candidatePaths.map(() => "?").join(",");
|
|
302
308
|
// Pull the shared entity names (joined by normalized casing) for display.
|
|
303
309
|
const sharedRows = db
|
|
304
|
-
.prepare(`SELECT e.
|
|
310
|
+
.prepare(`SELECT e.file_path AS file_path, e.entity AS entity
|
|
305
311
|
FROM graph_file_entities e
|
|
306
312
|
JOIN graph_file_entities target
|
|
307
313
|
ON target.stash_root = e.stash_root
|
|
308
314
|
AND target.entity_norm = e.entity_norm
|
|
309
|
-
WHERE e.
|
|
310
|
-
AND
|
|
315
|
+
WHERE e.file_path IN (${placeholders})
|
|
316
|
+
AND e.stash_root = ?
|
|
317
|
+
AND target.file_path = ?
|
|
311
318
|
AND target.stash_root = ?`)
|
|
312
|
-
.all(...
|
|
313
|
-
const
|
|
319
|
+
.all(...candidatePaths, stashRoot, filePath, stashRoot);
|
|
320
|
+
const sharedByPath = new Map();
|
|
314
321
|
for (const row of sharedRows) {
|
|
315
|
-
let bucket =
|
|
322
|
+
let bucket = sharedByPath.get(row.file_path);
|
|
316
323
|
if (!bucket) {
|
|
317
324
|
bucket = new Set();
|
|
318
|
-
|
|
325
|
+
sharedByPath.set(row.file_path, bucket);
|
|
319
326
|
}
|
|
320
327
|
bucket.add(row.entity);
|
|
321
328
|
}
|
|
322
329
|
// Relation count for each candidate (relations where either endpoint
|
|
323
330
|
// matches one of the shared entities).
|
|
324
|
-
const
|
|
331
|
+
const relationCountByPath = new Map();
|
|
325
332
|
const relationRows = db
|
|
326
|
-
.prepare(`SELECT
|
|
333
|
+
.prepare(`SELECT file_path, from_entity, to_entity
|
|
327
334
|
FROM graph_file_relations
|
|
328
|
-
WHERE
|
|
329
|
-
|
|
335
|
+
WHERE file_path IN (${placeholders})
|
|
336
|
+
AND stash_root = ?`)
|
|
337
|
+
.all(...candidatePaths, stashRoot);
|
|
330
338
|
for (const row of relationRows) {
|
|
331
|
-
const shared =
|
|
339
|
+
const shared = sharedByPath.get(row.file_path);
|
|
332
340
|
if (!shared)
|
|
333
341
|
continue;
|
|
334
342
|
if (shared.has(row.from_entity) || shared.has(row.to_entity)) {
|
|
335
|
-
|
|
343
|
+
relationCountByPath.set(row.file_path, (relationCountByPath.get(row.file_path) ?? 0) + 1);
|
|
336
344
|
}
|
|
337
345
|
}
|
|
338
346
|
// Optional: ref lookup via entries.entry_key. entry_key is stored as
|
|
339
347
|
// `${stash_dir}:${type}:${name}` — strip the stash-dir prefix to get the
|
|
340
|
-
// user-facing `type:name`.
|
|
341
|
-
|
|
348
|
+
// user-facing `type:name`. Resolve by (stash_dir, file_path) now that the
|
|
349
|
+
// graph rows are no longer keyed on entries.id.
|
|
350
|
+
const refByPath = new Map();
|
|
342
351
|
try {
|
|
343
352
|
const entryRows = db
|
|
344
|
-
.prepare(`SELECT
|
|
345
|
-
|
|
353
|
+
.prepare(`SELECT entry_key, stash_dir, file_path FROM entries
|
|
354
|
+
WHERE file_path IN (${placeholders}) AND stash_dir = ?`)
|
|
355
|
+
.all(...candidatePaths, stashRoot);
|
|
346
356
|
for (const row of entryRows) {
|
|
347
357
|
const ref = stripStashPrefix(row.entry_key, row.stash_dir);
|
|
348
358
|
if (ref)
|
|
349
|
-
|
|
359
|
+
refByPath.set(row.file_path, ref);
|
|
350
360
|
}
|
|
351
361
|
}
|
|
352
362
|
catch {
|
|
353
363
|
/* ignore — refs are best-effort */
|
|
354
364
|
}
|
|
355
365
|
return candidateRows.map((row) => {
|
|
356
|
-
const sharedSet =
|
|
366
|
+
const sharedSet = sharedByPath.get(row.file_path) ?? new Set();
|
|
357
367
|
const sharedEntities = [...sharedSet].sort((a, b) => a.localeCompare(b));
|
|
358
|
-
const ref =
|
|
368
|
+
const ref = refByPath.get(row.file_path);
|
|
359
369
|
return {
|
|
360
370
|
...(ref ? { ref } : {}),
|
|
361
371
|
path: row.file_path,
|
|
362
372
|
type: row.file_type,
|
|
363
373
|
sharedEntities,
|
|
364
|
-
relationCount:
|
|
374
|
+
relationCount: relationCountByPath.get(row.file_path) ?? 0,
|
|
365
375
|
};
|
|
366
376
|
});
|
|
367
377
|
}
|