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 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
- ![Version 0.1.3](https://img.shields.io/badge/Version-0.2.20-green.svg)
1
+ ![Version 0.1.3](https://img.shields.io/badge/Version-0.2.21-green.svg)
2
2
  ![License MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
3
3
  [![Forever Healthy](https://img.shields.io/badge/(c)_2026-Forever_Healthy-573D7D.svg)](https://forever-healthy.org)
4
4
  [![npm](https://img.shields.io/badge/npm-fh--library-cb3837.svg)](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, now = /* @__PURE__ */ new Date()) {
2147
+ function daysSince(date, now2 = /* @__PURE__ */ new Date()) {
2147
2148
  const t = new Date(date).getTime();
2148
- return Number.isFinite(t) ? (now.getTime() - t) / 864e5 : Infinity;
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: today(), reason: why };
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 now = Date.now();
2541
- while (calls.length && calls[0] <= now - WINDOW_MS) calls.shift();
2542
- if (calls.length >= PER_WINDOW) await new Promise((r) => setTimeout(r, calls[0] + WINDOW_MS - now + 200));
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, now = /* @__PURE__ */ new Date(), retryBlocked = false) {
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 = (now.getTime() - new Date(p.date).getTime()) / 864e5;
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: today(), converted_by: converter };
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: today(), reason };
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.20",
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",