Öffentliches Register
Software-GesundheitsberichtSchema 0.27.0 · Metriken 1.13.0 · 2026-07-23 08:35 UTC

joaquimserafim / openjsxl

Fast, zero-dependency, TypeScript-first Excel (.xlsx) reader for Node, Deno, Bun, and the browser.

TypeScriptMIT★ 1 Stern⑂ 0 Forksseit Juni 2026Auf GitHub ansehen ↗

joaquimserafim/openjsxl erreicht einen Gesundheitsindex von 53 von 100 und liegt damit im Bereich Mittel. Am stärksten schneidet es bei Engineering Quality (84/100) ab, am schwächsten bei Community & Adoption (33/100). Zuletzt vor 5 Tagen aktualisiert. Ein einzelner Mitwirkender trägt den Großteil der jüngsten Arbeit.

53
gesamt / 100
Mittel

Software-Gesundheitsindex

Metriken werden auf einer Skala von 1–100 in gewichtete Kategorien gruppiert. Der Gesamtwert beginnt als ihr Mittel; sobald öffentliche Evidenz die Richtlinie für Hochrisikojurisdiktionen auslöst, wird die Bewertung angepasst und erhält die Obergrenze 49 (Gefährdet). AI Readiness liegt außerhalb.

53
Exzellent85-100Vorbildlich; erfüllt im Wesentlichen alle geprüften Kriterien
Gut70-84Gesund; geringfügige Lücken
Mittel50-69Akzeptabel mit deutlichen Lücken; Überprüfung empfohlen
Gefährdet30-49Erhebliche Schwächen; eine Übernahme erfordert Vorsicht
Kritisch1-29Schwerwiegende Probleme (aufgegeben, nur ein Maintainer, keine Hygiene)
VitalitätCommunity &VerbreitungNachhaltigkeit &GovernanceEngineering-QualitätSicherheitAI Readiness

Bewertungsprofil

Jede Achse ist eine Kategorie. Die Form zählt mehr als der Durchschnitt — ein gesundes Projekt füllt die gesamte Fläche, während ein Profil aus Spitzen und Kratern bedeutet, dass Stärke in einer Dimension Risiken in einer anderen verdeckt.

Eigentümerschaft

Joaquim SerafimPersönliches Konto
86 Follower233 öffentliche Reposseit Okt. 2012

Dieses Repository gehört einem persönlichen Konto. Ein Projekt mit nur einem Eigentümer trägt ein höheres Kontinuitätsrisiko als ein organisationsgetragenes.

Paket-Ökosysteme

RegistryPaketVersionDownloads / MonatVersionenZuletzt veröffentlichtTags
npm@openjsxl/core1.0.02.13813vor 5 Tagenxlsxexcelspreadsheetooxmlreaderzipdeflate
npmopenjsxl1.0.02.02513vor 5 Tagenxlsxexcelspreadsheetooxmlreaderwriteropenpyxlcalamine

Metriken nach Kategorie

Vitalität

Lebt das Projekt — wird Code geschrieben und werden Releases ausgeliefert?

69Mittel · 22 % des Gesamtindex
Wie die Bewertung erfolgt
36/36Push-Aktualität — letzter Push vor 5 Tagen
2.8/36Commit-Rhythmus — 4/52 Wochen mit Commits
18/18Commit-Volumen — 129 Commits im letzten Jahr
0/10OpenSSF Scorecard: Maintained — project was created within the last 90 days. Please review its contents carefully
Verwendete Eingangsdaten
commits_last_year129
human_commit_share1
days_since_last_push5
active_weeks_last_year4
Wie die Bewertung erfolgt
16.2/27Liefert Releases aus — 11 Versions-Tags (keine GitHub-Releases)
36/36Release-Aktualität — letztes Release vor 5 Tagen
27/27Release-Rhythmus — ein Release etwa alle 1,8 Tage
0/10OpenSSF Scorecard: Signed-Releases — keine Daten
Verwendete Eingangsdaten
releases_count11
latest_release_tagv1.0.0
releases_from_tagsja
days_since_latest_release5
mean_days_between_releases1,8
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): OpenSSF Scorecard: Signed-Releases. Die verbleibenden Gewichte wurden renormalisiert.

Community & Verbreitung

Hat das Projekt Nutzer, Downloads, Aufmerksamkeit und ein einladendes Umfeld für Beitragende?

33Gefährdet · 18 % des Gesamtindex
Wie die Bewertung erfolgt
0/60Stars — 1 Stars
0/25Forks — 0 Forks
0/15Watcher — 0 Watcher
Verwendete Eingangsdaten
forks0
stars1
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Wie die Bewertung erfolgt
22.5/22.5README
22.5/22.5Lizenz — anerkannte Lizenz (MIT)
0/18CONTRIBUTING-Leitfaden
0/13.5Verhaltenskodex
0/7.2Issue-Vorlage
0/6.3PR-Vorlage
Verwendete Eingangsdaten
has_readmeja
has_licenseja
has_contributingnein
has_issue_templatenein
has_code_of_conductnein
has_pull_request_templatenein
Wie die Bewertung erfolgt
48.3/80Downloads pro Monat — 4.163 Downloads/Monat über npm
0/20Abhängige in der Registry — von diesem Ökosystem nicht ausgewiesen
Verwendete Eingangsdaten
packages@openjsxl/core, openjsxl
dependents
ecosystemsnpm
total_downloads
monthly_downloads4.163
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): Abhängige in der Registry. Die verbleibenden Gewichte wurden renormalisiert.

Nachhaltigkeit & Governance

Überdauert das Projekt die Menschen, die es tragen — Bus-Faktor, Reaktionsfähigkeit, Trägerschaft und Paketpflege?

38Gefährdet · 24 % des Gesamtindex
Wie die Bewertung erfolgt
9/54Bus-Faktor — 1 Beitragende decken die Hälfte aller Commits ab
0/22.5Commit-Verteilung — wichtigste beitragende Person verfasste 100 % der Commits
1.4/13.5Breite der Beitragenden — 1 Beitragende
0/10OpenSSF Scorecard: Contributors — project has 0 contributing companies or organizations -- score normalized to 0
Verwendete Eingangsdaten
bus_factor1
contributors_sampled1
top_contributor_share1
Wie die Bewertung erfolgt
0/46.8Issue-Lösungsquote — keine Issues oder keine Daten
0/38.3PR-Annahme — keine entschiedenen Pull Requests oder keine Daten
0/15OpenSSF Scorecard: Code-Review — Found 0/30 approved changesets -- score normalized to 0
Verwendete Eingangsdaten
merged_prs0
open_issues0
closed_issues0
issue_closed_ratio
closed_unmerged_prs0
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): Issue-Lösungsquote, PR-Annahme. Die verbleibenden Gewichte wurden renormalisiert.
Wie die Bewertung erfolgt
10/30Organisatorische Trägerschaft — persönliches (Nutzer-)Konto
0/20Verifizierte Domain — für Nutzerkonten nicht anwendbar
13.9/25Reichweite des Inhabers — 86 Follower von joaquimserafim
25/25Kontohistorie — 233 öffentliche Repos, Kontoalter ca. 13 Jahre
Verwendete Eingangsdaten
followers86
owner_typeUser
is_verified
owner_loginjoaquimserafim
public_repos233
account_age_days5.036
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): Verifizierte Domain. Die verbleibenden Gewichte wurden renormalisiert.

Paketpflege

100Exzellent
Wie die Bewertung erfolgt
25/25Veröffentlicht & auflösbar — 2 Paket(e) auf npm
35/35Veröffentlichungsaktualität — letzte Veröffentlichung vor 5 Tagen
20/20Versionshistorie — 13 veröffentlichte Versionen
20/20Nicht veraltet — aktiv, nicht veraltet oder zurückgezogen
Verwendete Eingangsdaten
packages@openjsxl/core, openjsxl
ecosystemsnpm
any_deprecatednein
min_days_since_publish5

Engineering-Qualität

Sind grundlegende Engineering- und Dokumentationspraktiken vorhanden?

84Gut · 20 % des Gesamtindex
Wie die Bewertung erfolgt
24/24CI-Workflows — 1 Workflow(s)
24/24Tests vorhanden
16/16Linter-Konfiguration — biome.json
0/9.6Pre-Commit-Hooks
0/6.4.editorconfig
0/20OpenSSF Scorecard: CI-Tests — keine Daten
Verwendete Eingangsdaten
has_cija
has_testsja
has_editorconfignein
has_linter_configja
has_precommit_confignein
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): OpenSSF Scorecard: CI-Tests. Die verbleibenden Gewichte wurden renormalisiert.

Dokumentation

90Exzellent
Wie die Bewertung erfolgt
30/30README
25/25Dokumentationsverzeichnis
15/15Dokumentations-/Homepage-Site — https://www.npmjs.com/package/openjsxl
10/10Repository-Beschreibung
10/10Topics — 14 Topics
0/10Wiki
Verwendete Eingangsdaten
topicsbrowser, bun, deno, esm, excel, javascript, nodejs, ooxml, spreadsheet, spreadsheet-parser, typescript, xlsx, xlsx-parser, zero-dependency
has_wikinein
homepagehttps://www.npmjs.com/package/openjsxl
has_readmeja
has_docs_dirja
has_descriptionja

Sicherheit

Sind die sichtbaren Sicherheits- und Lieferkettenpraktiken belastbar, ohne ungeklärte Exposition gegenüber Hochrisikojurisdiktionen?

36Gefährdet · 16 % des Gesamtindex

Sicherheitslage

36Gefährdet
Wie die Bewertung erfolgt
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
0/2.5CI-Tests — keine Daten
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/30 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
5/5Fuzzing — project is fuzzed
2.5/2.5Lizenz — license file detected
0/7.5Maintained — project was created within the last 90 days. Please review its contents carefully
0/5Packaging — keine Daten
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
0/5SAST — no SAST tool detected
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — keine Daten
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
7.5/7.5Vulnerabilities — 0 existing vulnerabilities detected
Verwendete Eingangsdaten
sourceopenssf_scorecard
checks_evaluated15
scorecard_versionv5.5.0
checks_inconclusive3
scorecard_aggregate3,6
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): ci_tests, packaging, signed_releases. Die verbleibenden Gewichte wurden renormalisiert.

AI Readiness

Wie gut ist das Repository dafür ausgestattet, mit KI-Coding-Agenten entwickelt und gepflegt zu werden? Ein unabhängiges, experimentelles Badge — Gewicht 0,0, es wird eigenständig ausgewiesen und verändert den Gesamt-Gesundheitswert nicht.

