akm-cli 0.7.0 → 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/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 -996
- package/dist/tests/bench/cleanup-sigint.test.js +0 -83
- package/dist/tests/bench/cleanup.js +0 -234
- package/dist/tests/bench/cleanup.test.js +0 -166
- package/dist/tests/bench/cli.js +0 -1018
- package/dist/tests/bench/cli.test.js +0 -445
- package/dist/tests/bench/compare.test.js +0 -556
- package/dist/tests/bench/corpus.js +0 -317
- package/dist/tests/bench/corpus.test.js +0 -258
- package/dist/tests/bench/doctor.js +0 -525
- package/dist/tests/bench/driver.js +0 -401
- package/dist/tests/bench/driver.test.js +0 -584
- package/dist/tests/bench/environment.js +0 -233
- package/dist/tests/bench/environment.test.js +0 -199
- 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 -647
- package/dist/tests/bench/evolve.test.js +0 -624
- package/dist/tests/bench/failure-modes.test.js +0 -349
- package/dist/tests/bench/feedback-integrity.test.js +0 -457
- package/dist/tests/bench/leakage.test.js +0 -228
- package/dist/tests/bench/learning-curve.test.js +0 -134
- package/dist/tests/bench/metrics.js +0 -2395
- package/dist/tests/bench/metrics.test.js +0 -1150
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
- package/dist/tests/bench/opencode-config.js +0 -194
- package/dist/tests/bench/opencode-config.test.js +0 -370
- package/dist/tests/bench/report.js +0 -1885
- package/dist/tests/bench/report.test.js +0 -1038
- package/dist/tests/bench/run-config.js +0 -355
- package/dist/tests/bench/run-config.test.js +0 -298
- package/dist/tests/bench/run-curate-test.js +0 -32
- package/dist/tests/bench/run-failing-tasks.js +0 -56
- package/dist/tests/bench/run-full-bench.js +0 -51
- package/dist/tests/bench/run-items36-targeted.js +0 -69
- package/dist/tests/bench/run-nano-quick.js +0 -42
- package/dist/tests/bench/run-waveg-targeted.js +0 -62
- package/dist/tests/bench/runner.js +0 -699
- 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 -131
- package/dist/tests/bench/trajectory.js +0 -116
- package/dist/tests/bench/trajectory.test.js +0 -127
- package/dist/tests/bench/verifier.js +0 -114
- 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 -345
- package/dist/tests/bench/workflow-spec.test.js +0 -363
- package/dist/tests/bench/workflow-trace.js +0 -472
- 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 -204
- package/dist/tests/commands/events.test.js +0 -370
- package/dist/tests/commands/history.test.js +0 -418
- 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 -569
- 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 -1419
- 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 -97
- 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 -570
- 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 -218
- package/dist/tests/output-shapes-unit.test.js +0 -478
- 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 -394
- 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 -238
- package/dist/tests/registry-resolve.test.js +0 -126
- package/dist/tests/registry-search.test.js +0 -923
- package/dist/tests/remember-frontmatter.test.js +0 -378
- 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 -286
- 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 -281
- 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 -395
- package/dist/tests/workflows/indexer-rejection.test.js +0 -213
- /package/dist/{src/cli.js → cli.js} +0 -0
- /package/dist/{src/commands → commands}/completions.js +0 -0
- /package/dist/{src/commands → commands}/config-cli.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}/history.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}/registry-search.js +0 -0
- /package/dist/{src/commands → commands}/remember.js +0 -0
- /package/dist/{src/commands → commands}/search.js +0 -0
- /package/dist/{src/commands → commands}/self-update.js +0 -0
- /package/dist/{src/commands → commands}/show.js +0 -0
- /package/dist/{src/commands → commands}/source-add.js +0 -0
- /package/dist/{src/commands → commands}/source-clone.js +0 -0
- /package/dist/{src/commands → commands}/source-manage.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}/config.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-search.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}/indexer.js +0 -0
- /package/dist/{src/indexer → indexer}/manifest.js +0 -0
- /package/dist/{src/indexer → indexer}/matchers.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}/search-source.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/profiles.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/spawn.js +0 -0
- /package/dist/{src/integrations → integrations}/github.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/remote.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}/cli-hints.js +0 -0
- /package/dist/{src/output → output}/context.js +0 -0
- /package/dist/{src/output → output}/renderers.js +0 -0
- /package/dist/{src/output → output}/shapes.js +0 -0
- /package/dist/{src/output → output}/text.js +0 -0
- /package/dist/{src/registry → registry}/build-index.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/static-index.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}/setup.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/wiki → wiki}/wiki.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}/runs.js +0 -0
- /package/dist/{src/workflows → workflows}/schema.js +0 -0
- /package/dist/{src/workflows → workflows}/validator.js +0 -0
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { chatCompletion, redactErrorBody } from "../src/llm/client";
|
|
3
|
-
// ── redactErrorBody ─────────────────────────────────────────────────────────
|
|
4
|
-
describe("redactErrorBody", () => {
|
|
5
|
-
test("redacts Bearer tokens", () => {
|
|
6
|
-
const out = redactErrorBody("Authorization: Bearer abc123XYZ.token-value");
|
|
7
|
-
expect(out).not.toContain("abc123XYZ");
|
|
8
|
-
expect(out).toContain("Bearer [REDACTED]");
|
|
9
|
-
});
|
|
10
|
-
test("redacts sk-* style API keys", () => {
|
|
11
|
-
const out = redactErrorBody('{"error":"bad key sk-proj-Abcdef1234567890ZZZ"}');
|
|
12
|
-
expect(out).not.toContain("sk-proj-Abcdef");
|
|
13
|
-
expect(out).toContain("[REDACTED]");
|
|
14
|
-
});
|
|
15
|
-
test("redacts JSON api_key fields", () => {
|
|
16
|
-
const out = redactErrorBody('{"api_key":"super-secret-12345","other":"safe"}');
|
|
17
|
-
expect(out).not.toContain("super-secret-12345");
|
|
18
|
-
expect(out).toContain("[REDACTED]");
|
|
19
|
-
expect(out).toContain("other");
|
|
20
|
-
});
|
|
21
|
-
test("redacts JSON apiKey camelCase fields", () => {
|
|
22
|
-
const out = redactErrorBody('{"apiKey": "topsecretvalue"}');
|
|
23
|
-
expect(out).not.toContain("topsecretvalue");
|
|
24
|
-
expect(out).toContain("[REDACTED]");
|
|
25
|
-
});
|
|
26
|
-
test("trims output to 200 chars", () => {
|
|
27
|
-
const huge = "x".repeat(2000);
|
|
28
|
-
const out = redactErrorBody(huge);
|
|
29
|
-
expect(out.length).toBeLessThanOrEqual(201); // 200 + ellipsis char
|
|
30
|
-
});
|
|
31
|
-
test("returns empty for empty input", () => {
|
|
32
|
-
expect(redactErrorBody("")).toBe("");
|
|
33
|
-
});
|
|
34
|
-
test("preserves non-secret content under cap", () => {
|
|
35
|
-
expect(redactErrorBody("plain error message")).toBe("plain error message");
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
// ── chatCompletion error path ───────────────────────────────────────────────
|
|
39
|
-
function createErrorServer(statusCode, body) {
|
|
40
|
-
const server = Bun.serve({
|
|
41
|
-
port: 0,
|
|
42
|
-
fetch() {
|
|
43
|
-
return new Response(body, { status: statusCode });
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
return { url: `http://localhost:${server.port}`, server };
|
|
47
|
-
}
|
|
48
|
-
describe("chatCompletion error redaction", () => {
|
|
49
|
-
test("redacts API key from 401 response body and keeps status + URL", async () => {
|
|
50
|
-
const leakBody = '{"error":{"message":"Invalid API key sk-proj-LEAKYKEYABCDEF12345"}}';
|
|
51
|
-
const { url, server } = createErrorServer(401, leakBody);
|
|
52
|
-
try {
|
|
53
|
-
const config = {
|
|
54
|
-
endpoint: url,
|
|
55
|
-
model: "test-model",
|
|
56
|
-
apiKey: "sk-proj-LEAKYKEYABCDEF12345",
|
|
57
|
-
};
|
|
58
|
-
let caught;
|
|
59
|
-
try {
|
|
60
|
-
await chatCompletion(config, [{ role: "user", content: "hi" }]);
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
caught = err;
|
|
64
|
-
}
|
|
65
|
-
expect(caught).toBeDefined();
|
|
66
|
-
expect(caught?.message).toContain("(401)");
|
|
67
|
-
expect(caught?.message).toContain(url);
|
|
68
|
-
expect(caught?.message).not.toContain("sk-proj-LEAKYKEYABCDEF12345");
|
|
69
|
-
expect(caught?.message).toContain("[REDACTED]");
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
72
|
-
server.stop();
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
test("trims oversized error body but keeps status code intact", async () => {
|
|
76
|
-
const huge = "A".repeat(5000);
|
|
77
|
-
const { url, server } = createErrorServer(503, huge);
|
|
78
|
-
try {
|
|
79
|
-
const config = { endpoint: url, model: "test-model" };
|
|
80
|
-
let caught;
|
|
81
|
-
try {
|
|
82
|
-
await chatCompletion(config, [{ role: "user", content: "hi" }]);
|
|
83
|
-
}
|
|
84
|
-
catch (err) {
|
|
85
|
-
caught = err;
|
|
86
|
-
}
|
|
87
|
-
expect(caught).toBeDefined();
|
|
88
|
-
expect(caught?.message).toContain("(503)");
|
|
89
|
-
// Status + URL prefix should remain; the body portion is truncated.
|
|
90
|
-
expect((caught?.message ?? "").length).toBeLessThan(huge.length);
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
server.stop();
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
test("redacts Bearer header echoed back by provider", async () => {
|
|
97
|
-
const body = "Got header Authorization: Bearer abcXYZsupersecret999";
|
|
98
|
-
const { url, server } = createErrorServer(403, body);
|
|
99
|
-
try {
|
|
100
|
-
const config = { endpoint: url, model: "test-model", apiKey: "abcXYZsupersecret999" };
|
|
101
|
-
let caught;
|
|
102
|
-
try {
|
|
103
|
-
await chatCompletion(config, [{ role: "user", content: "hi" }]);
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
caught = err;
|
|
107
|
-
}
|
|
108
|
-
expect(caught?.message).not.toContain("abcXYZsupersecret999");
|
|
109
|
-
expect(caught?.message).toContain("Bearer [REDACTED]");
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
server.stop();
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-feature LLM gate seam (v1 spec §14, #227).
|
|
3
|
-
*
|
|
4
|
-
* Locks:
|
|
5
|
-
* - `isLlmFeatureEnabled` returns `true` only when the named flag is the
|
|
6
|
-
* literal boolean `true` in config (defaults are `false`).
|
|
7
|
-
* - `tryLlmFeature` returns the fallback on disablement, on any thrown
|
|
8
|
-
* error, and on hard-timeout — and never lets `fn`'s exception bubble.
|
|
9
|
-
* - The fallback may be a value or a thunk; the thunk is only invoked on
|
|
10
|
-
* the fallback path.
|
|
11
|
-
* - The wrapper notifies the optional `onFallback` sink with a structured
|
|
12
|
-
* `{ feature, reason, error? }` event, where `reason` is one of
|
|
13
|
-
* `"disabled" | "timeout" | "error"`.
|
|
14
|
-
*/
|
|
15
|
-
import { describe, expect, test } from "bun:test";
|
|
16
|
-
import { isLlmFeatureEnabled, LlmFeatureTimeoutError, tryLlmFeature } from "../src/llm/feature-gate";
|
|
17
|
-
const baseLlm = {
|
|
18
|
-
endpoint: "http://example.invalid/v1/chat",
|
|
19
|
-
model: "test-model",
|
|
20
|
-
};
|
|
21
|
-
function configWith(features) {
|
|
22
|
-
return {
|
|
23
|
-
stashDir: "/tmp/stash",
|
|
24
|
-
llm: { ...baseLlm, features },
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
describe("isLlmFeatureEnabled", () => {
|
|
28
|
-
test("returns false when no llm config is present", () => {
|
|
29
|
-
expect(isLlmFeatureEnabled(undefined, "curate_rerank")).toBe(false);
|
|
30
|
-
expect(isLlmFeatureEnabled({}, "curate_rerank")).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
test("returns false when the features block is missing", () => {
|
|
33
|
-
const cfg = { stashDir: "/tmp", llm: baseLlm };
|
|
34
|
-
expect(isLlmFeatureEnabled(cfg, "feedback_distillation")).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
test("returns false when the key is absent (default-false)", () => {
|
|
37
|
-
const cfg = configWith({});
|
|
38
|
-
expect(isLlmFeatureEnabled(cfg, "graph_extraction")).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
test("returns true only on literal boolean true", () => {
|
|
41
|
-
expect(isLlmFeatureEnabled(configWith({ feedback_distillation: true }), "feedback_distillation")).toBe(true);
|
|
42
|
-
expect(isLlmFeatureEnabled(configWith({ feedback_distillation: false }), "feedback_distillation")).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
describe("tryLlmFeature", () => {
|
|
46
|
-
test("returns the fallback (and never calls fn) when the gate is disabled", async () => {
|
|
47
|
-
let called = false;
|
|
48
|
-
const events = [];
|
|
49
|
-
const result = await tryLlmFeature("curate_rerank", configWith({}), async () => {
|
|
50
|
-
called = true;
|
|
51
|
-
return "real";
|
|
52
|
-
}, "fallback", { onFallback: (e) => events.push(e) });
|
|
53
|
-
expect(result).toBe("fallback");
|
|
54
|
-
expect(called).toBe(false);
|
|
55
|
-
expect(events).toEqual([{ feature: "curate_rerank", reason: "disabled" }]);
|
|
56
|
-
});
|
|
57
|
-
test("invokes a thunk fallback only on the fallback path", async () => {
|
|
58
|
-
let fallbackInvocations = 0;
|
|
59
|
-
const result = await tryLlmFeature("memory_inference", configWith({ memory_inference: true }), async () => "real", () => {
|
|
60
|
-
fallbackInvocations += 1;
|
|
61
|
-
return "thunk-fallback";
|
|
62
|
-
});
|
|
63
|
-
expect(result).toBe("real");
|
|
64
|
-
expect(fallbackInvocations).toBe(0);
|
|
65
|
-
});
|
|
66
|
-
test("returns the fallback on a synchronous throw", async () => {
|
|
67
|
-
const events = [];
|
|
68
|
-
const result = await tryLlmFeature("curate_rerank", configWith({ curate_rerank: true }), () => {
|
|
69
|
-
throw new Error("boom");
|
|
70
|
-
}, "fallback", { onFallback: (e) => events.push({ reason: e.reason, error: e.error }) });
|
|
71
|
-
expect(result).toBe("fallback");
|
|
72
|
-
expect(events).toHaveLength(1);
|
|
73
|
-
expect(events[0].reason).toBe("error");
|
|
74
|
-
expect(events[0].error?.message).toBe("boom");
|
|
75
|
-
});
|
|
76
|
-
test("returns the fallback on an async rejection", async () => {
|
|
77
|
-
const result = await tryLlmFeature("graph_extraction", configWith({ graph_extraction: true }), async () => {
|
|
78
|
-
throw new Error("kaboom");
|
|
79
|
-
}, 42);
|
|
80
|
-
expect(result).toBe(42);
|
|
81
|
-
});
|
|
82
|
-
test("returns the fallback on hard timeout", async () => {
|
|
83
|
-
const events = [];
|
|
84
|
-
const result = await tryLlmFeature("memory_inference", configWith({ memory_inference: true }), () => new Promise((resolve) => setTimeout(() => resolve("late"), 200)), "fallback", { timeoutMs: 25, onFallback: (e) => events.push({ reason: e.reason, error: e.error }) });
|
|
85
|
-
expect(result).toBe("fallback");
|
|
86
|
-
expect(events).toHaveLength(1);
|
|
87
|
-
expect(events[0].reason).toBe("timeout");
|
|
88
|
-
expect(events[0].error).toBeInstanceOf(LlmFeatureTimeoutError);
|
|
89
|
-
});
|
|
90
|
-
test("returns fn's result when enabled and successful", async () => {
|
|
91
|
-
const result = await tryLlmFeature("feedback_distillation", configWith({ feedback_distillation: true }), async () => ({ ok: true }), { ok: false });
|
|
92
|
-
expect(result).toEqual({ ok: true });
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
// ── #284 GAP-LOW: parametrise over the stable feature keys ─────────────────
|
|
96
|
-
//
|
|
97
|
-
// Wave B may drop `tag_dedup` / `memory_consolidation` / `embedding_fallback_score`
|
|
98
|
-
// — we restrict this parametrised sweep to the 4 keys that are
|
|
99
|
-
// definitely actually-implemented and used by the current code.
|
|
100
|
-
const STABLE_FEATURE_KEYS = ["feedback_distillation", "memory_inference", "graph_extraction", "curate_rerank"];
|
|
101
|
-
describe("isLlmFeatureEnabled — parametrised over stable feature keys (#284)", () => {
|
|
102
|
-
for (const key of STABLE_FEATURE_KEYS) {
|
|
103
|
-
test(`${key}: default-false when features block is missing`, () => {
|
|
104
|
-
const cfg = { stashDir: "/tmp", llm: baseLlm };
|
|
105
|
-
// biome-ignore lint/suspicious/noExplicitAny: gate accepts any LlmFeatureKey
|
|
106
|
-
expect(isLlmFeatureEnabled(cfg, key)).toBe(false);
|
|
107
|
-
});
|
|
108
|
-
test(`${key}: false when key is absent (default-false)`, () => {
|
|
109
|
-
const cfg = configWith({});
|
|
110
|
-
// biome-ignore lint/suspicious/noExplicitAny: gate accepts any LlmFeatureKey
|
|
111
|
-
expect(isLlmFeatureEnabled(cfg, key)).toBe(false);
|
|
112
|
-
});
|
|
113
|
-
test(`${key}: literal true → enabled`, () => {
|
|
114
|
-
// biome-ignore lint/suspicious/noExplicitAny: gate accepts any LlmFeatureKey
|
|
115
|
-
expect(isLlmFeatureEnabled(configWith({ [key]: true }), key)).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
test(`${key}: literal false → disabled`, () => {
|
|
118
|
-
// biome-ignore lint/suspicious/noExplicitAny: gate accepts any LlmFeatureKey
|
|
119
|
-
expect(isLlmFeatureEnabled(configWith({ [key]: false }), key)).toBe(false);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
describe("tryLlmFeature — parametrised over stable feature keys (#284)", () => {
|
|
124
|
-
for (const key of STABLE_FEATURE_KEYS) {
|
|
125
|
-
test(`${key}: disabled → returns fallback, never calls fn`, async () => {
|
|
126
|
-
let called = false;
|
|
127
|
-
const result = await tryLlmFeature(
|
|
128
|
-
// biome-ignore lint/suspicious/noExplicitAny: gate accepts any LlmFeatureKey
|
|
129
|
-
key, configWith({}), async () => {
|
|
130
|
-
called = true;
|
|
131
|
-
return "real";
|
|
132
|
-
}, "fallback");
|
|
133
|
-
expect(result).toBe("fallback");
|
|
134
|
-
expect(called).toBe(false);
|
|
135
|
-
});
|
|
136
|
-
test(`${key}: enabled + happy → returns fn's result`, async () => {
|
|
137
|
-
const result = await tryLlmFeature(
|
|
138
|
-
// biome-ignore lint/suspicious/noExplicitAny: gate accepts any LlmFeatureKey
|
|
139
|
-
key, configWith({ [key]: true }), async () => "real", "fallback");
|
|
140
|
-
expect(result).toBe("real");
|
|
141
|
-
});
|
|
142
|
-
test(`${key}: enabled + throw → returns fallback`, async () => {
|
|
143
|
-
const result = await tryLlmFeature(
|
|
144
|
-
// biome-ignore lint/suspicious/noExplicitAny: gate accepts any LlmFeatureKey
|
|
145
|
-
key, configWith({ [key]: true }), async () => {
|
|
146
|
-
throw new Error("boom");
|
|
147
|
-
}, "fallback");
|
|
148
|
-
expect(result).toBe("fallback");
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
});
|
package/dist/tests/llm.test.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { enhanceMetadata } from "../src/llm/metadata-enhance";
|
|
3
|
-
// These tests verify the LLM module's response parsing logic.
|
|
4
|
-
// They use a mock server to simulate an OpenAI-compatible endpoint.
|
|
5
|
-
function createMockServer(responseBody, statusCode = 200, onRequest) {
|
|
6
|
-
const server = Bun.serve({
|
|
7
|
-
port: 0,
|
|
8
|
-
async fetch(request) {
|
|
9
|
-
const body = (await request.json());
|
|
10
|
-
onRequest?.(body);
|
|
11
|
-
return new Response(JSON.stringify({
|
|
12
|
-
choices: [{ message: { content: responseBody } }],
|
|
13
|
-
}), {
|
|
14
|
-
status: statusCode,
|
|
15
|
-
headers: { "Content-Type": "application/json" },
|
|
16
|
-
});
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
return { url: `http://localhost:${server.port}`, server };
|
|
20
|
-
}
|
|
21
|
-
function createErrorServer(statusCode, body = "error") {
|
|
22
|
-
const server = Bun.serve({
|
|
23
|
-
port: 0,
|
|
24
|
-
fetch() {
|
|
25
|
-
return new Response(body, { status: statusCode });
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
return { url: `http://localhost:${server.port}`, server };
|
|
29
|
-
}
|
|
30
|
-
describe("enhanceMetadata", () => {
|
|
31
|
-
test("parses valid LLM JSON response", async () => {
|
|
32
|
-
const { url, server } = createMockServer(JSON.stringify({
|
|
33
|
-
description: "Builds Docker images from Dockerfiles",
|
|
34
|
-
searchHints: ["build a docker image", "create container image", "package application"],
|
|
35
|
-
tags: ["docker", "container", "build", "image"],
|
|
36
|
-
}));
|
|
37
|
-
try {
|
|
38
|
-
const config = { endpoint: url, model: "test-model" };
|
|
39
|
-
const entry = { name: "build-image", type: "script", description: "build image" };
|
|
40
|
-
const result = await enhanceMetadata(config, entry);
|
|
41
|
-
expect(result.description).toBe("Builds Docker images from Dockerfiles");
|
|
42
|
-
expect(result.searchHints).toHaveLength(3);
|
|
43
|
-
expect(result.tags).toContain("docker");
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
server.stop();
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
test("handles markdown-fenced JSON response", async () => {
|
|
50
|
-
const { url, server } = createMockServer('```json\n{"description":"test desc","searchHints":["do thing"],"tags":["tag1"]}\n```');
|
|
51
|
-
try {
|
|
52
|
-
const config = { endpoint: url, model: "test-model" };
|
|
53
|
-
const entry = { name: "test", type: "script" };
|
|
54
|
-
const result = await enhanceMetadata(config, entry);
|
|
55
|
-
expect(result.description).toBe("test desc");
|
|
56
|
-
expect(result.searchHints).toEqual(["do thing"]);
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
server.stop();
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
test("returns empty object on unparseable response", async () => {
|
|
63
|
-
const { url, server } = createMockServer("This is not JSON at all");
|
|
64
|
-
try {
|
|
65
|
-
const config = { endpoint: url, model: "test-model" };
|
|
66
|
-
const entry = { name: "test", type: "script" };
|
|
67
|
-
const result = await enhanceMetadata(config, entry);
|
|
68
|
-
expect(result).toEqual({});
|
|
69
|
-
}
|
|
70
|
-
finally {
|
|
71
|
-
server.stop();
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
test("throws on HTTP error", async () => {
|
|
75
|
-
const { url, server } = createErrorServer(500, "Internal Server Error");
|
|
76
|
-
try {
|
|
77
|
-
const config = { endpoint: url, model: "test-model" };
|
|
78
|
-
const entry = { name: "test", type: "script" };
|
|
79
|
-
await expect(enhanceMetadata(config, entry)).rejects.toThrow("LLM request failed (500)");
|
|
80
|
-
}
|
|
81
|
-
finally {
|
|
82
|
-
server.stop();
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
test("uses configured temperature and maxTokens", async () => {
|
|
86
|
-
let requestBody;
|
|
87
|
-
const { url, server } = createMockServer(JSON.stringify({ description: "ok" }), 200, (body) => {
|
|
88
|
-
requestBody = body;
|
|
89
|
-
});
|
|
90
|
-
try {
|
|
91
|
-
const config = {
|
|
92
|
-
endpoint: url,
|
|
93
|
-
model: "test-model",
|
|
94
|
-
temperature: 0.7,
|
|
95
|
-
maxTokens: 256,
|
|
96
|
-
};
|
|
97
|
-
const entry = { name: "test", type: "script" };
|
|
98
|
-
await enhanceMetadata(config, entry);
|
|
99
|
-
expect(requestBody).toMatchObject({
|
|
100
|
-
model: "test-model",
|
|
101
|
-
temperature: 0.7,
|
|
102
|
-
max_tokens: 256,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
finally {
|
|
106
|
-
server.stop();
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
test("caps searchHints at 8 items", async () => {
|
|
110
|
-
const { url, server } = createMockServer(JSON.stringify({
|
|
111
|
-
searchHints: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
|
|
112
|
-
}));
|
|
113
|
-
try {
|
|
114
|
-
const config = { endpoint: url, model: "test-model" };
|
|
115
|
-
const entry = { name: "test", type: "script" };
|
|
116
|
-
const result = await enhanceMetadata(config, entry);
|
|
117
|
-
expect(result.searchHints?.length).toBeLessThanOrEqual(8);
|
|
118
|
-
}
|
|
119
|
-
finally {
|
|
120
|
-
server.stop();
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
test("filters non-string values from searchHints and tags", async () => {
|
|
124
|
-
const { url, server } = createMockServer(JSON.stringify({
|
|
125
|
-
searchHints: ["valid", 123, null, "also valid"],
|
|
126
|
-
tags: ["good", false, "fine"],
|
|
127
|
-
}));
|
|
128
|
-
try {
|
|
129
|
-
const config = { endpoint: url, model: "test-model" };
|
|
130
|
-
const entry = { name: "test", type: "script" };
|
|
131
|
-
const result = await enhanceMetadata(config, entry);
|
|
132
|
-
expect(result.searchHints).toEqual(["valid", "also valid"]);
|
|
133
|
-
expect(result.tags).toEqual(["good", "fine"]);
|
|
134
|
-
}
|
|
135
|
-
finally {
|
|
136
|
-
server.stop();
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
});
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { readLockfile, removeLockEntry, upsertLockEntry, writeLockfile, } from "../src/integrations/lockfile";
|
|
6
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
7
|
-
const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
8
|
-
let testConfigDir = "";
|
|
9
|
-
function makeTmpDir() {
|
|
10
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), "akm-lockfile-test-"));
|
|
11
|
-
}
|
|
12
|
-
function getLockfilePath() {
|
|
13
|
-
return path.join(testConfigDir, "akm", "akm.lock");
|
|
14
|
-
}
|
|
15
|
-
function writeRawLockfile(content) {
|
|
16
|
-
const lockPath = getLockfilePath();
|
|
17
|
-
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
18
|
-
fs.writeFileSync(lockPath, content, "utf8");
|
|
19
|
-
}
|
|
20
|
-
function validEntry(overrides) {
|
|
21
|
-
return {
|
|
22
|
-
id: "test-entry",
|
|
23
|
-
source: "npm",
|
|
24
|
-
ref: "@scope/pkg",
|
|
25
|
-
...overrides,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
testConfigDir = makeTmpDir();
|
|
30
|
-
process.env.XDG_CONFIG_HOME = testConfigDir;
|
|
31
|
-
});
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
if (originalXdgConfigHome === undefined) {
|
|
34
|
-
delete process.env.XDG_CONFIG_HOME;
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
|
|
38
|
-
}
|
|
39
|
-
if (testConfigDir) {
|
|
40
|
-
fs.rmSync(testConfigDir, { recursive: true, force: true });
|
|
41
|
-
testConfigDir = "";
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
// ── readLockfile ────────────────────────────────────────────────────────────
|
|
45
|
-
describe("readLockfile", () => {
|
|
46
|
-
test("returns empty array when lockfile does not exist", () => {
|
|
47
|
-
expect(readLockfile()).toEqual([]);
|
|
48
|
-
});
|
|
49
|
-
test("returns empty array for corrupted JSON", () => {
|
|
50
|
-
writeRawLockfile("not valid json {{{");
|
|
51
|
-
expect(readLockfile()).toEqual([]);
|
|
52
|
-
});
|
|
53
|
-
test("returns empty array for non-array JSON", () => {
|
|
54
|
-
writeRawLockfile('{"key": "value"}');
|
|
55
|
-
expect(readLockfile()).toEqual([]);
|
|
56
|
-
});
|
|
57
|
-
test("returns empty array for JSON string value", () => {
|
|
58
|
-
writeRawLockfile('"just a string"');
|
|
59
|
-
expect(readLockfile()).toEqual([]);
|
|
60
|
-
});
|
|
61
|
-
test("reads valid entries", () => {
|
|
62
|
-
const entries = [validEntry()];
|
|
63
|
-
writeRawLockfile(JSON.stringify(entries));
|
|
64
|
-
const result = readLockfile();
|
|
65
|
-
expect(result).toHaveLength(1);
|
|
66
|
-
expect(result[0].id).toBe("test-entry");
|
|
67
|
-
expect(result[0].source).toBe("npm");
|
|
68
|
-
expect(result[0].ref).toBe("@scope/pkg");
|
|
69
|
-
});
|
|
70
|
-
test("filters out invalid entries from the array", () => {
|
|
71
|
-
const raw = [
|
|
72
|
-
validEntry(),
|
|
73
|
-
{ id: "", source: "npm", ref: "pkg" }, // empty id
|
|
74
|
-
{ id: "x", source: "invalid-source", ref: "y" }, // bad source
|
|
75
|
-
{ id: "y", source: "github", ref: "" }, // empty ref
|
|
76
|
-
null,
|
|
77
|
-
42,
|
|
78
|
-
"string",
|
|
79
|
-
{ source: "npm", ref: "no-id" }, // missing id
|
|
80
|
-
];
|
|
81
|
-
writeRawLockfile(JSON.stringify(raw));
|
|
82
|
-
const result = readLockfile();
|
|
83
|
-
expect(result).toHaveLength(1);
|
|
84
|
-
expect(result[0].id).toBe("test-entry");
|
|
85
|
-
});
|
|
86
|
-
test("preserves optional fields on valid entries", () => {
|
|
87
|
-
const entry = validEntry({
|
|
88
|
-
resolvedVersion: "1.2.3",
|
|
89
|
-
resolvedRevision: "abc123",
|
|
90
|
-
integrity: "sha512-xyz",
|
|
91
|
-
});
|
|
92
|
-
writeRawLockfile(JSON.stringify([entry]));
|
|
93
|
-
const result = readLockfile();
|
|
94
|
-
expect(result).toHaveLength(1);
|
|
95
|
-
expect(result[0].resolvedVersion).toBe("1.2.3");
|
|
96
|
-
expect(result[0].resolvedRevision).toBe("abc123");
|
|
97
|
-
expect(result[0].integrity).toBe("sha512-xyz");
|
|
98
|
-
});
|
|
99
|
-
test("accepts all valid source types", () => {
|
|
100
|
-
const entries = [
|
|
101
|
-
validEntry({ id: "a", source: "npm" }),
|
|
102
|
-
validEntry({ id: "b", source: "github" }),
|
|
103
|
-
validEntry({ id: "c", source: "git" }),
|
|
104
|
-
validEntry({ id: "d", source: "local" }),
|
|
105
|
-
];
|
|
106
|
-
writeRawLockfile(JSON.stringify(entries));
|
|
107
|
-
const result = readLockfile();
|
|
108
|
-
expect(result).toHaveLength(4);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
// ── writeLockfile ───────────────────────────────────────────────────────────
|
|
112
|
-
describe("writeLockfile", () => {
|
|
113
|
-
test("writes formatted JSON with trailing newline", () => {
|
|
114
|
-
const entries = [validEntry()];
|
|
115
|
-
writeLockfile(entries);
|
|
116
|
-
const raw = fs.readFileSync(getLockfilePath(), "utf8");
|
|
117
|
-
expect(raw.endsWith("\n")).toBe(true);
|
|
118
|
-
expect(JSON.parse(raw)).toEqual(entries);
|
|
119
|
-
expect(raw).toContain(" "); // pretty-printed
|
|
120
|
-
});
|
|
121
|
-
test("creates directory structure if not present", () => {
|
|
122
|
-
const entries = [validEntry()];
|
|
123
|
-
writeLockfile(entries);
|
|
124
|
-
expect(fs.existsSync(getLockfilePath())).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
test("overwrites existing lockfile atomically", () => {
|
|
127
|
-
writeLockfile([validEntry({ id: "first" })]);
|
|
128
|
-
writeLockfile([validEntry({ id: "second" })]);
|
|
129
|
-
const result = readLockfile();
|
|
130
|
-
expect(result).toHaveLength(1);
|
|
131
|
-
expect(result[0].id).toBe("second");
|
|
132
|
-
});
|
|
133
|
-
test("does not leave temp file on success", () => {
|
|
134
|
-
writeLockfile([validEntry()]);
|
|
135
|
-
const dir = path.dirname(getLockfilePath());
|
|
136
|
-
const files = fs.readdirSync(dir);
|
|
137
|
-
const tmpFiles = files.filter((f) => f.includes(".tmp."));
|
|
138
|
-
expect(tmpFiles).toHaveLength(0);
|
|
139
|
-
});
|
|
140
|
-
test("writes empty array", () => {
|
|
141
|
-
writeLockfile([]);
|
|
142
|
-
const raw = fs.readFileSync(getLockfilePath(), "utf8");
|
|
143
|
-
expect(JSON.parse(raw)).toEqual([]);
|
|
144
|
-
});
|
|
145
|
-
test("roundtrips with readLockfile", () => {
|
|
146
|
-
const entries = [
|
|
147
|
-
validEntry({ id: "a", source: "github", ref: "owner/repo" }),
|
|
148
|
-
validEntry({ id: "b", source: "npm", ref: "@scope/pkg" }),
|
|
149
|
-
];
|
|
150
|
-
writeLockfile(entries);
|
|
151
|
-
expect(readLockfile()).toEqual(entries);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
// ── upsertLockEntry ─────────────────────────────────────────────────────────
|
|
155
|
-
describe("upsertLockEntry", () => {
|
|
156
|
-
test("adds entry when lockfile is empty", async () => {
|
|
157
|
-
await upsertLockEntry(validEntry({ id: "new-entry" }));
|
|
158
|
-
const result = readLockfile();
|
|
159
|
-
expect(result).toHaveLength(1);
|
|
160
|
-
expect(result[0].id).toBe("new-entry");
|
|
161
|
-
});
|
|
162
|
-
test("adds entry when lockfile does not exist", async () => {
|
|
163
|
-
await upsertLockEntry(validEntry({ id: "first" }));
|
|
164
|
-
expect(readLockfile()).toHaveLength(1);
|
|
165
|
-
});
|
|
166
|
-
test("replaces entry with same id", async () => {
|
|
167
|
-
writeLockfile([validEntry({ id: "pkg", ref: "old-ref" })]);
|
|
168
|
-
await upsertLockEntry(validEntry({ id: "pkg", ref: "new-ref" }));
|
|
169
|
-
const result = readLockfile();
|
|
170
|
-
expect(result).toHaveLength(1);
|
|
171
|
-
expect(result[0].ref).toBe("new-ref");
|
|
172
|
-
});
|
|
173
|
-
test("preserves other entries when upserting", async () => {
|
|
174
|
-
writeLockfile([validEntry({ id: "keep-me", ref: "keep" }), validEntry({ id: "update-me", ref: "old" })]);
|
|
175
|
-
await upsertLockEntry(validEntry({ id: "update-me", ref: "new" }));
|
|
176
|
-
const result = readLockfile();
|
|
177
|
-
expect(result).toHaveLength(2);
|
|
178
|
-
const kept = result.find((e) => e.id === "keep-me");
|
|
179
|
-
const updated = result.find((e) => e.id === "update-me");
|
|
180
|
-
expect(kept?.ref).toBe("keep");
|
|
181
|
-
expect(updated?.ref).toBe("new");
|
|
182
|
-
});
|
|
183
|
-
test("appends new entry when id does not exist", async () => {
|
|
184
|
-
writeLockfile([validEntry({ id: "existing" })]);
|
|
185
|
-
await upsertLockEntry(validEntry({ id: "brand-new" }));
|
|
186
|
-
const result = readLockfile();
|
|
187
|
-
expect(result).toHaveLength(2);
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
// ── removeLockEntry ─────────────────────────────────────────────────────────
|
|
191
|
-
describe("removeLockEntry", () => {
|
|
192
|
-
test("removes entry by id", async () => {
|
|
193
|
-
writeLockfile([validEntry({ id: "remove-me" }), validEntry({ id: "keep-me" })]);
|
|
194
|
-
await removeLockEntry("remove-me");
|
|
195
|
-
const result = readLockfile();
|
|
196
|
-
expect(result).toHaveLength(1);
|
|
197
|
-
expect(result[0].id).toBe("keep-me");
|
|
198
|
-
});
|
|
199
|
-
test("no-op when id does not exist", async () => {
|
|
200
|
-
writeLockfile([validEntry({ id: "existing" })]);
|
|
201
|
-
await removeLockEntry("nonexistent");
|
|
202
|
-
const result = readLockfile();
|
|
203
|
-
expect(result).toHaveLength(1);
|
|
204
|
-
expect(result[0].id).toBe("existing");
|
|
205
|
-
});
|
|
206
|
-
test("works when lockfile does not exist", async () => {
|
|
207
|
-
// Should not throw
|
|
208
|
-
await removeLockEntry("anything");
|
|
209
|
-
expect(readLockfile()).toEqual([]);
|
|
210
|
-
});
|
|
211
|
-
test("removes all entries if all match", async () => {
|
|
212
|
-
writeLockfile([validEntry({ id: "only-one" })]);
|
|
213
|
-
await removeLockEntry("only-one");
|
|
214
|
-
expect(readLockfile()).toEqual([]);
|
|
215
|
-
});
|
|
216
|
-
});
|