akm-cli 0.9.0-beta.9 → 0.9.0-rc.1
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 +715 -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/prompts/workflow-unit-preamble.md +26 -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 -21
- 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-binding.js +95 -0
- package/dist/commands/env/env-cli.js +228 -292
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +156 -155
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +3 -3
- 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 -1091
- 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 +1295 -1277
- 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 +228 -605
- 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 +54 -3
- 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 +157 -10
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +28 -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 +485 -2764
- 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 +37 -35
- 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 +206 -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 +2 -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 -895
- package/dist/commands/read/curate.js +410 -111
- 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/migration-help.js +7 -4
- 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 +19 -39
- 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 +291 -13
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +79 -5
- 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 +403 -62
- 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/json-schema.js +142 -0
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +18 -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 +132 -1126
- 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 +261 -770
- 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 +36 -92
- 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 +18 -11
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +182 -204
- 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 +48 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +41 -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 +16 -2
- package/dist/integrations/agent/profiles.js +79 -6
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +83 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +206 -81
- package/dist/integrations/harnesses/aider/agent-builder.js +113 -0
- package/dist/integrations/harnesses/aider/index.js +58 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +153 -0
- package/dist/integrations/harnesses/amazonq/index.js +59 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -7
- package/dist/integrations/harnesses/claude/index.js +27 -23
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/claude/session-log.js +10 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +137 -0
- package/dist/integrations/harnesses/codex/index.js +63 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
- package/dist/integrations/harnesses/copilot/index.js +60 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +121 -0
- package/dist/integrations/harnesses/gemini/index.js +60 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +28 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +17 -16
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/harness.js +65 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +10 -34
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +642 -71
- package/dist/integrations/harnesses/openhands/agent-builder.js +126 -0
- package/dist/integrations/harnesses/openhands/index.js +58 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +104 -0
- package/dist/integrations/harnesses/pi/index.js +58 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -0
- package/dist/integrations/session-logs/index.js +40 -11
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +34 -11
- 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 +87 -15
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +6 -1
- package/dist/output/text/helpers.js +241 -2
- package/dist/output/text/workflow.js +4 -1
- 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/schemas/akm-config.json +14225 -0
- package/dist/schemas/akm-workflow.json +328 -0
- package/dist/scripts/migrate-storage.js +2743 -8390
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +1652 -607
- 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/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/repositories/workflow-runs-repository.js +189 -1
- 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 +1 -1
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +5 -0
- package/dist/workflows/db.js +138 -4
- package/dist/workflows/exec/brief.js +484 -0
- package/dist/workflows/exec/native-executor.js +975 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1295 -0
- package/dist/workflows/exec/run-workflow.js +596 -0
- package/dist/workflows/exec/scheduler.js +100 -0
- package/dist/workflows/exec/step-work.js +1156 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +388 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +33 -0
- package/dist/workflows/ir/schema.js +4 -0
- package/dist/workflows/parser.js +3 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +760 -0
- package/dist/workflows/program/project.js +105 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/runs.js +248 -153
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +188 -0
- package/dist/workflows/validate-summary.js +26 -10
- package/dist/workflows/validator.js +1 -1
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +7 -5
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- 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,114 @@
|
|
|
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
|
+
* Incremental dir-staleness engine.
|
|
6
|
+
*
|
|
7
|
+
* Decides, per stash directory, whether the directory's indexed rows are still
|
|
8
|
+
* fresh relative to what is on disk — so an incremental `akm index` run can
|
|
9
|
+
* skip unchanged directories instead of regenerating their metadata.
|
|
10
|
+
*
|
|
11
|
+
* Two persisted signals back the decision:
|
|
12
|
+
* 1. The `entries` rows already indexed for the directory (`getEntriesByDir`).
|
|
13
|
+
* 2. The `index_dir_state` fingerprint row (`getIndexDirState`), which caches
|
|
14
|
+
* the file-set hash + max mtime for directories that legitimately produced
|
|
15
|
+
* zero rows, so they are not rescanned every run.
|
|
16
|
+
*
|
|
17
|
+
* `computeDirFingerprint` derives the fingerprint (basename set + max mtime)
|
|
18
|
+
* that both the freshness check and the persisted `index_dir_state` row use.
|
|
19
|
+
*/
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { getEntriesByDir, getIndexDirState } from "../db/db.js";
|
|
23
|
+
export function getDirIndexState(db, dirPath, files, builtAtMs) {
|
|
24
|
+
const prevEntries = getEntriesByDir(db, dirPath);
|
|
25
|
+
const fingerprint = computeDirFingerprint(dirPath, files);
|
|
26
|
+
if (prevEntries.length > 0) {
|
|
27
|
+
const staleReason = getDirStaleReason(dirPath, files, prevEntries, builtAtMs);
|
|
28
|
+
if (!staleReason) {
|
|
29
|
+
return { stale: false, reason: { kind: "unchanged" }, persistedRowCount: prevEntries.length };
|
|
30
|
+
}
|
|
31
|
+
return { stale: true, reason: staleReason, persistedRowCount: prevEntries.length };
|
|
32
|
+
}
|
|
33
|
+
const cachedState = getIndexDirState(db, dirPath);
|
|
34
|
+
if (cachedState &&
|
|
35
|
+
cachedState.fileSetHash === fingerprint.fileSetHash &&
|
|
36
|
+
cachedState.fileMtimeMaxMs === fingerprint.fileMtimeMaxMs) {
|
|
37
|
+
return {
|
|
38
|
+
stale: false,
|
|
39
|
+
reason: { kind: "cached-zero-row-state", detail: cachedState.reason },
|
|
40
|
+
persistedRowCount: 0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
stale: true,
|
|
45
|
+
reason: { kind: "no-previous-rows", detail: cachedState ? `cached=${cachedState.reason}` : undefined },
|
|
46
|
+
persistedRowCount: 0,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function getCachedZeroRowDirState(db, dirPath, files, builtAtMs, priorDirsChanged) {
|
|
50
|
+
const state = getDirIndexState(db, dirPath, files, builtAtMs);
|
|
51
|
+
if (state.stale || state.reason.kind !== "cached-zero-row-state")
|
|
52
|
+
return undefined;
|
|
53
|
+
if (!canUseIncrementalSkip(state, priorDirsChanged))
|
|
54
|
+
return undefined;
|
|
55
|
+
return state;
|
|
56
|
+
}
|
|
57
|
+
export function canUseIncrementalSkip(state, priorDirsChanged) {
|
|
58
|
+
return !(priorDirsChanged &&
|
|
59
|
+
state.reason.kind === "cached-zero-row-state" &&
|
|
60
|
+
state.reason.detail === "deduped-zero-row");
|
|
61
|
+
}
|
|
62
|
+
export function computeDirFingerprint(_dirPath, files) {
|
|
63
|
+
const normalizedFiles = [...new Set(files.map((file) => path.basename(file)))].sort();
|
|
64
|
+
let fileMtimeMaxMs = 0;
|
|
65
|
+
for (const file of files) {
|
|
66
|
+
try {
|
|
67
|
+
fileMtimeMaxMs = Math.max(fileMtimeMaxMs, fs.statSync(file).mtimeMs);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
fileMtimeMaxMs = Number.POSITIVE_INFINITY;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
fileSetHash: normalizedFiles.join("\0"),
|
|
76
|
+
fileMtimeMaxMs,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function getDirStaleReason(_dirPath, currentFiles, previousEntries, builtAtMs) {
|
|
80
|
+
const prevFileNames = new Set(previousEntries
|
|
81
|
+
.map((ie) => {
|
|
82
|
+
const fromPath = path.basename(ie.filePath);
|
|
83
|
+
return fromPath || ie.entry.filename;
|
|
84
|
+
})
|
|
85
|
+
.filter((e) => !!e));
|
|
86
|
+
const currFileNames = new Set(currentFiles.map((f) => path.basename(f)));
|
|
87
|
+
if (prevFileNames.size !== currFileNames.size) {
|
|
88
|
+
return { kind: "file-set-changed", detail: `${prevFileNames.size} -> ${currFileNames.size} files` };
|
|
89
|
+
}
|
|
90
|
+
for (const name of currFileNames) {
|
|
91
|
+
if (!prevFileNames.has(name))
|
|
92
|
+
return { kind: "file-set-changed", detail: name };
|
|
93
|
+
}
|
|
94
|
+
for (const file of currentFiles) {
|
|
95
|
+
try {
|
|
96
|
+
if (fs.statSync(file).mtimeMs > builtAtMs)
|
|
97
|
+
return { kind: "mtime-changed", detail: path.basename(file) };
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return { kind: "missing-file", detail: path.basename(file) };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
export function inferZeroRowReason(stash, priorReason, warnings, dirPath, dedupedRows) {
|
|
106
|
+
if (dedupedRows > 0)
|
|
107
|
+
return "deduped-zero-row";
|
|
108
|
+
const workflowNoise = warnings.some((warning) => warning.startsWith("Skipped workflow ") && warning.includes(dirPath));
|
|
109
|
+
if (workflowNoise)
|
|
110
|
+
return "workflow-noise";
|
|
111
|
+
if (!stash || stash.entries.length === 0)
|
|
112
|
+
return "empty-generated-set";
|
|
113
|
+
return `zero-row:${priorReason?.kind ?? "unknown"}`;
|
|
114
|
+
}
|
|
@@ -72,6 +72,7 @@ const FM_CAPTURE_MODE = "captureMode";
|
|
|
72
72
|
*/
|
|
73
73
|
export async function runMemoryInferencePass(ctx) {
|
|
74
74
|
const { config, sources, signal, db, reEnrich, onProgress, options = {} } = ctx;
|
|
75
|
+
const compressMemoryToDerivedMemory = options.compressMemoryToDerivedMemory ?? memoryInfer.compressMemoryToDerivedMemory;
|
|
75
76
|
const result = {
|
|
76
77
|
considered: 0,
|
|
77
78
|
cacheHits: 0,
|
|
@@ -172,14 +173,14 @@ export async function runMemoryInferencePass(ctx) {
|
|
|
172
173
|
retryAttempts += 1;
|
|
173
174
|
};
|
|
174
175
|
const derived = db
|
|
175
|
-
? await withLlmCache(db, record.filePath, record.body, reEnrich ?? false, () =>
|
|
176
|
+
? await withLlmCache(db, record.filePath, record.body, reEnrich ?? false, () => compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
|
|
176
177
|
warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
|
|
177
178
|
}, inferTelemetry, onRetryAttempt), validate, undefined, "", {
|
|
178
179
|
onCacheHit: () => {
|
|
179
180
|
fromCache = true;
|
|
180
181
|
},
|
|
181
182
|
})
|
|
182
|
-
: await
|
|
183
|
+
: await compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
|
|
183
184
|
warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
|
|
184
185
|
}, inferTelemetry, onRetryAttempt);
|
|
185
186
|
if (!derived) {
|
|
@@ -425,10 +426,17 @@ function markParentProcessed(parent) {
|
|
|
425
426
|
warn(`memory inference: failed to re-read parent ${parent.filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
426
427
|
return;
|
|
427
428
|
}
|
|
428
|
-
const updatedFm = { ...parent.data, [FM_INFERENCE_PROCESSED]: true };
|
|
429
429
|
const block = parseFrontmatterBlock(raw);
|
|
430
|
-
|
|
431
|
-
|
|
430
|
+
if (!block) {
|
|
431
|
+
// Cannot safely rewrite malformed frontmatter — skip marking so the memory
|
|
432
|
+
// is retried on the next run once the frontmatter is repaired. Writing with
|
|
433
|
+
// `body = raw` would wrap the entire file (including the bad frontmatter)
|
|
434
|
+
// in a new block, producing a duplicate-frontmatter corruption.
|
|
435
|
+
warn(`memory inference: skipping markParentProcessed for ${parent.filePath} — could not parse frontmatter block`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const updatedFm = { ...parent.data, [FM_INFERENCE_PROCESSED]: true };
|
|
439
|
+
const next = assembleAsset(updatedFm, block.content);
|
|
432
440
|
try {
|
|
433
441
|
fs.writeFileSync(parent.filePath, next, "utf8");
|
|
434
442
|
}
|
|
@@ -191,6 +191,15 @@ export function validateStashEntry(entry) {
|
|
|
191
191
|
const contradictedBy = normalizeNonEmptyStringList(e.contradictedBy);
|
|
192
192
|
if (contradictedBy)
|
|
193
193
|
result.contradictedBy = contradictedBy;
|
|
194
|
+
// R5 — consolidation provenance fields must survive the whitelist too, or
|
|
195
|
+
// stash.json-overridden merge products lose merge-following + generation
|
|
196
|
+
// counting in the collapse detector.
|
|
197
|
+
if (typeof e.generation === "number" && Number.isFinite(e.generation) && e.generation > 0) {
|
|
198
|
+
result.generation = Math.floor(e.generation);
|
|
199
|
+
}
|
|
200
|
+
const sourceRefs = normalizeNonEmptyStringList(e.sourceRefs);
|
|
201
|
+
if (sourceRefs)
|
|
202
|
+
result.sourceRefs = sourceRefs;
|
|
194
203
|
const currentBeliefRefs = normalizeNonEmptyStringList(e.currentBeliefRefs);
|
|
195
204
|
if (currentBeliefRefs)
|
|
196
205
|
result.currentBeliefRefs = currentBeliefRefs;
|
|
@@ -342,6 +351,17 @@ export function applyCuratedFrontmatter(entry, fmData) {
|
|
|
342
351
|
const contradictedBy = normalizeStringListOrUndefined(fmData.contradictedBy);
|
|
343
352
|
if (contradictedBy)
|
|
344
353
|
entry.contradictedBy = contradictedBy;
|
|
354
|
+
// R5 — consolidation provenance. `generation` (merge depth counter) and
|
|
355
|
+
// `source_refs` (merge/distill provenance pointers) are written by the
|
|
356
|
+
// improve pipeline; captured into the index so the collapse detector can
|
|
357
|
+
// count over-generation assets and follow merges without filesystem reads.
|
|
358
|
+
const generation = fmData.generation;
|
|
359
|
+
if (typeof generation === "number" && Number.isFinite(generation) && generation > 0) {
|
|
360
|
+
entry.generation = Math.floor(generation);
|
|
361
|
+
}
|
|
362
|
+
const sourceRefs = normalizeStringListOrUndefined(fmData.source_refs);
|
|
363
|
+
if (sourceRefs)
|
|
364
|
+
entry.sourceRefs = sourceRefs;
|
|
345
365
|
const currentBeliefRefs = normalizeStringListOrUndefined(fmData.currentBeliefRefs);
|
|
346
366
|
if (currentBeliefRefs)
|
|
347
367
|
entry.currentBeliefRefs = currentBeliefRefs;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { ensureIndex } from "./ensure-index.js";
|
|
5
|
+
import { resolveSourceEntries } from "./search/search-source.js";
|
|
6
|
+
/** Resolve the active read sources using the same resolution rules as search/show. */
|
|
7
|
+
export function resolveReadSources(overrideStashDir, existingConfig) {
|
|
8
|
+
const sources = resolveSourceEntries(overrideStashDir, existingConfig);
|
|
9
|
+
return { sources, primarySource: sources[0] };
|
|
10
|
+
}
|
|
11
|
+
/** Ensure the primary source index is readable for reads, when a primary exists. */
|
|
12
|
+
export async function ensurePrimaryIndexForRead(primarySource) {
|
|
13
|
+
if (!primarySource?.path)
|
|
14
|
+
return false;
|
|
15
|
+
return ensureIndex(primarySource.path);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Convenience helper for callers that only need to ensure a read index from a
|
|
19
|
+
* configured stash path and default config.
|
|
20
|
+
*/
|
|
21
|
+
export async function ensurePrimaryIndexFromConfig(overrideStashDir, existingConfig) {
|
|
22
|
+
return ensurePrimaryIndexForRead(resolveReadSources(overrideStashDir, existingConfig).primarySource);
|
|
23
|
+
}
|
|
@@ -20,7 +20,7 @@ import { defaultRendererRegistry } from "../../core/asset/asset-registry.js";
|
|
|
20
20
|
import { getDbPath } from "../../core/paths.js";
|
|
21
21
|
import { warn } from "../../core/warn.js";
|
|
22
22
|
import { getCurrentWorkflowScopeKey } from "../../workflows/authoring/scope-key.js";
|
|
23
|
-
import { closeDatabase, getAllEntries, getEntryById, getEntryCount, getMeta, getPositiveFeedbackCountsByIds, openExistingDatabase, sanitizeFtsQuery, searchFts, searchVec, } from "../db/db.js";
|
|
23
|
+
import { closeDatabase, getAllEntries, getBaseBeliefStatesForDerivedTwins, getEntryById, getEntryCount, getMeta, getPositiveFeedbackCountsByIds, openExistingDatabase, sanitizeFtsQuery, searchFts, searchVec, } from "../db/db.js";
|
|
24
24
|
import { ensureIndex } from "../ensure-index.js";
|
|
25
25
|
import { collectGraphRelatedHit, computeGraphBoost, loadGraphBoostContext, } from "../graph/graph-boost.js";
|
|
26
26
|
import { isProposedQuality } from "../passes/metadata.js";
|
|
@@ -29,6 +29,29 @@ import { applyRankingRules, combineSearchScores, normalizeFtsScores } from "./ra
|
|
|
29
29
|
import { enrichSearchHit } from "./search-hit-enrichers.js";
|
|
30
30
|
import { buildEditHint, findSourceForPath, isEditable } from "./search-source.js";
|
|
31
31
|
import { deriveSemanticProviderFingerprint, getEffectiveSemanticStatus, isSemanticRuntimeReady, readSemanticStatus, } from "./semantic-status.js";
|
|
32
|
+
/**
|
|
33
|
+
* Age past which search surfaces a "run akm index" hint. Reads serve the
|
|
34
|
+
* existing index as-is (freshness is the writers' job — `indexWrittenAssets`
|
|
35
|
+
* plus full runs), so on installs with no improve cron a hand-edited or
|
|
36
|
+
* git-pulled file stays invisible until someone reindexes. The hint makes that
|
|
37
|
+
* actionable without re-introducing read-triggered reindexing.
|
|
38
|
+
*/
|
|
39
|
+
const STALE_INDEX_HINT_MS = 7 * 24 * 60 * 60 * 1000;
|
|
40
|
+
function buildStaleIndexHint(db) {
|
|
41
|
+
try {
|
|
42
|
+
const builtAt = getMeta(db, "builtAt");
|
|
43
|
+
if (!builtAt)
|
|
44
|
+
return undefined;
|
|
45
|
+
const ageMs = Date.now() - new Date(builtAt).getTime();
|
|
46
|
+
if (!Number.isFinite(ageMs) || ageMs < STALE_INDEX_HINT_MS)
|
|
47
|
+
return undefined;
|
|
48
|
+
const days = Math.floor(ageMs / (24 * 60 * 60 * 1000));
|
|
49
|
+
return `Search index was last built ${days} day(s) ago. Files added or edited outside akm since then are not searchable — run 'akm index' to refresh.`;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
32
55
|
export function buildLocalAction(type, ref, registry = defaultRendererRegistry) {
|
|
33
56
|
return buildActionFromContributors({ type, ref }, defaultActionContributors(registry)) ?? `akm show ${ref}`;
|
|
34
57
|
}
|
|
@@ -65,6 +88,9 @@ export async function searchLocal(input) {
|
|
|
65
88
|
const includeProposed = input.includeProposed === true;
|
|
66
89
|
const beliefFilter = input.beliefFilter ?? "all";
|
|
67
90
|
const restrictToSources = input.restrictToSources === true;
|
|
91
|
+
const includeExcludedTypes = input.includeExcludedTypes === true;
|
|
92
|
+
const disableProjectContext = input.disableProjectContext === true;
|
|
93
|
+
const disableScopedUtility = input.disableScopedUtility === true;
|
|
68
94
|
const rendererRegistry = input.rendererRegistry ?? defaultRendererRegistry;
|
|
69
95
|
const allSourceDirs = sources.map((s) => s.path);
|
|
70
96
|
const rawStatus = readSemanticStatus();
|
|
@@ -92,7 +118,9 @@ export async function searchLocal(input) {
|
|
|
92
118
|
if (config.semanticSearchMode === "auto" && semanticStatus === "blocked") {
|
|
93
119
|
warnings.push("Semantic search is currently blocked. Using keyword search until the semantic backend is healthy again.");
|
|
94
120
|
}
|
|
95
|
-
//
|
|
121
|
+
// Bootstrap-only: builds the index inline when it cannot serve this stash.
|
|
122
|
+
// Content freshness is the writers' job (indexWrittenAssets + full runs);
|
|
123
|
+
// reads serve the existing index as-is.
|
|
96
124
|
await ensureIndex(stashDir);
|
|
97
125
|
const dbPath = getDbPath();
|
|
98
126
|
if (!fs.existsSync(dbPath)) {
|
|
@@ -114,7 +142,10 @@ export async function searchLocal(input) {
|
|
|
114
142
|
mode: "keyword",
|
|
115
143
|
};
|
|
116
144
|
}
|
|
117
|
-
const
|
|
145
|
+
const staleHint = buildStaleIndexHint(db);
|
|
146
|
+
if (staleHint)
|
|
147
|
+
warnings.push(staleHint);
|
|
148
|
+
const { hits, embedMs, rankMs } = await searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry, filters, includeProposed, beliefFilter, restrictToSources, includeExcludedTypes, disableProjectContext, disableScopedUtility);
|
|
118
149
|
return {
|
|
119
150
|
hits,
|
|
120
151
|
tip: hits.length === 0
|
|
@@ -131,14 +162,19 @@ export async function searchLocal(input) {
|
|
|
131
162
|
}
|
|
132
163
|
}
|
|
133
164
|
// ── Database search ─────────────────────────────────────────────────────────
|
|
134
|
-
async function searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry = defaultRendererRegistry, filters, includeProposed = false, beliefFilter = "all", restrictToSources = false) {
|
|
165
|
+
async function searchDatabase(db, query, searchType, limit, stashDir, allSourceDirs, config, sources, rendererRegistry = defaultRendererRegistry, filters, includeProposed = false, beliefFilter = "all", restrictToSources = false, includeExcludedTypes = false, disableProjectContext = false, disableScopedUtility = false) {
|
|
135
166
|
const hasSearchableTokens = query.length > 0 && sanitizeFtsQuery(query).length > 0;
|
|
167
|
+
// #627 — resolve the default type-exclusion policy. It applies ONLY on the
|
|
168
|
+
// untyped ('any') path and only when the caller did not opt back in via
|
|
169
|
+
// `includeExcludedTypes`. When the config key is ABSENT a built-in default of
|
|
170
|
+
// ['session'] is applied; an explicit empty list disables exclusion.
|
|
171
|
+
const defaultExcludes = searchType === "any" && !includeExcludedTypes ? (config.search?.defaultExcludeTypes ?? ["session"]) : [];
|
|
136
172
|
// Empty queries — including ones that sanitize down to no searchable FTS
|
|
137
173
|
// tokens such as "." — should enumerate matching entries instead of
|
|
138
174
|
// returning an empty result set from FTS.
|
|
139
175
|
if (!hasSearchableTokens) {
|
|
140
176
|
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
141
|
-
const allEntries = getAllEntries(db, typeFilter);
|
|
177
|
+
const allEntries = getAllEntries(db, typeFilter, defaultExcludes);
|
|
142
178
|
// Deduplicate by file path — multiple entries can share the same file
|
|
143
179
|
const seenFilePaths = new Set();
|
|
144
180
|
const uniqueEntries = allEntries.filter((ie) => {
|
|
@@ -165,7 +201,11 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
165
201
|
const qualityFiltered = includeProposed
|
|
166
202
|
? scopeFiltered
|
|
167
203
|
: scopeFiltered.filter((ie) => !isProposedQuality(ie.entry.quality));
|
|
168
|
-
|
|
204
|
+
// 03-R3: derived twins inherit their base's demoting belief state here too,
|
|
205
|
+
// so the belief FILTER (and the reported hit state) stays consistent on the
|
|
206
|
+
// enumerate/browse path — not only on the FTS-scored path below.
|
|
207
|
+
inheritDerivedTwinBeliefStates(db, qualityFiltered);
|
|
208
|
+
const beliefFiltered = qualityFiltered.filter((ie) => matchBeliefFilter(ie.entry.beliefState, beliefFilter));
|
|
169
209
|
const selected = beliefFiltered.slice(0, limit);
|
|
170
210
|
const hits = await Promise.all(selected.map((ie) => buildDbHit({
|
|
171
211
|
entry: ie.entry,
|
|
@@ -187,7 +227,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
187
227
|
const typeFilter = searchType === "any" ? undefined : searchType;
|
|
188
228
|
const tEmbed0 = Date.now();
|
|
189
229
|
const embeddingPromise = tryVecScores(db, query, limit * 3, config);
|
|
190
|
-
const ftsResults = searchFts(db, query, limit * 3, typeFilter);
|
|
230
|
+
const ftsResults = searchFts(db, query, limit * 3, typeFilter, defaultExcludes);
|
|
191
231
|
const embeddingScores = await embeddingPromise;
|
|
192
232
|
const embedMs = Date.now() - tEmbed0;
|
|
193
233
|
const tRank0 = Date.now();
|
|
@@ -208,6 +248,11 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
208
248
|
embedScoreMap,
|
|
209
249
|
getEntryById: (id) => getEntryById(db, id) ?? undefined,
|
|
210
250
|
typeFilter,
|
|
251
|
+
// #627 — also exclude default-hidden types from the vector-only branch so a
|
|
252
|
+
// session asset that is a top-k vector neighbor (but not an FTS match) does
|
|
253
|
+
// not leak into default ('any') results. defaultExcludes is already []
|
|
254
|
+
// unless this is the untyped path without includeExcludedTypes.
|
|
255
|
+
excludeTypes: defaultExcludes,
|
|
211
256
|
});
|
|
212
257
|
// ── Scoring Phase ──────────────────────────────────────────────────────
|
|
213
258
|
// Apply boosts as multiplicative factors (all boosts in a single phase
|
|
@@ -236,7 +281,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
236
281
|
// Resolve project-context tokens from the current working directory once
|
|
237
282
|
// per search invocation. Returns null when running from home dir / /tmp,
|
|
238
283
|
// or when the caller has set AKM_DISABLE_PROJECT_CONTEXT=1.
|
|
239
|
-
const projectContext =
|
|
284
|
+
const projectContext = disableProjectContext ? null : resolveProjectContext(process.cwd());
|
|
240
285
|
// Phase 2A / Rec 5: resolve forgetting-curve config and skip the feedback
|
|
241
286
|
// count query when the boost cannot make a difference (default ≤ 1.0 means
|
|
242
287
|
// boost^count == 1 — zero overhead for the common case).
|
|
@@ -256,11 +301,14 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
256
301
|
// AKM_DISABLE_SCOPED_UTILITY=1 opts out (e.g. for registry searches or tests).
|
|
257
302
|
let scopeKey;
|
|
258
303
|
try {
|
|
259
|
-
scopeKey =
|
|
304
|
+
scopeKey = disableScopedUtility ? undefined : getCurrentWorkflowScopeKey();
|
|
260
305
|
}
|
|
261
306
|
catch {
|
|
262
307
|
// Non-fatal — ranking proceeds without scoped utility on any error.
|
|
263
308
|
}
|
|
309
|
+
// 03-R3: derived twins inherit their base's demoting belief state before
|
|
310
|
+
// ranking, so the (03) belief-state ranker demotes a stale flag-free twin.
|
|
311
|
+
inheritDerivedTwinBeliefStates(db, scored);
|
|
264
312
|
applyRankingRules({
|
|
265
313
|
db,
|
|
266
314
|
query,
|
|
@@ -317,7 +365,7 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
317
365
|
const qualityFiltered = includeProposed
|
|
318
366
|
? scopeFiltered
|
|
319
367
|
: scopeFiltered.filter((item) => !isProposedQuality(item.entry.quality));
|
|
320
|
-
const beliefFiltered = qualityFiltered.filter((item) => matchBeliefFilter(item.entry.
|
|
368
|
+
const beliefFiltered = qualityFiltered.filter((item) => matchBeliefFilter(item.entry.beliefState, beliefFilter));
|
|
321
369
|
const rankMs = Date.now() - tRank0;
|
|
322
370
|
const selected = beliefFiltered.slice(0, limit);
|
|
323
371
|
const hits = await Promise.all(selected.map(({ entry, filePath, score, rankingMode, utilityBoosted }) => {
|
|
@@ -345,11 +393,39 @@ async function searchDatabase(db, query, searchType, limit, stashDir, allSourceD
|
|
|
345
393
|
}));
|
|
346
394
|
return { embedMs, rankMs, hits };
|
|
347
395
|
}
|
|
348
|
-
|
|
396
|
+
/**
|
|
397
|
+
* 03-R3: let each `.derived` twin inherit its base memory's demoting belief
|
|
398
|
+
* state for this ranking pass, so a stale flag-free twin is demoted like its
|
|
399
|
+
* corrected base. The base carries the flag (a contradicted base takes a real
|
|
400
|
+
* ranking penalty); its near-duplicate `.derived` twin carries none and would
|
|
401
|
+
* otherwise outrank the corrected copy. Done in-memory at search time — NOT by
|
|
402
|
+
* writing the twin's frontmatter — because the SCC belief resolver refreshes any
|
|
403
|
+
* non-frozen state written to a derived memory back to `active` on the next
|
|
404
|
+
* improve run, erasing it. Only twins with no state of their own inherit; an
|
|
405
|
+
* explicit twin state always wins. Reuses the (03) belief-state ranker + filter.
|
|
406
|
+
*/
|
|
407
|
+
function inheritDerivedTwinBeliefStates(db, items) {
|
|
408
|
+
const DEMOTING = new Set(["contradicted", "superseded", "deprecated", "archived"]);
|
|
409
|
+
const twins = items.filter((it) => it.entry.type === "memory" &&
|
|
410
|
+
it.entry.beliefState === undefined &&
|
|
411
|
+
it.entry.name.toLowerCase().endsWith(".derived"));
|
|
412
|
+
if (twins.length === 0)
|
|
413
|
+
return;
|
|
414
|
+
const baseBeliefByTwinId = getBaseBeliefStatesForDerivedTwins(db, twins.map((t) => t.id));
|
|
415
|
+
for (const t of twins) {
|
|
416
|
+
const baseBelief = baseBeliefByTwinId.get(t.id);
|
|
417
|
+
// Only inherit DEMOTIONS — never let a base's active/asserted state lift a twin.
|
|
418
|
+
if (baseBelief && DEMOTING.has(baseBelief)) {
|
|
419
|
+
t.entry.beliefState = baseBelief;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function matchBeliefFilter(beliefState, filter) {
|
|
349
424
|
if (filter === "all")
|
|
350
425
|
return true;
|
|
351
|
-
|
|
352
|
-
|
|
426
|
+
// 03: the belief filter applies to ANY flagged entry, not just memories, so
|
|
427
|
+
// `current`/`historical` filters catch contradicted/superseded KNOWLEDGE too.
|
|
428
|
+
// Unflagged entries (beliefState === undefined) still pass the `current` filter.
|
|
353
429
|
if (filter === "current") {
|
|
354
430
|
// Phase 1A: `asserted` is a "current" state (stronger authority than `active`);
|
|
355
431
|
// `deprecated` is excluded from current results.
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
* Pure FTS5 query-string helpers, extracted from indexer/db/db.ts.
|
|
6
|
+
*
|
|
7
|
+
* These transform a raw user query into an FTS5-safe MATCH expression. They
|
|
8
|
+
* touch no database state, so they are unit-testable with zero DB setup.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Sanitize a raw user query into an FTS5-safe implicit-AND expression.
|
|
12
|
+
*
|
|
13
|
+
* Allows only characters safe in FTS5 queries: letters, digits, underscores,
|
|
14
|
+
* and whitespace. Everything else (hyphens, dots, quotes, parens, asterisks,
|
|
15
|
+
* colons, carets, @, !, etc.) is replaced with a space so that compound
|
|
16
|
+
* identifiers like "code-review" or "k8s.setup" become AND-joined tokens
|
|
17
|
+
* ("code review", "k8s setup") rather than triggering FTS5 syntax errors.
|
|
18
|
+
*/
|
|
19
|
+
export function sanitizeFtsQuery(query) {
|
|
20
|
+
let sanitized = query.replace(/[^a-zA-Z0-9_\s]/g, " ");
|
|
21
|
+
// Neutralize the NEAR operator (FTS5 proximity syntax)
|
|
22
|
+
sanitized = sanitized.replace(/\bNEAR\b/g, " ");
|
|
23
|
+
const tokens = sanitized.split(/\s+/).filter((t) => t.length >= 1);
|
|
24
|
+
if (tokens.length === 0)
|
|
25
|
+
return "";
|
|
26
|
+
// Use implicit AND (space-separated tokens) for precision. FTS5 treats
|
|
27
|
+
// space-separated tokens as an implicit AND, matching only rows that
|
|
28
|
+
// contain ALL terms.
|
|
29
|
+
return tokens.join(" ");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build a prefix query from an FTS5 query string by appending `*` to each
|
|
33
|
+
* token that is 3+ characters long. Tokens shorter than 3 characters are
|
|
34
|
+
* kept as-is (no prefix expansion) to avoid overly broad matches.
|
|
35
|
+
*
|
|
36
|
+
* Returns null if no tokens qualify for prefix expansion.
|
|
37
|
+
*/
|
|
38
|
+
export function buildPrefixQuery(ftsQuery) {
|
|
39
|
+
const tokens = ftsQuery.split(/\s+/).filter(Boolean);
|
|
40
|
+
let hasPrefix = false;
|
|
41
|
+
const prefixTokens = tokens.map((t) => {
|
|
42
|
+
if (t.length >= 3) {
|
|
43
|
+
hasPrefix = true;
|
|
44
|
+
return `${t}*`;
|
|
45
|
+
}
|
|
46
|
+
return t;
|
|
47
|
+
});
|
|
48
|
+
if (!hasPrefix)
|
|
49
|
+
return null;
|
|
50
|
+
return prefixTokens.join(" ");
|
|
51
|
+
}
|
|
@@ -9,11 +9,22 @@ const TYPE_BOOST = {
|
|
|
9
9
|
agent: 0.3,
|
|
10
10
|
script: 0.2,
|
|
11
11
|
knowledge: 0.22,
|
|
12
|
+
// Facts are authoritative, durable declarations about the stash — rank them
|
|
13
|
+
// alongside knowledge so they surface reliably when relevant.
|
|
14
|
+
fact: 0.22,
|
|
12
15
|
memory: -0.02,
|
|
13
16
|
};
|
|
14
17
|
const MAX_BOOST_SUM = 3.0;
|
|
15
18
|
const UTILITY_WEIGHT = 0.5;
|
|
16
19
|
const UTILITY_MAX_BOOST = 1.5;
|
|
20
|
+
/**
|
|
21
|
+
* R2 (docs/design/improve-self-learning-analysis.md) — weight of the improve
|
|
22
|
+
* loop's `asset_salience.rank_score` in user-facing ranking. Bounded well
|
|
23
|
+
* below the utility boost so the composed signal refines, never dominates,
|
|
24
|
+
* lexical/semantic relevance. rank_score ∈ [0,1] → boost ∈ [1, 1.2].
|
|
25
|
+
*/
|
|
26
|
+
const SALIENCE_WEIGHT = 0.2;
|
|
27
|
+
const SALIENCE_MAX_BOOST = 1.2;
|
|
17
28
|
/**
|
|
18
29
|
* Phase 2A / Rec 5: default recency half-life (days) used when no
|
|
19
30
|
* `utilityDecayConfig` is supplied to the ranking pipeline. Matches the
|
|
@@ -29,8 +40,10 @@ const DEFAULT_RECENCY_HALF_LIFE_DAYS = 30;
|
|
|
29
40
|
const FEEDBACK_HALF_LIFE_CAP_MULTIPLIER = 4;
|
|
30
41
|
function beliefStateBoost(item) {
|
|
31
42
|
const entry = item.entry;
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
// 03: belief-state penalties/boosts apply to ANY flagged entry (memory OR
|
|
44
|
+
// knowledge), so contradicted/superseded KNOWLEDGE is demoted from results
|
|
45
|
+
// just like flagged memories. Entries without a belief state fall through to
|
|
46
|
+
// the `return 0` below (default-safe — no effect on unflagged assets).
|
|
34
47
|
// Phase 1A: `asserted` and `deprecated` are first-class states.
|
|
35
48
|
// `asserted` carries stronger user-explicit authority than `active`.
|
|
36
49
|
// `deprecated` is a frozen historical state — penalized but milder than `superseded`.
|
|
@@ -76,14 +89,18 @@ const typeRankingContributor = {
|
|
|
76
89
|
return TYPE_BOOST[item.entry.type] ?? 0;
|
|
77
90
|
},
|
|
78
91
|
};
|
|
79
|
-
const
|
|
80
|
-
name: "
|
|
92
|
+
const beliefStateRankingContributor = {
|
|
93
|
+
name: "belief-state-ranking",
|
|
81
94
|
appliesTo(item) {
|
|
82
|
-
|
|
95
|
+
// Fire for any entry that carries a belief state, regardless of type — so
|
|
96
|
+
// contradicted/superseded knowledge is demoted, not just memories. The
|
|
97
|
+
// `.derived`-twin `derivedBoost` (±0.12/−0.08) is deleted (03-R3): it made
|
|
98
|
+
// stale flag-free twins outrank their corrected base memory; belief-state
|
|
99
|
+
// demotion is the principled signal, not the twin-name heuristic.
|
|
100
|
+
return item.entry.beliefState !== undefined;
|
|
83
101
|
},
|
|
84
102
|
adjust(item) {
|
|
85
|
-
|
|
86
|
-
return derivedBoost + beliefStateBoost(item);
|
|
103
|
+
return beliefStateBoost(item);
|
|
87
104
|
},
|
|
88
105
|
};
|
|
89
106
|
const tagRankingContributor = {
|
|
@@ -206,6 +223,24 @@ const lessonStrengthContributor = {
|
|
|
206
223
|
return Math.min(0.3, 0.06 * strength);
|
|
207
224
|
},
|
|
208
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Pinned-fact boost.
|
|
228
|
+
*
|
|
229
|
+
* Facts marked `pinned: true` form the small always-injected "core context"
|
|
230
|
+
* (see docs/design/fact-asset-type.md). The fact metadata contributor records
|
|
231
|
+
* a `pinned` search hint; here we give those facts a modest additive boost so
|
|
232
|
+
* the core outranks ordinary facts on otherwise-equal queries. Capped small so
|
|
233
|
+
* it cannot overpower an exact-name match.
|
|
234
|
+
*/
|
|
235
|
+
const pinnedFactRankingContributor = {
|
|
236
|
+
name: "pinned-fact-ranking",
|
|
237
|
+
appliesTo(item) {
|
|
238
|
+
return item.entry.type === "fact" && (item.entry.searchHints?.includes("pinned") ?? false);
|
|
239
|
+
},
|
|
240
|
+
adjust() {
|
|
241
|
+
return 0.15;
|
|
242
|
+
},
|
|
243
|
+
};
|
|
209
244
|
/**
|
|
210
245
|
* Blend ratio for scoped vs. global utility signals.
|
|
211
246
|
*
|
|
@@ -301,7 +336,7 @@ const projectContextRankingContributor = {
|
|
|
301
336
|
export const defaultRankingContributors = [
|
|
302
337
|
exactNameRankingContributor,
|
|
303
338
|
typeRankingContributor,
|
|
304
|
-
|
|
339
|
+
beliefStateRankingContributor,
|
|
305
340
|
tagRankingContributor,
|
|
306
341
|
searchHintRankingContributor,
|
|
307
342
|
aliasRankingContributor,
|
|
@@ -310,9 +345,60 @@ export const defaultRankingContributors = [
|
|
|
310
345
|
graphRankingContributor,
|
|
311
346
|
captureModeRankingContributor,
|
|
312
347
|
lessonStrengthContributor,
|
|
348
|
+
pinnedFactRankingContributor,
|
|
313
349
|
projectContextRankingContributor,
|
|
314
350
|
];
|
|
315
|
-
|
|
351
|
+
/**
|
|
352
|
+
* R2 — compose the improve loop's salience core into user-facing ranking.
|
|
353
|
+
*
|
|
354
|
+
* `asset_salience.rank_score` (encoding + outcome + retrieval projection,
|
|
355
|
+
* maintained every improve run) previously drove only improve's INTERNAL
|
|
356
|
+
* maintenance selection — the "better assets surface more" loop ran solely
|
|
357
|
+
* through the utility EMA. This bounded multiplicative boost closes the outer
|
|
358
|
+
* loop: usage/outcome-reinforced assets rank higher in `search`/`curate`.
|
|
359
|
+
*/
|
|
360
|
+
const salienceRankingContributor = {
|
|
361
|
+
name: "salience-ranking",
|
|
362
|
+
appliesTo(item, ctx) {
|
|
363
|
+
const rank = ctx.salienceRankScores?.get(item.id);
|
|
364
|
+
return rank !== undefined && rank > 0;
|
|
365
|
+
},
|
|
366
|
+
apply(item, ctx) {
|
|
367
|
+
const rank = ctx.salienceRankScores?.get(item.id) ?? 0;
|
|
368
|
+
const rawBoost = 1 + Math.min(1, Math.max(0, rank)) * SALIENCE_WEIGHT;
|
|
369
|
+
item.score *= Math.min(rawBoost, SALIENCE_MAX_BOOST);
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
export const defaultUtilityRankingContributors = [
|
|
373
|
+
utilityRankingContributor,
|
|
374
|
+
salienceRankingContributor,
|
|
375
|
+
];
|
|
376
|
+
/**
|
|
377
|
+
* EVAL/DEBUG ONLY — remove named ranking contributors from a list.
|
|
378
|
+
*
|
|
379
|
+
* Driven by the `AKM_ABLATE_CONTRIBUTORS` env var (comma-separated contributor
|
|
380
|
+
* `name`s). A no-op — returns the input list unchanged (same reference) — when
|
|
381
|
+
* the env value is unset/empty, so production ranking is never affected unless
|
|
382
|
+
* the operator opts in. Its sole purpose is per-contributor ablation for the
|
|
383
|
+
* curate ablation harness (see `docs/technical/ranking-ablation-and-saturation-analysis.md`
|
|
384
|
+
* and `scripts/akm-eval/`): run the same fixture with and without a contributor
|
|
385
|
+
* and diff the ranked results to measure whether that contributor is load-bearing.
|
|
386
|
+
*
|
|
387
|
+
* NOTE (see the analysis doc): a contributor's ablation delta is only observable
|
|
388
|
+
* in the UNSATURATED score regime — once entries saturate at the `displayScore`
|
|
389
|
+
* ceiling their contributor deltas are absorbed and ablation reads Δ=0.
|
|
390
|
+
*/
|
|
391
|
+
export function applyContributorAblation(contributors, ablateEnv) {
|
|
392
|
+
if (!ablateEnv)
|
|
393
|
+
return contributors;
|
|
394
|
+
const ablated = new Set(ablateEnv
|
|
395
|
+
.split(",")
|
|
396
|
+
.map((s) => s.trim())
|
|
397
|
+
.filter(Boolean));
|
|
398
|
+
if (ablated.size === 0)
|
|
399
|
+
return contributors;
|
|
400
|
+
return contributors.filter((c) => !ablated.has(c.name));
|
|
401
|
+
}
|
|
316
402
|
export function applyScoreContributors(item, ctx, contributors = defaultRankingContributors) {
|
|
317
403
|
let boostSum = 0;
|
|
318
404
|
for (const contributor of contributors) {
|