akm-cli 0.9.0-beta.11 → 0.9.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +163 -0
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +5 -1
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/templates/html/health.html +25 -27
- package/dist/cli.js +2 -2
- package/dist/commands/agent/contribute-cli.js +16 -3
- package/dist/commands/feedback-cli.js +48 -44
- package/dist/commands/health/html-report.js +140 -16
- package/dist/commands/health.js +277 -1
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate.js +595 -105
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +119 -64
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +115 -1
- package/dist/commands/improve/extract-prompt.js +32 -1
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +210 -30
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +80 -7
- package/dist/commands/improve/improve-profiles.js +8 -0
- package/dist/commands/improve/improve.js +991 -61
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/proactive-maintenance.js +9 -35
- package/dist/commands/improve/procedural.js +409 -0
- package/dist/commands/improve/recombine.js +488 -0
- package/dist/commands/improve/reflect.js +20 -1
- package/dist/commands/improve/related-sessions.js +120 -0
- package/dist/commands/improve/salience.js +386 -0
- package/dist/commands/improve/triage.js +95 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +34 -1
- package/dist/commands/lint/index.js +30 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +5 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/validators/proposals.js +4 -0
- package/dist/commands/read/curate.js +284 -86
- package/dist/commands/read/search-cli.js +7 -0
- package/dist/commands/read/search.js +1 -0
- package/dist/commands/sources/installed-stashes.js +5 -1
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/config/config-schema.js +211 -3
- package/dist/core/config/config.js +2 -2
- package/dist/core/logs-db.js +4 -3
- package/dist/core/state-db.js +555 -29
- package/dist/indexer/db/db.js +250 -27
- package/dist/indexer/db/graph-db.js +81 -86
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/passes/memory-inference.js +10 -3
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +15 -4
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/integrations/harnesses/claude/session-log.js +10 -0
- package/dist/integrations/harnesses/opencode/session-log.js +9 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/embedder.js +27 -3
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/graph-extract.js +2 -1
- package/dist/llm/memory-infer.js +4 -8
- package/dist/llm/metadata-enhance.js +9 -1
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/text/helpers.js +9 -0
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +1025 -567
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +435 -269
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +1 -0
- package/package.json +5 -4
package/dist/llm/embedder.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
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
|
-
import { isTransformersAvailable, LocalEmbedder } from "./embedders/local.js";
|
|
5
|
+
import { DEFAULT_LOCAL_MODEL, isTransformersAvailable, LocalEmbedder } from "./embedders/local.js";
|
|
6
6
|
import { hasRemoteEndpoint, RemoteEmbedder } from "./embedders/remote.js";
|
|
7
7
|
// ── Re-exports (public API) ─────────────────────────────────────────────────
|
|
8
8
|
export { clearEmbeddingCache } from "./embedders/cache.js";
|
|
@@ -52,7 +52,8 @@ export async function embed(text, embeddingConfig, signal) {
|
|
|
52
52
|
/**
|
|
53
53
|
* Generate embeddings for multiple texts in batch.
|
|
54
54
|
* Uses the OpenAI-compatible batch API for remote endpoints (batches of 100).
|
|
55
|
-
*
|
|
55
|
+
* Uses the LocalEmbedder.embedBatch path for the local transformer pipeline,
|
|
56
|
+
* which processes texts in chunks of 32 for genuine batched inference.
|
|
56
57
|
*/
|
|
57
58
|
export async function embedBatch(texts, embeddingConfig, signal) {
|
|
58
59
|
if (texts.length === 0)
|
|
@@ -60,8 +61,13 @@ export async function embedBatch(texts, embeddingConfig, signal) {
|
|
|
60
61
|
if (embeddingConfig && hasRemoteEndpoint(embeddingConfig)) {
|
|
61
62
|
return new RemoteEmbedder(embeddingConfig).embedBatch(texts, signal);
|
|
62
63
|
}
|
|
63
|
-
// Local transformer:
|
|
64
|
+
// Local transformer: use the batched path (chunks of 32 via LocalEmbedder).
|
|
65
|
+
// When a localModel override is set we cannot share the singleton (which uses
|
|
66
|
+
// the default model), so fall back to per-text embedWithModel in that case.
|
|
64
67
|
const localModel = embeddingConfig?.localModel;
|
|
68
|
+
if (!localModel) {
|
|
69
|
+
return getLocalEmbedder().embedBatch(texts, signal);
|
|
70
|
+
}
|
|
65
71
|
const results = [];
|
|
66
72
|
for (const text of texts) {
|
|
67
73
|
if (signal?.aborted) {
|
|
@@ -77,6 +83,24 @@ export async function embedBatch(texts, embeddingConfig, signal) {
|
|
|
77
83
|
// facade and its `@huggingface/transformers` import chain. Re-export
|
|
78
84
|
// preserves the existing public API.
|
|
79
85
|
export { cosineSimilarity } from "./embedders/types.js";
|
|
86
|
+
// ── Model ID resolution ─────────────────────────────────────────────────────
|
|
87
|
+
/**
|
|
88
|
+
* Derive a stable string identifier for the embedding model in use.
|
|
89
|
+
* This is the `model_id` stored in `body_embeddings` (and used for the
|
|
90
|
+
* drop-all-on-mismatch purge when the model changes).
|
|
91
|
+
*
|
|
92
|
+
* Rules:
|
|
93
|
+
* - Remote endpoint: use `config.model` (the API-level model name).
|
|
94
|
+
* - Local transformers: use `config.localModel ?? DEFAULT_LOCAL_MODEL`.
|
|
95
|
+
* - No config: use `DEFAULT_LOCAL_MODEL` (the shared singleton model).
|
|
96
|
+
*/
|
|
97
|
+
export function resolveEmbeddingModelId(embeddingConfig) {
|
|
98
|
+
if (!embeddingConfig)
|
|
99
|
+
return DEFAULT_LOCAL_MODEL;
|
|
100
|
+
if (hasRemoteEndpoint(embeddingConfig))
|
|
101
|
+
return embeddingConfig.model ?? "remote";
|
|
102
|
+
return embeddingConfig.localModel ?? DEFAULT_LOCAL_MODEL;
|
|
103
|
+
}
|
|
80
104
|
// ── Availability check ──────────────────────────────────────────────────────
|
|
81
105
|
/**
|
|
82
106
|
* Check whether embedding is available with a detailed reason on failure.
|
|
@@ -19,8 +19,24 @@ import { getDirname, resolveModule } from "../../runtime.js";
|
|
|
19
19
|
* `all-MiniLM-L6-v2` at the same 384-dimension footprint.
|
|
20
20
|
*/
|
|
21
21
|
export const DEFAULT_LOCAL_MODEL = "Xenova/bge-small-en-v1.5";
|
|
22
|
+
/** Type-guard: true when the value looks like a batch Tensor (has .dims). */
|
|
23
|
+
function isBatchTensor(v) {
|
|
24
|
+
return (v !== null &&
|
|
25
|
+
typeof v === "object" &&
|
|
26
|
+
"data" in v &&
|
|
27
|
+
"dims" in v &&
|
|
28
|
+
Array.isArray(v.dims) &&
|
|
29
|
+
v.dims.length >= 2);
|
|
30
|
+
}
|
|
22
31
|
const LOCAL_EMBEDDER_DTYPE = "fp32";
|
|
23
32
|
const LOCAL_EMBEDDER_FALLBACK_DTYPE = "auto";
|
|
33
|
+
/**
|
|
34
|
+
* Maximum texts per batch for the local transformers pipeline. The pipeline
|
|
35
|
+
* can run genuine batched inference over a string array; 32 is a safe default
|
|
36
|
+
* that fits well inside most model context budgets while providing 10–50×
|
|
37
|
+
* throughput improvement over one-at-a-time calls on the cold minority.
|
|
38
|
+
*/
|
|
39
|
+
const LOCAL_BATCH_SIZE = 32;
|
|
24
40
|
/**
|
|
25
41
|
* Return the local model name that will be used for embedding.
|
|
26
42
|
* When `overrideModel` is provided it takes precedence; otherwise
|
|
@@ -77,15 +93,63 @@ export class LocalEmbedder {
|
|
|
77
93
|
}
|
|
78
94
|
return this.embedWithModel(text, this.defaultModel);
|
|
79
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Embed a batch of texts. Processes in chunks of `LOCAL_BATCH_SIZE` (32) so
|
|
98
|
+
* the transformers pipeline can run genuine batched inference rather than one
|
|
99
|
+
* call per text. Falls back to one-at-a-time if the pipeline does not support
|
|
100
|
+
* array input (older versions of @huggingface/transformers). Each chunk is
|
|
101
|
+
* checked against the AbortSignal between calls.
|
|
102
|
+
*/
|
|
80
103
|
async embedBatch(texts, signal) {
|
|
81
104
|
if (texts.length === 0)
|
|
82
105
|
return [];
|
|
106
|
+
if (signal?.aborted) {
|
|
107
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
108
|
+
}
|
|
109
|
+
const pipeline = await this.getPipeline(this.defaultModel);
|
|
83
110
|
const results = [];
|
|
84
|
-
for (
|
|
111
|
+
for (let i = 0; i < texts.length; i += LOCAL_BATCH_SIZE) {
|
|
85
112
|
if (signal?.aborted) {
|
|
86
113
|
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
87
114
|
}
|
|
88
|
-
|
|
115
|
+
const chunk = texts.slice(i, i + LOCAL_BATCH_SIZE);
|
|
116
|
+
try {
|
|
117
|
+
// @huggingface/transformers feature-extraction pipeline accepts a
|
|
118
|
+
// string[] and returns a batch Tensor (NOT an Array<{data}>).
|
|
119
|
+
// The Tensor has .data (flat Float32Array, length = batch * dim) and
|
|
120
|
+
// .dims = [batch, dim]. Slice .data into per-row vectors using .dims.
|
|
121
|
+
const batchResult = await pipeline(chunk, {
|
|
122
|
+
pooling: "mean",
|
|
123
|
+
normalize: true,
|
|
124
|
+
});
|
|
125
|
+
if (isBatchTensor(batchResult)) {
|
|
126
|
+
const dim = batchResult.dims[1];
|
|
127
|
+
for (let row = 0; row < chunk.length; row++) {
|
|
128
|
+
results.push(Array.from(batchResult.data.subarray(row * dim, (row + 1) * dim)));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (Array.isArray(batchResult)) {
|
|
132
|
+
// Older versions of @huggingface/transformers returned Array<{data}>.
|
|
133
|
+
for (const r of batchResult) {
|
|
134
|
+
results.push(Array.from(r.data));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Single-text result returned for a chunk — should not happen for
|
|
139
|
+
// string[] input, but handle defensively.
|
|
140
|
+
throw new Error("unexpected pipeline return shape for batch input");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Fallback: process one-at-a-time (older pipeline versions or mismatched
|
|
145
|
+
// return type). Fail-open per text: a single failure aborts the chunk.
|
|
146
|
+
for (const text of chunk) {
|
|
147
|
+
if (signal?.aborted) {
|
|
148
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("embedding interrupted");
|
|
149
|
+
}
|
|
150
|
+
results.push(await this.embedWithModel(text, this.defaultModel));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
89
153
|
}
|
|
90
154
|
return results;
|
|
91
155
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* the connection via `resolveIndexPassLLM("graph", config)` and pass it
|
|
21
21
|
* straight through.
|
|
22
22
|
*/
|
|
23
|
+
import systemPromptTemplate from "../assets/prompts/graph-extract-system.md" with { type: "text" };
|
|
23
24
|
import userPromptTemplate from "../assets/prompts/graph-extract-user-prompt.md" with { type: "text" };
|
|
24
25
|
import { toErrorMessage } from "../core/common.js";
|
|
25
26
|
import { warn, warnVerbose } from "../core/warn.js";
|
|
@@ -41,7 +42,7 @@ const NON_ARRAY_BATCH_DISABLE_THRESHOLD = 2;
|
|
|
41
42
|
const MAX_ENTITIES_PER_ASSET = 32;
|
|
42
43
|
/** Hard cap on relations returned per asset. */
|
|
43
44
|
const MAX_RELATIONS_PER_ASSET = 32;
|
|
44
|
-
const SYSTEM_PROMPT =
|
|
45
|
+
const SYSTEM_PROMPT = systemPromptTemplate;
|
|
45
46
|
const USER_PROMPT_PREFIX = userPromptTemplate
|
|
46
47
|
.replace("{{MAX_ENTITIES}}", String(MAX_ENTITIES_PER_ASSET))
|
|
47
48
|
.replace("{{MAX_RELATIONS}}", String(MAX_RELATIONS_PER_ASSET));
|
package/dist/llm/memory-infer.js
CHANGED
|
@@ -18,20 +18,16 @@
|
|
|
18
18
|
* the connection via `resolveIndexPassLLM("memory", config)` and pass it
|
|
19
19
|
* straight through.
|
|
20
20
|
*/
|
|
21
|
+
import memoryInferSystemPrompt from "../assets/prompts/memory-infer-system.md" with { type: "text" };
|
|
22
|
+
import memoryInferUserPrompt from "../assets/prompts/memory-infer-user.md" with { type: "text" };
|
|
21
23
|
import { toErrorMessage } from "../core/common.js";
|
|
22
24
|
import { warn } from "../core/warn.js";
|
|
23
25
|
import { chatCompletion, LlmCallError, parseEmbeddedJsonResponse } from "./client.js";
|
|
24
26
|
import { tryLlmFeature } from "./feature-gate.js";
|
|
25
27
|
/** Hard cap on body chars sent to the model — pragmatic and matches `runLlmEnrich`. */
|
|
26
28
|
const MAX_BODY_CHARS = 4000;
|
|
27
|
-
const SYSTEM_PROMPT =
|
|
28
|
-
|
|
29
|
-
const USER_PROMPT_PREFIX = `Compress the memory below into one derived memory. Output ONLY JSON:
|
|
30
|
-
{"title":"short title string","description":"one sentence summary string","tags":["tag1","tag2"],"searchHints":["search phrase 1","search phrase 2"],"content":"2-3 sentence compressed body preserving key facts verbatim"}
|
|
31
|
-
Rules: be specific, no vague generalizations, preserve key facts (names/versions/paths/config keys verbatim), merge related points, 3-8 tags, 3-6 searchHints. The content field must be a plain string with 2-3 sentences.
|
|
32
|
-
|
|
33
|
-
Memory:
|
|
34
|
-
`;
|
|
29
|
+
const SYSTEM_PROMPT = memoryInferSystemPrompt;
|
|
30
|
+
const USER_PROMPT_PREFIX = memoryInferUserPrompt;
|
|
35
31
|
/**
|
|
36
32
|
* Strict JSON Schema for the derived-memory payload. Sent to providers that
|
|
37
33
|
* opt in via `LlmConnectionConfig.supportsJsonSchema = true`; the client
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* LLM-driven metadata enhancement for stash entries.
|
|
6
|
+
*
|
|
7
|
+
* Split out of `llm.ts` so the higher-level workflow (prompting the LLM to
|
|
8
|
+
* improve descriptions/tags/searchHints) lives separately from the low-level
|
|
9
|
+
* transport client in `client.ts`.
|
|
10
|
+
*/
|
|
11
|
+
import metadataEnhanceSystemPrompt from "../assets/prompts/metadata-enhance-system.md" with { type: "text" };
|
|
4
12
|
import { chatCompletion, parseJsonResponse } from "./client.js";
|
|
5
13
|
import { tryLlmFeature } from "./feature-gate.js";
|
|
6
|
-
const SYSTEM_PROMPT =
|
|
14
|
+
const SYSTEM_PROMPT = metadataEnhanceSystemPrompt;
|
|
7
15
|
/**
|
|
8
16
|
* Use an LLM to enhance a stash entry's metadata: improve description,
|
|
9
17
|
* generate searchHints, and suggest tags.
|
|
@@ -5,7 +5,7 @@ import { capDescription, NORMAL_DESCRIPTION_LIMIT, pickFields } from "./helpers.
|
|
|
5
5
|
// Curation is a small, high-signal top-N. Even at `brief` we keep `followUp`
|
|
6
6
|
// (the actionable `akm show <ref>` command) and `reason` (why this asset was
|
|
7
7
|
// selected) — these are the point of curate, unlike a bulk search listing.
|
|
8
|
-
const BRIEF_FIELDS = ["source", "type", "name", "ref", "id", "followUp", "reason"];
|
|
8
|
+
const BRIEF_FIELDS = ["source", "type", "name", "ref", "id", "supportRefs", "followUp", "reason"];
|
|
9
9
|
const NORMAL_FIELDS = [
|
|
10
10
|
"source",
|
|
11
11
|
"type",
|
|
@@ -17,12 +17,24 @@ const NORMAL_FIELDS = [
|
|
|
17
17
|
"keys",
|
|
18
18
|
"parameters",
|
|
19
19
|
"run",
|
|
20
|
+
"supportRefs",
|
|
20
21
|
"followUp",
|
|
21
22
|
"reason",
|
|
22
23
|
"score",
|
|
23
24
|
];
|
|
24
25
|
// Agent shape: the minimal field set an LLM needs to decide and act.
|
|
25
|
-
const AGENT_FIELDS = [
|
|
26
|
+
const AGENT_FIELDS = [
|
|
27
|
+
"source",
|
|
28
|
+
"type",
|
|
29
|
+
"name",
|
|
30
|
+
"ref",
|
|
31
|
+
"id",
|
|
32
|
+
"description",
|
|
33
|
+
"supportRefs",
|
|
34
|
+
"followUp",
|
|
35
|
+
"reason",
|
|
36
|
+
"score",
|
|
37
|
+
];
|
|
26
38
|
function shapeCurateItem(item, detail, shape) {
|
|
27
39
|
if (shape === "agent") {
|
|
28
40
|
return capDescription(pickFields(item, AGENT_FIELDS), NORMAL_DESCRIPTION_LIMIT);
|
|
@@ -1005,6 +1005,15 @@ export function formatCuratePlain(r, detail) {
|
|
|
1005
1005
|
lines.push(` run: ${String(item.run)}`);
|
|
1006
1006
|
if (item.followUp)
|
|
1007
1007
|
lines.push(` show: ${String(item.followUp)}`);
|
|
1008
|
+
if (Array.isArray(item.supportRefs) && item.supportRefs.length > 0) {
|
|
1009
|
+
for (const support of item.supportRefs) {
|
|
1010
|
+
if (!support.ref)
|
|
1011
|
+
continue;
|
|
1012
|
+
const label = typeof support.type === "string" ? `[${support.type}] ` : "";
|
|
1013
|
+
const why = typeof support.reason === "string" ? ` — ${support.reason}` : "";
|
|
1014
|
+
lines.push(` support: ${label}${String(support.ref)}${why}`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1008
1017
|
if (detail !== "brief" && item.reason)
|
|
1009
1018
|
lines.push(` why: ${String(item.reason)}`);
|
|
1010
1019
|
}
|
package/dist/runtime.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "node:child_process";
|
|
24
24
|
import { createHash } from "node:crypto";
|
|
25
|
-
import { createWriteStream } from "node:fs";
|
|
25
|
+
import { createWriteStream, statfsSync } from "node:fs";
|
|
26
26
|
import { createRequire } from "node:module";
|
|
27
27
|
import path from "node:path";
|
|
28
28
|
import { Readable } from "node:stream";
|
|
@@ -200,6 +200,30 @@ export function resolveModule(spec, from) {
|
|
|
200
200
|
export function getDirname(importMetaUrl) {
|
|
201
201
|
return path.dirname(fileURLToPath(importMetaUrl));
|
|
202
202
|
}
|
|
203
|
+
// ── Filesystem type probe ─────────────────────────────────────────────────────
|
|
204
|
+
/**
|
|
205
|
+
* Best-effort filesystem-type probe: return the numeric `f_type` magic of the
|
|
206
|
+
* filesystem backing `path`, or `undefined` on any error (ENOENT/EPERM/
|
|
207
|
+
* unsupported). Used (via injection) by the SQLite journal-mode network-FS
|
|
208
|
+
* fallback (#628).
|
|
209
|
+
*
|
|
210
|
+
* This is a runtime primitive (a `statfs` filesystem-type probe) and therefore
|
|
211
|
+
* lives here, per the runtime boundary, even though it uses only `node:fs`. The
|
|
212
|
+
* "magic-number → is-network" classification stays a pure helper elsewhere.
|
|
213
|
+
*
|
|
214
|
+
* `fs.statfsSync` is a stable `node:fs` API (Node ≥ 18.15) and is implemented
|
|
215
|
+
* under Bun as well. It THROWS ENOENT on a non-existent path, so callers should
|
|
216
|
+
* probe an already-created directory — the swallowed error simply yields
|
|
217
|
+
* `undefined` (treated as "not network").
|
|
218
|
+
*/
|
|
219
|
+
export function statfsType(path) {
|
|
220
|
+
try {
|
|
221
|
+
return statfsSync(path).type;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
203
227
|
// ── Sleep ───────────────────────────────────────────────────────────────────
|
|
204
228
|
/**
|
|
205
229
|
* Block the current thread for `ms` without busy-spinning. On Bun uses the real
|