akm-cli 0.9.0-beta.2 → 0.9.0-beta.27
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 +660 -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/default.html +78 -0
- package/dist/assets/templates/html/health.html +730 -0
- package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
- package/dist/cli/shared.js +21 -5
- package/dist/cli.js +47 -5
- package/dist/commands/agent/contribute-cli.js +16 -3
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph.js +75 -71
- package/dist/commands/health/checks.js +48 -0
- package/dist/commands/health/html-report.js +790 -0
- package/dist/commands/health.js +478 -15
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate.js +634 -111
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +145 -69
- 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 +33 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +280 -35
- 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 +139 -6
- package/dist/commands/improve/improve-profiles.js +12 -0
- package/dist/commands/improve/improve.js +2079 -608
- 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 +87 -0
- package/dist/commands/improve/procedural.js +409 -0
- package/dist/commands/improve/recombine.js +488 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +51 -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/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain.js +73 -6
- package/dist/commands/proposal/proposal-cli.js +22 -10
- package/dist/commands/proposal/proposal.js +17 -1
- package/dist/commands/proposal/validators/proposals.js +369 -329
- package/dist/commands/read/curate.js +344 -80
- package/dist/commands/read/search-cli.js +7 -0
- package/dist/commands/read/search.js +1 -0
- package/dist/commands/read/show.js +67 -2
- package/dist/commands/remember.js +6 -2
- package/dist/commands/sources/installed-stashes.js +5 -1
- package/dist/commands/sources/stash-cli.js +10 -2
- package/dist/core/asset/asset-registry.js +2 -0
- package/dist/core/asset/asset-spec.js +14 -0
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/config/config-schema.js +255 -2
- package/dist/core/config/config.js +2 -2
- package/dist/core/logs-db.js +305 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/state-db.js +706 -42
- package/dist/indexer/db/db.js +364 -38
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +203 -3
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +114 -111
- package/dist/indexer/passes/memory-inference.js +71 -25
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +15 -4
- package/dist/indexer/search/ranking-contributors.js +22 -0
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/integrations/agent/prompts.js +1 -0
- package/dist/integrations/harnesses/claude/session-log.js +27 -5
- package/dist/integrations/harnesses/opencode/session-log.js +9 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +38 -4
- 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/llm/usage-persist.js +77 -0
- package/dist/llm/usage-telemetry.js +103 -0
- package/dist/output/context.js +3 -2
- package/dist/output/html-render.js +73 -0
- package/dist/output/renderers.js +73 -1
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +17 -1
- package/dist/output/text/helpers.js +78 -1
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +1262 -591
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +485 -270
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/runner.js +99 -16
- package/dist/workflows/db.js +5 -2
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +1 -0
- package/package.json +9 -6
package/dist/output/renderers.js
CHANGED
|
@@ -468,6 +468,44 @@ const sessionMdRenderer = {
|
|
|
468
468
|
};
|
|
469
469
|
},
|
|
470
470
|
};
|
|
471
|
+
// ── 9. fact-md ───────────────────────────────────────────────────────────────
|
|
472
|
+
/**
|
|
473
|
+
* Renderer for the `fact` asset type. A fact is durable stash-level semantic
|
|
474
|
+
* knowledge (personal/team/project details, coding conventions, stash-meta).
|
|
475
|
+
* It carries `category` (personal|team|project|convention|meta) and an
|
|
476
|
+
* optional `pinned` flag marking it as part of the always-injected core. The
|
|
477
|
+
* renderer surfaces a one-liner (category + pinned marker) so an agent can tell
|
|
478
|
+
* at a glance what kind of fact it is and whether it is core context.
|
|
479
|
+
*/
|
|
480
|
+
const factMdRenderer = {
|
|
481
|
+
name: "fact-md",
|
|
482
|
+
buildShowResponse(ctx) {
|
|
483
|
+
const name = deriveName(ctx);
|
|
484
|
+
const parsed = parseFrontmatter(ctx.content());
|
|
485
|
+
const fm = parsed.data;
|
|
486
|
+
const category = asNonEmptyString(fm.category);
|
|
487
|
+
const description = asNonEmptyString(fm.description);
|
|
488
|
+
const pinned = fm.pinned === true;
|
|
489
|
+
const headerParts = [
|
|
490
|
+
category ? `category: ${category}` : undefined,
|
|
491
|
+
pinned ? "pinned (core context)" : undefined,
|
|
492
|
+
].filter((p) => !!p);
|
|
493
|
+
const action = [
|
|
494
|
+
"Durable stash fact — apply it as background context.",
|
|
495
|
+
headerParts.length > 0 ? headerParts.join(" ") : undefined,
|
|
496
|
+
]
|
|
497
|
+
.filter((p) => !!p)
|
|
498
|
+
.join("\n");
|
|
499
|
+
return {
|
|
500
|
+
type: "fact",
|
|
501
|
+
name,
|
|
502
|
+
path: ctx.absPath,
|
|
503
|
+
action,
|
|
504
|
+
description,
|
|
505
|
+
content: parsed.content,
|
|
506
|
+
};
|
|
507
|
+
},
|
|
508
|
+
};
|
|
471
509
|
function applySessionMetadata(entry, ctx) {
|
|
472
510
|
try {
|
|
473
511
|
const fm = applyFrontmatterDescriptionAndTags(entry, ctx);
|
|
@@ -501,6 +539,34 @@ function applyTocMetadata(entry, ctx) {
|
|
|
501
539
|
// Non-fatal: skip TOC if file can't be read
|
|
502
540
|
}
|
|
503
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* Fact metadata: surface `category` and the `pinned` core marker as tags +
|
|
544
|
+
* search hints (no dedicated DB columns — same encoding pattern as session /
|
|
545
|
+
* task). `pinned` is mirrored to both a `pinned` tag and a `pinned` search
|
|
546
|
+
* hint so the ranking contributor can detect it and queries can target it.
|
|
547
|
+
*/
|
|
548
|
+
function applyFactMetadata(entry, ctx) {
|
|
549
|
+
try {
|
|
550
|
+
const fm = applyFrontmatterDescriptionAndTags(entry, ctx);
|
|
551
|
+
const tags = new Set([...(entry.tags ?? []), "fact"]);
|
|
552
|
+
const hints = new Set(entry.searchHints ?? []);
|
|
553
|
+
const category = asNonEmptyString(fm.category);
|
|
554
|
+
if (category) {
|
|
555
|
+
tags.add(category);
|
|
556
|
+
hints.add(`category:${category}`);
|
|
557
|
+
}
|
|
558
|
+
if (fm.pinned === true) {
|
|
559
|
+
tags.add("pinned");
|
|
560
|
+
hints.add("pinned");
|
|
561
|
+
}
|
|
562
|
+
entry.tags = Array.from(tags).filter(Boolean);
|
|
563
|
+
if (hints.size > 0)
|
|
564
|
+
entry.searchHints = Array.from(hints).filter(Boolean);
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
// Non-fatal: skip metadata extraction on parse error
|
|
568
|
+
}
|
|
569
|
+
}
|
|
504
570
|
/**
|
|
505
571
|
* Parse frontmatter, apply description (if not already set) and merge tags
|
|
506
572
|
* into `entry`. Returns the raw frontmatter data object so callers can access
|
|
@@ -660,6 +726,11 @@ registerMetadataContributor({
|
|
|
660
726
|
appliesTo: ({ rendererName }) => rendererName === "session-md",
|
|
661
727
|
contribute: (entry, ctx) => applySessionMetadata(entry, ctx.renderContext),
|
|
662
728
|
});
|
|
729
|
+
registerMetadataContributor({
|
|
730
|
+
name: "fact-md-metadata",
|
|
731
|
+
appliesTo: ({ rendererName }) => rendererName === "fact-md",
|
|
732
|
+
contribute: (entry, ctx) => applyFactMetadata(entry, ctx.renderContext),
|
|
733
|
+
});
|
|
663
734
|
// ── Registration ─────────────────────────────────────────────────────────────
|
|
664
735
|
/** All built-in renderers. */
|
|
665
736
|
const builtinRenderers = [
|
|
@@ -676,6 +747,7 @@ const builtinRenderers = [
|
|
|
676
747
|
secretFileRenderer,
|
|
677
748
|
taskMdRenderer,
|
|
678
749
|
sessionMdRenderer,
|
|
750
|
+
factMdRenderer,
|
|
679
751
|
];
|
|
680
752
|
/**
|
|
681
753
|
* Register all built-in renderers with the file-context registry.
|
|
@@ -687,4 +759,4 @@ export function registerBuiltinRenderers() {
|
|
|
687
759
|
}
|
|
688
760
|
}
|
|
689
761
|
// ── Named exports for testing ────────────────────────────────────────────────
|
|
690
|
-
export { agentMdRenderer, commandMdRenderer, envFileRenderer, INTERPRETER_MAP, knowledgeMdRenderer, lessonMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, secretFileRenderer, skillMdRenderer, wikiMdRenderer, workflowMdRenderer, };
|
|
762
|
+
export { agentMdRenderer, commandMdRenderer, envFileRenderer, factMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, lessonMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, secretFileRenderer, skillMdRenderer, wikiMdRenderer, workflowMdRenderer, };
|
|
@@ -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);
|
|
@@ -41,7 +41,21 @@ export function shapeProposalEntry(entry, detail) {
|
|
|
41
41
|
return pickFields(entry, ["id", "ref", "status", "source", "createdAt"]);
|
|
42
42
|
}
|
|
43
43
|
if (detail === "normal") {
|
|
44
|
-
|
|
44
|
+
// `confidence` and `gateDecision` (#577) explain why a proposal is pending,
|
|
45
|
+
// so they are projected at `normal` for `akm proposal list/show` — both are
|
|
46
|
+
// optional and absent on legacy proposals.
|
|
47
|
+
return pickFields(entry, [
|
|
48
|
+
"id",
|
|
49
|
+
"ref",
|
|
50
|
+
"status",
|
|
51
|
+
"source",
|
|
52
|
+
"sourceRun",
|
|
53
|
+
"createdAt",
|
|
54
|
+
"updatedAt",
|
|
55
|
+
"confidence",
|
|
56
|
+
"gateDecision",
|
|
57
|
+
"review",
|
|
58
|
+
]);
|
|
45
59
|
}
|
|
46
60
|
// full: project everything including the payload.
|
|
47
61
|
return pickFields(entry, [
|
|
@@ -52,6 +66,8 @@ export function shapeProposalEntry(entry, detail) {
|
|
|
52
66
|
"sourceRun",
|
|
53
67
|
"createdAt",
|
|
54
68
|
"updatedAt",
|
|
69
|
+
"confidence",
|
|
70
|
+
"gateDecision",
|
|
55
71
|
"payload",
|
|
56
72
|
"review",
|
|
57
73
|
]);
|
|
@@ -235,6 +235,50 @@ export function formatProposalProducerPlain(command, r) {
|
|
|
235
235
|
const status = String(proposal.status ?? "pending");
|
|
236
236
|
return `${command}: queued proposal ${id} (${ref}) [${status}]`;
|
|
237
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Render a one-line gate-decision summary for the proposal list / show surfaces
|
|
240
|
+
* (#577), e.g. `gate=deferred:below-threshold (0.72 < 0.90)`. Returns the empty
|
|
241
|
+
* string for a missing or malformed decision so legacy proposals render cleanly.
|
|
242
|
+
*/
|
|
243
|
+
export function formatGateDecisionSummary(raw) {
|
|
244
|
+
if (typeof raw !== "object" || raw === null)
|
|
245
|
+
return "";
|
|
246
|
+
const d = raw;
|
|
247
|
+
const outcome = typeof d.outcome === "string" ? d.outcome : undefined;
|
|
248
|
+
if (!outcome)
|
|
249
|
+
return "";
|
|
250
|
+
const reason = typeof d.reason === "string" && d.reason.length > 0 ? `:${d.reason}` : "";
|
|
251
|
+
const cmp = formatGateThresholdComparison(d);
|
|
252
|
+
return `gate=${outcome}${reason}${cmp ? ` (${cmp})` : ""}`;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Reconstruct the threshold comparison the gate applied, when both sides are
|
|
256
|
+
* present (e.g. confidence 0.72 vs. autoAccept 0.90 → "0.72 < 0.90"). Returns
|
|
257
|
+
* the empty string when the decision lacks the operands.
|
|
258
|
+
*/
|
|
259
|
+
function formatGateThresholdComparison(d) {
|
|
260
|
+
const thresholds = (typeof d.thresholds === "object" && d.thresholds !== null ? d.thresholds : {});
|
|
261
|
+
const confidence = typeof d.confidence === "number" ? d.confidence : undefined;
|
|
262
|
+
const autoAccept = typeof thresholds.autoAccept === "number" ? thresholds.autoAccept : undefined;
|
|
263
|
+
if (confidence !== undefined && autoAccept !== undefined) {
|
|
264
|
+
const op = confidence >= autoAccept ? ">=" : "<";
|
|
265
|
+
return `${confidence.toFixed(2)} ${op} ${autoAccept.toFixed(2)}`;
|
|
266
|
+
}
|
|
267
|
+
// Drain bands: when the measured value is present, render the full comparison
|
|
268
|
+
// ("210 > 200" / "1 < 5"); otherwise fall back to the bound alone (#577).
|
|
269
|
+
const measured = typeof d.measured === "number" ? d.measured : undefined;
|
|
270
|
+
if (typeof thresholds.maxDiffLines === "number") {
|
|
271
|
+
return measured !== undefined
|
|
272
|
+
? `${measured} > ${thresholds.maxDiffLines}`
|
|
273
|
+
: `maxDiffLines=${thresholds.maxDiffLines}`;
|
|
274
|
+
}
|
|
275
|
+
if (typeof thresholds.minContentLines === "number") {
|
|
276
|
+
return measured !== undefined
|
|
277
|
+
? `${measured} < ${thresholds.minContentLines}`
|
|
278
|
+
: `minContentLines=${thresholds.minContentLines}`;
|
|
279
|
+
}
|
|
280
|
+
return "";
|
|
281
|
+
}
|
|
238
282
|
export function formatProposalListPlain(r) {
|
|
239
283
|
const proposals = Array.isArray(r.proposals) ? r.proposals : [];
|
|
240
284
|
const total = typeof r.totalCount === "number" ? r.totalCount : proposals.length;
|
|
@@ -248,7 +292,11 @@ export function formatProposalListPlain(r) {
|
|
|
248
292
|
const status = String(p.status ?? "?");
|
|
249
293
|
const source = String(p.source ?? "?");
|
|
250
294
|
const created = String(p.createdAt ?? "?");
|
|
251
|
-
|
|
295
|
+
// #577: surface the gate verdict inline so the queue explains itself
|
|
296
|
+
// ("deferred: below-threshold"). Legacy proposals carry no gateDecision.
|
|
297
|
+
const gate = formatGateDecisionSummary(p.gateDecision);
|
|
298
|
+
const gateSuffix = gate ? ` ${gate}` : "";
|
|
299
|
+
lines.push(`${id} [${status}] ${ref} source=${source} ${created}${gateSuffix}`);
|
|
252
300
|
}
|
|
253
301
|
return lines.join("\n").trimEnd();
|
|
254
302
|
}
|
|
@@ -265,6 +313,26 @@ export function formatProposalShowPlain(r) {
|
|
|
265
313
|
lines.push(`createdAt: ${String(p.createdAt)}`);
|
|
266
314
|
if (p.updatedAt)
|
|
267
315
|
lines.push(`updatedAt: ${String(p.updatedAt)}`);
|
|
316
|
+
if (typeof p.confidence === "number")
|
|
317
|
+
lines.push(`confidence: ${p.confidence.toFixed(2)}`);
|
|
318
|
+
// #577: gate decision (auto-accepted / deferred / auto-rejected + reason +
|
|
319
|
+
// thresholds). Absent on legacy proposals — render "unknown" so the field is
|
|
320
|
+
// always present and the operator never sees a silent gap.
|
|
321
|
+
const gate = p.gateDecision;
|
|
322
|
+
if (gate && typeof gate.outcome === "string") {
|
|
323
|
+
lines.push(`gate.decision: ${String(gate.outcome)}`);
|
|
324
|
+
lines.push(`gate.reason: ${gate.reason ? String(gate.reason) : "unknown"}`);
|
|
325
|
+
const cmp = formatGateThresholdComparison(gate);
|
|
326
|
+
if (cmp)
|
|
327
|
+
lines.push(`gate.thresholds: ${cmp}`);
|
|
328
|
+
if (gate.gate)
|
|
329
|
+
lines.push(`gate.by: ${String(gate.gate)}`);
|
|
330
|
+
if (gate.decidedAt)
|
|
331
|
+
lines.push(`gate.decidedAt: ${String(gate.decidedAt)}`);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
lines.push("gate.decision: unknown");
|
|
335
|
+
}
|
|
268
336
|
const review = p.review;
|
|
269
337
|
if (review) {
|
|
270
338
|
lines.push(`review.outcome: ${String(review.outcome ?? "?")}`);
|
|
@@ -937,6 +1005,15 @@ export function formatCuratePlain(r, detail) {
|
|
|
937
1005
|
lines.push(` run: ${String(item.run)}`);
|
|
938
1006
|
if (item.followUp)
|
|
939
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
|
+
}
|
|
940
1017
|
if (detail !== "brief" && item.reason)
|
|
941
1018
|
lines.push(` why: ${String(item.reason)}`);
|
|
942
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
|