fh-library 0.2.20 → 0.2.21
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 +4 -0
- package/README.md +1 -1
- package/dist/index.js +13 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# FH Library — Change Log
|
|
2
2
|
|
|
3
|
+
### v0.2.21 — 2026-11-09
|
|
4
|
+
|
|
5
|
+
* **Cooldowns count from the moment:** a failed page, a not-found ID and a site's wall are remembered with the time, not the date — a bare date counted from midnight UTC, so a page that failed at 23:53 UTC was fetched up the whole ladder again 13 minutes later.
|
|
6
|
+
|
|
3
7
|
### v0.2.20 — 2026-11-09
|
|
4
8
|
|
|
5
9
|
* **`library_verify` without the reference list:** the decision model no longer gets the paper's References / Bibliography section — other papers' titles, which state nothing this paper found and could pass a claim on a cited title; 22.7% less text per full text (96 full texts measured). The stored text and `library_read` keep it.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
2
2
|

|
|
3
3
|
[_2026-Forever_Healthy-573D7D.svg)](https://forever-healthy.org)
|
|
4
4
|
[](https://www.npmjs.com/package/fh-library)
|
package/dist/index.js
CHANGED
|
@@ -1815,6 +1815,7 @@ function emptyStatus(requested, type, id, libraryDir) {
|
|
|
1815
1815
|
};
|
|
1816
1816
|
}
|
|
1817
1817
|
var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1818
|
+
var now = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 19) + "Z";
|
|
1818
1819
|
function headLine(r) {
|
|
1819
1820
|
return [
|
|
1820
1821
|
r.authors.join(", ") || null,
|
|
@@ -2143,9 +2144,9 @@ function refreshRecord(p, m, r, xml) {
|
|
|
2143
2144
|
writeMeta(p, next);
|
|
2144
2145
|
return next;
|
|
2145
2146
|
}
|
|
2146
|
-
function daysSince(date,
|
|
2147
|
+
function daysSince(date, now2 = /* @__PURE__ */ new Date()) {
|
|
2147
2148
|
const t = new Date(date).getTime();
|
|
2148
|
-
return Number.isFinite(t) ? (
|
|
2149
|
+
return Number.isFinite(t) ? (now2.getTime() - t) / 864e5 : Infinity;
|
|
2149
2150
|
}
|
|
2150
2151
|
var FUNDING_RE = /^[*_\s]*(funding|financial support|sources? of (financial )?support|support(ed by)?|grant support|this (work|study|research) was (supported|funded))\b[^:]*:?[*_\s]*(.*)$/i;
|
|
2151
2152
|
var COI_RE = /^[*_\s]*(conflicts? of interest|competing interests?|disclosures?|financial disclosures?|declarations? of (competing |conflicting )?interests?|conflict[- ]of[- ]interest statement)\b[^:]*:?[*_\s]*(.*)$/i;
|
|
@@ -2348,14 +2349,14 @@ function recordRung(url, rung2, ok, reason = "", dir = FH_LIBRARY_DIR) {
|
|
|
2348
2349
|
if (ok) {
|
|
2349
2350
|
if (!all[rung2]) return;
|
|
2350
2351
|
delete all[rung2];
|
|
2351
|
-
} else all[rung2] = { streak: (all[rung2]?.streak ?? 0) + 1, date:
|
|
2352
|
+
} else all[rung2] = { streak: (all[rung2]?.streak ?? 0) + 1, date: now(), reason: why };
|
|
2352
2353
|
try {
|
|
2353
2354
|
mkdirSync3(join5(dir, "walls"), { recursive: true });
|
|
2354
2355
|
writeAtomic(file(dir, h), JSON.stringify(all) + "\n");
|
|
2355
2356
|
} catch {
|
|
2356
2357
|
}
|
|
2357
2358
|
}
|
|
2358
|
-
var wallNote = (r) => `${r.reason} ${r.streak}\xD7 on this host, last ${r.date}`;
|
|
2359
|
+
var wallNote = (r) => `${r.reason} ${r.streak}\xD7 on this host, last ${r.date.slice(0, 10)}`;
|
|
2359
2360
|
|
|
2360
2361
|
// src/tiers/tier.ts
|
|
2361
2362
|
var looked = (t, p, ctx) => ctx.lookups?.get(t.name) ?? t.lookup(p);
|
|
@@ -2537,9 +2538,9 @@ var MAX_WAIT_MS = 65e3;
|
|
|
2537
2538
|
var TIMEOUT_MS3 = 1e4;
|
|
2538
2539
|
var calls = [];
|
|
2539
2540
|
async function pace() {
|
|
2540
|
-
const
|
|
2541
|
-
while (calls.length && calls[0] <=
|
|
2542
|
-
if (calls.length >= PER_WINDOW) await new Promise((r) => setTimeout(r, calls[0] + WINDOW_MS -
|
|
2541
|
+
const now2 = Date.now();
|
|
2542
|
+
while (calls.length && calls[0] <= now2 - WINDOW_MS) calls.shift();
|
|
2543
|
+
if (calls.length >= PER_WINDOW) await new Promise((r) => setTimeout(r, calls[0] + WINDOW_MS - now2 + 200));
|
|
2543
2544
|
calls.push(Date.now());
|
|
2544
2545
|
}
|
|
2545
2546
|
async function coreFetch(url, key) {
|
|
@@ -3011,13 +3012,13 @@ function parseAttempt(a) {
|
|
|
3011
3012
|
return m ? { tier: m[1], reason: m[2], date: m[3] ?? null } : null;
|
|
3012
3013
|
}
|
|
3013
3014
|
var attemptLabel = (m, date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) => `${m.tier} (${m.reason}) ${date}`;
|
|
3014
|
-
function shouldSkip(attempts, tier, retryAfterDays,
|
|
3015
|
+
function shouldSkip(attempts, tier, retryAfterDays, now2 = /* @__PURE__ */ new Date(), retryBlocked = false) {
|
|
3015
3016
|
for (const a of attempts) {
|
|
3016
3017
|
const p = parseAttempt(a);
|
|
3017
3018
|
if (!p || p.tier !== tier) continue;
|
|
3018
3019
|
if (isPermanent(p.reason, retryBlocked)) return true;
|
|
3019
3020
|
if (!p.date) return false;
|
|
3020
|
-
const age = (
|
|
3021
|
+
const age = (now2.getTime() - new Date(p.date).getTime()) / 864e5;
|
|
3021
3022
|
return age < retryAfterDays;
|
|
3022
3023
|
}
|
|
3023
3024
|
return false;
|
|
@@ -4001,7 +4002,7 @@ async function libraryFetch(libraryDir, requested, args, converter, logs = {}, f
|
|
|
4001
4002
|
if (!run) {
|
|
4002
4003
|
run = (async () => {
|
|
4003
4004
|
const r = await fetchImpl(url, logs.onTry, logs.onAlert);
|
|
4004
|
-
const base = { url, fetched:
|
|
4005
|
+
const base = { url, fetched: now(), converted_by: converter };
|
|
4005
4006
|
if (!r.ok) {
|
|
4006
4007
|
const error = r.first && /^HTTP \d{3}/.test(r.first) ? r.first : r.reason;
|
|
4007
4008
|
writeUrlMeta(p, { ...base, final_url: r.url ?? url, title: null, format: "failed", content_type: "", level: "title", retrieved_by: r.via, error, files: [`${p.entry}-meta.md`] });
|
|
@@ -4494,14 +4495,14 @@ function freshMissing(p, retryDays) {
|
|
|
4494
4495
|
return m && daysSince(m.checked) < retryDays ? m : null;
|
|
4495
4496
|
}
|
|
4496
4497
|
function rememberMissing(p, reason) {
|
|
4497
|
-
const m = { checked:
|
|
4498
|
+
const m = { checked: now(), reason };
|
|
4498
4499
|
mkdirSync10(p.entryDir, { recursive: true });
|
|
4499
4500
|
writeAtomic(missingFile(p), `checked: ${m.checked}
|
|
4500
4501
|
reason: ${m.reason}
|
|
4501
4502
|
`);
|
|
4502
4503
|
return m;
|
|
4503
4504
|
}
|
|
4504
|
-
var missingNote = (m, retryDays) => `${m.reason} (checked ${m.checked}; asked again after ${retryDays} day${retryDays === 1 ? "" : "s"})`;
|
|
4505
|
+
var missingNote = (m, retryDays) => `${m.reason} (checked ${m.checked.slice(0, 10)}; asked again after ${retryDays} day${retryDays === 1 ? "" : "s"})`;
|
|
4505
4506
|
|
|
4506
4507
|
// src/pubmedbatch.ts
|
|
4507
4508
|
var MAX_PMIDS = 200;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fh-library",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.21",
|
|
4
4
|
"mcpName": "io.github.forever-healthy/fh-library",
|
|
5
5
|
"description": "MCP Server that builds a local Markdown library of the scientific papers cited by evidence reviews — fetches metadata, abstract and full text per PubMed ID through a configurable cascade of retrieval services",
|
|
6
6
|
"type": "module",
|