55Mittel · 0 % des Gesamtindex
Wie die Bewertung erfolgt
0/45Agentenanweisungen — keine CLAUDE.md / AGENTS.md / Editor-Regeln
0/15Maschinenlesbare Doku (llms.txt)
40/40Lesbare Commit-Historie — 99 von 100 menschlichen Commits benennen ihre Absicht (strukturierter Betreff oder erläuternder Text)
Verwendete Eingangsdaten
has_llms_txtnein
legible_history_share0,99
agent_instruction_files
agent_instruction_max_bytes
Wie die Bewertung erfolgt
0/18Bootstrap mit einem Befehl
22/22Automatisierte Tests
11/11Lint-/Format-Konfiguration — biome.json
11/11Statische Typprüfung — packages/core/tsconfig.json, packages/openjsxl/tsconfig.json, tsconfig.json
10/10Reproduzierbare Umgebung — lockfile
0/10Belegte Agentenpraxis — keine von Agenten verfassten Commits unter den letzten 100
0/8Automatisierte Wartung — keine automatisierten Abhängigkeits-Updates beobachtet
0/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
Verwendete Eingangsdaten
has_nixnein
has_testsja
lockfilespnpm-lock.yaml
has_dockerfilenein
typed_languageja
bootstrap_files
has_devcontainernein
has_linter_configja
typecheck_configspackages/core/tsconfig.json, packages/openjsxl/tsconfig.json, tsconfig.json
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
Wie die Bewertung erfolgt
45/45Typprüfbarer Code — TypeScript (statisch typisiert)
54.7/55Handhabbare Dateigrößen — 1/209 Quelldateien über 60 KB
Verwendete Eingangsdaten
primary_languageTypeScript
largest_source_bytes102.140
source_files_sampled209
oversized_source_files1
Wie die Bewertung erfolgt
0/40API-Schema (OpenAPI/GraphQL/proto)
0/20MCP-Server
40/40Lauffähige Beispiele — examples
Verwendete Eingangsdaten
example_dirsexamples
has_mcp_signalnein
api_schema_files

Eckdaten

1GitHub-Sterne
1Mitwirkende
129Commits, letzte 12 Monate
5Tage seit letztem Push
11Releases
1Bus-Faktor
0offene Issues
npmPaket-Ökosysteme

Warnungen zur Datenerhebung

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository
  • deps.dev does not index npm:openjsxl@1.0.0; advisories assessed against the repository dependency graph instead

Weitere Details

OpenSSF Scorecard 3.6 / 10
3.6Gesamtwert

Unabhängige, werkzeugneutrale Sicherheitsbewertung durch das quelloffene OpenSSF Scorecard. Jede Prüfung honoriert eine Sicherheits-Praxis, nicht das Werkzeug eines bestimmten Anbieters. Prüfungen, die Scorecard nicht ermitteln konnte, sind mit k. A. markiert und vom Sicherheitswert ausgeschlossen (nie als null gezählt).Scorecard v5.5.0 · 2026-07-23 08:35 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
k. A.CI-Testsno pull request found
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/30 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
10Fuzzingproject is fuzzed
10Licenselicense file detected
0Maintainedproject was created within the last 90 days. Please review its contents carefully
k. A.Packagingpackaging workflow not detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
0SASTno SAST tool detected
0Security-Policysecurity policy file not detected
k. A.Signed-Releasesno releases found
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
10Vulnerabilities0 existing vulnerabilities detected
Direkte Abhängigkeiten 2
RegistryPaketVersionsvorgabeManifest
npmopenjsxlworkspace:*packages/bench/package.json
npm@openjsxl/coreworkspace:*packages/openjsxl/package.json
Alle Abhängigkeiten nicht erhoben

Der aufgelöste Abhängigkeitssatz konnte für diesen Bericht nicht erhoben werden: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

