akm-cli 0.7.0-rc1 → 0.7.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/dist/{src/cli.js → cli.js} +100 -16
- package/dist/{src/commands → commands}/config-cli.js +42 -0
- package/dist/{src/commands → commands}/history.js +78 -7
- package/dist/{src/commands → commands}/registry-search.js +69 -6
- package/dist/{src/commands → commands}/search.js +30 -3
- package/dist/{src/commands → commands}/show.js +29 -0
- package/dist/{src/commands → commands}/source-add.js +5 -1
- package/dist/{src/commands → commands}/source-manage.js +7 -1
- package/dist/{src/core → core}/config.js +28 -0
- package/dist/{src/indexer → indexer}/db-search.js +1 -0
- package/dist/{src/indexer → indexer}/indexer.js +16 -2
- package/dist/{src/indexer → indexer}/matchers.js +1 -1
- package/dist/{src/indexer → indexer}/search-source.js +4 -2
- package/dist/{src/integrations → integrations}/agent/profiles.js +1 -1
- package/dist/{src/integrations → integrations}/agent/spawn.js +67 -16
- package/dist/{src/integrations → integrations}/github.js +9 -3
- package/dist/{src/llm → llm}/embedders/remote.js +37 -3
- package/dist/{src/output → output}/cli-hints.js +15 -2
- package/dist/{src/output → output}/renderers.js +3 -1
- package/dist/{src/output → output}/shapes.js +8 -1
- package/dist/{src/output → output}/text.js +156 -3
- package/dist/{src/registry → registry}/build-index.js +5 -4
- package/dist/{src/registry → registry}/providers/static-index.js +3 -1
- package/dist/{src/setup → setup}/setup.js +9 -0
- package/dist/{src/wiki → wiki}/wiki.js +54 -6
- package/dist/{src/workflows → workflows}/runs.js +37 -3
- package/package.json +8 -8
- package/dist/tests/add-website-source.test.js +0 -119
- package/dist/tests/agent/agent-config-loader.test.js +0 -70
- package/dist/tests/agent/agent-config.test.js +0 -221
- package/dist/tests/agent/agent-detect.test.js +0 -100
- package/dist/tests/agent/agent-spawn.test.js +0 -234
- package/dist/tests/agent-output.test.js +0 -186
- package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
- package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
- package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
- package/dist/tests/asset-ref.test.js +0 -192
- package/dist/tests/asset-registry.test.js +0 -103
- package/dist/tests/asset-spec.test.js +0 -241
- package/dist/tests/bench/attribution.test.js +0 -995
- package/dist/tests/bench/cleanup-sigint.test.js +0 -83
- package/dist/tests/bench/cleanup.js +0 -203
- package/dist/tests/bench/cleanup.test.js +0 -166
- package/dist/tests/bench/cli.js +0 -683
- package/dist/tests/bench/cli.test.js +0 -177
- package/dist/tests/bench/compare.test.js +0 -556
- package/dist/tests/bench/corpus.js +0 -314
- package/dist/tests/bench/corpus.test.js +0 -258
- package/dist/tests/bench/driver.js +0 -346
- package/dist/tests/bench/driver.test.js +0 -443
- package/dist/tests/bench/evolve-metrics.js +0 -179
- package/dist/tests/bench/evolve-metrics.test.js +0 -187
- package/dist/tests/bench/evolve.js +0 -580
- package/dist/tests/bench/evolve.test.js +0 -616
- package/dist/tests/bench/failure-modes.test.js +0 -300
- package/dist/tests/bench/feedback-integrity.test.js +0 -456
- package/dist/tests/bench/leakage.test.js +0 -125
- package/dist/tests/bench/learning-curve.test.js +0 -133
- package/dist/tests/bench/metrics.js +0 -2319
- package/dist/tests/bench/metrics.test.js +0 -1144
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
- package/dist/tests/bench/report.js +0 -1821
- package/dist/tests/bench/report.test.js +0 -989
- package/dist/tests/bench/runner.js +0 -536
- package/dist/tests/bench/runner.test.js +0 -958
- package/dist/tests/bench/search-bridge.test.js +0 -331
- package/dist/tests/bench/tmp.js +0 -41
- package/dist/tests/bench/trajectory.js +0 -116
- package/dist/tests/bench/trajectory.test.js +0 -127
- package/dist/tests/bench/verifier.js +0 -109
- package/dist/tests/bench/verifier.test.js +0 -118
- package/dist/tests/bench/workflow-evaluator.js +0 -557
- package/dist/tests/bench/workflow-evaluator.test.js +0 -421
- package/dist/tests/bench/workflow-spec.js +0 -358
- package/dist/tests/bench/workflow-spec.test.js +0 -363
- package/dist/tests/bench/workflow-trace.js +0 -438
- package/dist/tests/bench/workflow-trace.test.js +0 -254
- package/dist/tests/benchmark-search-quality.js +0 -536
- package/dist/tests/benchmark-suite.js +0 -1441
- package/dist/tests/capture-cli.test.js +0 -112
- package/dist/tests/cli-errors.test.js +0 -203
- package/dist/tests/commands/events.test.js +0 -370
- package/dist/tests/commands/history.test.js +0 -223
- package/dist/tests/commands/import.test.js +0 -103
- package/dist/tests/commands/proposal-cli.test.js +0 -209
- package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
- package/dist/tests/commands/remember.test.js +0 -97
- package/dist/tests/commands/scope-flags.test.js +0 -300
- package/dist/tests/commands/search.test.js +0 -537
- package/dist/tests/commands/show-indexer-parity.test.js +0 -117
- package/dist/tests/commands/show.test.js +0 -294
- package/dist/tests/common.test.js +0 -266
- package/dist/tests/completions.test.js +0 -142
- package/dist/tests/config-cli.test.js +0 -193
- package/dist/tests/config-llm-features.test.js +0 -139
- package/dist/tests/config.test.js +0 -544
- package/dist/tests/contracts/migration-baseline.test.js +0 -43
- package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
- package/dist/tests/contracts/spec-helpers.js +0 -46
- package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
- package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
- package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
- package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
- package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
- package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
- package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
- package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
- package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
- package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
- package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
- package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
- package/dist/tests/core/write-source.test.js +0 -366
- package/dist/tests/curate-command.test.js +0 -87
- package/dist/tests/db-scoring.test.js +0 -201
- package/dist/tests/db.test.js +0 -654
- package/dist/tests/distill-cli-flag.test.js +0 -208
- package/dist/tests/distill.test.js +0 -515
- package/dist/tests/docker-install.test.js +0 -120
- package/dist/tests/e2e.test.js +0 -1398
- package/dist/tests/embedder.test.js +0 -340
- package/dist/tests/embedding-model-config.test.js +0 -379
- package/dist/tests/feedback-command.test.js +0 -172
- package/dist/tests/file-context.test.js +0 -552
- package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
- package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
- package/dist/tests/fixtures/stashes/load.js +0 -166
- package/dist/tests/fixtures/stashes/load.test.js +0 -88
- package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
- package/dist/tests/frontmatter.test.js +0 -190
- package/dist/tests/fts-field-weighting.test.js +0 -254
- package/dist/tests/fuzzy-search.test.js +0 -230
- package/dist/tests/git-provider-clone.test.js +0 -45
- package/dist/tests/github.test.js +0 -161
- package/dist/tests/graph-boost-ranking.test.js +0 -305
- package/dist/tests/graph-extraction.test.js +0 -282
- package/dist/tests/helpers/usage-events.js +0 -8
- package/dist/tests/index-pass-llm.test.js +0 -161
- package/dist/tests/indexer.test.js +0 -559
- package/dist/tests/info-command.test.js +0 -166
- package/dist/tests/init.test.js +0 -69
- package/dist/tests/install-script.test.js +0 -246
- package/dist/tests/integration/agent-real-profile.test.js +0 -94
- package/dist/tests/issue-36-repro.test.js +0 -304
- package/dist/tests/issues-191-194.test.js +0 -160
- package/dist/tests/lesson-lint.test.js +0 -111
- package/dist/tests/llm-client.test.js +0 -115
- package/dist/tests/llm-feature-gate.test.js +0 -151
- package/dist/tests/llm.test.js +0 -139
- package/dist/tests/lockfile.test.js +0 -216
- package/dist/tests/manifest.test.js +0 -205
- package/dist/tests/markdown.test.js +0 -126
- package/dist/tests/matchers-unit.test.js +0 -189
- package/dist/tests/memory-inference.test.js +0 -299
- package/dist/tests/merge-scoring.test.js +0 -136
- package/dist/tests/metadata.test.js +0 -313
- package/dist/tests/migration-help.test.js +0 -89
- package/dist/tests/origin-resolve.test.js +0 -124
- package/dist/tests/output-baseline.test.js +0 -217
- package/dist/tests/output-shapes-unit.test.js +0 -476
- package/dist/tests/parallel-search.test.js +0 -272
- package/dist/tests/parameter-metadata.test.js +0 -365
- package/dist/tests/paths.test.js +0 -177
- package/dist/tests/progressive-disclosure.test.js +0 -280
- package/dist/tests/proposals.test.js +0 -279
- package/dist/tests/proposed-quality.test.js +0 -271
- package/dist/tests/provider-registry.test.js +0 -32
- package/dist/tests/ranking-regression.test.js +0 -548
- package/dist/tests/reflect-propose.test.js +0 -455
- package/dist/tests/registry-build-index.test.js +0 -378
- package/dist/tests/registry-cli.test.js +0 -290
- package/dist/tests/registry-index-v2.test.js +0 -430
- package/dist/tests/registry-install.test.js +0 -728
- package/dist/tests/registry-providers/parity.test.js +0 -189
- package/dist/tests/registry-providers/skills-sh.test.js +0 -309
- package/dist/tests/registry-providers/static-index.test.js +0 -204
- package/dist/tests/registry-resolve.test.js +0 -126
- package/dist/tests/registry-search.test.js +0 -723
- package/dist/tests/remember-frontmatter.test.js +0 -380
- package/dist/tests/remember-unit.test.js +0 -123
- package/dist/tests/ripgrep-install.test.js +0 -251
- package/dist/tests/ripgrep-resolve.test.js +0 -108
- package/dist/tests/ripgrep.test.js +0 -163
- package/dist/tests/save-command.test.js +0 -94
- package/dist/tests/save-trust-qa-fixes.test.js +0 -270
- package/dist/tests/scoring-pipeline.test.js +0 -648
- package/dist/tests/search-include-proposed-cli.test.js +0 -118
- package/dist/tests/self-update.test.js +0 -442
- package/dist/tests/semantic-search-e2e.test.js +0 -512
- package/dist/tests/semantic-status.test.js +0 -471
- package/dist/tests/setup-run.integration.js +0 -877
- package/dist/tests/setup-wizard.test.js +0 -198
- package/dist/tests/setup.test.js +0 -131
- package/dist/tests/source-add.test.js +0 -11
- package/dist/tests/source-clone.test.js +0 -254
- package/dist/tests/source-manage.test.js +0 -366
- package/dist/tests/source-providers/filesystem.test.js +0 -82
- package/dist/tests/source-providers/git.test.js +0 -252
- package/dist/tests/source-providers/website.test.js +0 -128
- package/dist/tests/source-qa-fixes.test.js +0 -268
- package/dist/tests/source-registry.test.js +0 -350
- package/dist/tests/source-resolve.test.js +0 -100
- package/dist/tests/source-source.test.js +0 -221
- package/dist/tests/source.test.js +0 -533
- package/dist/tests/tar-utils-scan.test.js +0 -73
- package/dist/tests/toggle-components.test.js +0 -73
- package/dist/tests/usage-telemetry.test.js +0 -265
- package/dist/tests/utility-scoring.test.js +0 -558
- package/dist/tests/vault-load-error.test.js +0 -78
- package/dist/tests/vault-qa-fixes.test.js +0 -194
- package/dist/tests/vault.test.js +0 -429
- package/dist/tests/vector-search.test.js +0 -608
- package/dist/tests/walker.test.js +0 -252
- package/dist/tests/wave2-cluster-bc.test.js +0 -228
- package/dist/tests/wave2-cluster-d.test.js +0 -180
- package/dist/tests/wave2-cluster-e.test.js +0 -179
- package/dist/tests/wiki-qa-fixes.test.js +0 -270
- package/dist/tests/wiki.test.js +0 -529
- package/dist/tests/workflow-cli.test.js +0 -271
- package/dist/tests/workflow-markdown.test.js +0 -171
- package/dist/tests/workflow-path-escape.test.js +0 -132
- package/dist/tests/workflow-qa-fixes.test.js +0 -377
- package/dist/tests/workflows/indexer-rejection.test.js +0 -213
- /package/dist/{src/commands → commands}/completions.js +0 -0
- /package/dist/{src/commands → commands}/curate.js +0 -0
- /package/dist/{src/commands → commands}/distill.js +0 -0
- /package/dist/{src/commands → commands}/events.js +0 -0
- /package/dist/{src/commands → commands}/info.js +0 -0
- /package/dist/{src/commands → commands}/init.js +0 -0
- /package/dist/{src/commands → commands}/install-audit.js +0 -0
- /package/dist/{src/commands → commands}/installed-stashes.js +0 -0
- /package/dist/{src/commands → commands}/migration-help.js +0 -0
- /package/dist/{src/commands → commands}/proposal.js +0 -0
- /package/dist/{src/commands → commands}/propose.js +0 -0
- /package/dist/{src/commands → commands}/reflect.js +0 -0
- /package/dist/{src/commands → commands}/remember.js +0 -0
- /package/dist/{src/commands → commands}/self-update.js +0 -0
- /package/dist/{src/commands → commands}/source-clone.js +0 -0
- /package/dist/{src/commands → commands}/vault.js +0 -0
- /package/dist/{src/core → core}/asset-ref.js +0 -0
- /package/dist/{src/core → core}/asset-registry.js +0 -0
- /package/dist/{src/core → core}/asset-spec.js +0 -0
- /package/dist/{src/core → core}/common.js +0 -0
- /package/dist/{src/core → core}/errors.js +0 -0
- /package/dist/{src/core → core}/events.js +0 -0
- /package/dist/{src/core → core}/frontmatter.js +0 -0
- /package/dist/{src/core → core}/lesson-lint.js +0 -0
- /package/dist/{src/core → core}/markdown.js +0 -0
- /package/dist/{src/core → core}/paths.js +0 -0
- /package/dist/{src/core → core}/proposals.js +0 -0
- /package/dist/{src/core → core}/warn.js +0 -0
- /package/dist/{src/core → core}/write-source.js +0 -0
- /package/dist/{src/indexer → indexer}/db.js +0 -0
- /package/dist/{src/indexer → indexer}/file-context.js +0 -0
- /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
- /package/dist/{src/indexer → indexer}/graph-extraction.js +0 -0
- /package/dist/{src/indexer → indexer}/manifest.js +0 -0
- /package/dist/{src/indexer → indexer}/memory-inference.js +0 -0
- /package/dist/{src/indexer → indexer}/metadata.js +0 -0
- /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
- /package/dist/{src/indexer → indexer}/semantic-status.js +0 -0
- /package/dist/{src/indexer → indexer}/usage-events.js +0 -0
- /package/dist/{src/indexer → indexer}/walker.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/config.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/detect.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/index.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
- /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
- /package/dist/{src/llm → llm}/client.js +0 -0
- /package/dist/{src/llm → llm}/embedder.js +0 -0
- /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
- /package/dist/{src/llm → llm}/embedders/local.js +0 -0
- /package/dist/{src/llm → llm}/embedders/types.js +0 -0
- /package/dist/{src/llm → llm}/feature-gate.js +0 -0
- /package/dist/{src/llm → llm}/graph-extract.js +0 -0
- /package/dist/{src/llm → llm}/index-passes.js +0 -0
- /package/dist/{src/llm → llm}/memory-infer.js +0 -0
- /package/dist/{src/llm → llm}/metadata-enhance.js +0 -0
- /package/dist/{src/output → output}/context.js +0 -0
- /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
- /package/dist/{src/registry → registry}/factory.js +0 -0
- /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
- /package/dist/{src/registry → registry}/providers/index.js +0 -0
- /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
- /package/dist/{src/registry → registry}/providers/types.js +0 -0
- /package/dist/{src/registry → registry}/resolve.js +0 -0
- /package/dist/{src/registry → registry}/types.js +0 -0
- /package/dist/{src/setup → setup}/detect.js +0 -0
- /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
- /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
- /package/dist/{src/setup → setup}/steps.js +0 -0
- /package/dist/{src/sources → sources}/include.js +0 -0
- /package/dist/{src/sources → sources}/provider-factory.js +0 -0
- /package/dist/{src/sources → sources}/provider.js +0 -0
- /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
- /package/dist/{src/sources → sources}/providers/git.js +0 -0
- /package/dist/{src/sources → sources}/providers/index.js +0 -0
- /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
- /package/dist/{src/sources → sources}/providers/npm.js +0 -0
- /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
- /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
- /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
- /package/dist/{src/sources → sources}/providers/website.js +0 -0
- /package/dist/{src/sources → sources}/resolve.js +0 -0
- /package/dist/{src/sources → sources}/types.js +0 -0
- /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
- /package/dist/{src/version.js → version.js} +0 -0
- /package/dist/{src/workflows → workflows}/authoring.js +0 -0
- /package/dist/{src/workflows → workflows}/cli.js +0 -0
- /package/dist/{src/workflows → workflows}/db.js +0 -0
- /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
- /package/dist/{src/workflows → workflows}/parser.js +0 -0
- /package/dist/{src/workflows → workflows}/renderer.js +0 -0
- /package/dist/{src/workflows → workflows}/schema.js +0 -0
- /package/dist/{src/workflows → workflows}/validator.js +0 -0
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test";
|
|
2
|
-
import * as childProcess from "node:child_process";
|
|
3
|
-
import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../src/integrations/github";
|
|
4
|
-
// ── Environment helpers ─────────────────────────────────────────────────────
|
|
5
|
-
const originalGithubToken = process.env.GITHUB_TOKEN;
|
|
6
|
-
const originalGhToken = process.env.GH_TOKEN;
|
|
7
|
-
afterEach(() => {
|
|
8
|
-
mock.restore();
|
|
9
|
-
if (originalGithubToken === undefined) {
|
|
10
|
-
delete process.env.GITHUB_TOKEN;
|
|
11
|
-
}
|
|
12
|
-
else {
|
|
13
|
-
process.env.GITHUB_TOKEN = originalGithubToken;
|
|
14
|
-
}
|
|
15
|
-
if (originalGhToken === undefined) {
|
|
16
|
-
delete process.env.GH_TOKEN;
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
process.env.GH_TOKEN = originalGhToken;
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
// ── GITHUB_API_BASE ─────────────────────────────────────────────────────────
|
|
23
|
-
describe("GITHUB_API_BASE", () => {
|
|
24
|
-
test("is the GitHub API URL", () => {
|
|
25
|
-
expect(GITHUB_API_BASE).toBe("https://api.github.com");
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
// ── githubHeaders ───────────────────────────────────────────────────────────
|
|
29
|
-
describe("githubHeaders", () => {
|
|
30
|
-
test("includes Accept and User-Agent headers", () => {
|
|
31
|
-
delete process.env.GITHUB_TOKEN;
|
|
32
|
-
const headers = githubHeaders();
|
|
33
|
-
expect(headers.Accept).toBe("application/vnd.github+json");
|
|
34
|
-
expect(headers["User-Agent"]).toBe("akm-registry");
|
|
35
|
-
});
|
|
36
|
-
test("does not include Authorization when GITHUB_TOKEN is unset", () => {
|
|
37
|
-
delete process.env.GITHUB_TOKEN;
|
|
38
|
-
delete process.env.GH_TOKEN;
|
|
39
|
-
spyOn(childProcess, "spawnSync").mockReturnValue({ status: 1, stdout: "" });
|
|
40
|
-
const headers = githubHeaders();
|
|
41
|
-
expect(headers.Authorization).toBeUndefined();
|
|
42
|
-
});
|
|
43
|
-
test("includes Authorization when GITHUB_TOKEN is set", () => {
|
|
44
|
-
process.env.GITHUB_TOKEN = "ghp_test_token_123";
|
|
45
|
-
const headers = githubHeaders();
|
|
46
|
-
expect(headers.Authorization).toBe("Bearer ghp_test_token_123");
|
|
47
|
-
});
|
|
48
|
-
test("trims whitespace from GITHUB_TOKEN", () => {
|
|
49
|
-
process.env.GITHUB_TOKEN = " ghp_trimmed ";
|
|
50
|
-
delete process.env.GH_TOKEN;
|
|
51
|
-
const headers = githubHeaders();
|
|
52
|
-
expect(headers.Authorization).toBe("Bearer ghp_trimmed");
|
|
53
|
-
});
|
|
54
|
-
test("does not include Authorization when GITHUB_TOKEN is empty", () => {
|
|
55
|
-
process.env.GITHUB_TOKEN = "";
|
|
56
|
-
delete process.env.GH_TOKEN;
|
|
57
|
-
const headers = githubHeaders();
|
|
58
|
-
expect(headers.Authorization).toBeUndefined();
|
|
59
|
-
});
|
|
60
|
-
test("does not include Authorization when GITHUB_TOKEN is whitespace-only", () => {
|
|
61
|
-
process.env.GITHUB_TOKEN = " ";
|
|
62
|
-
delete process.env.GH_TOKEN;
|
|
63
|
-
const headers = githubHeaders();
|
|
64
|
-
expect(headers.Authorization).toBeUndefined();
|
|
65
|
-
});
|
|
66
|
-
test("uses GH_TOKEN when GITHUB_TOKEN is unset", () => {
|
|
67
|
-
delete process.env.GITHUB_TOKEN;
|
|
68
|
-
process.env.GH_TOKEN = "ghs_from_gh_token";
|
|
69
|
-
const headers = githubHeaders();
|
|
70
|
-
expect(headers.Authorization).toBe("Bearer ghs_from_gh_token");
|
|
71
|
-
});
|
|
72
|
-
test("prefers GITHUB_TOKEN over GH_TOKEN", () => {
|
|
73
|
-
process.env.GITHUB_TOKEN = "ghp_preferred";
|
|
74
|
-
process.env.GH_TOKEN = "ghs_fallback";
|
|
75
|
-
const headers = githubHeaders();
|
|
76
|
-
expect(headers.Authorization).toBe("Bearer ghp_preferred");
|
|
77
|
-
});
|
|
78
|
-
test("falls back to gh auth token when env vars are unset", () => {
|
|
79
|
-
delete process.env.GITHUB_TOKEN;
|
|
80
|
-
delete process.env.GH_TOKEN;
|
|
81
|
-
const spawnSyncSpy = spyOn(childProcess, "spawnSync").mockReturnValue({
|
|
82
|
-
status: 0,
|
|
83
|
-
stdout: "gho_cli_token\n",
|
|
84
|
-
});
|
|
85
|
-
const headers = githubHeaders();
|
|
86
|
-
expect(headers.Authorization).toBe("Bearer gho_cli_token");
|
|
87
|
-
expect(spawnSyncSpy).toHaveBeenCalledWith("gh", ["auth", "token"], {
|
|
88
|
-
encoding: "utf8",
|
|
89
|
-
timeout: 5000,
|
|
90
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
test("does not include gh auth token for non-GitHub URLs", () => {
|
|
94
|
-
delete process.env.GITHUB_TOKEN;
|
|
95
|
-
delete process.env.GH_TOKEN;
|
|
96
|
-
spyOn(childProcess, "spawnSync").mockReturnValue({ status: 0, stdout: "gho_cli_token\n" });
|
|
97
|
-
const headers = githubHeaders("https://example.com/file.tgz");
|
|
98
|
-
expect(headers.Authorization).toBeUndefined();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
// ── asRecord ────────────────────────────────────────────────────────────────
|
|
102
|
-
describe("asRecord", () => {
|
|
103
|
-
test("returns object as-is for a plain object", () => {
|
|
104
|
-
const obj = { key: "value", num: 42 };
|
|
105
|
-
expect(asRecord(obj)).toBe(obj);
|
|
106
|
-
});
|
|
107
|
-
test("returns empty object for null", () => {
|
|
108
|
-
expect(asRecord(null)).toEqual({});
|
|
109
|
-
});
|
|
110
|
-
test("returns empty object for undefined", () => {
|
|
111
|
-
expect(asRecord(undefined)).toEqual({});
|
|
112
|
-
});
|
|
113
|
-
test("returns empty object for a string", () => {
|
|
114
|
-
expect(asRecord("hello")).toEqual({});
|
|
115
|
-
});
|
|
116
|
-
test("returns empty object for a number", () => {
|
|
117
|
-
expect(asRecord(42)).toEqual({});
|
|
118
|
-
});
|
|
119
|
-
test("returns empty object for a boolean", () => {
|
|
120
|
-
expect(asRecord(true)).toEqual({});
|
|
121
|
-
});
|
|
122
|
-
test("returns empty object for an array", () => {
|
|
123
|
-
expect(asRecord([1, 2, 3])).toEqual({});
|
|
124
|
-
});
|
|
125
|
-
test("returns the object for nested objects", () => {
|
|
126
|
-
const nested = { a: { b: "c" } };
|
|
127
|
-
const result = asRecord(nested);
|
|
128
|
-
expect(result).toBe(nested);
|
|
129
|
-
expect(result.a).toEqual({ b: "c" });
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
// ── asString ────────────────────────────────────────────────────────────────
|
|
133
|
-
describe("asString", () => {
|
|
134
|
-
test("returns string for a non-empty string", () => {
|
|
135
|
-
expect(asString("hello")).toBe("hello");
|
|
136
|
-
});
|
|
137
|
-
test("returns undefined for an empty string", () => {
|
|
138
|
-
expect(asString("")).toBeUndefined();
|
|
139
|
-
});
|
|
140
|
-
test("returns undefined for null", () => {
|
|
141
|
-
expect(asString(null)).toBeUndefined();
|
|
142
|
-
});
|
|
143
|
-
test("returns undefined for undefined", () => {
|
|
144
|
-
expect(asString(undefined)).toBeUndefined();
|
|
145
|
-
});
|
|
146
|
-
test("returns undefined for a number", () => {
|
|
147
|
-
expect(asString(42)).toBeUndefined();
|
|
148
|
-
});
|
|
149
|
-
test("returns undefined for a boolean", () => {
|
|
150
|
-
expect(asString(true)).toBeUndefined();
|
|
151
|
-
});
|
|
152
|
-
test("returns undefined for an object", () => {
|
|
153
|
-
expect(asString({ toString: () => "obj" })).toBeUndefined();
|
|
154
|
-
});
|
|
155
|
-
test("returns undefined for an array", () => {
|
|
156
|
-
expect(asString(["hello"])).toBeUndefined();
|
|
157
|
-
});
|
|
158
|
-
test("returns string with whitespace preserved", () => {
|
|
159
|
-
expect(asString(" spaced ")).toBe(" spaced ");
|
|
160
|
-
});
|
|
161
|
-
});
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration test for the search-time graph boost (#207).
|
|
3
|
-
*
|
|
4
|
-
* Asserts deterministically that for a corpus with a known graph-eligible
|
|
5
|
-
* query, the rank of the expected target improves when a `graph.json`
|
|
6
|
-
* artifact is present versus absent. No LLM calls are made — the graph
|
|
7
|
-
* file is written directly to the fixture stash, simulating what the
|
|
8
|
-
* extraction pass would produce.
|
|
9
|
-
*
|
|
10
|
-
* The test uses TWO independent runs against the SAME database state:
|
|
11
|
-
* 1. Baseline — `graph.json` deleted before search.
|
|
12
|
-
* 2. Boosted — `graph.json` present before search.
|
|
13
|
-
*
|
|
14
|
-
* Acceptance: the target's rank in the boosted run must be ≤ its rank in
|
|
15
|
-
* the baseline run, and at least one of (rank improves) OR (score
|
|
16
|
-
* strictly increases) must hold. This is a deterministic comparison, not
|
|
17
|
-
* a percentage threshold.
|
|
18
|
-
*
|
|
19
|
-
* It also verifies that the graph signal feeds the SAME `score` field on
|
|
20
|
-
* `SourceSearchHit` — i.e. there is no second SearchHit scorer; the
|
|
21
|
-
* graph-aware run produces a (weakly) higher score on the same hit.
|
|
22
|
-
*/
|
|
23
|
-
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
24
|
-
import fs from "node:fs";
|
|
25
|
-
import os from "node:os";
|
|
26
|
-
import path from "node:path";
|
|
27
|
-
import { akmSearch } from "../src/commands/search";
|
|
28
|
-
import { resetConfigCache, saveConfig } from "../src/core/config";
|
|
29
|
-
import { getDbPath } from "../src/core/paths";
|
|
30
|
-
import { closeDatabase, openDatabase, rebuildFts, setMeta, upsertEntry } from "../src/indexer/db";
|
|
31
|
-
import { GRAPH_FILE_SCHEMA_VERSION, getGraphFilePath } from "../src/indexer/graph-extraction";
|
|
32
|
-
import { buildSearchText } from "../src/indexer/search-fields";
|
|
33
|
-
// ── Environment isolation ───────────────────────────────────────────────────
|
|
34
|
-
let stashDir = "";
|
|
35
|
-
let originalXdgCacheHome;
|
|
36
|
-
let originalXdgConfigHome;
|
|
37
|
-
let originalAkmStashDir;
|
|
38
|
-
let testCacheDir = "";
|
|
39
|
-
let testConfigDir = "";
|
|
40
|
-
beforeAll(() => {
|
|
41
|
-
originalXdgCacheHome = process.env.XDG_CACHE_HOME;
|
|
42
|
-
originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
43
|
-
originalAkmStashDir = process.env.AKM_STASH_DIR;
|
|
44
|
-
testCacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-graph-rank-cache-"));
|
|
45
|
-
testConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-graph-rank-config-"));
|
|
46
|
-
stashDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-graph-rank-stash-"));
|
|
47
|
-
process.env.XDG_CACHE_HOME = testCacheDir;
|
|
48
|
-
process.env.XDG_CONFIG_HOME = testConfigDir;
|
|
49
|
-
process.env.AKM_STASH_DIR = stashDir;
|
|
50
|
-
resetConfigCache();
|
|
51
|
-
saveConfig({
|
|
52
|
-
semanticSearchMode: "off",
|
|
53
|
-
sources: [{ type: "filesystem", path: stashDir }],
|
|
54
|
-
registries: [],
|
|
55
|
-
});
|
|
56
|
-
buildFixture();
|
|
57
|
-
});
|
|
58
|
-
afterAll(() => {
|
|
59
|
-
if (originalXdgCacheHome === undefined)
|
|
60
|
-
delete process.env.XDG_CACHE_HOME;
|
|
61
|
-
else
|
|
62
|
-
process.env.XDG_CACHE_HOME = originalXdgCacheHome;
|
|
63
|
-
if (originalXdgConfigHome === undefined)
|
|
64
|
-
delete process.env.XDG_CONFIG_HOME;
|
|
65
|
-
else
|
|
66
|
-
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
|
|
67
|
-
if (originalAkmStashDir === undefined)
|
|
68
|
-
delete process.env.AKM_STASH_DIR;
|
|
69
|
-
else
|
|
70
|
-
process.env.AKM_STASH_DIR = originalAkmStashDir;
|
|
71
|
-
resetConfigCache();
|
|
72
|
-
for (const dir of [testCacheDir, testConfigDir, stashDir]) {
|
|
73
|
-
if (dir)
|
|
74
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
// ── Fixture builder ─────────────────────────────────────────────────────────
|
|
78
|
-
//
|
|
79
|
-
// The corpus is small but deliberately constructed so the lexical signal
|
|
80
|
-
// is roughly even between two candidates — the graph boost is what
|
|
81
|
-
// separates them deterministically:
|
|
82
|
-
//
|
|
83
|
-
// - knowledge:database-runbook — recovery procedure for a database
|
|
84
|
-
// outage. The TARGET of the test query. Doesn't have any postgres-
|
|
85
|
-
// specific name match, so a postgres-aware query splits between this
|
|
86
|
-
// and the FAQ on lexical signals alone.
|
|
87
|
-
// - knowledge:database-faq — Q&A document. Lexical match on the same
|
|
88
|
-
// query terms but unrelated to outage recovery operationally. The
|
|
89
|
-
// COMPETITOR.
|
|
90
|
-
// - memory:incident-2024-shard — operational note. Anchors the graph
|
|
91
|
-
// edge connecting "outage recovery" to the runbook file.
|
|
92
|
-
//
|
|
93
|
-
// With graph.json present, the runbook's entities directly match the
|
|
94
|
-
// query tokens and pick up the graph boost; the FAQ has no graph node
|
|
95
|
-
// and gets nothing. The deterministic acceptance is "rank improves OR
|
|
96
|
-
// score strictly increases" — both work even if the baseline already
|
|
97
|
-
// happens to put the runbook on top.
|
|
98
|
-
function buildFixture() {
|
|
99
|
-
// Asset files on disk — the search-time graph boost matches by absolute
|
|
100
|
-
// file path, so paths must be consistent between fixture build and
|
|
101
|
-
// graph.json contents.
|
|
102
|
-
const knowledgeDir = path.join(stashDir, "knowledge");
|
|
103
|
-
const memoryDir = path.join(stashDir, "memories");
|
|
104
|
-
fs.mkdirSync(knowledgeDir, { recursive: true });
|
|
105
|
-
fs.mkdirSync(memoryDir, { recursive: true });
|
|
106
|
-
const runbookPath = path.join(knowledgeDir, "database-runbook.md");
|
|
107
|
-
fs.writeFileSync(runbookPath, "---\ntype: knowledge\n---\n\nThe runbook for database outage recovery after a hardware fault.\n");
|
|
108
|
-
const faqPath = path.join(knowledgeDir, "database-faq.md");
|
|
109
|
-
fs.writeFileSync(faqPath, "---\ntype: knowledge\n---\n\nA database FAQ covering connection limits, recovery tunables, and outage post-mortems.\n");
|
|
110
|
-
const memoryPath = path.join(memoryDir, "incident-2024-shard.md");
|
|
111
|
-
fs.writeFileSync(memoryPath, "---\ntype: memory\n---\n\nDuring the 2024 database outage we recovered shard-3 by following the runbook.\n");
|
|
112
|
-
// Index the corpus directly into the SQLite DB.
|
|
113
|
-
const dbPath = getDbPath();
|
|
114
|
-
// Make sure the cache dir exists (akm-graph-rank-cache-* is fresh).
|
|
115
|
-
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
116
|
-
const db = openDatabase(dbPath);
|
|
117
|
-
try {
|
|
118
|
-
const entries = [
|
|
119
|
-
{
|
|
120
|
-
entry: {
|
|
121
|
-
name: "database-runbook",
|
|
122
|
-
type: "knowledge",
|
|
123
|
-
filename: "database-runbook.md",
|
|
124
|
-
description: "Runbook for database outage recovery after a hardware fault.",
|
|
125
|
-
},
|
|
126
|
-
filePath: runbookPath,
|
|
127
|
-
dirPath: knowledgeDir,
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
entry: {
|
|
131
|
-
name: "database-faq",
|
|
132
|
-
type: "knowledge",
|
|
133
|
-
filename: "database-faq.md",
|
|
134
|
-
description: "Database FAQ covering connection limits, recovery tunables, and outage post-mortems.",
|
|
135
|
-
},
|
|
136
|
-
filePath: faqPath,
|
|
137
|
-
dirPath: knowledgeDir,
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
entry: {
|
|
141
|
-
name: "incident-2024-shard",
|
|
142
|
-
type: "memory",
|
|
143
|
-
filename: "incident-2024-shard.md",
|
|
144
|
-
description: "We recovered shard-3 during the 2024 database outage by following the runbook.",
|
|
145
|
-
},
|
|
146
|
-
filePath: memoryPath,
|
|
147
|
-
dirPath: memoryDir,
|
|
148
|
-
},
|
|
149
|
-
];
|
|
150
|
-
for (const e of entries) {
|
|
151
|
-
const entryKey = `${stashDir}:${e.entry.type}:${e.entry.name}`;
|
|
152
|
-
const searchText = buildSearchText(e.entry);
|
|
153
|
-
upsertEntry(db, entryKey, e.dirPath, e.filePath, stashDir, e.entry, searchText);
|
|
154
|
-
}
|
|
155
|
-
rebuildFts(db);
|
|
156
|
-
setMeta(db, "stashDir", stashDir);
|
|
157
|
-
setMeta(db, "builtAt", new Date().toISOString());
|
|
158
|
-
setMeta(db, "stashDirs", JSON.stringify([stashDir]));
|
|
159
|
-
setMeta(db, "hasEmbeddings", "0");
|
|
160
|
-
}
|
|
161
|
-
finally {
|
|
162
|
-
closeDatabase(db);
|
|
163
|
-
}
|
|
164
|
-
// Pre-build the graph file once, but DON'T install it yet — each test
|
|
165
|
-
// installs/removes it as needed. The entity set is exactly what the
|
|
166
|
-
// graph-extraction LLM helper would have produced for these bodies.
|
|
167
|
-
const graphFile = {
|
|
168
|
-
schemaVersion: GRAPH_FILE_SCHEMA_VERSION,
|
|
169
|
-
generatedAt: new Date().toISOString(),
|
|
170
|
-
stashRoot: stashDir,
|
|
171
|
-
files: [
|
|
172
|
-
{
|
|
173
|
-
path: runbookPath,
|
|
174
|
-
type: "knowledge",
|
|
175
|
-
entities: ["database", "outage", "recovery", "runbook"],
|
|
176
|
-
relations: [
|
|
177
|
-
{ from: "outage", to: "recovery" },
|
|
178
|
-
{ from: "recovery", to: "database" },
|
|
179
|
-
],
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
path: memoryPath,
|
|
183
|
-
type: "memory",
|
|
184
|
-
entities: ["database", "outage", "recovery", "shard-3"],
|
|
185
|
-
relations: [{ from: "outage", to: "recovery" }],
|
|
186
|
-
},
|
|
187
|
-
// database-faq has NO graph entries — the FAQ doesn't operationally
|
|
188
|
-
// relate to outage recovery; it just shares vocabulary. This is the
|
|
189
|
-
// asymmetry that lets the graph signal differentiate the runbook
|
|
190
|
-
// from a vocabulary-matching FAQ.
|
|
191
|
-
],
|
|
192
|
-
};
|
|
193
|
-
// Stash the prepared graph payload as a JSON file alongside the stash so
|
|
194
|
-
// tests can install/uninstall by file rename.
|
|
195
|
-
fs.mkdirSync(path.join(stashDir, ".akm"), { recursive: true });
|
|
196
|
-
fs.writeFileSync(path.join(stashDir, ".akm", "graph.prepared.json"), `${JSON.stringify(graphFile, null, 2)}\n`, "utf8");
|
|
197
|
-
}
|
|
198
|
-
function installGraph() {
|
|
199
|
-
const prepared = path.join(stashDir, ".akm", "graph.prepared.json");
|
|
200
|
-
fs.copyFileSync(prepared, getGraphFilePath(stashDir));
|
|
201
|
-
}
|
|
202
|
-
function uninstallGraph() {
|
|
203
|
-
const target = getGraphFilePath(stashDir);
|
|
204
|
-
if (fs.existsSync(target))
|
|
205
|
-
fs.unlinkSync(target);
|
|
206
|
-
}
|
|
207
|
-
async function searchHits(query) {
|
|
208
|
-
const result = await akmSearch({ query, source: "stash", limit: 20 });
|
|
209
|
-
return result.hits;
|
|
210
|
-
}
|
|
211
|
-
function rankOf(hits, name) {
|
|
212
|
-
const idx = hits.findIndex((h) => h.name === name);
|
|
213
|
-
return idx === -1 ? Infinity : idx + 1;
|
|
214
|
-
}
|
|
215
|
-
function scoreOf(hits, name) {
|
|
216
|
-
const hit = hits.find((h) => h.name === name);
|
|
217
|
-
return hit?.score ?? 0;
|
|
218
|
-
}
|
|
219
|
-
// ── Tests ───────────────────────────────────────────────────────────────────
|
|
220
|
-
describe("graph boost — search-time integration (#207)", () => {
|
|
221
|
-
test("graph signal lifts runbook above FAQ for outage-recovery query", async () => {
|
|
222
|
-
const query = "database outage recovery";
|
|
223
|
-
// Baseline: no graph file.
|
|
224
|
-
uninstallGraph();
|
|
225
|
-
const baselineHits = await searchHits(query);
|
|
226
|
-
const baselineRank = rankOf(baselineHits, "database-runbook");
|
|
227
|
-
const baselineScore = scoreOf(baselineHits, "database-runbook");
|
|
228
|
-
expect(baselineRank).not.toBe(Infinity);
|
|
229
|
-
// Boosted: graph file present.
|
|
230
|
-
installGraph();
|
|
231
|
-
const boostedHits = await searchHits(query);
|
|
232
|
-
const boostedRank = rankOf(boostedHits, "database-runbook");
|
|
233
|
-
const boostedScore = scoreOf(boostedHits, "database-runbook");
|
|
234
|
-
expect(boostedRank).not.toBe(Infinity);
|
|
235
|
-
// Acceptance criterion (deterministic, not a percentage threshold):
|
|
236
|
-
// - rank must not regress, AND
|
|
237
|
-
// - score must not regress.
|
|
238
|
-
// Per CLAUDE.md / spec §9, displayed scores are clamped to [0,1]; on
|
|
239
|
-
// the strong-match runbook fixture both runs may clamp to the ceiling,
|
|
240
|
-
// so the observable contract collapses from "score strictly increases"
|
|
241
|
-
// to "score does not regress" while rank ordering separately confirms
|
|
242
|
-
// the graph signal lifted the runbook over the FAQ.
|
|
243
|
-
expect(boostedRank).toBeLessThanOrEqual(baselineRank);
|
|
244
|
-
expect(boostedScore).toBeGreaterThanOrEqual(baselineScore);
|
|
245
|
-
});
|
|
246
|
-
test("absent graph file has no effect on score (no parallel scoring track)", async () => {
|
|
247
|
-
// With the graph file uninstalled, a non-graph-eligible query should
|
|
248
|
-
// produce the same hit ordering as it did before #207 landed. This
|
|
249
|
-
// confirms the graph integration is purely additive — there's no
|
|
250
|
-
// hidden second scorer running unconditionally.
|
|
251
|
-
uninstallGraph();
|
|
252
|
-
const without = await searchHits("database");
|
|
253
|
-
expect(without.length).toBeGreaterThan(0);
|
|
254
|
-
// Re-run the same query while the file is uninstalled — must be
|
|
255
|
-
// byte-identical (same hits, same scores) within the deterministic
|
|
256
|
-
// tiebreaker.
|
|
257
|
-
const without2 = await searchHits("database");
|
|
258
|
-
expect(without2.map((h) => h.name)).toEqual(without.map((h) => h.name));
|
|
259
|
-
expect(without2.map((h) => h.score)).toEqual(without.map((h) => h.score));
|
|
260
|
-
});
|
|
261
|
-
test("score is clamped to [0,1] even when boosts would push above 1.0", async () => {
|
|
262
|
-
// Fixes a pre-existing breach of the CLAUDE.md / spec §9 contract that
|
|
263
|
-
// locks `SearchHit.score` to [0,1]: the boost loop in db-search.ts can
|
|
264
|
-
// accumulate FTS-base + multiple additive boosts whose product exceeds
|
|
265
|
-
// 1.0, and the addition of #207's graph boost (up to ~1.05 additive)
|
|
266
|
-
// makes the breach detectable in practice. The runbook fixture above
|
|
267
|
-
// matches an exact-name query (boost +2.0) AND has a full graph hit,
|
|
268
|
-
// which combined push the raw computed score above 1.0. The clamp
|
|
269
|
-
// (`Math.min(1, Math.max(0, score))` near `Math.round(score * 10000)`
|
|
270
|
-
// in `searchDatabase`) guarantees the final SearchHit.score is exactly
|
|
271
|
-
// 1 in that case rather than overflowing.
|
|
272
|
-
installGraph();
|
|
273
|
-
const hits = await searchHits("database-runbook");
|
|
274
|
-
const target = hits.find((h) => h.name === "database-runbook");
|
|
275
|
-
expect(target).toBeDefined();
|
|
276
|
-
expect(typeof target?.score).toBe("number");
|
|
277
|
-
// Every emitted score must satisfy the locked contract.
|
|
278
|
-
for (const h of hits) {
|
|
279
|
-
expect(h.score ?? 0).toBeLessThanOrEqual(1);
|
|
280
|
-
expect(h.score ?? 0).toBeGreaterThanOrEqual(0);
|
|
281
|
-
}
|
|
282
|
-
// The exact-name + graph-boosted case clamps to the ceiling.
|
|
283
|
-
expect(target?.score).toBe(1);
|
|
284
|
-
});
|
|
285
|
-
test("graph signal feeds the same SearchHit.score field — no second scorer", async () => {
|
|
286
|
-
// Verify the boost lands on the same `score` property of the same hit
|
|
287
|
-
// object. This is the contract: the graph signal is one boost
|
|
288
|
-
// component inside the FTS5+boosts loop, not a parallel ranking.
|
|
289
|
-
const query = "database outage recovery";
|
|
290
|
-
uninstallGraph();
|
|
291
|
-
const baseline = await searchHits(query);
|
|
292
|
-
const baselineHit = baseline.find((h) => h.name === "database-runbook");
|
|
293
|
-
expect(baselineHit).toBeDefined();
|
|
294
|
-
expect(typeof baselineHit?.score).toBe("number");
|
|
295
|
-
installGraph();
|
|
296
|
-
const boosted = await searchHits(query);
|
|
297
|
-
const boostedHit = boosted.find((h) => h.name === "database-runbook");
|
|
298
|
-
expect(boostedHit).toBeDefined();
|
|
299
|
-
expect(typeof boostedHit?.score).toBe("number");
|
|
300
|
-
// Same hit shape, same score field — just (weakly) higher.
|
|
301
|
-
expect(boostedHit?.path).toBe(baselineHit?.path);
|
|
302
|
-
expect(boostedHit?.ref).toBe(baselineHit?.ref);
|
|
303
|
-
expect(boostedHit?.score ?? 0).toBeGreaterThanOrEqual(baselineHit?.score ?? 0);
|
|
304
|
-
});
|
|
305
|
-
});
|