akm-cli 0.9.16 → 0.9.17-alpha.10
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 +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +83 -21
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -31
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/tasks/source/task-to-v3.js +0 -507
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -77
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -8,65 +8,50 @@
|
|
|
8
8
|
* high-signal set of stash + registry hits and enrich each with the data
|
|
9
9
|
* needed to act (ref, run, parameters, follow-up command).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Curation is one search with the fused ranking, the top `limit` hits, and
|
|
12
|
+
* per-hit enrichment (preview, run and parameters, support refs from the
|
|
13
|
+
* hit's declared links). An optional reranker reorders the top fused
|
|
14
|
+
* candidates first.
|
|
15
|
+
*
|
|
16
|
+
* The exported `akmCurate()` API is the single entry point; tests can also
|
|
17
|
+
* drive `curateSearchResults` with a fixture search response.
|
|
15
18
|
*/
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { getIndexPassConfig, loadConfig } from "../../core/config/config.js";
|
|
19
|
+
import { parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
20
|
+
import { typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
|
|
21
|
+
import { loadConfig } from "../../core/config/config.js";
|
|
20
22
|
import { rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
|
|
21
23
|
import { appendEvent } from "../../core/events.js";
|
|
24
|
+
import { nonTaskInput } from "../../core/non-task-input.js";
|
|
25
|
+
import { redactCredentialPatterns } from "../../core/redaction.js";
|
|
22
26
|
import { withStateDbTelemetry } from "../../core/state-db.js";
|
|
23
|
-
import {
|
|
27
|
+
import { searchHitContent } from "../../indexer/search/db-search.js";
|
|
24
28
|
import { copySearchHitAttribution, getSearchHitAttribution, usageEventAttributionMetadata, } from "../../indexer/search/search-attribution.js";
|
|
25
|
-
import { findSourceForPath, resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
26
29
|
import { insertUsageEvent } from "../../indexer/usage/usage-events.js";
|
|
27
30
|
import { estimateTokenCount } from "../../llm/embedders/remote.js";
|
|
28
|
-
import { tryLlmFeature } from "../../llm/feature-gate.js";
|
|
31
|
+
import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
|
|
29
32
|
import { rerankDocuments } from "../../llm/rerank-client.js";
|
|
30
33
|
import { truncateDescription } from "../../output/shapes/helpers.js";
|
|
31
34
|
import { TELEMETRY_BUSY_TIMEOUT_MS, withIndexDb } from "../../storage/repositories/index-db.js";
|
|
32
35
|
import { findEntryIdByRef, getItemRefById } from "../../storage/repositories/index-entries-repository.js";
|
|
33
|
-
import { computeBodyHash } from "../../storage/repositories/index-llm-cache-repository.js";
|
|
34
36
|
import { akmSearch, parseSearchSource } from "./search.js";
|
|
35
37
|
import { akmShowUnified } from "./show.js";
|
|
36
|
-
const CURATE_FALLBACK_FILTER_WORDS = new Set([
|
|
37
|
-
"a",
|
|
38
|
-
"an",
|
|
39
|
-
"and",
|
|
40
|
-
"for",
|
|
41
|
-
"how",
|
|
42
|
-
"i",
|
|
43
|
-
"in",
|
|
44
|
-
"of",
|
|
45
|
-
"or",
|
|
46
|
-
"the",
|
|
47
|
-
"to",
|
|
48
|
-
"with",
|
|
49
|
-
]);
|
|
50
|
-
const CURATE_SHORT_FALLBACK_TOKENS = new Set(["ai", "ci", "cd", "go", "js", "ts"]);
|
|
51
|
-
const MIN_CURATE_FALLBACK_TOKEN_LENGTH = 3;
|
|
52
|
-
const MAX_CURATE_FALLBACK_KEYWORDS = 6;
|
|
53
|
-
export const CURATE_SEARCH_LIMIT_MULTIPLIER = 4;
|
|
54
|
-
export const MIN_CURATE_SEARCH_LIMIT = 12;
|
|
55
38
|
const DEFAULT_CURATE_LIMIT = 4;
|
|
56
|
-
const CURATE_CLOSE_SCORE_BAND = 0.12;
|
|
57
|
-
// Used by `shouldRunCurateFallback` to judge whether the initial search
|
|
58
|
-
// already returned strong results (a different purpose from ranking/keeping
|
|
59
|
-
// hits — see that function for its own rationale).
|
|
60
|
-
const CURATE_RELATIVE_SCORE_FLOOR = 0.7;
|
|
61
|
-
const CURATE_FALLBACK_TOP_SCORE_THRESHOLD = 0.8;
|
|
62
|
-
const CURATE_FALLBACK_STRONG_SCORE_FLOOR = 0.35;
|
|
63
39
|
const MAX_CURATE_SUPPORT_REFS = 2;
|
|
64
|
-
|
|
40
|
+
/** Fused candidates the reranker reorders when `search.curateRerank.topN` is unset. */
|
|
41
|
+
const DEFAULT_CURATE_RERANK_TOP_N = 30;
|
|
42
|
+
/** Characters of name, description and content sent to the reranker per candidate. */
|
|
43
|
+
const RERANK_DOCUMENT_CHARS = 2000;
|
|
65
44
|
/**
|
|
66
45
|
* Fire-and-forget: log a curate event to the usage_events table and events.jsonl.
|
|
67
46
|
* Never blocks the caller; errors are silently ignored.
|
|
68
47
|
*/
|
|
69
|
-
function logCurateEvent(
|
|
48
|
+
function logCurateEvent(rawQuery, result, eventSource = "user", attributionProjection = "full") {
|
|
49
|
+
// Credentials pasted into a query (e.g. by the Claude Code hook that
|
|
50
|
+
// curates every user prompt) must never reach state.db verbatim — see
|
|
51
|
+
// `redactCredentialPatterns`. Redacted once here so every persistence call
|
|
52
|
+
// below (events.metadata_json via appendEvent, usage_events.query via
|
|
53
|
+
// insertUsageEvent) gets the same scrubbed text.
|
|
54
|
+
const query = redactCredentialPatterns(rawQuery);
|
|
70
55
|
const itemRefs = result.items.map((item) => ("ref" in item ? item.ref : `registry:${item.id}`));
|
|
71
56
|
appendEvent({
|
|
72
57
|
eventType: "curate",
|
|
@@ -118,14 +103,29 @@ export async function akmCurate(options) {
|
|
|
118
103
|
if (!trimmedQuery) {
|
|
119
104
|
throw new UsageError('A curation query is required. Usage: akm curate "<task or prompt>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT");
|
|
120
105
|
}
|
|
106
|
+
const nonTask = nonTaskInput(trimmedQuery);
|
|
107
|
+
if (nonTask) {
|
|
108
|
+
const abstained = {
|
|
109
|
+
query: options.query,
|
|
110
|
+
summary: `Curate abstained: the input is ${nonTask}, not a task.`,
|
|
111
|
+
items: [],
|
|
112
|
+
tip: 'Nothing was selected on purpose. To curate for it, pass the task itself: akm curate "<what you are trying to do>".',
|
|
113
|
+
};
|
|
114
|
+
if (!options.skipLogging) {
|
|
115
|
+
logCurateEvent(options.query, abstained, options.eventSource, options.attributionProjection);
|
|
116
|
+
}
|
|
117
|
+
return abstained;
|
|
118
|
+
}
|
|
121
119
|
const limit = options.limit && options.limit > 0 ? options.limit : DEFAULT_CURATE_LIMIT;
|
|
122
120
|
const source = options.source ?? parseSearchSource("local");
|
|
123
121
|
const searchResponse = options.searchResponse ??
|
|
124
|
-
(await
|
|
122
|
+
(await akmSearch({
|
|
125
123
|
query: options.query,
|
|
126
124
|
type: options.type,
|
|
127
|
-
|
|
125
|
+
// An enabled reranker reorders the top fused candidates, not just the final `limit`.
|
|
126
|
+
limit: Math.max(limit, rerankTopN(loadConfig())),
|
|
128
127
|
source,
|
|
128
|
+
skipLogging: true,
|
|
129
129
|
}));
|
|
130
130
|
const result = await curateSearchResults(options.query, searchResponse, limit, options.type, options.eventSource);
|
|
131
131
|
if (!options.skipLogging) {
|
|
@@ -136,27 +136,18 @@ export async function akmCurate(options) {
|
|
|
136
136
|
export async function curateSearchResults(query, result, limit, selectedType, eventSource) {
|
|
137
137
|
const allStashHits = result.hits.filter((hit) => hit.type !== "registry");
|
|
138
138
|
const registryHits = result.registryHits ?? [];
|
|
139
|
-
// F3/R-018: `--type`
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
// then running the SAME pipeline over it gives `--type` its intended
|
|
144
|
-
// "narrow, don't disable" semantics — and matters even when the caller's
|
|
145
|
-
// search already applied a type filter at the DB layer, because
|
|
146
|
-
// `curateSearchResults` is also driven directly (tests, `searchResponse`
|
|
147
|
-
// fixtures) with a `SearchResponse` that was never type-filtered.
|
|
139
|
+
// F3/R-018: `--type` NARROWS the candidate pool. The caller's search
|
|
140
|
+
// usually applied the filter already, but `curateSearchResults` is also
|
|
141
|
+
// driven directly (tests, `searchResponse` fixtures) with a
|
|
142
|
+
// `SearchResponse` that was never type-filtered.
|
|
148
143
|
const stashHits = selectedType && selectedType !== "any" ? allStashHits.filter((hit) => hit.type === selectedType) : allStashHits;
|
|
149
|
-
const
|
|
150
|
-
const selectedStashHits = await maybeRerankCuratedStashHits(query, selected.selected);
|
|
151
|
-
const supportRefsByRef = selected.supportRefsByRef;
|
|
144
|
+
const selectedStashHits = (await maybeRerankCuratedStashHits(query, stashHits)).slice(0, limit);
|
|
152
145
|
// F4/R-019: respect `--limit` for registry fill instead of hard-capping it
|
|
153
146
|
// at a bare literal 2 — the remaining slots after stash hits ARE the cap.
|
|
154
147
|
const selectedRegistryHits = selectedStashHits.length >= limit ? [] : registryHits.slice(0, limit - selectedStashHits.length);
|
|
155
148
|
const selectedRefs = new Set(selectedStashHits.map((hit) => hit.ref));
|
|
156
149
|
const items = [
|
|
157
|
-
...(await Promise.all(selectedStashHits
|
|
158
|
-
.slice(0, limit)
|
|
159
|
-
.map((hit) => enrichCuratedStashHit(query, hit, supportRefsByRef.get(hit.ref) ?? [], selectedRefs, eventSource)))),
|
|
150
|
+
...(await Promise.all(selectedStashHits.map((hit) => enrichCuratedStashHit(query, hit, selectedRefs, eventSource)))),
|
|
160
151
|
...selectedRegistryHits.map((hit) => buildCuratedRegistryItem(query, hit)),
|
|
161
152
|
].slice(0, limit);
|
|
162
153
|
return {
|
|
@@ -214,7 +205,7 @@ export async function packCuratedHits(result, budgetTokens) {
|
|
|
214
205
|
}
|
|
215
206
|
return { query: result.query, budget: budgetTokens, tokens: used, items: packed };
|
|
216
207
|
}
|
|
217
|
-
async function enrichCuratedStashHit(query, hit,
|
|
208
|
+
async function enrichCuratedStashHit(query, hit, selectedRefs, eventSource) {
|
|
218
209
|
let shown;
|
|
219
210
|
try {
|
|
220
211
|
shown = await akmShowUnified({ ref: hit.ref, eventSource, skipLogging: true });
|
|
@@ -222,14 +213,9 @@ async function enrichCuratedStashHit(query, hit, supportRefs, selectedRefs, even
|
|
|
222
213
|
catch {
|
|
223
214
|
shown = undefined;
|
|
224
215
|
}
|
|
225
|
-
// #624-P3: when lazy graph extraction is opted in, enqueue an ungraphed
|
|
226
|
-
// asset for a later pass to extract. Fire-and-forget, non-blocking, NO inline
|
|
227
|
-
// extraction and NO LLM call here. Default-off (flag unset) = byte-identical.
|
|
228
|
-
if (shown?.path)
|
|
229
|
-
maybeEnqueueLazyGraph(shown.path);
|
|
230
216
|
const description = shown?.description ?? hit.description;
|
|
231
217
|
const preview = buildCuratedPreview(shown, hit);
|
|
232
|
-
const
|
|
218
|
+
const supportRefs = buildCurateSupportRefs(shown?.links, selectedRefs, hit.ref);
|
|
233
219
|
const item = {
|
|
234
220
|
source: "local",
|
|
235
221
|
type: shown?.type ?? hit.type,
|
|
@@ -245,7 +231,7 @@ async function enrichCuratedStashHit(query, hit, supportRefs, selectedRefs, even
|
|
|
245
231
|
...(shown?.keys?.length ? { keys: shown.keys } : {}),
|
|
246
232
|
...(shown?.parameters?.length ? { parameters: shown.parameters } : {}),
|
|
247
233
|
...(shown?.run ? { run: shown.run } : {}),
|
|
248
|
-
...(
|
|
234
|
+
...(supportRefs.length > 0 ? { supportRefs } : {}),
|
|
249
235
|
followUp: `akm show ${hit.ref}`,
|
|
250
236
|
reason: buildCuratedReason(query, shown?.type ?? hit.type),
|
|
251
237
|
...(hit.score !== undefined ? { score: hit.score } : {}),
|
|
@@ -253,44 +239,6 @@ async function enrichCuratedStashHit(query, hit, supportRefs, selectedRefs, even
|
|
|
253
239
|
copySearchHitAttribution(hit, item, item.description);
|
|
254
240
|
return item;
|
|
255
241
|
}
|
|
256
|
-
/**
|
|
257
|
-
* #624-P3 — enqueue an ungraphed asset for lazy graph extraction when the
|
|
258
|
-
* `index.graph.lazyGraphExtraction` flag is on. Pure side-effect, fully
|
|
259
|
-
* best-effort: any failure (config, fs, db) is swallowed so curate never fails
|
|
260
|
-
* on it. NO LLM call and NO inline extraction — only a cheap queue insert.
|
|
261
|
-
* Default-off (flag unset) returns immediately = byte-identical behavior.
|
|
262
|
-
*/
|
|
263
|
-
function maybeEnqueueLazyGraph(assetPath) {
|
|
264
|
-
try {
|
|
265
|
-
const config = loadConfig();
|
|
266
|
-
if (getIndexPassConfig(config.index, "graph")?.lazyGraphExtraction !== true)
|
|
267
|
-
return;
|
|
268
|
-
const sources = resolveSourceEntries();
|
|
269
|
-
const source = findSourceForPath(assetPath, sources);
|
|
270
|
-
const stashRoot = source?.path;
|
|
271
|
-
if (!stashRoot)
|
|
272
|
-
return;
|
|
273
|
-
let raw;
|
|
274
|
-
try {
|
|
275
|
-
raw = fs.readFileSync(assetPath, "utf8");
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const body = parseFrontmatter(raw).content.trim();
|
|
281
|
-
if (!body)
|
|
282
|
-
return;
|
|
283
|
-
const bodyHash = computeBodyHash(body);
|
|
284
|
-
withIndexDb((db) => {
|
|
285
|
-
if (!hasGraphData(db, stashRoot, assetPath)) {
|
|
286
|
-
enqueueGraphExtraction(db, stashRoot, assetPath, bodyHash, 0);
|
|
287
|
-
}
|
|
288
|
-
}, { busyTimeoutMs: TELEMETRY_BUSY_TIMEOUT_MS });
|
|
289
|
-
}
|
|
290
|
-
catch (err) {
|
|
291
|
-
rethrowIfTestIsolationError(err);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
242
|
function buildCuratedRegistryItem(query, hit) {
|
|
295
243
|
return {
|
|
296
244
|
source: "registry",
|
|
@@ -341,274 +289,20 @@ function buildCurateSummary(query, items) {
|
|
|
341
289
|
const labels = items.map((item) => ("ref" in item ? item.ref : `${item.type}:${item.name}`));
|
|
342
290
|
return `Selected ${items.length} curated result${items.length === 1 ? "" : "s"}: ${labels.join(", ")}.`;
|
|
343
291
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const tokens = Array.from(new Set(query
|
|
350
|
-
.toLowerCase()
|
|
351
|
-
.split(/[^a-z0-9]+/)
|
|
352
|
-
.map((token) => token.trim())
|
|
353
|
-
.filter((token) => token.length > 0 &&
|
|
354
|
-
!CURATE_FALLBACK_FILTER_WORDS.has(token) &&
|
|
355
|
-
(token.length >= MIN_CURATE_FALLBACK_TOKEN_LENGTH || CURATE_SHORT_FALLBACK_TOKENS.has(token))))).slice(0, MAX_CURATE_FALLBACK_KEYWORDS);
|
|
356
|
-
if (tokens.length === 1 && tokens[0] === normalizedWhole)
|
|
357
|
-
return [];
|
|
358
|
-
return tokens;
|
|
359
|
-
}
|
|
360
|
-
export function mergeCurateSearchResponses(base, extras) {
|
|
361
|
-
// The base (full-query) ranking is authoritative by default; keyword
|
|
362
|
-
// fallbacks add recall without comparing their raw scores to the full-query
|
|
363
|
-
// score scale. Base order is preserved and fallback-only hits append below,
|
|
364
|
-
// except for the narrow weak-provenance promotion documented below. Duplicate
|
|
365
|
-
// refs keep their base position but take the maximum score for the downstream
|
|
366
|
-
// relevance floor.
|
|
367
|
-
const bestExtraStashScore = new Map();
|
|
368
|
-
for (const result of extras) {
|
|
369
|
-
for (const hit of result.hits.filter((entry) => entry.type !== "registry")) {
|
|
370
|
-
const prev = bestExtraStashScore.get(hit.ref);
|
|
371
|
-
if (prev === undefined || (hit.score ?? 0) > prev)
|
|
372
|
-
bestExtraStashScore.set(hit.ref, hit.score ?? 0);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const baseRefs = new Set();
|
|
376
|
-
const baseStash = [];
|
|
377
|
-
for (const hit of base.hits.filter((entry) => entry.type !== "registry")) {
|
|
378
|
-
baseRefs.add(hit.ref);
|
|
379
|
-
const extraScore = bestExtraStashScore.get(hit.ref);
|
|
380
|
-
baseStash.push(extraScore !== undefined && extraScore > (hit.score ?? 0) ? { ...hit, score: extraScore } : hit);
|
|
381
|
-
}
|
|
382
|
-
const extraOnly = new Map();
|
|
383
|
-
for (const result of extras) {
|
|
384
|
-
for (const hit of result.hits.filter((entry) => entry.type !== "registry")) {
|
|
385
|
-
if (baseRefs.has(hit.ref))
|
|
386
|
-
continue;
|
|
387
|
-
const existing = extraOnly.get(hit.ref);
|
|
388
|
-
if (!existing || (hit.score ?? 0) > (existing.score ?? 0))
|
|
389
|
-
extraOnly.set(hit.ref, hit);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
// Fallback-only hits ordinarily stay below every full-query hit through the
|
|
393
|
-
// downstream selector, which re-sorts by score and derives its relevance
|
|
394
|
-
// floor from the leader. Restamping them below the minimum base score keeps a
|
|
395
|
-
// single-token score from leapfrogging contextual results. The one exception
|
|
396
|
-
// is a strong name match repairing a lexically weak base, selected below.
|
|
397
|
-
const sortedExtra = [...extraOnly.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
398
|
-
const lexicalAttribution = (hit) => getSearchHitAttribution(hit)?.lexical;
|
|
399
|
-
// The full-query base remains authoritative unless its lexical provenance is
|
|
400
|
-
// itself weak: relaxed recovery, or no hit whose name covers the query. In
|
|
401
|
-
// that case one fallback discovery with strong name coverage may lead. This
|
|
402
|
-
// preserves contextual base ranking while allowing measured recall to repair
|
|
403
|
-
// a thin match without comparing unrelated raw FTS score scales.
|
|
404
|
-
const weakLexicalBase = baseStash.length > 0 &&
|
|
405
|
-
baseStash.every((hit) => {
|
|
406
|
-
const lexical = lexicalAttribution(hit);
|
|
407
|
-
return lexical !== undefined && (lexical.execution === "relaxed" || lexical.nameMatchTier < 2);
|
|
408
|
-
});
|
|
409
|
-
const promotedExtraIndex = weakLexicalBase
|
|
410
|
-
? sortedExtra.findIndex((hit) => (lexicalAttribution(hit)?.nameMatchTier ?? 0) >= 2)
|
|
411
|
-
: -1;
|
|
412
|
-
const promotedExtra = promotedExtraIndex >= 0 ? sortedExtra[promotedExtraIndex] : undefined;
|
|
413
|
-
const remainingExtra = promotedExtraIndex >= 0 ? sortedExtra.filter((_, index) => index !== promotedExtraIndex) : sortedExtra;
|
|
414
|
-
const minBaseScore = baseStash.length
|
|
415
|
-
? Math.min(...baseStash.map((hit) => hit.score ?? 0))
|
|
416
|
-
: Number.POSITIVE_INFINITY;
|
|
417
|
-
const cappedExtra = baseStash.length
|
|
418
|
-
? remainingExtra.map((hit, i) => ({ ...hit, score: minBaseScore - 1e-6 * (i + 1) }))
|
|
419
|
-
: remainingExtra;
|
|
420
|
-
const mergedHits = [
|
|
421
|
-
...(promotedExtra ? [{ ...promotedExtra, score: minBaseScore }] : []),
|
|
422
|
-
...baseStash,
|
|
423
|
-
...cappedExtra,
|
|
424
|
-
];
|
|
425
|
-
// Registry hits are supplemental fill — same rule: base first (max score on
|
|
426
|
-
// dups), then fallback-only registry hits appended by score.
|
|
427
|
-
const bestExtraRegScore = new Map();
|
|
428
|
-
for (const result of extras) {
|
|
429
|
-
for (const hit of result.registryHits ?? []) {
|
|
430
|
-
const prev = bestExtraRegScore.get(hit.id);
|
|
431
|
-
if (prev === undefined || (hit.score ?? 0) > prev)
|
|
432
|
-
bestExtraRegScore.set(hit.id, hit.score ?? 0);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
const baseRegIds = new Set();
|
|
436
|
-
const baseReg = [];
|
|
437
|
-
for (const hit of base.registryHits ?? []) {
|
|
438
|
-
baseRegIds.add(hit.id);
|
|
439
|
-
const extraScore = bestExtraRegScore.get(hit.id);
|
|
440
|
-
baseReg.push(extraScore !== undefined && extraScore > (hit.score ?? 0) ? { ...hit, score: extraScore } : hit);
|
|
441
|
-
}
|
|
442
|
-
const extraRegOnly = new Map();
|
|
443
|
-
for (const result of extras) {
|
|
444
|
-
for (const hit of result.registryHits ?? []) {
|
|
445
|
-
if (baseRegIds.has(hit.id))
|
|
446
|
-
continue;
|
|
447
|
-
const existing = extraRegOnly.get(hit.id);
|
|
448
|
-
if (!existing || (hit.score ?? 0) > (existing.score ?? 0))
|
|
449
|
-
extraRegOnly.set(hit.id, hit);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
const warnings = Array.from(new Set([...(base.warnings ?? []), ...extras.flatMap((result) => result.warnings ?? [])]));
|
|
453
|
-
const mergedRegistryHits = [
|
|
454
|
-
...baseReg,
|
|
455
|
-
...[...extraRegOnly.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0)),
|
|
456
|
-
];
|
|
457
|
-
const modes = [base.searchMode, ...extras.map((result) => result.searchMode)].filter((mode) => mode !== undefined);
|
|
458
|
-
const searchMode = modes.includes("fts-fallback")
|
|
459
|
-
? "fts-fallback"
|
|
460
|
-
: modes.includes("semantic")
|
|
461
|
-
? "semantic"
|
|
462
|
-
: modes.includes("keyword")
|
|
463
|
-
? "keyword"
|
|
464
|
-
: undefined;
|
|
465
|
-
return {
|
|
466
|
-
...base,
|
|
467
|
-
hits: mergedHits,
|
|
468
|
-
...(mergedRegistryHits.length > 0 ? { registryHits: mergedRegistryHits } : {}),
|
|
469
|
-
...(warnings.length > 0 ? { warnings } : {}),
|
|
470
|
-
...(searchMode ? { searchMode } : {}),
|
|
471
|
-
...(mergedHits.length > 0 || mergedRegistryHits.length > 0 ? { tip: undefined } : {}),
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
export async function searchForCuration(input) {
|
|
475
|
-
const initial = await akmSearch({ ...input, skipLogging: true });
|
|
476
|
-
if (!shouldRunCurateFallback(initial, input.limit))
|
|
477
|
-
return initial;
|
|
478
|
-
const fallbackQueries = deriveCurateFallbackQueries(input.query);
|
|
479
|
-
if (fallbackQueries.length === 0)
|
|
480
|
-
return initial;
|
|
481
|
-
const fallbackResults = await Promise.all(fallbackQueries.map((token) => akmSearch({
|
|
482
|
-
query: token,
|
|
483
|
-
type: input.type,
|
|
484
|
-
limit: input.limit,
|
|
485
|
-
source: input.source,
|
|
486
|
-
skipLogging: true,
|
|
487
|
-
})));
|
|
488
|
-
return mergeCurateSearchResponses(initial, fallbackResults);
|
|
489
|
-
}
|
|
490
|
-
function parseCurateIntent(query) {
|
|
491
|
-
const lower = query.toLowerCase();
|
|
492
|
-
return {
|
|
493
|
-
executionHeavy: /(run|script|bash|shell|cli|execute|automation|deploy|build|test|lint)/.test(lower),
|
|
494
|
-
multiStep: /(plan|workflow|steps?|procedure|rollout|review|migration|release|checklist)/.test(lower),
|
|
495
|
-
delegation: /(agent|assistant|planner|reviewer|architect|prompt)/.test(lower),
|
|
496
|
-
recall: /(memory|context|recall|remember)/.test(lower),
|
|
497
|
-
reference: CURATE_REFERENCE_QUERY_RE.test(lower),
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
function computeCurateTypeNudge(type, intent) {
|
|
501
|
-
let nudge = 0;
|
|
502
|
-
if (intent.executionHeavy) {
|
|
503
|
-
if (type === "script")
|
|
504
|
-
nudge += 0.06;
|
|
505
|
-
else if (type === "command")
|
|
506
|
-
nudge += 0.04;
|
|
507
|
-
else if (type === "memory")
|
|
508
|
-
nudge -= 0.04;
|
|
509
|
-
}
|
|
510
|
-
if (intent.multiStep) {
|
|
511
|
-
if (type === "workflow")
|
|
512
|
-
nudge += 0.06;
|
|
513
|
-
else if (type === "skill")
|
|
514
|
-
nudge += 0.04;
|
|
515
|
-
else if (type === "knowledge")
|
|
516
|
-
nudge -= 0.02;
|
|
517
|
-
}
|
|
518
|
-
if (intent.delegation && type === "agent")
|
|
519
|
-
nudge += 0.06;
|
|
520
|
-
if (intent.recall && type === "memory")
|
|
521
|
-
nudge += 0.08;
|
|
522
|
-
if (intent.reference) {
|
|
523
|
-
if (type === "knowledge")
|
|
524
|
-
nudge += 0.05;
|
|
525
|
-
else if (type === "skill")
|
|
526
|
-
nudge += 0.02;
|
|
527
|
-
}
|
|
528
|
-
return nudge;
|
|
529
|
-
}
|
|
530
|
-
function getCurateFamily(ref) {
|
|
531
|
-
try {
|
|
532
|
-
// F4b: `ref` is a search-hit ref in the 0.9.0 conceptId grammar — parse via
|
|
533
|
-
// the new-grammar `parseRefInput` so skill/reference family grouping still
|
|
534
|
-
// recognizes it. Search may add an opaque Markdown selector; identity and
|
|
535
|
-
// family ownership are on the parent asset, not that selector.
|
|
536
|
-
const parsed = parseRefInput(ref.split("#", 1)[0]);
|
|
537
|
-
if (parsed.type === "skill") {
|
|
538
|
-
return { key: parsed.name, role: "root" };
|
|
539
|
-
}
|
|
540
|
-
if (parsed.type !== "knowledge")
|
|
541
|
-
return undefined;
|
|
542
|
-
const match = /^skills\/(.+?)\/references\/(.+)$/.exec(parsed.name);
|
|
543
|
-
if (!match)
|
|
544
|
-
return undefined;
|
|
545
|
-
return {
|
|
546
|
-
key: match[1],
|
|
547
|
-
role: "reference",
|
|
548
|
-
topicTokens: match[2]
|
|
549
|
-
.split(/[^a-z0-9]+/i)
|
|
550
|
-
.map((token) => token.trim().toLowerCase())
|
|
551
|
-
.filter(Boolean),
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
catch {
|
|
555
|
-
return undefined;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
function annotateCurateHit(query, hit, index, intent) {
|
|
559
|
-
const rawScore = hit.score ?? 0;
|
|
560
|
-
const family = getCurateFamily(hit.ref);
|
|
561
|
-
let adjustedScore = rawScore + computeCurateTypeNudge(hit.type, intent);
|
|
562
|
-
if (family?.role === "root" && !isNarrowReferenceFamilyQuery(query, family))
|
|
563
|
-
adjustedScore += 0.07;
|
|
564
|
-
if (family?.role === "reference" && isNarrowReferenceFamilyQuery(query, family))
|
|
565
|
-
adjustedScore += 0.07;
|
|
566
|
-
return {
|
|
567
|
-
hit,
|
|
568
|
-
rawScore,
|
|
569
|
-
adjustedScore,
|
|
570
|
-
originalIndex: index,
|
|
571
|
-
family,
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
function compareCurateHits(a, b) {
|
|
575
|
-
const rawDiff = b.rawScore - a.rawScore;
|
|
576
|
-
if (Math.abs(rawDiff) > CURATE_CLOSE_SCORE_BAND)
|
|
577
|
-
return rawDiff;
|
|
578
|
-
const adjustedDiff = b.adjustedScore - a.adjustedScore;
|
|
579
|
-
if (adjustedDiff !== 0)
|
|
580
|
-
return adjustedDiff;
|
|
581
|
-
if (rawDiff !== 0)
|
|
582
|
-
return rawDiff;
|
|
583
|
-
return a.originalIndex - b.originalIndex;
|
|
584
|
-
}
|
|
585
|
-
function isNarrowReferenceFamilyQuery(query, family) {
|
|
586
|
-
if (!family || family.role !== "reference")
|
|
587
|
-
return false;
|
|
588
|
-
const lower = query.toLowerCase();
|
|
589
|
-
if (CURATE_REFERENCE_QUERY_RE.test(lower))
|
|
590
|
-
return true;
|
|
591
|
-
return family.topicTokens.some((token) => token.length >= 3 && lower.includes(token));
|
|
292
|
+
/** How many fused candidates curate fetches for the reranker; 0 when reranking is off. */
|
|
293
|
+
function rerankTopN(config) {
|
|
294
|
+
if (!isLlmFeatureEnabled(config, "curate_rerank"))
|
|
295
|
+
return 0;
|
|
296
|
+
return config.search?.curateRerank?.topN ?? DEFAULT_CURATE_RERANK_TOP_N;
|
|
592
297
|
}
|
|
593
|
-
function appendCurateSupportRef(supportRefsByRef, ownerRef, supportRef) {
|
|
594
|
-
const existing = supportRefsByRef.get(ownerRef) ?? [];
|
|
595
|
-
if (existing.some((entry) => entry.ref === supportRef.ref))
|
|
596
|
-
return;
|
|
597
|
-
supportRefsByRef.set(ownerRef, [...existing, supportRef]);
|
|
598
|
-
}
|
|
599
|
-
/** Default number of `selectCuratedStashHits` candidates sent to the reranker when `search.curateRerank.topN` isn't set. */
|
|
600
|
-
const DEFAULT_CURATE_RERANK_TOP_N = 8;
|
|
601
298
|
/**
|
|
602
|
-
* Optional cross-encoder rerank
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
609
|
-
* Only the top `topN` (default {@link DEFAULT_CURATE_RERANK_TOP_N}) already-
|
|
610
|
-
* selected hits are sent (bounded request size); anything past that keeps its
|
|
611
|
-
* original position appended after the reranked prefix.
|
|
299
|
+
* Optional cross-encoder rerank of the top fused candidates (#951). Disabled
|
|
300
|
+
* by default (`search.curateRerank.enabled`) and, when enabled, best-effort:
|
|
301
|
+
* any failure (misconfigured endpoint, network error, timeout, malformed
|
|
302
|
+
* response) keeps the fused order — a reranker outage must never turn into a
|
|
303
|
+
* curate failure. The top `topN` candidates (default
|
|
304
|
+
* {@link DEFAULT_CURATE_RERANK_TOP_N}) are sent as name, description and the
|
|
305
|
+
* start of the indexed content; the rest keep their fused order after them.
|
|
612
306
|
*/
|
|
613
307
|
async function maybeRerankCuratedStashHits(query, hits) {
|
|
614
308
|
if (hits.length <= 1)
|
|
@@ -616,109 +310,58 @@ async function maybeRerankCuratedStashHits(query, hits) {
|
|
|
616
310
|
const config = loadConfig();
|
|
617
311
|
const rerankConfig = config.search?.curateRerank;
|
|
618
312
|
return tryLlmFeature("curate_rerank", config, async () => {
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
const documents = head.map((hit) => [hit.name, hit.description].filter(Boolean).join(" — "));
|
|
623
|
-
const ranked = await rerankDocuments(rerankConfig ?? {}, query, documents);
|
|
313
|
+
const head = hits.slice(0, rerankTopN(config));
|
|
314
|
+
const tail = hits.slice(head.length);
|
|
315
|
+
const ranked = await rerankDocuments(rerankConfig ?? {}, query, rerankDocumentTexts(head));
|
|
624
316
|
const rerankedHead = ranked
|
|
625
317
|
.map(({ index }) => head[index])
|
|
626
318
|
.filter((hit) => hit !== undefined);
|
|
627
319
|
return [...rerankedHead, ...tail];
|
|
628
320
|
}, hits, { timeoutMs: rerankConfig?.timeoutMs ?? null });
|
|
629
321
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
return { selected: ranked.slice(0, limit).map((entry) => entry.hit), supportRefsByRef };
|
|
322
|
+
/**
|
|
323
|
+
* Name, description and the start of each hit's indexed content, capped for
|
|
324
|
+
* the reranker. The content is the index's safe projection (env and secret
|
|
325
|
+
* values never reach it), never the raw file.
|
|
326
|
+
*/
|
|
327
|
+
function rerankDocumentTexts(hits) {
|
|
328
|
+
return hits.map((hit) => [hit.name, hit.description, searchHitContent(hit)].filter(Boolean).join("\n").slice(0, RERANK_DOCUMENT_CHARS));
|
|
638
329
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
: (bestReference ?? group.root);
|
|
665
|
-
if (!representative)
|
|
666
|
-
continue;
|
|
667
|
-
collapsedFamilies.push(representative);
|
|
668
|
-
const supportCandidates = [group.root, ...group.references]
|
|
669
|
-
.filter((entry) => {
|
|
670
|
-
return entry !== undefined && entry.hit.ref !== representative.hit.ref;
|
|
671
|
-
})
|
|
672
|
-
.sort((a, b) => {
|
|
673
|
-
if (a === group.root)
|
|
674
|
-
return -1;
|
|
675
|
-
if (b === group.root)
|
|
676
|
-
return 1;
|
|
677
|
-
return a.hit.ref.localeCompare(b.hit.ref);
|
|
678
|
-
});
|
|
679
|
-
for (const support of supportCandidates) {
|
|
680
|
-
appendCurateSupportRef(supportRefsByRef, representative.hit.ref, {
|
|
681
|
-
ref: support.hit.ref,
|
|
682
|
-
type: support.hit.type,
|
|
683
|
-
reason: "Related family asset to inspect next.",
|
|
684
|
-
});
|
|
330
|
+
/**
|
|
331
|
+
* Up to {@link MAX_CURATE_SUPPORT_REFS} assets the hit's declared links (#935)
|
|
332
|
+
* name, not already selected: what the hit links to first, then what links to
|
|
333
|
+
* it, in the order `akm show` lists them. The LLM entity graph's `related`
|
|
334
|
+
* list no longer feeds them.
|
|
335
|
+
*/
|
|
336
|
+
function buildCurateSupportRefs(links, selectedRefs, ownerRef) {
|
|
337
|
+
const supportRefs = [];
|
|
338
|
+
for (const [part, direction] of [
|
|
339
|
+
["outgoing", "from"],
|
|
340
|
+
["incoming", "to"],
|
|
341
|
+
]) {
|
|
342
|
+
for (const [kind, group] of Object.entries(links?.[part] ?? {})) {
|
|
343
|
+
for (const ref of group.refs) {
|
|
344
|
+
if (ref === ownerRef || selectedRefs.has(ref) || supportRefs.some((existing) => existing.ref === ref))
|
|
345
|
+
continue;
|
|
346
|
+
const type = supportRefType(ref);
|
|
347
|
+
supportRefs.push({
|
|
348
|
+
ref,
|
|
349
|
+
...(type ? { type } : {}),
|
|
350
|
+
reason: `Declared link (${kind}) ${direction} this asset.`,
|
|
351
|
+
});
|
|
352
|
+
if (supportRefs.length >= MAX_CURATE_SUPPORT_REFS)
|
|
353
|
+
return supportRefs;
|
|
354
|
+
}
|
|
685
355
|
}
|
|
686
356
|
}
|
|
687
|
-
return
|
|
688
|
-
hits: [...passthrough, ...collapsedFamilies].sort((a, b) => a.originalIndex - b.originalIndex),
|
|
689
|
-
supportRefsByRef,
|
|
690
|
-
};
|
|
357
|
+
return supportRefs;
|
|
691
358
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
continue;
|
|
697
|
-
if (merged.some((existing) => existing.ref === entry.ref))
|
|
698
|
-
continue;
|
|
699
|
-
merged.push(entry);
|
|
700
|
-
if (merged.length >= MAX_CURATE_SUPPORT_REFS)
|
|
701
|
-
return merged;
|
|
359
|
+
/** The asset type a ref's conceptId names (`memories/x` → `memory`), when it names one. */
|
|
360
|
+
function supportRefType(ref) {
|
|
361
|
+
try {
|
|
362
|
+
return typeNameFromConceptId(parseBundleRef(ref).conceptId)?.type;
|
|
702
363
|
}
|
|
703
|
-
|
|
704
|
-
return
|
|
705
|
-
for (const hit of relatedHits) {
|
|
706
|
-
if (!hit.ref || hit.ref === ownerRef || selectedRefs.has(hit.ref))
|
|
707
|
-
continue;
|
|
708
|
-
if (merged.some((existing) => existing.ref === hit.ref))
|
|
709
|
-
continue;
|
|
710
|
-
merged.push({ ref: hit.ref, type: hit.type, reason: "Related asset via shared entities." });
|
|
711
|
-
if (merged.length >= MAX_CURATE_SUPPORT_REFS)
|
|
712
|
-
break;
|
|
364
|
+
catch {
|
|
365
|
+
return undefined;
|
|
713
366
|
}
|
|
714
|
-
return merged;
|
|
715
|
-
}
|
|
716
|
-
function shouldRunCurateFallback(initial, desiredCount) {
|
|
717
|
-
const stashHits = initial.hits.filter((hit) => hit.type !== "registry");
|
|
718
|
-
if (stashHits.length === 0)
|
|
719
|
-
return true;
|
|
720
|
-
const topScore = stashHits[0]?.score ?? 0;
|
|
721
|
-
const strongFloor = Math.max(CURATE_FALLBACK_STRONG_SCORE_FLOOR, topScore * CURATE_RELATIVE_SCORE_FLOOR);
|
|
722
|
-
const strongCount = stashHits.filter((hit) => (hit.score ?? 0) >= strongFloor).length;
|
|
723
|
-
return !(topScore >= CURATE_FALLBACK_TOP_SCORE_THRESHOLD && strongCount >= Math.min(2, desiredCount));
|
|
724
367
|
}
|