JSON-Rohbericht maschinenlesbar
{
  "data": {
    "repo": {
      "topics": [
        "browser",
        "bun",
        "deno",
        "esm",
        "excel",
        "javascript",
        "nodejs",
        "ooxml",
        "spreadsheet",
        "spreadsheet-parser",
        "typescript",
        "xlsx",
        "xlsx-parser",
        "zero-dependency"
      ],
      "is_fork": false,
      "size_kb": 3157,
      "has_wiki": false,
      "homepage": "https://www.npmjs.com/package/openjsxl",
      "languages": {
        "Python": 10291,
        "JavaScript": 95720,
        "TypeScript": 1347089
      },
      "pushed_at": "2026-07-17T23:54:51Z",
      "created_at": "2026-06-29T22:42:04Z",
      "owner_type": "User",
      "updated_at": "2026-07-17T23:15:10Z",
      "description": "Fast, zero-dependency, TypeScript-first Excel (.xlsx) reader for Node, Deno, Bun, and the browser.",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "main",
      "license_spdx_raw": "MIT",
      "primary_language": "TypeScript",
      "significant_languages": [
        "TypeScript"
      ]
    },
    "owner": {
      "blog": "https://joaquimserafim.com",
      "name": "Joaquim Serafim",
      "type": "User",
      "login": "joaquimserafim",
      "company": null,
      "location": "London",
      "followers": 86,
      "avatar_url": "https://avatars.githubusercontent.com/u/2507889?v=4",
      "created_at": "2012-10-08T00:30:30Z",
      "is_verified": null,
      "public_repos": 233,
      "account_age_days": 5036
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v1.0.0",
          "kind": "major",
          "published_at": "2026-07-17T22:58:12Z"
        },
        {
          "tag": "v0.9.1",
          "kind": "patch",
          "published_at": "2026-07-14T21:09:49Z"
        },
        {
          "tag": "v0.8.0",
          "kind": "minor",
          "published_at": "2026-07-12T20:28:28Z"
        },
        {
          "tag": "v0.7.0",
          "kind": "minor",
          "published_at": "2026-07-11T11:21:24Z"
        },
        {
          "tag": "v0.6.0",
          "kind": "minor",
          "published_at": "2026-07-04T13:02:50Z"
        },
        {
          "tag": "v0.5.1",
          "kind": "patch",
          "published_at": "2026-07-03T16:37:15Z"
        },
        {
          "tag": "v0.5.0",
          "kind": "minor",
          "published_at": "2026-07-03T15:40:00Z"
        },
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2026-07-02T15:14:20Z"
        },
        {
          "tag": "v0.3.0",
          "kind": "minor",
          "published_at": "2026-07-02T00:12:13Z"
        },
        {
          "tag": "v0.2.1",
          "kind": "patch",
          "published_at": "2026-07-01T08:03:51Z"
        },
        {
          "tag": "v0.2.0",
          "kind": "minor",
          "published_at": "2026-07-01T07:28:06Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "8ec0796fd4217f4c0802f1acb4d0633656814788",
          "body": null,
          "is_bot": false,
          "headline": "docs: size table → published 1.0.0 (0.52 MB)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-17T22:58:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b0f1303cd02304bb4e453783d4007215f6ca2b50",
          "body": "The stable 1.0 API. Lock-step version bump across the monorepo root,\n`@openjsxl/core`, and `openjsxl` (fixtures stays private).\n\n- Keep ROADMAP.md and IMPLEMENTATION.md local (untracked + gitignored); add\n  ARCHITECTURE.md — a short public overview of the internals and approach.\n- README: status → 1\n[…]\nan at 1.0.0 with the\nfacade's core dep rewritten from workspace:*; import and require both resolve the main\nentry and /formula on Node 24. Emitted .xlsx bytes unchanged (source diff is comments only).",
          "is_bot": false,
          "headline": "release: v1.0.0",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-17T22:48:57Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b4ec72e04696898ba67ca3271273c17bf803dd6b",
          "body": null,
          "is_bot": false,
          "headline": "docs: refresh benchmarks on the M10 build (2026-07-17)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-17T22:33:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a3716bc93981957c382242b9308c33d8970c2c3c",
          "body": null,
          "is_bot": false,
          "headline": "docs: F10.7 — README as canonical API reference",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-17T22:09:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d2b45d30dcde874ae83f13390a3f52376105cc56",
          "body": null,
          "is_bot": false,
          "headline": "docs: mark F10.6 complete in tracker",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-17T21:43:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b2084e9758fc2eaf0b672944628507f34aca0111",
          "body": "…e stance, hygiene)\n\nPre-1.0 freeze of the public surface so the 1.0 contract is intentional.\n\n- a1 helpers (columnToIndex/indexToColumn/parseRef/formatRef) now throw\n  XlsxError('invalid-input') instead of bare Error — the last untyped public error\n  contract; a1 tests tightened to assert the typed\n[…]\nync\n\nGates green: biome 0, tsc 0, vitest 1049 passed / 2 skipped. Byte-identity held —\nwriter changes are confined to error/rejection paths and a never-emitted dedup key;\nthe in-tree golden pins pass.",
          "is_bot": false,
          "headline": "feat(core): F10.6 — API freeze (typed errors, M10 type exports, modul…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-17T21:37:58Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3750ca027bd3136eff1fc4728ff85e260295937a",
          "body": "- record the 2026-07-16 five-lens adversarial review in §F10.6 context\n  (23 confirmed / 8 refuted / 0 unverified)\n- new tasks: export the eight M10 model types + export-surface pin test;\n  ESM stance decision (default condition vs documented ESM-only) +\n  ./package.json subpath; freeze-hygiene mino\n[…]\nint32 ceiling, autoFilter\n  backwards-range policy, workbook-level corpus carries, awaited rejects\n  assert, openjsxl/formula test consumer)\n- acceptance extended accordingly; F10.7 example-14 true-up",
          "is_bot": false,
          "headline": "docs: extend F10.6 scope with v1-readiness review findings",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-16T23:43:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "20faae1aaef2350aa786abb30f2335dffbff4d25",
          "body": "…nabled)\n\nMake every remaining round-trip drop a documented contract line, and surface the\nlast silent one: an .xlsm opens and reads, but rewriting writes a plain .xlsx\nwithout its VBA macros. Workbook.macroEnabled (sniffed from the workbook content\ntype) flags a macro-enabled source so a caller can\n[…]\npins: crafted-macro-enabled.xlsm (macroEnabled true; rewrite drops\n  macros, data survives) and openpyxl-dropped-features.xlsx (outline/tabColor/\n  docProps read clean + round-trip, never bare-throw).",
          "is_bot": false,
          "headline": "feat(core): F10.5 — fidelity truth-up + .xlsm policy (Workbook.macroE…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-16T21:40:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9dd73f410b943e09da25d454c38036bfb9f80af1",
          "body": "…header/footer)\n\nRead, write, and carry the four worksheet print elements so a report's page\nlayout round-trips instead of silently reverting to defaults. Worksheet and\nSheetInput carry pageMargins, pageSetup, printOptions, and headerFooter.\n\n- Model: PageMargins/PageSetup/PrintOptions/HeaderFooter \n[…]\n\n\nByte-identity holds (no-print-setup input, incl. styled cells -> pre-F10.4 bytes);\nopenpyxl cross-validates both directions; adversarial review found zero defects;\nfixture openpyxl-print-setup.xlsx.",
          "is_bot": false,
          "headline": "feat(core): F10.4 — print setup (margins, page setup, print options, …",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-16T21:03:43Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e7e97f2590937c37d9c09078cd089aa52eef4005",
          "body": "…dden)\n\nRead, write, and carry protection so a protected template round-trips still\nprotected instead of silently re-saving unprotected. Worksheet.protection and\nWorkbook.protection carry <sheetProtection>/<workbookProtection>; CellStyle.protection\ncarries per-cell locked/hidden via the xf <protecti\n[…]\nll protection rides the style pass-through.\n\nByte-identity holds (unprotected input, incl. styled cells -> pre-F10.3 bytes); openpyxl\ncross-validates both directions; fixture openpyxl-protection.xlsx.",
          "is_bot": false,
          "headline": "feat(core): F10.3 — protection carry (workbook, sheet, cell locked/hi…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-16T19:31:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e13c6a7490b534958c1e2a456ebbf1b9a166fbb8",
          "body": "Read and write a sheet's autoFilter range (filter dropdowns), so a filtered\nworkbook round-trips instead of silently losing its filter. SheetInput.autoFilter\nand Worksheet.autoFilter carry { ref }; the paired hidden _xlnm._FilterDatabase\nname Excel expects is synthesized on write and stripped on rea\n[…]\nria and sort state are a documented drop.\n\nByte-identity holds (unfiltered input -> pre-F10.2 bytes); openpyxl cross-validates\nboth directions; fixture openpyxl-autofilter.xlsx pins the criteria drop.",
          "is_bot": false,
          "headline": "feat(core): F10.2 — sheet-level autoFilter read + write + bridge",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-16T16:38:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3593c7570343bc3faa3653ecb258de407fc7b1fa",
          "body": "Emit <definedNames> and carry Workbook.definedNames through workbookToInput,\nso a named-range workbook round-trips instead of losing its names (formulas\nrecalculating rather than #NAME?). Global and sheet-scoped names, constants,\nhidden names, and _xlnm.* built-ins are supported on both writers.\n\n- \n[…]\nocated to writer/xml.ts).\n\nAdversarial review: sheet-count mismatch + @name ST_Xstring fixed, pinned.\nByte-identity holds (no-names input -> pre-F10.1 bytes); openpyxl cross-validates\nboth directions.",
          "is_bot": false,
          "headline": "feat(core): F10.1 — defined names write + bridge carry",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-15T12:27:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0a612ad9df62e5f11bd017c2a06152a0d5a0bc9e",
          "body": "Eight features from the 2026-07-14 pre-1.0 review: defined-names write+carry,\nsheet autoFilter, protection carry, print setup, fidelity truth-up + .xlsm\npolicy, API freeze pass, README as canonical docs, 1.0 release. Element-order\nslots, shared-bounds posture, and per-feature oracles pinned in the milestone\ndecisions; deferrals moved to a durable M11+ outline. ROADMAP 1.0 row reworded\n(documented round-trip contract; docs site post-1.0).",
          "is_bot": false,
          "headline": "docs: scope M10 — Stable (v1.0) into the tracker",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-14T21:46:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cbb7e794eac656b20ac5c53555300a54559082e6",
          "body": "Bump root + @openjsxl/core + openjsxl to 0.9.1 (facade→core stays\nworkspace:*, rewritten on publish). 0.9.1 ships the F9.6/F9.7 hardening\nthat landed after the published 0.9.0: ST_Xstring escaping, Unicode table\nnames, MAX_CF_FORMULAS, default decompression-bomb guards, O(n) streaming\ntokenizer, and\n[…]\non the string-ceiling. No API change;\n.xlsx output for existing input stays byte-identical.\n\nPUBLISHING changelog split: 0.9.0 reverted to what npm actually shipped,\nnew 0.9.1 entry for the hardening.",
          "is_bot": false,
          "headline": "release: 0.9.1",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-14T21:09:49Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7683e3a6fe4b107ca9d8d4cea87c3501b90efbc3",
          "body": "… hardening)\n\n- benchmarks: re-run the full matrix on the M2 Pro (2026-07-14); numbers\n  within noise of the prior run (F9.7 didn't regress — write byte-identical,\n  normal read unaffected)\n- size: sizes.mjs points at the published `openjsxl` (latest) with version\n  resolution — every library measur\n[…]\nEADME: document the default decompression-bomb guards and\n  the maxPartBytes / maxCompressionRatio knobs\n- ROADMAP: mark 0.9 done; PUBLISHING: extend the 0.9.0 changelog with the\n  F9.6/F9.7 hardening",
          "is_bot": false,
          "headline": "docs: true up READMEs/roadmap/benchmarks/size for 0.9 (tables/DV/CF +…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-14T21:04:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d9157f1ff0b1d48730f4734bed7a443cce23ab1f",
          "body": "…ty, typed string-ceiling\n\nPost-commit review of 477b2bb found five defects, all fixed:\n- xml/stream.ts: a 1-char terminator (`</…>`, `<!…>`) kept the whole\n  resume window (slice(-0)) and re-searched it each push — the O(n²) the\n  F9.7 rewrite claimed to remove; tail is now empty for a 1-char termi\n[…]\noo-large\n\nRe-verified: differential fuzz streamed==one-shot 0 divergences over 40k\nstrings + curated; the declaration case is linear again; both string-ceiling\ncrashes are typed. Tests added for each.",
          "is_bot": false,
          "headline": "fix(core): F9.7 review follow-ups — decl O(n²), stream/tokenizer pari…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-14T20:22:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "477b2bbbe2c9c84034c92179eb886d9590847092",
          "body": "…b defaults)\n\n- xml/stream.ts: rewrite createXmlStream to hold an unfinished construct as\n  chunk pieces and search only the newest chunk — linear total work (was\n  O(n²) re-scanning the whole buffer each push); scanTag is now a faithful\n  tokenizer-mirroring phase machine, so streamed tokens match \n[…]\nemaining findings are LOW, by-design trade-offs of the chosen guard numbers\n(ratio margin ~9x vs measured real-file ratios; the absolute cap on a >2 GiB\nsingle streamed sheet is documented + opt-out).",
          "is_bot": false,
          "headline": "feat(core): F9.7 — hostile-input hardening (streaming O(n²) + zip-bom…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-14T18:12:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a8ae973c5881716b67c812f2a5568b126c5e1dca",
          "body": "…la cap, docs true-up\n\n- table names: legality rule accepts Unicode letters (\\p{L}), so non-English\n  Excel locales' default names (Таблица1) read verbatim and round-trip;\n  fixture openpyxl-table-unicode.xlsx pins it\n- ST_Xstring: new ooxml/xstring.ts codec; reader decodes _xHHHH_ in string\n  conte\n[…]\n5 cross-validated both directions; byte-identity vs the\npre-F9.6 writer verified on 9 canonical inputs (CR-bearing strings are the\none disclosed deviation — their old raw-CR bytes were already lossy).",
          "is_bot": false,
          "headline": "feat(core): F9.6 — Unicode table names, ST_Xstring escaping, CF formu…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-14T15:43:02Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "546082cb946253e8465fb2742a80dcad2ef1f574",
          "body": "…g) to the round-trip fidelity table",
          "is_bot": false,
          "headline": "docs: add M9 features (tables, data validation, conditional formattin…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T21:14:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "24be7d0616909c2e0159723b51bdef454024a93b",
          "body": "Tables, data validation & conditional formatting (M9) — read + write +\nround-trip. Bump openjsxl + @openjsxl/core (+ root) to 0.9.0.\n\n- READMEs: document tables/dataValidations/conditionalFormatting on the\n  read + write + round-trip paths; correct install size ~0.3 → ~0.45 MB.\n- examples/13-tables-\n[…]\n1, vitest\n  3.2.7, @types/node 24.13.3, typescript 5.9.3); fix an unsafe optional\n  chaining in xlsb.test.ts that biome 2.5.3 flagged. fast-check stays at\n  3.23.2 (4.x is a breaking major, deferred).",
          "is_bot": false,
          "headline": "release: 0.9.0",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T21:07:20Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "320019ebd08267989f6480a0d1f458c756a68503",
          "body": null,
          "is_bot": false,
          "headline": "docs: check off F9.5 tasks",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T20:37:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cbf3df960b57f09776bb98ca64efb3bce1c19505",
          "body": "…gal (F9.5)\n\nTables now obey the reader-degrades/writer-accepts invariant (like data\nvalidation and conditional formatting): a foreign table with an odd name\nopens AND re-saves instead of aborting.\n\n- ooxml/table.ts: tableNameProblem (single-sourced legality rule),\n  normalizeTableName (repair an il\n[…]\nys. Adversarial review: normalizeTableName fuzzed\n500k hostile strings (always-legal + idempotent). Residuals (non-canonical\nref, mismatch+non-text-header) documented — all fail typed, synthetic-only.",
          "is_bot": false,
          "headline": "feat(core): table round-trip hardening — reader degrades to writer-le…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T20:32:57Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3c18bd649f3ceab6d0b273dfd208274a64f6236a",
          "body": null,
          "is_bot": false,
          "headline": "docs: scope F9.5 table round-trip hardening (shared bounds)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T18:50:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "46da9654836817e0da925a01158f9778d689bbf1",
          "body": "New private @openjsxl/fuzz (fast-check only devDep; core stays zero-dep):\n- Half A: fast-check writer properties (resolve-or-typed-error, valid\n  round-trip, write-twice determinism, scalar round-trip) over a valid\n  and a hostile corpus.\n- Half B: seeded xorshift byte/zip/XML mutation engine replay\n[…]\nith a typed invalid-input error (requireWorkbookObject) instead of a\nraw TypeError - found by the fuzzer, pinned in workbook/stream tests.\n\n~100k mutants/seed over 39 fixtures x 5 openers: 0 crashers.",
          "is_bot": false,
          "headline": "feat(fuzz): property + mutation fuzzing harness, M9 hardening (F9.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T18:11:42Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "40fb90c11f1a7d241a7b150ab379aaf61ef4998a",
          "body": null,
          "is_bot": false,
          "headline": "feat(core): table dxf highlights — F9.3 retrofit onto tables",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T15:49:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5e07f0425206c3b19ee9ff019719d6a4189d9051",
          "body": null,
          "is_bot": false,
          "headline": "feat(core): conditional formatting + dxfs — read + write + bridge (F9.3)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T13:53:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4f1d99f7fe8f82e5ca5dd98a078cb38b64776f60",
          "body": null,
          "is_bot": false,
          "headline": "feat(core): data validation — read + write + bridge (F9.2)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T11:37:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "29ec2b7706f251708bf787b93fdb5d6d04b83c12",
          "body": null,
          "is_bot": false,
          "headline": "docs: mark F9.1 real-Excel repair-prompt check confirmed",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-13T09:30:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c4d191b854d63dae7bf51892622773e2dd4cd5d0",
          "body": "First M9 feature. A defined table (xl/tables/tableN.xml) round-trips: read into\nWorksheet.tables, written from SheetInput.tables, carried by the bridge.\n\n- Reader: ooxml/table.ts parseTable (tolerant) + TableInfo/TableColumn/\n  TableStyleInfo; Worksheet.tables reads the sheet's table parts via the r\n[…]\n bare-throwing / emitting a reversed\nautoFilter, and autoFilter is omitted for header-less tables (Excel never emits\nit); plus a proactive single-read TOCTOU close.\n\nGate: biome 0 / tsc 0 / 803 tests.",
          "is_bot": false,
          "headline": "feat(core): tables — read + write + bridge (F9.1)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-12T23:12:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d10d2e6ac156ef1449dd08e2adaad2d1ad52974d",
          "body": "The opt-in formula chunk (dist/formula.js 87 KB + formula.d.ts 14 KB) grows the\nunpacked install footprint to ~341 KB (openjsxl + @openjsxl/core), up from the\nold 0.2 MB claim. Update the root README size table and the facade README.\ndocs/benchmarks.md's size row is bench-generated (pins 0.6.0) - refresh it with\n`pnpm sizes` once 0.8.0 is on npm.",
          "is_bot": false,
          "headline": "docs: update install-size claim to ~0.3 MB for 0.8.0",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-12T20:37:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f85bb6b605329fba6d1ac43ad62ddb86f9507195",
          "body": "Formulas. An opt-in, zero-dependency evaluation engine behind the new\nopenjsxl/formula entry: parseFormula (typed AST), evaluateWorkbook/evaluateCell\n(pull-based, cycle-safe, O(1) native stack on deep chains), ~97 built-in\nfunctions (SUM/IF/VLOOKUP/INDEX/MATCH/SUMIF(S)/text/date/...) plus caller UDF\n[…]\nROADMAP milestones 0.7 + 0.8 marked done. Preflight green\n(build/typecheck/biome/782 tests); tarball dry-run verified (dist ships\ndist/formula.{js,d.ts}; facade @openjsxl/core dep rewritten to 0.8.0).",
          "is_bot": false,
          "headline": "release: 0.8.0",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-12T20:28:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "70c3101a0c891b3b535f54be78c3dcd201381ab3",
          "body": "Closes M8. openjsxl/formula gets a runnable example and full docs, an\noracle-corpus integration test, and the fixes from the full-milestone review.\n\n- Example 12-formulas.mjs: parse -> evaluate -> UDF -> error/#CYCLE! -> volatile gate.\n- Integration test: a realistic workbook evaluated whole (oracle\n[…]\nop-list corrected (SUM(A1:A3*B1:B3) -> #VALUE!, not top-left).\n\nAll changes are within the openjsxl/formula subpath, so the \".\" entry and writer\nbytes are unchanged. Gate: biome 0 / tsc 0 / 782 tests.",
          "is_bot": false,
          "headline": "feat(core): formula integration, docs, example + M8 review fixes (F8.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-12T20:03:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0118366b103667035d03ec561c5f7e5ae865b8ec",
          "body": "~85 built-ins behind openjsxl/formula: SUM/AVERAGE/COUNT*/MIN/MAX,\nIF/IFERROR/AND/OR, VLOOKUP/HLOOKUP/INDEX/MATCH/CHOOSE, SUMIF(S)/COUNTIF(S)/\nAVERAGEIF(S), text (LEN/LEFT/MID/TRIM/SUBSTITUTE/…), IS*, date (DATE/YEAR/\nEDATE/EOMONTH/…), and the gated volatiles (TODAY/NOW/RAND/RANDBETWEEN).\n\n- Registe\n[…]\nUNT error-ignoring, and a ReDoS in wildcard\nmatching (regex → linear two-pointer glob). Isolated behind the subpath, so\nthe \".\" entry and writer bytes are unchanged. Gate: biome 0 / tsc 0 / 775 tests.",
          "is_bot": false,
          "headline": "feat(core): formula function library — tier 1 + tier 2 built-ins (F8.3)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-12T18:37:38Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b83172af13335b0bf63a43bf0d22fc281181e2e3",
          "body": "CI typechecks before it builds, so the facade's `export * from\n\"@openjsxl/core/formula\"` had no dist/formula.d.ts to resolve against and\nfailed with TS2307. Add the subpath to tsconfig.base.json `paths` (→\npackages/core/src/formula/index.ts), mirroring the \"@openjsxl/core\" entry,\nso resolution hits source and no build is required.",
          "is_bot": false,
          "headline": "fix(build): resolve @openjsxl/core/formula to source in typecheck",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-12T13:43:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8104eac7726fc82e8a99f412e6267e28d36d3706",
          "body": "Opt-in evaluator behind openjsxl/formula. evaluateWorkbook / evaluateCell\nrun a one-shot, memoizing, tri-color walker over the shared Workbook\nsurface: the decision-6 coercion matrix, errors-as-values, cycles as a\n#CYCLE! value (unrelated cells unaffected), lazy RangeView over used cells,\ndefined-na\n[…]\ndversarial\nreview fixed two pinned safety defects: a self-referential range formula\nthat hung, and a deep left-associative operator chain that overflowed the\nnative stack — both now bounded and typed.",
          "is_bot": false,
          "headline": "feat(core): formula evaluator + workbook defined names (F8.2)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-12T13:33:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "731a78f4a0b6479bfd7052eca053019456167e81",
          "body": "Opt-in, module-graph-isolated formula parser behind the new \"./formula\"\nsubpath (both packages; tsup second entry, splitting:false). parseFormula\nreturns a typed FormulaAst over the full stored form (ECMA-376 §18.17):\nliterals, $-refs, whole-col/row ranges, quoted/unquoted/3-D sheet refs,\nunions, ca\n[…]\nial review (parser lens) fixed two pinned defects: spill `#` on a\nrange endpoint (SPILL_BP raised above `:`) and the deleted-sheet form\n`#REF!!A1`; plus widened quoted-external detection to `[ ] / \\`.",
          "is_bot": false,
          "headline": "feat(core): formula parser + openjsxl/formula entry point (F8.1)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-11T14:23:26Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "be3285d5c07955f0d3417117f23f2b980016a6e4",
          "body": "M8 (v0.8) — opt-in formula parser + evaluator behind an openjsxl/formula\nsubpath entry: F8.1 Pratt parser + entry point (explicit stack, depth caps,\nsplitting:false keeps the \".\" dist byte-identical) → F8.2 pull-based evaluator\n(tri-color walk, Excel coercion matrix, cycle-error values, fuel budget,\n[…]\nom a 2-agent research pass (verified against ECMA-376, openpyxl 3.1.5\nprobes, and working oracle installs) plus 1 adversarial draft review (8 findings\napplied). Later-milestones outline retitled M10+.",
          "is_bot": false,
          "headline": "docs(implementation): scope M8 formulas + M9 breadth/hardening",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-11T12:15:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7de65c82ce0ab3d4f0439fbb4cbb141e01b0b1c0",
          "body": "More formats to read. openXlsb (BIFF12), openOds (OpenDocument), and openCsv\n(RFC 4180 .csv/.tsv) open into the same Workbook surface as openXlsx, with\ndetectSpreadsheetFormat to route by container; .xlsm/.xltx open through openXlsx.\nRead-only — conversion to .xlsx is the bridge (workbookToInput → w\n[…]\nlock-step); PUBLISHING.md step-2 note + 0.7.0\nversioning history entry. Preflight green (install/build/typecheck/607 tests/\nbiome); tarball dry-run verified (dist-only, facade dep rewritten to 0.7.0).",
          "is_bot": false,
          "headline": "release: 0.7.0",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-11T11:21:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ac5c0e4ad90e50970ca7c214b38faa6ec5c850e1",
          "body": "…+ bench corrections\n\nCost-capped adversarial review of the F7.4 + bench commits (3 finders, refuting\nverifiers): 7 findings, 0 refuted, all fixed.\n\ndetect: the content.xml ODS fallback ran even when a present, non-empty,\nNON-spreadsheet mimetype had already been read — classifying files openOds\ndet\n[…]\nsured ~1.3-1.6x (workload-dependent).\n\nAlso clears the five biome warnings pnpm check printed: a useOptionalChain in the\nnew detect code and four pre-existing non-null assertions in the formula tests.",
          "is_bot": false,
          "headline": "fix(core,bench): post-M7 review follow-up — detect ODS-fallback gate …",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-11T11:06:38Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c2f6f6a39190e2779690e3f818f860ff5a5a0ff7",
          "body": "Post-M7 benchmark pass on the M7 build. Two additions to the private\n@openjsxl/bench harness (no shipped code changes — the published packages stay\nzero-dependency):\n\nRead by format — the same numbers data authored in .xlsx / .xlsb / .ods / .csv,\neach read by the libraries that support it. Adapters \n[…]\nbenchmarks.md (new Library-size + Read-by-format + Python-by-format\nsections; xlsx read/write numbers re-measured on the M7 build) and cites the size\ncomparison in the root, facade, and bench READMEs.",
          "is_bot": false,
          "headline": "chore(bench): cross-format read lanes + library size matrix",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-11T09:40:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5cc880219ae1061d0da896c8e959742da6a14e07",
          "body": "detectSpreadsheetFormat(source, options?) → 'xlsx'|'xlsb'|'ods'|'csv'|undefined\n(reader/detect.ts, facade-exported with SpreadsheetFormat). It sniffs the zip\ncontainer by reusing the hardened openZip: an ODF mimetype → 'ods' (a mimetype-\nless ODS is still recognized via its content.xml office:spread\n[…]\nholes (the conversion reject-set and the equivalence full-cell-set). No runtime\ndependencies; no any, no type assertions.\n\nBench read lanes + the library size matrix follow as a separate post-M7 pass.",
          "is_bot": false,
          "headline": "feat(core): format detection + cross-format corpus (F7.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-11T08:47:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b22c462e0ed379a7a5415e9593a9d19cfea7e1a4",
          "body": "Adds a synchronous .csv/.tsv reader behind the shared Worksheet seam. A pure\nRFC 4180 scanner (parseDelimited) splits text into raw fields — quote-at-field-\nstart only, \"\" escape, CR/LF/CRLF endings, BOM strip — pinned cell-for-cell\nagainst Python's stdlib csv on an 18-case matrix. Conservative type\n[…]\ns + a synthesized dimension and degrades every unsupported accessor.\nGrid clamped to MAX_ROW/MAX_COL. New basic.csv fixture (CRLF, quoted delimiter,\nembedded newline, escaped quote, leading-zero ids).",
          "is_bot": false,
          "headline": "feat(core): .csv/.tsv read — openCsv + an RFC 4180 scanner (F7.3)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-10T17:24:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3f6bada6aad8d4a442f9bb67f7811633b81b28c4",
          "body": "…7.2)\n\nopenXlsb reads an Excel Binary Workbook into the SAME public Workbook the xlsx\nreader returns, reusing the zip reader and the F1.4 relationship graph verbatim\n(.xlsb is the same OPC container). New biff/ record layer (variable-id + 7-bit\nvarint length framing grounded in pyxlsb's source; RK/d\n[…]\nher-formats.mjs (read .xlsb + .ods, convert to xlsx).\nDeferred for xlsb (no oracle to verify without a real Excel file, documented):\nmerged cells and the 1904 date system. biome 0 / tsc 0 / 546 tests.",
          "is_bot": false,
          "headline": "feat(core): .xlsb read — openXlsb + the BIFF12 binary record layer (F…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-10T15:58:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c94775eb8e7ac1d1a497a16878275d943b221f5c",
          "body": "openOds reads an OpenDocument spreadsheet into the SAME public Workbook the\nxlsx reader returns. The seam: Worksheet becomes a structural interface (the\nxlsx class is now XlsxWorksheet implements Worksheet — a pure rename; the\nWorkbook class is reused), OdsWorksheet implements the same shape, and\nun\n[…]\n\nrepeat-bomb cell cap is document-wide (was per-sheet, an OOM across many bomb\nsheets). Fixtures: real odf-basic.ods (odfpy, calamine-checked) + crafted\nedge/reject cases. biome 0 / tsc 0 / 528 tests.",
          "is_bot": false,
          "headline": "feat(core): .ods read — openOds + the multi-format reader seam (F7.1)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-10T12:38:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "09c485105188e707f4a47d033287dfaab085f77f",
          "body": "Legacy .xls (BIFF8) was the milestone's biggest lift (CFB container + BIFF\nrecords + the SST Continue trap) for a declining install base — deferred to\nM8+. F7.3 is now .csv/.tsv read (openCsv): the universal delimited-text export\n(Google Sheets / Excel / every tool), zero-dep and small. Updates the M7 theme,\ndecisions, dependency order, the full F7.3 section, and F7.4 detection/corpus;\nROADMAP 0.7 row too.",
          "is_bot": false,
          "headline": "docs(implementation): re-scope M7 F7.3 — csv/tsv instead of legacy .xls",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-10T12:37:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a622974308cbc828da45eb5e38f040cd7b6e15b6",
          "body": "…7.3 xls, F7.4 corpus)\n\nExpand the M7 outline into the full tracker section: read-only ods/xlsb/xls\nreaders behind the one shared Workbook surface (degrading accessors, explicit\nper-format entry points, python-calamine as the independent oracle), BIFF8-only\nxls with typed rejection of encrypted/legacy inputs, per-format adversarial\ncases named up front, and a cross-format conversion corpus + docs/bench\nwrap-up. Later-milestones outline retitled M8+.",
          "is_bot": false,
          "headline": "docs(implementation): scope M7 — more formats (F7.1 ods, F7.2 xlsb, F…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-10T09:25:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3cfbffc7e7081710aa685d4c0bae9476d714dc4e",
          "body": "The milestone table still showed only M0 done. Everything through\n0.6 (Images) is released and live on npm — flip 0.1–0.6 to ☑ and add\na Shipped marker (0.6.0, 2026-07-04; next up 0.7 — more formats).",
          "is_bot": false,
          "headline": "docs(roadmap): mark 0.1–0.6 shipped; note 0.6.0 published",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-04T15:59:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0f0ca65c5de6d2131803bc481dcf91ead1b10b94",
          "body": "Anchored-picture read (async Worksheet.images()), picture write on both\nwriters with workbook-level media dedup, and bridge carry — pictures\nround-trip byte-exact for png, jpeg, gif, bmp, tiff, webp, emf, wmf.\nThe tolerant reader clamps out-of-range anchor values into writable\nbounds. Additive API: Worksheet.images(), SheetInput.images, and the\nSheetImage/ImageAnchor/AnchorPoint types.",
          "is_bot": false,
          "headline": "chore(release): 0.6.0 — images",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-04T13:02:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "edd3c112c7fd405e361a90bcb28852fa8da9ac10",
          "body": "The picture-write allowlist widens to the full read set — bmp, tiff,\nwebp, emf, and wmf join png/jpeg/gif — so reader and writer are\nsymmetric and any real file with embedded pictures round-trips through\nthe bridge. Only a genuinely unknown media type still refuses typed.\nThe mime error message now \n[…]\n accessor,\nwrite section, fidelity table row + drop-list entries), the streaming\nmemory claims are scoped to \"constant in rows\" with the image caveat,\nand images() documents its per-sheet media cache.",
          "is_bot": false,
          "headline": "feat(core): write all readable image types; 0.6 release docs",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-04T12:56:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9395ee93fad1ac4e6d7402a53be066cdb73a84b8",
          "body": "… analysis)\n\nPost-milestone analysis follow-up. parseDrawing now clamps out-of-range\nanchor numbers into the writer's legal ranges (grid cols/rows, EMU\n0..2^31-1) instead of returning them verbatim — a well-formed file with\na negative offset or 2^32 extent used to read fine and then fail the\nwhole r\n[…]\nntical (12/12 vs pre-change on both writers).\n\nSix new pinned tests: clamps, prototype-key mime, GIF end-to-end,\nspecial-char picture name, multiple drawing rels per sheet, external-\ntarget blip skip.",
          "is_bot": false,
          "headline": "fix(core): reader clamps anchor bounds; single-source media types (M6…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-04T12:39:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f769a377cf7b2f4cc709cfc33795ec78a2adc9f4",
          "body": "workbookToInput now awaits worksheet.images() per sheet and attaches\nimages only when non-empty, so an imageless workbook keeps the exact\npre-F6.3 bytes. The corpus property snapshot gains images (compared by\nanchor + mime + name + a content digest, not raw bytes); new example\n10-images.mjs; all exa\n[…]\nhe field the kind calls for\n(twoCellAnchor→to, oneCellAnchor→ext), recovering a stray-field picture\nand dropping one whose required field is absent (the same degradation as\na missing blip). Closes M6.",
          "is_bot": false,
          "headline": "feat(core): bridge carries pictures — workbookToInput.images (F6.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-04T12:03:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7654e751ea4e7fc1d8bf73230eb54e9783764b65",
          "body": "Write drawingML pictures, the mirror of F6.2's read. A sheet takes\n`images: SheetImage[]` (the reader's exact shape); the writer validates each,\nemits xl/drawings/drawingN.xml + its rels + the <drawing r:id> body element\n(before <legacyDrawing> in schema order), and writes deduplicated media parts\nx\n[…]\nre). Also plain-language comments across the image code.\n\nGate: biome 0 / tsc 0 / 492 tests / byte-identity 10/10 imageless /\nwrite->read round-trip / dedup / streamed==buffered / unzip -t + openpyxl.",
          "is_bot": false,
          "headline": "feat(core): picture write — SheetInput.images (F6.3)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-04T07:28:38Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9a5259c13e92ff47dee28097df4dc5aef8c24056",
          "body": "Read drawingML pictures: parseDrawing (pure SAX) turns a spreadsheetDrawing\npart into picture anchors, and Worksheet.images() resolves the /drawing rel ->\ndrawing part -> its rels -> media bytes. Raw anchor model (1-based cell +\nverbatim EMU offsets/extents, like colors); bytes opaque; mime from the\n[…]\next (emitted by real Excel/LibreOffice) was overwriting the\nanchor's own <ext>; now only the anchor-level ext is captured.\n\nGate: biome 0 / tsc 0 / 473 tests / openpyxl cross-check on bytes + anchors.",
          "is_bot": false,
          "headline": "feat(core): picture read — Worksheet.images() (F6.2)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-04T06:33:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "786ee0fe71e32fb8df16b3e32701db41b6f36606",
          "body": "The per-sheet rel block and side-part names were spelled in 2-3 hand-synced\nplaces across the buffered and streaming writers — the duplication the M5\nanalysis flagged, and images would have made it a fourth copy.\n\n- streamWorksheet now calls the shared sheetRelPlumbing (deletes its inline\n  copy of \n[…]\ntream.test.ts\n— both writers emit the identical OPC part set, and sheet1 rels stay ordered\nhyperlink/comments/vmlDrawing. Sets up M6 image parts to extend SheetSideParts\nrather than add a fourth copy.",
          "is_bot": false,
          "headline": "refactor(core): unify per-sheet part/rel wiring for both writers (F6.1)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-04T04:59:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "369f36ffb5df508e8f3701607cfd80e949c036df",
          "body": "…idence\n\nFull feature breakdown for M6 in IMPLEMENTATION.md: F6.1 per-sheet part\nwiring dedup (pays the M5-analysis duplication debt before images add a\nfourth per-sheet part family), F6.2 picture read (raw anchor model, lazy\nWorksheet.images, tolerant degrades), F6.3 picture write in both writers\n(\n[…]\nenchmarks put pure-TS within ~1.5x of native calamine,\nso the prebuilt-binary release matrix isn't worth its maintenance — and it\nserves the browser/edge differentiator worst. ROADMAP 0.6 row updated.",
          "is_bot": false,
          "headline": "docs: scope M6 — images (v0.6); defer the native lane on benchmark ev…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T17:11:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "77056f3fb40ac18d950589630a134804d771b364",
          "body": "… fix\n\nBoth public packages to 0.5.1 in lock-step; PUBLISHING.md versioning notes.\nShips the two writer fixes from the post-release M5 review (streamXlsx no\nlonger drops rowProperties past the last streamed row; the zip entry count\ncan no longer collide with the ZIP64 sentinel). Benchmarks re-measured on\nthe fixed build — no regression; README figures reconciled. No API change.",
          "is_bot": false,
          "headline": "chore(release): 0.5.1 — stream trailing row properties, zip entry-cap…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T16:37:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e19c24de7a680c19d52f7a58e6063a3955feafe0",
          "body": "…he ZIP64 sentinel\n\nTwo defects from the cross-cutting M5 analysis:\n\n- streamXlsx silently dropped rowProperties addressed past the last streamed\n  row, where writeXlsx emits property-only <row/> elements — same input,\n  different metadata out. The streaming path now consumes row attrs per row\n  (th\n[…]\ng readers\n  hunting for a ZIP64 record that doesn't exist. MAX_ENTRIES is now 0xfffe in\n  both writers (buffered's boundary unchanged; it already capped at 65,534),\n  pinned by a shared-constant test.",
          "is_bot": false,
          "headline": "fix(core): stream trailing row properties; keep zip entry count off t…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T16:21:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a5e4c0d4e9bbc1b2bd20e1527fb3764854d4d41e",
          "body": "Both public packages to 0.5.0 in lock-step; PUBLISHING.md versioning notes.\nShips M5: comments write (legacy VML, Excel-visible), formula text\nread/translate/write, custom-theme carry + resolveColor, the constant-memory\nstreamXlsx writer, and published benchmarks (docs/benchmarks.md). The\nround-trip drop-list is down to bare error cells.",
          "is_bot": false,
          "headline": "chore(release): 0.5.0 — comments, formulas, themes, streaming writer",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T15:40:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "291b266d1187b2468f737086a8ba2cd081e7e915",
          "body": "…table\n\nREADMEs and examples catch up with M5 before any 0.5 bump:\n\n- Root README: \"New in 0.5\" status; formula(ref) + resolveColor in the reader\n  tour (ARGB values verified against the library); a \"Comments, formulas &\n  streaming\" write section; fidelity table moves comments, formula text +\n  cac\n[…]\nlas-theme.mjs (comments, live formulas with\n  cached values, theme colors resolved and carried through the bridge), wired\n  into the examples README and the `all` script — all nine examples run green.",
          "is_bot": false,
          "headline": "docs: 0.5 guide — comments/formulas/theme write, streaming, fidelity …",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T15:35:02Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ee2a6705a885eb3f3b1eff77f85ee6d27a199755",
          "body": "New private @openjsxl/bench workspace (never published — ExcelJS/SheetJS are devDeps\nthere, so openjsxl and @openjsxl/core stay zero-dependency). `pnpm bench` builds the\nreal bundle, authors read fixtures once with ExcelJS, then runs every (library, op,\nworkload, size) cell in its own isolated `node\n[…]\ncalamine reference numbers.\n- Published docs/benchmarks.md (M2 Pro, Node 24) + README Performance section + facade\n  README benchmark link — no unmeasured \"fast\" claim left in the docs.\n\nCompletes M5.",
          "is_bot": false,
          "headline": "feat(bench): benchmark harness + published numbers (F5.5)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T15:19:48Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f7209023da04e63ab2f32224ac3a4c0522721e03",
          "body": "Add streamXlsx(workbook, options?): ReadableStream<Uint8Array>, the\nconstant-memory mirror of writeXlsx. Each sheet's rows may be a sync or\nasync iterable (a DB cursor, a paged fetch), pulled only as the consumer\nreads, so a large sheet never lives in memory — peak live heap stays flat\n(~5.5MB) from\n[…]\nc/454 tests; buffered byte-identity 9/9 vs pre-F5.1; a streamed\nasync-cursor file passes unzip -t and openpyxl; reader-equivalence with\nwriteXlsx; adversarial review (4 confirmed bugs fixed + pinned).",
          "is_bot": false,
          "headline": "feat(core): constant-memory streaming writer (F5.1)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T13:42:34Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "54052cbb74b5931238235cb04b3125b0f1259abf",
          "body": "Revert the JavaScript formatter to Biome's default (semicolons everywhere)\nand reformat the tree. Purely mechanical — no behavior change; the full test\nsuite is unaffected. The README code snippets get semicolons too (single\nquotes left as they were).",
          "is_bot": false,
          "headline": "style: use Biome's default semicolons (drop semicolons: asNeeded)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T12:33:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5c30040cdc3f752a2d5539594a789c412fe77646",
          "body": "Preserve formula text across read → modify → write (no evaluation). The\nreader exposes Worksheet.formula(ref): plain and array-master formulas\nverbatim, and shared-formula dependents translated to their position.\n\nNew ooxml/formula.ts tokenizes a formula and shifts relative references by\nthe depende\n[…]\nxt (documented).\n\nNew fixture shared-formula.xlsx. Gate: biome/tsc/446 tests; translator\nmatches openpyxl on 32 vectors; byte-identity 8/8 vs pre-F5.4; openpyxl reads\nour formulas data_only both ways.",
          "is_bot": false,
          "headline": "feat(core): formula text — read, translate, write (F5.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T10:58:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "47822111990139394b2d58733ed6e34972b12f7e",
          "body": "Resolve theme colors to concrete RGB and stop flattening custom themes on\nrewrite. New ooxml/theme.ts parses theme1.xml's <a:clrScheme> into a\n12-slot table (with the SpreadsheetML dark/light index swap and the sysClr\nlastClr fallback) and applies Excel's tint algorithm — the Win32 integer\nHLS trans\n[…]\n0). Gate:\nbiome/tsc/418 tests; tint validated against 96 independent vectors + real\nExcel swatches; byte-identity 7/7 vs the pre-F5.3 build; openpyxl reads our\ncustom-theme rewrite warnings-as-errors.",
          "is_bot": false,
          "headline": "feat(core): theme fidelity — resolveColor + custom-theme carry (F5.3)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T09:37:46Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fb5853542be577d0ee82d4f8b59e7812ff758494",
          "body": "Emit cell comments so a read→modify→write round trip no longer drops them.\nPer commented sheet the writer now produces xl/commentsN.xml (deduped,\nfirst-occurrence-ordered authors; an author-less comment shares one \"\"\nentry) and a paired xl/drawings/vmlDrawingN.vml legacy drawing (one hidden\nnote sha\n[…]\nxl-comments.xlsx (two resolved authors + an author-less\ncomment). Gate: biome/tsc/397 tests; byte-identity 7/7 vs the pre-F5.2\nbuild; openpyxl cross-validated both ways; adversarial review 0 findings.",
          "is_bot": false,
          "headline": "feat(core): comments write via legacy VML (F5.2)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T06:34:26Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4203ab4269eef74a9619b856aa930c21d9769369",
          "body": "CLAUDE.md is working guidance for agents/humans in this repo; it doesn't\nbelong in the published remote. Remove it from Git tracking (the file\nstays on disk) and gitignore it so it won't be re-added.",
          "is_bot": false,
          "headline": "chore: untrack CLAUDE.md (keep as local-only operating contract)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T05:12:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7dfeab42e6656d47bfc2c4338b80390ff3baf834",
          "body": null,
          "is_bot": false,
          "headline": "cleanup",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T05:08:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "98ec925e1a643cc11b55bc2334741a2175d50717",
          "body": "… contract\n\nIMPLEMENTATION.md M5 is expanded into five hand-off-ready features with pre-made\ndesign decisions and a dependency order: F5.2 comments write (VML + per-sheet\nrels refactor), F5.3 theme parse/resolve/carry, F5.4 formula-text fidelity\n(pulled forward from M8; evaluation stays), F5.1 const\n[…]\n milestone table follows. CLAUDE.md captures\nthe operating contract — working agreements, quality gates, core invariants,\nand the adversarial-review protocol — so any agent can pick up a feature cold.",
          "is_bot": false,
          "headline": "docs: scope M5 — fidelity + streaming writer; add CLAUDE.md operating…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T04:34:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7dcbe5f8830e12c5405b71d073f748405992eed5",
          "body": "SheetJS is reframed accurately — its public npm release is frozen on the\npre-security-fix 2022 build (0.18.5) with current releases CDN-only and styled\nwrite behind a paid tier — rather than implying the project itself is dead (its\ncode is still actively committed). ExcelJS stays flagged as MIT but effectively\nunmaintained. Also gitignore a local, unpublished competitive-analysis snapshot.",
          "is_bot": false,
          "headline": "docs: correct the competitive framing in the README \"Why\"",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-03T04:21:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3545d99170c1ca025f029425c6a58c051145e7c7",
          "body": null,
          "is_bot": false,
          "headline": "chore(release): 0.4.0 — styles, geometry, structural metadata",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T15:14:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4312880d4d5a5b2181eb75e183f5d9070381399b",
          "body": "…ple 07",
          "is_bot": false,
          "headline": "docs: 0.4 guide — styles & layout write, updated fidelity table, exam…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T15:14:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3b08336e14bd82f41092c544172843b2b0473bc8",
          "body": "…y (F4.6)\n\nSheetInput gains merges (canonical A1 ranges → <mergeCells>; malformed,\nsingle-cell, reversed, out-of-grid, and overlapping ranges rejected typed —\noverlap check is a row sweep whose actives are provably column-disjoint, so\nadversarial volume rejects in ms), hyperlinks (reader-shaped reco\n[…]\nreal-producer fixture openpyxl-metadata.xlsx (all three states, merges,\nexternal+location links). 376 tests; byte-identity to the pre-styles writer\nholds 5/5; openpyxl cross-validates both directions.",
          "is_bot": false,
          "headline": "feat(core): structural metadata write — merges, hyperlinks, visibilit…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T15:00:39Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8b7b2716432232a5dc6c2b1f27522afca5cabe20",
          "body": "… (F4.5)\n\nRead, write, and bridge the geometry that makes a sheet look right:\ncolumn widths and hidden columns (<cols>), row heights and hidden rows\n(<row ht hidden>), and frozen panes (<pane state=\"frozen\">).\n\nRead: three lazy Worksheet accessors — columns, rowProperties, freeze —\nin the mergedCell\n[…]\nt-width column, heights, a hidden property-only row, a B3 freeze)\nand by openpyxl reading our own output back: widths, hidden flags,\nheights, and freeze_panes == \"C2\" for {rows: 1, cols: 2} all agree.",
          "is_bot": false,
          "headline": "feat(core): sheet geometry — column widths, row heights, freeze panes…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T11:28:02Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0ddc402feb589c4f987bd06926219a139b234d83",
          "body": "…(F4.4)\n\nworkbookToInput now attaches each cell's resolved style: bare values when\nunstyled (v0.3-era workbooks rewrite byte-identically, dates included),\n{ value, style } otherwise — including styled EMPTY cells (<c s/>), so\nborders and fills on blank cells survive read -> modify -> write. The\nREAD\n[…]\ne+tint, custom\ncodes) deep-equal across the bridge per-cell over the whole corpus;\nopenpyxl independently reads the bridge-rewritten file with every style\nintact; unstyled rewrites are byte-identical.",
          "is_bot": false,
          "headline": "feat(core): bridge carries styles + reader/writer bounds unification …",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T10:24:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8a88aebf209f120f214e970f2dac6a91e0791a97",
          "body": "…ed recommended)",
          "is_bot": false,
          "headline": "chore(biome): migrate config to 2.5.1 (rules.preset replaces deprecat…",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T05:50:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cb2afefdb31a836b51e871a53be3f823e3d22749",
          "body": "Flip javascript.formatter.quoteStyle from 'single' to 'double' (Biome's\nown default) and reformat the repo. Mechanical: string CONTENTS are\nunchanged — golden-pin and byte-identity tests pass untouched — only\nsource delimiters move. Strings containing double quotes keep single\nquotes per Biome's preferred-quote rule.",
          "is_bot": false,
          "headline": "style: switch Biome to double quotes",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T05:39:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "247ed1b1b06b7bc8615607efd8bb0dc7ad6be69e",
          "body": "Activate CellStyle.numberFormat in the writer as a format CODE string —\nthe same currency numberFormat(ref) returns; ids stay file-internal.\nCodes exactly matching a built-in reverse-map to its id (via a map\nderived from the reader's own BUILTIN_FORMATS, so the tables cannot\ndrift) with no <numFmts>\n[…]\n font names, and sheet names alike.\n\nBare-input byte-identity vs the pre-styles writer holds (5/5); openpyxl\nindependently confirms built-in id reuse, custom codes verbatim, and\nboth review scenarios.",
          "is_bot": false,
          "headline": "feat(core): number-format write (F4.3)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T05:36:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b13b8aaa406a78c2ac5179d5d85437ea4f322e93",
          "body": "Rows now accept CellInput = CellValue | { value, style? } — the style\nbeing exactly what Worksheet.style(ref) returns, so read -> modify ->\nwrite carries styles as a pass-through. A styled BLANK ({value: null,\nstyle}) emits <c r s/> and counts toward the dimension, which is how a\nborder or fill land\n[…]\nnto\nstyles.xml — validators now single-read every property.\n\nopenpyxl independently reads back every styled component: fonts, fills\n(incl. styled blanks), borders, alignment, theme+tint, styled dates.",
          "is_bot": false,
          "headline": "feat(core): styled-cell write input + style interner (F4.2)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T04:39:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1839e718d114aeb658b484b1f1471d2e78513d84",
          "body": "Extend parseStyles' single tokenizer pass to collect fonts, fills,\nborders, and each cellXf's component ids + inline alignment, alongside\nthe numFmt data it already kept. StyleTable gains cellStyle(i) — the\nresolved CellStyle for an xf index, materialized lazily and cached, one\nreference-stable obje\n[…]\nts, rgb/theme+tint/indexed colors, pattern fills, per-edge borders,\nalignment, built-in + custom number formats) — plus a corpus smoke over\nevery fixture: 0 throws, 0 style/numberFormat disagreements.",
          "is_bot": false,
          "headline": "feat(core): full style read model, Worksheet.style(ref) (F4.1)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T01:51:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0d4f9d33ecaea756c2ce551a51c45d6355ff0c2d",
          "body": "Expand the M4 outline into six planned features: F4.1 full style read\nmodel + Worksheet.style(ref), F4.2 styled-cell write input + style\ninterner, F4.3 number-format write (code strings, custom ids >= 164),\nF4.4 bridge round-trip fidelity for styles, F4.5 sheet geometry (column\nwidths, row heights, \n[…]\nings, never ids) and the named\ndeferrals (comments write -> 0.5 for the VML part, theme parsing,\ngradient fills, named-style inheritance, split panes, ...). The M5+\noutline absorbs the deferred items.",
          "is_bot": false,
          "headline": "docs: scope M4 — styles (v0.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T01:09:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f2d381427a28cebfbf22eb93f1ae541141674c91",
          "body": "Bump both public packages (and the monorepo) to 0.3.0, the writer release:\nwriteXlsx and workbookToInput are now public.\n\n- READMEs: document writing (writeXlsx, read -> modify -> write), a\n  round-trip fidelity table, and the writer in @openjsxl/core's exports.\n- examples/06-write.mjs: author an .xlsx and round-trip an existing one.\n- PUBLISHING.md: 0.3.0 version note.\n\nNo source changes; API and tests unchanged from F3.1–F3.3.",
          "is_bot": false,
          "headline": "chore(release): 0.3.0 — writer",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T00:12:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a5564332321a378d4fd004d364f20b0fc8b03e2a",
          "body": "Replace an array-hole literal with an explicit `undefined` (identical\nmeaning to the writer) and reformat, so `round-trip.test.ts` passes\n`biome check`. No behavior change. (Missed in b02e366 due to a truncated\nlint-check invocation.)",
          "is_bot": false,
          "headline": "test(core): satisfy biome in the round-trip test",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-02T00:12:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b02e36609ec27dde198b58d765d7b536bde56fd4",
          "body": "Add workbookToInput(workbook): turn an open Workbook into writeXlsx input,\nplacing each populated cell at its own A1 ref and keeping rows/columns sparse\n(array holes), so a file can be read, tweaked, and written back. Public,\nexported from the package index; closes the round trip.\n\nFidelity is scope\n[…]\nd->write->read stability over real fixtures, a golden pin on\nthe emitted worksheet XML, and an independent openpyxl cross-check that the\noriginal and bridge-rewritten basic.xlsx hold identical values.",
          "is_bot": false,
          "headline": "feat(core): reader->writer bridge + round-trip fidelity (F3.3)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T15:12:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "16052667c96b0324d24bc20dccf13130878ce7c9",
          "body": "Add the public writer: writeXlsx({ sheets }, options?) -> Promise<Uint8Array>,\nthe mirror of openXlsx. A workbook is described as plain data (sheets of\nrow-major value arrays); the OOXML cell type is inferred from each JS value\n(string -> inline string, number, boolean, Date -> date-styled serial,\nn\n[…]\ne, multi-line, emoji,\nCJK), numbers (incl. scientific notation), booleans, dates, datetimes,\nsparse cells, and multiple sheets. Exported from the package index;\n`import { writeXlsx } from 'openjsxl'`.",
          "is_bot": false,
          "headline": "feat(core): minimal workbook writer, writeXlsx (F3.2)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T14:15:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9537517d51de02d652c8e1b3cd94dd38436f08c5",
          "body": "Add an internal writeZip() that packs named byte-parts into a ZIP whose\nbytes re-read byte-identically through the reader's openZip — the mirror\nimage of the container reader, and the foundation for the workbook writer\n(F3.2).\n\n- writer/crc32.ts: table-driven CRC-32 over the uncompressed bytes.\n- wr\n[…]\n' that the\nreader drops as directory placeholders.\n\nRound-trip, determinism, and guard tests cover both compression methods.\nwriteZip stays internal; the public openjsxl/write surface arrives in F3.2.",
          "is_bot": false,
          "headline": "feat(core): ZIP/OPC container writer (F3.1)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T12:12:03Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "98804b9e35a17fc3ec88d04b751aad2a116242bc",
          "body": "Publish the source-map trim (install size ~178 KB → ~55 KB); no API\nchange. Bump openjsxl + @openjsxl/core (and the private root) to 0.2.1\nin lock-step.",
          "is_bot": false,
          "headline": "chore(release): 0.2.1",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T08:03:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f6e07f2d2d9730f316ed97efbfb14bb910dc6fdb",
          "body": "Set sourcemap: false in both tsup configs. The output isn't minified\n(so it's already legible) and the map was ~126 KB in @openjsxl/core —\n~70% of the install. Install size drops from ~178 KB to ~55 KB with no\nruntime or dev-workflow impact (tests run against TS source, not dist).",
          "is_bot": false,
          "headline": "chore(build): drop published source maps to cut install size",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T08:01:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dfbe36a6918c3066cf96a7ebed7a5922bd21f85b",
          "body": "TypeScript deprecates baseUrl (removed in TS 7.0). It isn't needed here —\nthe path mappings resolve relative to the config without it. Root and\nper-package typecheck stay green.",
          "is_bot": false,
          "headline": "chore(tsconfig): drop deprecated baseUrl (paths are already relative)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T07:53:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "93f0ff789243482b40fc0f1431c9eed75b268de4",
          "body": "CI runs lint · typecheck · test · build on Node 24 (pnpm). Add badges to\nthe READMEs — npm version, install size, types, license on root + facade;\nversion, zero-deps, license on @openjsxl/core — and refresh the root\nstatus banner now that the reader is published. CI command sequence\nverified locally (all steps exit 0).",
          "is_bot": false,
          "headline": "ci: add GitHub Actions workflow and README badges",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T07:46:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eba9d16a2d4c07db61421a437da538850c219519",
          "body": "Bump openjsxl and @openjsxl/core to 0.2.0 — a documentation release\n(self-contained per-package READMEs, PUBLISHING.md, runnable examples;\nreader code unchanged from 0.1.0). Rename the private root package from\n@openjsxl/root to openjsxl-monorepo and version it 0.2.0 in step.",
          "is_bot": false,
          "headline": "chore(release): 0.2.0; rename root workspace to openjsxl-monorepo",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T07:28:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "377c5cac9fa34e0b465db62cd8b9c559743af6d7",
          "body": "An examples workspace package that imports `openjsxl` like a real consumer:\ntyped cell access, sheet→JSON, constant-memory streaming, metadata (number\nformats, merges, hyperlinks, comments, visibility), and typed-error handling\n— each reading a committed sample.xlsx. Wired into the pnpm workspace so\n`import from 'openjsxl'` resolves; all five run green.",
          "is_bot": false,
          "headline": "docs(examples): add runnable usage examples",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T07:22:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ee329041891ead99494b2f8723a5dfd8ed6201f1",
          "body": null,
          "is_bot": false,
          "headline": "docs(core): make the @openjsxl/core README self-contained",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T07:07:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "942dfc58f6325a4f32c345fac087c5cf0eb50001",
          "body": null,
          "is_bot": false,
          "headline": "docs: add PUBLISHING.md release runbook",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T06:57:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "449cea91a4f6c891841bf37f8e82cac3a4626650",
          "body": "Add repository/homepage/bugs/author (and core keywords) to both public\npackages, a per-package README + LICENSE so npm renders and bundles\nthem, and a prepack build hook so a tarball never ships stale dist.\nDe-bundle the facade: @openjsxl/core is external and re-exported rather\nthan bundled, so a single copy keeps `instanceof XlsxError` consistent\nacross `openjsxl` and `@openjsxl/core`. Verified via a cross-package\ninstanceof check.",
          "is_bot": false,
          "headline": "chore(release): make openjsxl and @openjsxl/core publish-ready",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T06:54:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "50b1c9fc3f90654f4cadff7138e104aea5639787",
          "body": null,
          "is_bot": false,
          "headline": "docs: show M2 metadata, number formats, and typed errors in the README",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T06:54:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc5b9cd48e53d6fa4c16c7786ff738e551206b8c",
          "body": "A cell's effective number format now resolves cell s → row default\n(<row s customFormat>) → column default (<col style>) → style 0, so a\ndate column whose cells omit their own s reads as dates and\nnumberFormat returns the right code. Both date detection and\nnumberFormat share the resolution, and parseCellStyles addresses no-r\ncells positionally like the row assembler so the two accessors agree.\nFixture col-row-styles.xlsx via an extended buildWorkbook (columns +\nrowStyles).",
          "is_bot": false,
          "headline": "feat(core): resolve column and row default styles (F2.4 #22)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T06:20:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "23b099a976592cf681b0233908df1294bcf6a7e7",
          "body": "Seeded-PRNG fuzz over tokenize/createXmlStream/openZip/openXlsx/\nstreamSheetRows asserting only XlsxError ever escapes — no bare\nError/TypeError/RangeError, no hang. An adversarial review found one\nescape the seed can't reach: an overflowing column ref made\ncolumnToIndex return Infinity, which reached formatRef and threw a\nbare Error out of the read path. Reject the overflow in columnToIndex\nso safeColumn falls back to positional addressing. Regression covered\nby edge-overflow-col.xlsx.",
          "is_bot": false,
          "headline": "test(core): fuzz reader; reject overflowing column refs (F2.4e)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T05:37:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "399deb9641557d8abaa1e807fbaabf83e8b8e805",
          "body": "Decide the zip entry policy: directory placeholders (name ending in '/') are\nskipped — they are not parts — and duplicate part names are refused with\nXlsxError('corrupt-zip'). OPC forbids duplicate names, so an ambiguous package\nis malformed/malicious (a zip-confusion vector); refusing it beats silently\nresolving to one entry. Real OOXML files never trigger either.\n\nAdds generated edge fixtures (edge-duplicate-entry, edge-with-directory).",
          "is_bot": false,
          "headline": "feat(core): reject duplicate + skip directory zip entries (F2.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T03:06:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ec9c1ec6d3c16fa300c8312ded47012ca22a8cfe",
          "body": "openXlsx/streamSheetRows accept a ReadOptions { maxPartBytes } — an absolute\nceiling on any single part's declared decompressed size, independent of the\narchive's own (attacker-controllable) uncompressedSize the inflate already caps\nat. A part over the limit throws XlsxError('part-too-large') before any\ndecompression, bounding per-part memory to maxPartBytes. Omit for no ceiling.\n\nAdds the 'part-too-large' error code — a resource-limit rejection, distinct\nfrom the corruption/structure codes.",
          "is_bot": false,
          "headline": "feat(core): configurable maxPartBytes zip-bomb guard (F2.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T02:55:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "90da2413545c653dd56478cf758bfe616bdbeb31",
          "body": "Audit confirms the reader already degrades on missing optional parts and throws\ntyped errors on missing required ones; this proves it with generated fixtures.\n\nbuildWorkbook now omits styles.xml/sharedStrings.xml (and their rels + content\ntypes) when a workbook has no styles/strings, so a minimal wo\n[…]\nackParts for crafting broken packages.\n\nNew fixtures: minimal.xlsx (no optional parts) and broken-no-officedoc /\nbroken-no-workbook.xlsx (valid ZIP, invalid OOXML — XlsxError not-xlsx /\nmissing-part).",
          "is_bot": false,
          "headline": "test(core): tolerate missing optional parts, error on required (F2.4)",
          "author_name": "joaquimserafim",
          "author_login": "joaquimserafim",
          "committed_at": "2026-07-01T02:45:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        }
      ],
      "releases_count": 11,
      "commits_last_year": 129,
      "latest_release_at": "2026-07-17T22:58:12Z",
      "latest_release_tag": "v1.0.0",
      "releases_from_tags": true,
      "days_since_last_push": 5,
      "active_weeks_last_year": 4,
      "days_since_latest_release": 5,
      "mean_days_between_releases": 1.8
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": false,
      "health_percentage": 42,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "@openjsxl/core",
          "exists": true,
          "license": "MIT",
          "keywords": [
            "xlsx",
            "excel",
            "spreadsheet",
            "ooxml",
            "reader",
            "zip",
            "deflate"
          ],
          "ecosystem": "npm",
          "matches_repo": true,
          "registry_url": "https://www.npmjs.com/package/@openjsxl/core",
          "is_deprecated": false,
          "latest_version": "1.0.0",
          "repository_url": "https://github.com/joaquimserafim/openjsxl",
          "versions_count": 13,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": 1,
          "monthly_downloads": 2138,
          "first_published_at": "2026-07-01T06:59:55.126000Z",
          "latest_published_at": "2026-07-17T22:51:52.193000Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 5
        },
        {
          "name": "openjsxl",
          "exists": true,
          "license": "MIT",
          "keywords": [
            "xlsx",
            "excel",
            "spreadsheet",
            "ooxml",
            "reader",
            "writer",
            "openpyxl",
            "calamine"
          ],
          "ecosystem": "npm",
          "matches_repo": true,
          "registry_url": "https://www.npmjs.com/package/openjsxl",
          "is_deprecated": false,
          "latest_version": "1.0.0",
          "repository_url": "https://github.com/joaquimserafim/openjsxl",
          "versions_count": 13,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": 1,
          "monthly_downloads": 2025,
          "first_published_at": "2026-07-01T07:00:24.940000Z",
          "latest_published_at": "2026-07-17T22:52:09.101000Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 5
        }
      ]
    },
    "popularity": {
      "forks": 0,
      "stars": 1,
      "watchers": 0,
      "fork_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_forks": 0
      },
      "star_history": null,
      "open_issues_and_prs": 0
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "examples"
      ],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [
        "packages/core/tsconfig.json",
        "packages/openjsxl/tsconfig.json",
        "tsconfig.json"
      ],
      "toolchain_manifests": [],
      "largest_source_bytes": 102140,
      "source_files_sampled": 209,
      "oversized_source_files": 1,
      "agent_instruction_files": [],
      "agent_instruction_max_bytes": null
    },
    "dependencies": {
      "manifests": [
        "examples/package.json",
        "package.json"
      ],
      "advisories": {
        "error": null,
        "scope": null,
        "source": null,
        "findings": [],
        "collected": false,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 0,
        "malicious_count": 0,
        "assessed_package": null,
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "npm"
      ],
      "dependencies": [
        {
          "name": "openjsxl",
          "manifest": "packages/bench/package.json",
          "ecosystem": "npm",
          "version_constraint": "workspace:*"
        },
        {
          "name": "@openjsxl/core",
          "manifest": "packages/openjsxl/package.json",
          "ecosystem": "npm",
          "version_constraint": "workspace:*"
        }
      ],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 0,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 0
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "joaquimserafim",
          "commits": 129,
          "avatar_url": "https://avatars.githubusercontent.com/u/2507889?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [
        "biome.json"
      ],
      "has_editorconfig": false,
      "has_linter_config": true,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "pnpm-lock.yaml"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": null,
            "reason": "no pull request found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/30 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 10,
            "reason": "project is fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 0,
            "reason": "project was created within the last 90 days. Please review its contents carefully",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": null,
            "reason": "packaging workflow not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 0,
            "reason": "dependency not pinned by hash detected -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "no SAST tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": null,
            "reason": "no releases found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 10,
            "reason": "0 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "8ec0796fd4217f4c0802f1acb4d0633656814788",
        "ran_at": "2026-07-23T08:35:23Z",
        "aggregate_score": 3.6,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-17T23:15:52Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": null,
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/joaquimserafim/openjsxl",
    "host": "github.com",
    "name": "openjsxl",
    "owner": "joaquimserafim"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 53,
      "inputs": {
        "security": 36,
        "vitality": 69,
        "community": 33,
        "governance": 38,
        "engineering": 84
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "moderate",
        "name": "Vitality",
        "value": 69,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 57,
            "inputs": {
              "commits_last_year": 129,
              "human_commit_share": 1,
              "days_since_last_push": 5,
              "active_weeks_last_year": 4
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 5 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "4/52 weeks with commits",
                "points": 2.8,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 4
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "129 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 129
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 88,
            "inputs": {
              "releases_count": 11,
              "latest_release_tag": "v1.0.0",
              "releases_from_tags": true,
              "days_since_latest_release": 5,
              "mean_days_between_releases": 1.8
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "11 version tags (no GitHub releases)",
                "points": 16.2,
                "status": "partial",
                "details": [
                  {
                    "code": "version_tags_no_releases",
                    "params": {
                      "count": 11
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 5 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~1.8 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 1.8
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "at_risk",
        "name": "Community & Adoption",
        "value": 33,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 1,
            "inputs": {
              "forks": 0,
              "stars": 1,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "1 stars",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "0 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "moderate",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          },
          {
            "key": "ecosystem_adoption",
            "band": "moderate",
            "name": "Ecosystem adoption (downloads)",
            "note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "registry_dependents"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 60,
            "inputs": {
              "packages": [
                "@openjsxl/core",
                "openjsxl"
              ],
              "dependents": null,
              "ecosystems": "npm",
              "total_downloads": null,
              "monthly_downloads": 4163
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "4,163 downloads/month across npm",
                "points": 48.3,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 4163,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "at_risk",
        "name": "Sustainability & Governance",
        "value": 38,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "critical",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution, PR acceptance. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution",
                    "pr_acceptance"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 1,
            "inputs": {
              "merged_prs": 0,
              "open_issues": 0,
              "closed_issues": 0,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 0
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "no issues or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_issues_or_data",
                    "params": {}
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "no decided pull requests or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_decided_prs_or_data",
                    "params": {}
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "moderate",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 61,
            "inputs": {
              "followers": 86,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "joaquimserafim",
              "public_repos": 233,
              "account_age_days": 5036
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "86 followers of joaquimserafim",
                "points": 13.9,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 86,
                      "login": "joaquimserafim"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "233 public repos, account ~13 yr old",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 233
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 13
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "@openjsxl/core",
                "openjsxl"
              ],
              "ecosystems": "npm",
              "any_deprecated": false,
              "min_days_since_publish": 5
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "2 package(s) on npm",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 2,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 5 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "13 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 13
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "good",
        "name": "Engineering Quality",
        "value": 84,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "good",
            "name": "Engineering practices",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: CI-Tests. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_ci_tests"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 80,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": true,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "1 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": "biome.json",
                "points": 16,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "biome.json"
                    }
                  }
                ],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "excellent",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "topics": [
                "browser",
                "bun",
                "deno",
                "esm",
                "excel",
                "javascript",
                "nodejs",
                "ooxml",
                "spreadsheet",
                "spreadsheet-parser",
                "typescript",
                "xlsx",
                "xlsx-parser",
                "zero-dependency"
              ],
              "has_wiki": false,
              "homepage": "https://www.npmjs.com/package/openjsxl",
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": "https://www.npmjs.com/package/openjsxl",
                "points": 15,
                "status": "met",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": "14 topics",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "topics_count",
                    "params": {
                      "count": 14
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "at_risk",
        "name": "Security",
        "value": 36,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "at_risk",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): CI-Tests, Packaging, Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "ci_tests",
                    "packaging",
                    "signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 36,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 15,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 3,
              "scorecard_aggregate": 3.6
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is fuzzed",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow not detected",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "no SAST tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "0 existing vulnerabilities detected",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "excellent",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "review_only_matches": 0,
              "assessed_self_published_locations": 2
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 55,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "at_risk",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.99,
              "agent_instruction_files": [],
              "agent_instruction_max_bytes": null
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "no CLAUDE.md / AGENTS.md / editor rules",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_instructions",
                    "params": {}
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "99 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 99,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 54,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "pnpm-lock.yaml"
              ],
              "has_dockerfile": false,
              "typed_language": true,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": true,
              "typecheck_configs": [
                "packages/core/tsconfig.json",
                "packages/openjsxl/tsconfig.json",
                "tsconfig.json"
              ],
              "agent_commit_share": 0,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": "biome.json",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "biome.json"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "packages/core/tsconfig.json, packages/openjsxl/tsconfig.json, tsconfig.json",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "packages/core/tsconfig.json, packages/openjsxl/tsconfig.json, tsconfig.json"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "excellent",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "primary_language": "TypeScript",
              "largest_source_bytes": 102140,
              "source_files_sampled": 209,
              "oversized_source_files": 1
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "TypeScript (statically typed)",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "TypeScript"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "1/209 source files over 60KB",
                "points": 54.7,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 209,
                      "oversized": 1
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          },
          {
            "key": "ai_interfaces",
            "band": "at_risk",
            "name": "Machine-readable interfaces",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "example_dirs": [
                "examples"
              ],
              "has_mcp_signal": false,
              "api_schema_files": []
            },
            "components": [
              {
                "key": "api_schema_openapi_graphql_proto",
                "name": "API schema (OpenAPI/GraphQL/proto)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 40
              },
              {
                "key": "mcp_server",
                "name": "MCP server",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "runnable_examples",
                "name": "Runnable examples",
                "detail": "examples",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "examples"
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
    "deps.dev does not index npm:openjsxl@1.0.0; advisories assessed against the repository dependency graph instead"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-23T08:35:28.051249Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/j/joaquimserafim/openjsxl.svg",
  "full_name": "joaquimserafim/openjsxl",
  "license_state": "standard",
  "license_spdx": "MIT"
}

Bewertungen sind Signale, keine Garantien. Sie spiegeln öffentlich sichtbare Praxis auf GitHub wider — kein Code-Audit und keine Sicherheitsgarantie.

Fehlende Daten werden ausgeschlossen und die Gewichte neu normiert, nie als null bewertet. Die Methodik ist versioniert und offen: Metriken v1.13.0, Schema v0.27.0 — vollständige Methodik · Metriken-Wiki.

Wie ein einzelnes Ergebnis im Gesamtregister steht: aggregierte Statistikennpm.