akm-cli 0.9.0-beta.53 → 0.9.0-beta.54
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/cli/clack.js +56 -0
- package/dist/cli/confirm.js +1 -1
- package/dist/commands/health/html-report.js +33 -10
- package/dist/commands/health.js +154 -21
- package/dist/commands/improve/outcome-loop.js +18 -16
- package/dist/commands/improve/preparation.js +19 -3
- package/dist/commands/read/curate.js +4 -4
- package/dist/commands/read/search-cli.js +6 -4
- package/dist/commands/read/search.js +7 -3
- package/dist/commands/read/show.js +3 -5
- package/dist/commands/sources/add-cli.js +1 -1
- package/dist/commands/sources/init.js +12 -0
- package/dist/commands/sources/stash-cli.js +1 -1
- package/dist/commands/tasks/default-tasks.js +12 -0
- package/dist/core/config/config.js +12 -0
- package/dist/core/warn.js +21 -0
- package/dist/indexer/db/db.js +6 -0
- package/dist/indexer/ensure-index.js +3 -2
- package/dist/indexer/index-writer-lock.js +9 -0
- package/dist/indexer/indexer.js +16 -4
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +1 -1
- package/dist/llm/client.js +12 -0
- package/dist/llm/embedder.js +26 -2
- package/dist/llm/embedders/local.js +7 -1
- package/dist/scripts/migrate-storage.js +26 -2
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +5 -1
- package/dist/setup/detect.js +9 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/setup.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- package/dist/tasks/runner.js +9 -0
- package/package.json +2 -2
|
@@ -28,10 +28,10 @@ import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../../co
|
|
|
28
28
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
29
29
|
import { closeDatabase, computeBodyHash, findEntryIdByRef, openExistingDatabase } from "../../indexer/db/db.js";
|
|
30
30
|
import { hasGraphData } from "../../indexer/db/graph-db.js";
|
|
31
|
-
import { ensureIndex } from "../../indexer/ensure-index.js";
|
|
32
31
|
import { listRelatedPathsForFile } from "../../indexer/graph/graph-boost.js";
|
|
33
32
|
import { extractGraphForSingleFile } from "../../indexer/graph/graph-extraction.js";
|
|
34
33
|
import { lookup } from "../../indexer/indexer.js";
|
|
34
|
+
import { ensurePrimaryIndexForRead, resolveReadSources } from "../../indexer/read-preflight.js";
|
|
35
35
|
import { buildEditHint, findSourceForPath, isEditable, resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
36
36
|
import { insertUsageEvent } from "../../indexer/usage/usage-events.js";
|
|
37
37
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../../indexer/walk/file-context.js";
|
|
@@ -147,10 +147,8 @@ export async function akmShowUnified(input) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
// Auto-index when stale so the index is current before lookup.
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
await ensureIndex(allSources[0].path);
|
|
153
|
-
}
|
|
150
|
+
const { primarySource } = resolveReadSources();
|
|
151
|
+
await ensurePrimaryIndexForRead(primarySource);
|
|
154
152
|
// Try local filesystem (FTS5 index lookup)
|
|
155
153
|
const result = await showLocal(input);
|
|
156
154
|
// Scope filter narrows resolution: if --scope was supplied, the asset's
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import * as p from "@clack/prompts";
|
|
7
6
|
import { defineCommand } from "citty";
|
|
7
|
+
import * as p from "../../cli/clack.js";
|
|
8
8
|
import { output, runWithJsonErrors } from "../../cli/shared.js";
|
|
9
9
|
import { UsageError } from "../../core/errors.js";
|
|
10
10
|
import { appendEvent } from "../../core/events.js";
|
|
@@ -55,7 +55,19 @@ function assertInitSandbox(stashDir, dirExplicitlyProvided) {
|
|
|
55
55
|
function isUnderTestRunner() {
|
|
56
56
|
return process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
57
57
|
}
|
|
58
|
+
// ── Test seam ────────────────────────────────────────────────────────────────
|
|
59
|
+
// Swap-and-restore override. Inert in production; only tests call the setter.
|
|
60
|
+
let akmInitOverride;
|
|
61
|
+
/** TEST-ONLY. Swap the implementation of `akmInit`; pass undefined to restore. */
|
|
62
|
+
export function _setAkmInitForTests(fake) {
|
|
63
|
+
akmInitOverride = fake;
|
|
64
|
+
}
|
|
58
65
|
export async function akmInit(options) {
|
|
66
|
+
if (akmInitOverride)
|
|
67
|
+
return akmInitOverride(options);
|
|
68
|
+
return akmInitReal(options);
|
|
69
|
+
}
|
|
70
|
+
async function akmInitReal(options) {
|
|
59
71
|
const dirExplicitlyProvided = options?.dir != null;
|
|
60
72
|
const setDefault = options?.setDefault === true;
|
|
61
73
|
const stashDir = options?.dir ? path.resolve(options.dir) : getDefaultStashDir();
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* SIGINT/SIGTERM handlers in a try/finally — left byte-for-byte untouched.
|
|
26
26
|
*/
|
|
27
27
|
import path from "node:path";
|
|
28
|
-
import * as p from "@clack/prompts";
|
|
29
28
|
import { defineCommand } from "citty";
|
|
29
|
+
import * as p from "../../cli/clack.js";
|
|
30
30
|
import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
|
|
31
31
|
import { assertFlatAssetName } from "../../core/asset/asset-create.js";
|
|
32
32
|
import { isHttpUrl } from "../../core/common.js";
|
|
@@ -76,12 +76,19 @@ const DEFAULT_DEPS = {
|
|
|
76
76
|
list: akmTasksList,
|
|
77
77
|
add: akmTasksAdd,
|
|
78
78
|
};
|
|
79
|
+
let defaultTasksOverrides;
|
|
80
|
+
/** TEST-ONLY. Swap the CI/server/register functions; pass undefined to restore. */
|
|
81
|
+
export function _setDefaultTasksForTests(fakes) {
|
|
82
|
+
defaultTasksOverrides = fakes;
|
|
83
|
+
}
|
|
79
84
|
/**
|
|
80
85
|
* Decide whether `akm setup` is running in a CI environment, where it must
|
|
81
86
|
* register NO scheduled tasks. Mirrors the common `CI=true` convention used by
|
|
82
87
|
* GitHub Actions, GitLab CI, CircleCI, etc.
|
|
83
88
|
*/
|
|
84
89
|
export function isCiEnvironment(env = process.env) {
|
|
90
|
+
if (defaultTasksOverrides?.isCiEnvironment)
|
|
91
|
+
return defaultTasksOverrides.isCiEnvironment(env);
|
|
85
92
|
const ci = env.CI;
|
|
86
93
|
if (ci === undefined || ci === null)
|
|
87
94
|
return false;
|
|
@@ -95,6 +102,8 @@ export function isCiEnvironment(env = process.env) {
|
|
|
95
102
|
* Used as the default when setup is non-interactive (no TTY / --yes / CI).
|
|
96
103
|
*/
|
|
97
104
|
export function detectServerDefault() {
|
|
105
|
+
if (defaultTasksOverrides?.detectServerDefault)
|
|
106
|
+
return defaultTasksOverrides.detectServerDefault();
|
|
98
107
|
if (os.platform() !== "linux")
|
|
99
108
|
return false;
|
|
100
109
|
// A laptop exposes a battery under /sys/class/power_supply/BAT*. Absence of
|
|
@@ -121,6 +130,9 @@ export function detectServerDefault() {
|
|
|
121
130
|
* never re-disable a user-enabled task).
|
|
122
131
|
*/
|
|
123
132
|
export async function registerDefaultTasks(options = {}) {
|
|
133
|
+
if (defaultTasksOverrides?.registerDefaultTasks) {
|
|
134
|
+
return defaultTasksOverrides.registerDefaultTasks(options);
|
|
135
|
+
}
|
|
124
136
|
if (isCiEnvironment()) {
|
|
125
137
|
return { skipped: true, reason: "ci", created: [], existing: [], toggled: [] };
|
|
126
138
|
}
|
|
@@ -278,7 +278,19 @@ export function loadConfig() {
|
|
|
278
278
|
warnIfProjectConfigPresent(process.cwd());
|
|
279
279
|
return loadUserConfig();
|
|
280
280
|
}
|
|
281
|
+
let saveConfigOverride;
|
|
282
|
+
/** TEST-ONLY. Swap the implementation of `saveConfig`; pass undefined to restore. */
|
|
283
|
+
export function _setSaveConfigForTests(fake) {
|
|
284
|
+
saveConfigOverride = fake;
|
|
285
|
+
}
|
|
281
286
|
export function saveConfig(config) {
|
|
287
|
+
if (saveConfigOverride) {
|
|
288
|
+
saveConfigOverride(config);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
saveConfigReal(config);
|
|
292
|
+
}
|
|
293
|
+
function saveConfigReal(config) {
|
|
282
294
|
cachedConfig = undefined;
|
|
283
295
|
const configPath = getConfigPath();
|
|
284
296
|
const dir = path.dirname(configPath);
|
package/dist/core/warn.js
CHANGED
|
@@ -17,6 +17,11 @@ import path from "node:path";
|
|
|
17
17
|
let quiet = false;
|
|
18
18
|
let verbose = false;
|
|
19
19
|
let logFilePath;
|
|
20
|
+
let sinkOverride;
|
|
21
|
+
/** TEST-ONLY. Swap the output sink; pass undefined to restore real output. */
|
|
22
|
+
export function _setWarnSinkForTests(fake) {
|
|
23
|
+
sinkOverride = fake;
|
|
24
|
+
}
|
|
20
25
|
export function setQuiet(value) {
|
|
21
26
|
quiet = value;
|
|
22
27
|
}
|
|
@@ -96,6 +101,10 @@ function appendToLogFile(level, args) {
|
|
|
96
101
|
* Use for progress counters and status lines (replaces console.error used for progress).
|
|
97
102
|
*/
|
|
98
103
|
export function info(...args) {
|
|
104
|
+
if (sinkOverride) {
|
|
105
|
+
sinkOverride("info", args);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
99
108
|
appendToLogFile("INFO", args);
|
|
100
109
|
if (!quiet) {
|
|
101
110
|
console.warn(...args);
|
|
@@ -107,6 +116,10 @@ export function info(...args) {
|
|
|
107
116
|
* Drop-in replacement for console.warn() across the codebase.
|
|
108
117
|
*/
|
|
109
118
|
export function warn(...args) {
|
|
119
|
+
if (sinkOverride) {
|
|
120
|
+
sinkOverride("warn", args);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
110
123
|
appendToLogFile("WARN", args);
|
|
111
124
|
if (!quiet) {
|
|
112
125
|
console.warn(...args);
|
|
@@ -118,6 +131,10 @@ export function warn(...args) {
|
|
|
118
131
|
* Drop-in replacement for console.error() used for diagnostic failures.
|
|
119
132
|
*/
|
|
120
133
|
export function error(...args) {
|
|
134
|
+
if (sinkOverride) {
|
|
135
|
+
sinkOverride("error", args);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
121
138
|
appendToLogFile("ERROR", args);
|
|
122
139
|
if (!quiet) {
|
|
123
140
|
console.error(...args);
|
|
@@ -129,6 +146,10 @@ export function error(...args) {
|
|
|
129
146
|
* default verbosity (e.g. registry-content workflow validation errors).
|
|
130
147
|
*/
|
|
131
148
|
export function warnVerbose(...args) {
|
|
149
|
+
if (sinkOverride) {
|
|
150
|
+
sinkOverride("warnVerbose", args);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
132
153
|
if (isVerbose()) {
|
|
133
154
|
warn(...args);
|
|
134
155
|
}
|
package/dist/indexer/db/db.js
CHANGED
|
@@ -1633,6 +1633,11 @@ function bareRef(ref) {
|
|
|
1633
1633
|
* entry_ref populated (see logCurateEvent), so curation is a real retrieval
|
|
1634
1634
|
* signal here. Legacy summary-only curate rows with a NULL entry_ref simply
|
|
1635
1635
|
* contribute nothing.
|
|
1636
|
+
*
|
|
1637
|
+
* Machine-sourced events (`source` = 'improve' or 'task') are EXCLUDED: this
|
|
1638
|
+
* count feeds salience/ranking, and pipeline probe traffic counting as demand
|
|
1639
|
+
* creates a self-reinforcing loop (meta-review 05 DRIFT-6). NULL sources
|
|
1640
|
+
* (pre-column rows) count as user demand.
|
|
1636
1641
|
*/
|
|
1637
1642
|
export function getRetrievalCounts(db, refs) {
|
|
1638
1643
|
if (refs.length === 0)
|
|
@@ -1671,6 +1676,7 @@ export function getRetrievalCounts(db, refs) {
|
|
|
1671
1676
|
FROM usage_events
|
|
1672
1677
|
WHERE event_type IN ('search','show','curate')
|
|
1673
1678
|
AND entry_ref IS NOT NULL
|
|
1679
|
+
AND (source IS NULL OR source NOT IN ('improve','task'))
|
|
1674
1680
|
AND CASE
|
|
1675
1681
|
WHEN instr(entry_ref, '//') > 0
|
|
1676
1682
|
THEN substr(entry_ref, instr(entry_ref, '//') + 2)
|
|
@@ -185,7 +185,7 @@ async function runInlineReindex(stashDir) {
|
|
|
185
185
|
}
|
|
186
186
|
catch (error) {
|
|
187
187
|
warn("Auto-index failed, proceeding with existing index:", error instanceof Error ? error.message : String(error));
|
|
188
|
-
return
|
|
188
|
+
return false;
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
/**
|
|
@@ -200,7 +200,8 @@ async function runInlineReindex(stashDir) {
|
|
|
200
200
|
* trigger and waits for it. Use this for callers like `improve` whose
|
|
201
201
|
* planning logic depends on a current `entries` table in the same process.
|
|
202
202
|
*
|
|
203
|
-
* Returns `true`
|
|
203
|
+
* Returns `true` only when an inline index run succeeds.
|
|
204
|
+
* A rebuild attempt that fails (throws) resolves to `false`.
|
|
204
205
|
*/
|
|
205
206
|
export async function ensureIndex(stashDir, options = {}) {
|
|
206
207
|
if (options.mode === "blocking") {
|
|
@@ -7,6 +7,7 @@ import { probeLock, releaseLock, releaseLockIfOwned, tryAcquireLockSync } from "
|
|
|
7
7
|
import { getDbPath, getIndexWriterLockPath } from "../core/paths.js";
|
|
8
8
|
const INDEX_WRITER_LOCK_STALE_AFTER_MS = 12 * 60 * 60 * 1000;
|
|
9
9
|
const INDEX_WRITER_WAIT_MS = 100;
|
|
10
|
+
const DEFAULT_INDEX_WRITER_MAX_WAIT_MS = 10 * 60 * 1000;
|
|
10
11
|
const heldLocks = new Map();
|
|
11
12
|
function buildPayload(purpose, pid = process.pid) {
|
|
12
13
|
return JSON.stringify({
|
|
@@ -49,6 +50,8 @@ function retainHeldLock(lockPath) {
|
|
|
49
50
|
export async function acquireIndexWriterLease(options) {
|
|
50
51
|
const mode = options.mode ?? "wait";
|
|
51
52
|
const lockPath = getIndexWriterLockPath();
|
|
53
|
+
const startedAt = Date.now();
|
|
54
|
+
const maxWaitMs = options.maxWaitMs ?? DEFAULT_INDEX_WRITER_MAX_WAIT_MS;
|
|
52
55
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
53
56
|
if (heldLocks.has(lockPath)) {
|
|
54
57
|
return retainHeldLock(lockPath);
|
|
@@ -68,6 +71,12 @@ export async function acquireIndexWriterLease(options) {
|
|
|
68
71
|
}
|
|
69
72
|
if (mode === "try")
|
|
70
73
|
return undefined;
|
|
74
|
+
// Held by another live process. Time out only *after* a real acquisition
|
|
75
|
+
// attempt, so a caller with maxWaitMs:0 still gets one chance at a free lock
|
|
76
|
+
// instead of throwing before it ever tries.
|
|
77
|
+
if (maxWaitMs >= 0 && Date.now() - startedAt >= maxWaitMs) {
|
|
78
|
+
throw new Error(`timed out waiting for index writer lease for ${options.purpose}`);
|
|
79
|
+
}
|
|
71
80
|
await delay(INDEX_WRITER_WAIT_MS);
|
|
72
81
|
}
|
|
73
82
|
}
|
package/dist/indexer/indexer.js
CHANGED
|
@@ -118,7 +118,7 @@ async function runWalkPhase(ctx) {
|
|
|
118
118
|
ctx.timing.tWalkEnd = Date.now();
|
|
119
119
|
throwIfAborted(signal);
|
|
120
120
|
// LLM enrichment for directories that need it
|
|
121
|
-
await enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal,
|
|
121
|
+
await enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, reEnrich);
|
|
122
122
|
onProgress({
|
|
123
123
|
phase: "llm",
|
|
124
124
|
message: resolveIndexPassLLM("enrichment", config)
|
|
@@ -226,7 +226,19 @@ function runCleanPass(db, dryRun) {
|
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
228
|
// ── Indexer ──────────────────────────────────────────────────────────────────
|
|
229
|
+
// ── Test seam ────────────────────────────────────────────────────────────────
|
|
230
|
+
// Swap-and-restore override. Inert in production; only tests call the setter.
|
|
231
|
+
let akmIndexOverride;
|
|
232
|
+
/** TEST-ONLY. Swap the implementation of `akmIndex`; pass undefined to restore. */
|
|
233
|
+
export function _setAkmIndexForTests(fake) {
|
|
234
|
+
akmIndexOverride = fake;
|
|
235
|
+
}
|
|
229
236
|
export async function akmIndex(options) {
|
|
237
|
+
if (akmIndexOverride)
|
|
238
|
+
return akmIndexOverride(options);
|
|
239
|
+
return akmIndexReal(options);
|
|
240
|
+
}
|
|
241
|
+
async function akmIndexReal(options) {
|
|
230
242
|
return withIndexWriterLease({ purpose: "akm-index", signal: options?.signal }, async () => {
|
|
231
243
|
const stashDir = options?.stashDir || resolveStashDir();
|
|
232
244
|
const onProgress = options?.onProgress ?? (() => { });
|
|
@@ -640,7 +652,7 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
|
|
|
640
652
|
insertTransaction();
|
|
641
653
|
return { scannedDirs, skippedDirs, generatedCount, warnings, dirsNeedingLlm };
|
|
642
654
|
}
|
|
643
|
-
async function enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal,
|
|
655
|
+
async function enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, reEnrich = false) {
|
|
644
656
|
// Resolve per-pass LLM config via the unified shim. Returns undefined when
|
|
645
657
|
// either no `akm.llm` is configured or the user opted this pass out via
|
|
646
658
|
// `index.enrichment.llm = false`. (#208)
|
|
@@ -977,7 +989,7 @@ function resolveIndexedFiles(dirPath, files, stash) {
|
|
|
977
989
|
for (const entry of stash.entries) {
|
|
978
990
|
const entryPath = entry.filename
|
|
979
991
|
? path.join(dirPath, entry.filename)
|
|
980
|
-
: matchEntryToFile(entry.name, fileBasenameMap
|
|
992
|
+
: matchEntryToFile(entry.name, fileBasenameMap);
|
|
981
993
|
if (entryPath)
|
|
982
994
|
resolved.add(entryPath);
|
|
983
995
|
}
|
|
@@ -1096,7 +1108,7 @@ export function buildFileBasenameMap(files) {
|
|
|
1096
1108
|
* try matching the last segment
|
|
1097
1109
|
* 3. No implicit file fallback: ambiguous legacy entries are skipped
|
|
1098
1110
|
*/
|
|
1099
|
-
export function matchEntryToFile(entryName, fileMap
|
|
1111
|
+
export function matchEntryToFile(entryName, fileMap) {
|
|
1100
1112
|
// Exact match on entry name
|
|
1101
1113
|
const exact = fileMap.get(entryName);
|
|
1102
1114
|
if (exact)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { ensureIndex } from "./ensure-index.js";
|
|
5
|
+
import { resolveSourceEntries } from "./search/search-source.js";
|
|
6
|
+
/** Resolve the active read sources using the same resolution rules as search/show. */
|
|
7
|
+
export function resolveReadSources(overrideStashDir, existingConfig) {
|
|
8
|
+
const sources = resolveSourceEntries(overrideStashDir, existingConfig);
|
|
9
|
+
return { sources, primarySource: sources[0] };
|
|
10
|
+
}
|
|
11
|
+
/** Ensure the primary source index is readable for reads, when a primary exists. */
|
|
12
|
+
export async function ensurePrimaryIndexForRead(primarySource) {
|
|
13
|
+
if (!primarySource?.path)
|
|
14
|
+
return false;
|
|
15
|
+
return ensureIndex(primarySource.path);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Convenience helper for callers that only need to ensure a read index from a
|
|
19
|
+
* configured stash path and default config.
|
|
20
|
+
*/
|
|
21
|
+
export async function ensurePrimaryIndexFromConfig(overrideStashDir, existingConfig) {
|
|
22
|
+
return ensurePrimaryIndexForRead(resolveReadSources(overrideStashDir, existingConfig).primarySource);
|
|
23
|
+
}
|
|
@@ -148,20 +148,28 @@ function isInsideGitRepo(dir) {
|
|
|
148
148
|
* read (e.g. permission errors).
|
|
149
149
|
*/
|
|
150
150
|
export function* walkMarkdownFiles(root) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const full = path.join(root, entry.name);
|
|
160
|
-
if (entry.isDirectory()) {
|
|
161
|
-
yield* walkMarkdownFiles(full);
|
|
151
|
+
const stack = [root];
|
|
152
|
+
while (stack.length > 0) {
|
|
153
|
+
const current = stack.pop();
|
|
154
|
+
if (!current)
|
|
155
|
+
continue;
|
|
156
|
+
let entries;
|
|
157
|
+
try {
|
|
158
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
162
159
|
}
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
const full = path.join(current, entry.name);
|
|
165
|
+
if (entry.isSymbolicLink())
|
|
166
|
+
continue;
|
|
167
|
+
if (entry.isDirectory()) {
|
|
168
|
+
stack.push(full);
|
|
169
|
+
}
|
|
170
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
171
|
+
yield full;
|
|
172
|
+
}
|
|
165
173
|
}
|
|
166
174
|
}
|
|
167
175
|
}
|
|
@@ -55,6 +55,11 @@ export function defaultWhich(bin, envSource = process.env) {
|
|
|
55
55
|
}
|
|
56
56
|
return undefined;
|
|
57
57
|
}
|
|
58
|
+
let detectOverrides;
|
|
59
|
+
/** TEST-ONLY. Swap the detection implementations; pass undefined to restore. */
|
|
60
|
+
export function _setAgentDetectForTests(fakes) {
|
|
61
|
+
detectOverrides = fakes;
|
|
62
|
+
}
|
|
58
63
|
/**
|
|
59
64
|
* Probe every resolvable agent profile (built-ins plus user overrides)
|
|
60
65
|
* for an installed CLI.
|
|
@@ -64,6 +69,8 @@ export function defaultWhich(bin, envSource = process.env) {
|
|
|
64
69
|
* @param whichFn Binary lookup. Tests should inject a stub.
|
|
65
70
|
*/
|
|
66
71
|
export function detectAgentCliProfiles(agent, whichFn = defaultWhich) {
|
|
72
|
+
if (detectOverrides?.detectAgentCliProfiles)
|
|
73
|
+
return detectOverrides.detectAgentCliProfiles(agent, whichFn);
|
|
67
74
|
const profiles = listResolvedAgentProfiles(agent);
|
|
68
75
|
return profiles.map((profile) => probeProfile(profile, whichFn));
|
|
69
76
|
}
|
|
@@ -87,6 +94,8 @@ function probeProfile(profile, whichFn) {
|
|
|
87
94
|
* writing `agent.default`.
|
|
88
95
|
*/
|
|
89
96
|
export function pickDefaultAgentProfile(results, existingDefault) {
|
|
97
|
+
if (detectOverrides?.pickDefaultAgentProfile)
|
|
98
|
+
return detectOverrides.pickDefaultAgentProfile(results, existingDefault);
|
|
90
99
|
if (existingDefault) {
|
|
91
100
|
const match = results.find((r) => r.name === existingDefault && r.available);
|
|
92
101
|
if (match)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
export { getCommandBuilder } from "./builders.js";
|
|
5
5
|
export { DEFAULT_AGENT_TIMEOUT_MS, listAgentProfileNames, listResolvedAgentProfiles, requireAgentProfile, resolveAgentProfile, resolveDefaultProfileName, resolveProfileFromConfig, } from "./config.js";
|
|
6
|
-
export { defaultWhich, detectAgentCliProfiles, pickDefaultAgentProfile } from "./detect.js";
|
|
6
|
+
export { _setAgentDetectForTests, defaultWhich, detectAgentCliProfiles, pickDefaultAgentProfile } from "./detect.js";
|
|
7
7
|
export { listBuiltinModelAliases, resolveModel } from "./model-aliases.js";
|
|
8
8
|
export { BUILTIN_AGENT_PROFILE_NAMES, getBuiltinAgentProfile, listBuiltinAgentProfiles, } from "./profiles.js";
|
|
9
9
|
export { buildProposePrompt, buildReflectPrompt, buildSchemaRepairPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "./prompts.js";
|
package/dist/llm/client.js
CHANGED
|
@@ -165,7 +165,19 @@ function isRetryable(err) {
|
|
|
165
165
|
}
|
|
166
166
|
return false;
|
|
167
167
|
}
|
|
168
|
+
// ── Test seam ────────────────────────────────────────────────────────────────
|
|
169
|
+
// Swap-and-restore override. Inert in production; only tests call the setter.
|
|
170
|
+
let chatCompletionOverride;
|
|
171
|
+
/** TEST-ONLY. Swap the implementation of `chatCompletion`; pass undefined to restore. */
|
|
172
|
+
export function _setChatCompletionForTests(fake) {
|
|
173
|
+
chatCompletionOverride = fake;
|
|
174
|
+
}
|
|
168
175
|
export async function chatCompletion(config, messages, options) {
|
|
176
|
+
if (chatCompletionOverride)
|
|
177
|
+
return chatCompletionOverride(config, messages, options);
|
|
178
|
+
return chatCompletionReal(config, messages, options);
|
|
179
|
+
}
|
|
180
|
+
async function chatCompletionReal(config, messages, options) {
|
|
169
181
|
const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
|
|
170
182
|
const started = Date.now();
|
|
171
183
|
try {
|
package/dist/llm/embedder.js
CHANGED
|
@@ -3,11 +3,26 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { embedCacheKey, getCachedEmbedding, setCachedEmbedding } from "./embedders/cache.js";
|
|
5
5
|
import { DETERMINISTIC_EMBED_MODEL_ID, deterministicEmbed, isDeterministicEmbedEnabled, } from "./embedders/deterministic.js";
|
|
6
|
-
import { DEFAULT_LOCAL_MODEL, isTransformersAvailable, LocalEmbedder } from "./embedders/local.js";
|
|
6
|
+
import { DEFAULT_LOCAL_MODEL, isTransformersAvailable as isTransformersAvailableReal, LocalEmbedder, } from "./embedders/local.js";
|
|
7
7
|
import { hasRemoteEndpoint, RemoteEmbedder } from "./embedders/remote.js";
|
|
8
8
|
// ── Re-exports (public API) ─────────────────────────────────────────────────
|
|
9
9
|
export { clearEmbeddingCache } from "./embedders/cache.js";
|
|
10
|
-
export {
|
|
10
|
+
export { _setTransformersLoaderForTests, DEFAULT_LOCAL_MODEL } from "./embedders/local.js";
|
|
11
|
+
let embedderOverrides;
|
|
12
|
+
/** TEST-ONLY. Swap embedder implementations; pass undefined to restore. */
|
|
13
|
+
export function _setEmbedderForTests(fakes) {
|
|
14
|
+
embedderOverrides = fakes;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check whether the @huggingface/transformers package is importable.
|
|
18
|
+
* Delegating wrapper around `./embedders/local`'s probe so tests can swap it
|
|
19
|
+
* via {@link _setEmbedderForTests}.
|
|
20
|
+
*/
|
|
21
|
+
export function isTransformersAvailable() {
|
|
22
|
+
if (embedderOverrides?.isTransformersAvailable)
|
|
23
|
+
return embedderOverrides.isTransformersAvailable();
|
|
24
|
+
return isTransformersAvailableReal();
|
|
25
|
+
}
|
|
11
26
|
// ── Singleton local embedder ────────────────────────────────────────────────
|
|
12
27
|
// `_localEmbedder` is an intentional module-level singleton but constructed
|
|
13
28
|
// lazily on first use. The underlying @huggingface/transformers pipeline is
|
|
@@ -40,6 +55,8 @@ export function resetLocalEmbedder() {
|
|
|
40
55
|
* and embedding config. Repeated identical queries return the cached vector.
|
|
41
56
|
*/
|
|
42
57
|
export async function embed(text, embeddingConfig, signal) {
|
|
58
|
+
if (embedderOverrides?.embed)
|
|
59
|
+
return embedderOverrides.embed(text, embeddingConfig, signal);
|
|
43
60
|
// Deterministic mode (env-gated, test/bench only): model-free, stable.
|
|
44
61
|
if (isDeterministicEmbedEnabled()) {
|
|
45
62
|
return deterministicEmbed(text);
|
|
@@ -61,6 +78,8 @@ export async function embed(text, embeddingConfig, signal) {
|
|
|
61
78
|
* which processes texts in chunks of 32 for genuine batched inference.
|
|
62
79
|
*/
|
|
63
80
|
export async function embedBatch(texts, embeddingConfig, signal) {
|
|
81
|
+
if (embedderOverrides?.embedBatch)
|
|
82
|
+
return embedderOverrides.embedBatch(texts, embeddingConfig, signal);
|
|
64
83
|
if (texts.length === 0)
|
|
65
84
|
return [];
|
|
66
85
|
// Deterministic mode (env-gated, test/bench only): model-free, stable.
|
|
@@ -104,6 +123,8 @@ export { cosineSimilarity } from "./embedders/types.js";
|
|
|
104
123
|
* - No config: use `DEFAULT_LOCAL_MODEL` (the shared singleton model).
|
|
105
124
|
*/
|
|
106
125
|
export function resolveEmbeddingModelId(embeddingConfig) {
|
|
126
|
+
if (embedderOverrides?.resolveEmbeddingModelId)
|
|
127
|
+
return embedderOverrides.resolveEmbeddingModelId(embeddingConfig);
|
|
107
128
|
if (isDeterministicEmbedEnabled())
|
|
108
129
|
return DETERMINISTIC_EMBED_MODEL_ID;
|
|
109
130
|
if (!embeddingConfig)
|
|
@@ -117,6 +138,9 @@ export function resolveEmbeddingModelId(embeddingConfig) {
|
|
|
117
138
|
* Check whether embedding is available with a detailed reason on failure.
|
|
118
139
|
*/
|
|
119
140
|
export async function checkEmbeddingAvailability(embeddingConfig) {
|
|
141
|
+
if (embedderOverrides?.checkEmbeddingAvailability) {
|
|
142
|
+
return embedderOverrides.checkEmbeddingAvailability(embeddingConfig);
|
|
143
|
+
}
|
|
120
144
|
// Deterministic mode (env-gated): always available — no model, no network.
|
|
121
145
|
if (isDeterministicEmbedEnabled()) {
|
|
122
146
|
return { available: true };
|
|
@@ -28,6 +28,12 @@ function isBatchTensor(v) {
|
|
|
28
28
|
Array.isArray(v.dims) &&
|
|
29
29
|
v.dims.length >= 2);
|
|
30
30
|
}
|
|
31
|
+
const realTransformersLoader = () => import("@huggingface/transformers");
|
|
32
|
+
let transformersLoader = realTransformersLoader;
|
|
33
|
+
/** TEST-ONLY. Swap the transformers module loader; pass undefined to restore. */
|
|
34
|
+
export function _setTransformersLoaderForTests(fake) {
|
|
35
|
+
transformersLoader = fake ?? realTransformersLoader;
|
|
36
|
+
}
|
|
31
37
|
const LOCAL_EMBEDDER_DTYPE = "fp32";
|
|
32
38
|
const LOCAL_EMBEDDER_FALLBACK_DTYPE = "auto";
|
|
33
39
|
/**
|
|
@@ -180,7 +186,7 @@ export class LocalEmbedder {
|
|
|
180
186
|
}
|
|
181
187
|
let pipeline;
|
|
182
188
|
try {
|
|
183
|
-
const mod = await
|
|
189
|
+
const mod = await transformersLoader();
|
|
184
190
|
pipeline = mod.pipeline;
|
|
185
191
|
}
|
|
186
192
|
catch (importError) {
|
|
@@ -7595,23 +7595,35 @@ function appendToLogFile(level, args) {
|
|
|
7595
7595
|
}
|
|
7596
7596
|
}
|
|
7597
7597
|
function warn(...args) {
|
|
7598
|
+
if (sinkOverride) {
|
|
7599
|
+
sinkOverride("warn", args);
|
|
7600
|
+
return;
|
|
7601
|
+
}
|
|
7598
7602
|
appendToLogFile("WARN", args);
|
|
7599
7603
|
if (!quiet) {
|
|
7600
7604
|
console.warn(...args);
|
|
7601
7605
|
}
|
|
7602
7606
|
}
|
|
7603
7607
|
function error(...args) {
|
|
7608
|
+
if (sinkOverride) {
|
|
7609
|
+
sinkOverride("error", args);
|
|
7610
|
+
return;
|
|
7611
|
+
}
|
|
7604
7612
|
appendToLogFile("ERROR", args);
|
|
7605
7613
|
if (!quiet) {
|
|
7606
7614
|
console.error(...args);
|
|
7607
7615
|
}
|
|
7608
7616
|
}
|
|
7609
7617
|
function warnVerbose(...args) {
|
|
7618
|
+
if (sinkOverride) {
|
|
7619
|
+
sinkOverride("warnVerbose", args);
|
|
7620
|
+
return;
|
|
7621
|
+
}
|
|
7610
7622
|
if (isVerbose()) {
|
|
7611
7623
|
warn(...args);
|
|
7612
7624
|
}
|
|
7613
7625
|
}
|
|
7614
|
-
var quiet = false, verbose = false, logFilePath;
|
|
7626
|
+
var quiet = false, verbose = false, logFilePath, sinkOverride;
|
|
7615
7627
|
var init_warn = () => {};
|
|
7616
7628
|
|
|
7617
7629
|
// src/indexer/walk/file-context.ts
|
|
@@ -16451,6 +16463,7 @@ __export(exports_config, {
|
|
|
16451
16463
|
getIndexPassConfig: () => getIndexPassConfig,
|
|
16452
16464
|
getEffectiveRegistries: () => getEffectiveRegistries,
|
|
16453
16465
|
getDefaultLlmConfig: () => getDefaultLlmConfig,
|
|
16466
|
+
_setSaveConfigForTests: () => _setSaveConfigForTests,
|
|
16454
16467
|
VALID_HARNESS_IDS: () => VALID_HARNESS_IDS,
|
|
16455
16468
|
FEEDBACK_FAILURE_MODES: () => FEEDBACK_FAILURE_MODES,
|
|
16456
16469
|
DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE: () => DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE,
|
|
@@ -16593,7 +16606,17 @@ function loadConfig() {
|
|
|
16593
16606
|
warnIfProjectConfigPresent(process.cwd());
|
|
16594
16607
|
return loadUserConfig();
|
|
16595
16608
|
}
|
|
16609
|
+
function _setSaveConfigForTests(fake) {
|
|
16610
|
+
saveConfigOverride = fake;
|
|
16611
|
+
}
|
|
16596
16612
|
function saveConfig(config) {
|
|
16613
|
+
if (saveConfigOverride) {
|
|
16614
|
+
saveConfigOverride(config);
|
|
16615
|
+
return;
|
|
16616
|
+
}
|
|
16617
|
+
saveConfigReal(config);
|
|
16618
|
+
}
|
|
16619
|
+
function saveConfigReal(config) {
|
|
16597
16620
|
cachedConfig = undefined;
|
|
16598
16621
|
const configPath = getConfigPath();
|
|
16599
16622
|
const dir = path11.dirname(configPath);
|
|
@@ -16762,7 +16785,7 @@ function isFile(filePath) {
|
|
|
16762
16785
|
return false;
|
|
16763
16786
|
}
|
|
16764
16787
|
}
|
|
16765
|
-
var FEEDBACK_FAILURE_MODES, DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE = 4, GRAPH_EXTRACTION_CHARS_PER_BODY = 1500, DEFAULT_CONFIG, PROJECT_CONFIG_RELATIVE_PATH, cachedConfig, INDEX_RESERVED_KEYS, PROJECT_CONFIG_DEPRECATION_WARNED;
|
|
16788
|
+
var FEEDBACK_FAILURE_MODES, DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE = 4, GRAPH_EXTRACTION_CHARS_PER_BODY = 1500, DEFAULT_CONFIG, PROJECT_CONFIG_RELATIVE_PATH, cachedConfig, saveConfigOverride, INDEX_RESERVED_KEYS, PROJECT_CONFIG_DEPRECATION_WARNED;
|
|
16766
16789
|
var init_config2 = __esm(() => {
|
|
16767
16790
|
init_errors();
|
|
16768
16791
|
init_config_io();
|
|
@@ -17920,6 +17943,7 @@ function getRetrievalCounts(db, refs) {
|
|
|
17920
17943
|
FROM usage_events
|
|
17921
17944
|
WHERE event_type IN ('search','show','curate')
|
|
17922
17945
|
AND entry_ref IS NOT NULL
|
|
17946
|
+
AND (source IS NULL OR source NOT IN ('improve','task'))
|
|
17923
17947
|
AND CASE
|
|
17924
17948
|
WHEN instr(entry_ref, '//') > 0
|
|
17925
17949
|
THEN substr(entry_ref, instr(entry_ref, '//') + 2)
|
|
@@ -53,12 +53,16 @@ function appendToLogFile(level, args) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
function warn(...args) {
|
|
56
|
+
if (sinkOverride) {
|
|
57
|
+
sinkOverride("warn", args);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
56
60
|
appendToLogFile("WARN", args);
|
|
57
61
|
if (!quiet) {
|
|
58
62
|
console.warn(...args);
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
|
-
var quiet = false, logFilePath;
|
|
65
|
+
var quiet = false, logFilePath, sinkOverride;
|
|
62
66
|
var init_warn = () => {};
|
|
63
67
|
|
|
64
68
|
// node_modules/dotenv/lib/main.js
|
package/dist/setup/detect.js
CHANGED
|
@@ -14,6 +14,11 @@ import { defaultWhich } from "../integrations/agent/detect.js";
|
|
|
14
14
|
import { SESSION_LOG_HARNESSES } from "../integrations/harnesses/index.js";
|
|
15
15
|
import { spawn } from "../runtime.js";
|
|
16
16
|
import { detectHarnessConfigs } from "./harness-config-import.js";
|
|
17
|
+
let detectOverrides;
|
|
18
|
+
/** TEST-ONLY. Swap the network/host probes; pass undefined to restore. */
|
|
19
|
+
export function _setDetectForTests(fakes) {
|
|
20
|
+
detectOverrides = fakes;
|
|
21
|
+
}
|
|
17
22
|
// ── Ollama Detection ────────────────────────────────────────────────────────
|
|
18
23
|
const OLLAMA_BASE = "http://localhost:11434";
|
|
19
24
|
/**
|
|
@@ -23,6 +28,8 @@ const OLLAMA_BASE = "http://localhost:11434";
|
|
|
23
28
|
* via subprocess. Returns available models sorted alphabetically.
|
|
24
29
|
*/
|
|
25
30
|
export async function detectOllama() {
|
|
31
|
+
if (detectOverrides?.detectOllama)
|
|
32
|
+
return detectOverrides.detectOllama();
|
|
26
33
|
const result = { available: false, models: [], endpoint: OLLAMA_BASE };
|
|
27
34
|
// Try HTTP API first
|
|
28
35
|
try {
|
|
@@ -120,6 +127,8 @@ const AGENT_PLATFORMS = SESSION_LOG_HARNESSES.filter((h) => h.setupDetectionDir)
|
|
|
120
127
|
* Supports both HOME (Unix) and USERPROFILE (Windows).
|
|
121
128
|
*/
|
|
122
129
|
export function detectAgentPlatforms() {
|
|
130
|
+
if (detectOverrides?.detectAgentPlatforms)
|
|
131
|
+
return detectOverrides.detectAgentPlatforms();
|
|
123
132
|
const home = process.env.HOME?.trim() || process.env.USERPROFILE?.trim();
|
|
124
133
|
if (!home)
|
|
125
134
|
return [];
|