Public record
Software health reportschema 0.26.0 · metrics 1.13.0 · 2026-07-22 02:15 UTC

encryption4all / postguard-js

TypeScriptNo license detected★ 0 stars⑂ 0 forkssince Mar 2026View on GitHub ↗

encryption4all/postguard-js holds a health index of 57 out of 100, placing it in the Moderate band. It scores highest on Vitality (85/100) and lowest on Community & Adoption (24/100). It was last updated today. A single contributor accounts for most of its recent work.

57
overall / 100
Moderate

Software health index

Metrics are grouped into weighted categories on one standardized 1–100 scale. Overall starts as their weighted mean; when public evidence triggers the High-Risk Jurisdiction Policy, the rating is adjusted and receives an At risk ceiling of 49. AI Readiness sits outside the overall score.

57
Excellent85-100Exemplary; meets essentially all checked criteria
Good70-84Healthy; minor gaps
Moderate50-69Acceptable with notable gaps; review recommended
At risk30-49Significant weaknesses; adoption warrants caution
Critical1-29Severe problems (abandoned, single-maintainer, no hygiene)
VitalityCommunity &AdoptionSustainability &GovernanceEngineeringQualitySecurityAI Readiness

Score profile

Each axis is a category. The shape matters more than the average — a healthy subject fills the whole shape, while a spike-and-crater profile means strength in one dimension is masking risk in another.

Ownership

Encryption for allOrganization
6 followers16 public repossince Feb 2021

This repository is backed by an organization — shared, accountable stewardship that can outlive any single maintainer.

Package ecosystems

RegistryPackageVersionDownloads / moVersionsLast publishTags
npm@e4a/pg-js2.3.14,063514 days agopostguardibeencryptionidentity-based-encryptionyiviirma

Metrics by category

Vitality

Is the project alive — is code being written and are releases shipping?

85Excellent · 22% of overall
How it's scored
36/36Push recency — last push 0 days ago
11.1/36Commit cadence — 16/52 weeks with commits
18/18Commit volume — 210 commits in the last year
10/10OpenSSF Scorecard: Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Inputs used
commits_last_year210
human_commit_share0.74
days_since_last_push0
active_weeks_last_year16

Release discipline

100Excellent
How it's scored
27/27Ships releases — 49 releases published
36/36Release recency — latest release 4 days ago
27/27Release cadence — a release every ~1.6 days
0/10OpenSSF Scorecard: Signed-Releases — no data
Inputs used
releases_count49
latest_release_tagv2.3.1
releases_from_tagsno
days_since_latest_release4
mean_days_between_releases1.6
Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.

Community & Adoption

Does the project have users, downloads, attention, and a welcoming setup for contributors?

24Critical · 18% of overall
How it's scored
0/60Stars — 0 stars
0/25Forks — 0 forks
0/15Watchers — 0 watchers
Inputs used
forks0
stars0
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
How it's scored
22.5/22.5README
0/22.5License — no license file detected
0/18CONTRIBUTING guide
0/13.5Code of conduct
0/7.2Issue template
0/6.3PR template
Inputs used
has_readmeyes
has_licenseno
has_contributingno
has_issue_templateno
has_code_of_conductno
has_pull_request_templateno
How it's scored
48.1/80Monthly downloads — 4,063 downloads/month across npm
0/20Registry dependents — not reported by this ecosystem
Inputs used
packages@e4a/pg-js
dependents
ecosystemsnpm
total_downloads
monthly_downloads4,063
Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.

Sustainability & Governance

Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?

60Moderate · 24% of overall
How it's scored
9/54Bus factor — 1 contributor(s) cover half of all commits
0/22.5Commit distribution — top contributor authored 100% of commits
1.4/13.5Contributor breadth — 1 contributors
0/10OpenSSF Scorecard: Contributors — project has 0 contributing companies or organizations -- score normalized to 0
Inputs used
bus_factor1
contributors_sampled1
top_contributor_share1
How it's scored
41.9/46.8Issue resolution — 90% of issues closed
37.7/38.3PR acceptance — 75/76 decided PRs merged
12/15OpenSSF Scorecard: Code-Review — Found 4/5 approved changesets -- score normalized to 8
Inputs used
merged_prs75
open_issues4
closed_issues35
issue_closed_ratio0.897
closed_unmerged_prs1
How it's scored
30/30Ownership backing — organization-owned
0/20Verified domain
6.1/25Owner reach — 6 followers of encryption4all
19.9/25Track record — 16 public repos, account ~5 yr old
Inputs used
followers6
owner_typeOrganization
is_verified
owner_loginencryption4all
public_repos16
account_age_days1,992
How it's scored
25/25Published & resolvable — 1 package(s) on npm
35/35Publish recency — latest publish 4 days ago
20/20Version history — 51 published versions
20/20Not deprecated — active, not deprecated or yanked
Inputs used
packages@e4a/pg-js
ecosystemsnpm
any_deprecatedno
min_days_since_publish4

Engineering Quality

Are baseline engineering and documentation practices in place?

57Moderate · 20% of overall
How it's scored
24/24CI workflows — 3 workflow(s)
24/24Tests present
0/16Linter config
0/9.6Pre-commit hooks
0/6.4.editorconfig
20/20OpenSSF Scorecard: CI-Tests — 14 out of 14 merged PRs checked by a CI test -- score normalized to 10
Inputs used
has_ciyes
has_testsyes
has_editorconfigno
has_linter_configno
has_precommit_configno

Documentation

40At risk
How it's scored
30/30README
0/25Documentation directory
0/15Documentation / homepage site
0/10Repository description
0/10Topics
10/10Wiki
Inputs used
topics
has_wikiyes
homepage
has_readmeyes
has_docs_dirno
has_descriptionno

Security

Are visible security and supply-chain practices strong, without unresolved high-risk jurisdiction exposure?

53Moderate · 16% of overall
How it's scored
7.5/7.5Binary-Artifacts — no binaries found in the repo
3.8/7.5Branch-Protection — branch protection is not maximal on development and all release branches
2.5/2.5CI-Tests — 14 out of 14 merged PRs checked by a CI test -- score normalized to 10
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
6/7.5Code-Review — Found 4/5 approved changesets -- score normalized to 8
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
0/5Fuzzing — project is not fuzzed
0/2.5License — license file not detected
7.5/7.5Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
5/5Packaging — packaging workflow detected
2.5/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 5
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — no data
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
6.8/7.5Vulnerabilities — 1 existing vulnerabilities detected
Inputs used
sourceopenssf_scorecard
checks_evaluated17
scorecard_versionv5.5.0
checks_inconclusive1
scorecard_aggregate5.3
Excluded from scoring (no data or not applicable): signed_releases. Remaining weights renormalized.

AI Readiness

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.

75Good · 0% of overall
How it's scored
45/45Agent instructions — CLAUDE.md
0/15Machine-readable docs (llms.txt)
40/40Legible commit history — 74 of 74 human commits state their intent (structured subject or explanatory body)
Inputs used
has_llms_txtno
legible_history_share1
agent_instruction_filesCLAUDE.md
agent_instruction_max_bytes10,266
How it's scored
0/18One-command bootstrap
22/22Automated tests
0/11Lint / format config
11/11Static type checking — tsconfig.json
10/10Reproducible environment — lockfile
10/10Demonstrated agent practice — 13 of the last 100 commits agent-authored or agent-credited
0/8Automated maintenance — no automated dependency updates observed
5/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 5
Inputs used
has_nixno
has_testsyes
lockfilespackage-lock.json
has_dockerfileno
typed_languageyes
bootstrap_files
has_devcontainerno
has_linter_configno
typecheck_configstsconfig.json
agent_commit_share0.13
toolchain_manifests
dependency_bot_commit_share0
How it's scored
45/45Type-checkable code — TypeScript (statically typed)
55/55Manageable file sizes — 0/60 source files over 60KB
Inputs used
primary_languageTypeScript
largest_source_bytes41,642
source_files_sampled60
oversized_source_files0

Key facts

0GitHub stars
1contributors
210commits, last 12 months
0days since last push
49releases
1bus factor
4open issues
npmpackage ecosystems

Data collection warnings

  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository
  • deps.dev does not index npm:@e4a/pg-js@2.3.1; advisories assessed against the repository dependency graph instead

More detail

OpenSSF Scorecard 5.3 / 10
5.3aggregate

Independent, tool-agnostic security assessment from the open-source OpenSSF Scorecard. Each check rewards a security practice, not a specific vendor's tool. Checks Scorecard could not determine are marked n/a and excluded from the security score (never counted as zero).Scorecard v5.5.0 · 2026-07-22 02:15 UTC

10Binary-Artifactsno binaries found in the repo
5Branch-Protectionbranch protection is not maximal on development and all release branches
10CI-Tests14 out of 14 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
8Code-ReviewFound 4/5 approved changesets -- score normalized to 8
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
0Licenselicense file not detected
10Maintained30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
10Packagingpackaging workflow detected
5Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 5
0SASTSAST tool is not run on all commits -- score normalized to 0
0Security-Policysecurity policy file not detected
n/aSigned-Releasesno releases found
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
9Vulnerabilities1 existing vulnerabilities detected
Direct dependencies 6
RegistryPackageVersion constraintManifest
npm@e4a/pg-wasm^0.6.1package.json
npm@privacybydesign/yivi-client^1.0.0package.json
npm@privacybydesign/yivi-core^1.0.0package.json
npm@privacybydesign/yivi-css^1.0.1package.json
npm@privacybydesign/yivi-web^1.0.1package.json
npm@transcend-io/conflux^6.1.3package.json
All dependencies not collected

The resolved dependency set could not be collected for this report: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Raw JSON report machine-readable
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 512,
      "has_wiki": true,
      "homepage": null,
      "languages": {
        "JavaScript": 11281,
        "TypeScript": 283658
      },
      "pushed_at": "2026-07-21T14:56:52Z",
      "created_at": "2026-03-25T12:12:25Z",
      "owner_type": "Organization",
      "updated_at": "2026-07-21T15:00:34Z",
      "description": null,
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": null,
      "default_branch": "main",
      "license_spdx_raw": null,
      "primary_language": "TypeScript",
      "significant_languages": [
        "TypeScript"
      ]
    },
    "owner": {
      "blog": null,
      "name": "Encryption for all",
      "type": "Organization",
      "login": "encryption4all",
      "company": null,
      "location": null,
      "followers": 6,
      "avatar_url": "https://avatars.githubusercontent.com/u/78606495?v=4",
      "created_at": "2021-02-05T15:03:12Z",
      "is_verified": null,
      "public_repos": 16,
      "account_age_days": 1992
    },
    "license": {
      "state": "absent",
      "spdx_id": null,
      "raw_spdx": null,
      "file_present": false,
      "scorecard_found": false,
      "profile_has_license": false
    },
    "activity": {
      "releases": [
        {
          "tag": "v2.3.1",
          "kind": "patch",
          "published_at": "2026-07-17T05:57:23Z"
        },
        {
          "tag": "v2.3.0",
          "kind": "minor",
          "published_at": "2026-07-16T18:02:59Z"
        },
        {
          "tag": "v2.2.0",
          "kind": "minor",
          "published_at": "2026-07-15T07:34:21Z"
        },
        {
          "tag": "v2.1.7",
          "kind": "patch",
          "published_at": "2026-07-15T07:16:00Z"
        },
        {
          "tag": "v2.1.6",
          "kind": "patch",
          "published_at": "2026-07-15T07:12:38Z"
        },
        {
          "tag": "v2.1.5",
          "kind": "patch",
          "published_at": "2026-07-14T14:13:46Z"
        },
        {
          "tag": "v2.1.4",
          "kind": "patch",
          "published_at": "2026-07-06T13:44:27Z"
        },
        {
          "tag": "v2.1.3",
          "kind": "patch",
          "published_at": "2026-07-06T13:39:24Z"
        },
        {
          "tag": "v2.1.2",
          "kind": "patch",
          "published_at": "2026-07-02T11:37:42Z"
        },
        {
          "tag": "v2.1.1",
          "kind": "patch",
          "published_at": "2026-07-02T11:36:31Z"
        },
        {
          "tag": "v2.1.0",
          "kind": "minor",
          "published_at": "2026-06-19T12:09:24Z"
        },
        {
          "tag": "v2.0.2",
          "kind": "patch",
          "published_at": "2026-06-08T12:49:02Z"
        },
        {
          "tag": "v2.0.1",
          "kind": "patch",
          "published_at": "2026-06-03T09:49:36Z"
        },
        {
          "tag": "v2.0.0",
          "kind": "major",
          "published_at": "2026-06-02T10:52:46Z"
        },
        {
          "tag": "v1.11.0",
          "kind": "minor",
          "published_at": "2026-06-02T07:46:35Z"
        },
        {
          "tag": "v1.10.3",
          "kind": "patch",
          "published_at": "2026-06-02T07:36:39Z"
        },
        {
          "tag": "v1.10.2",
          "kind": "patch",
          "published_at": "2026-06-02T07:35:28Z"
        },
        {
          "tag": "v1.10.1",
          "kind": "patch",
          "published_at": "2026-06-02T07:34:23Z"
        },
        {
          "tag": "v1.10.0",
          "kind": "minor",
          "published_at": "2026-05-24T13:45:10Z"
        },
        {
          "tag": "v1.9.0",
          "kind": "minor",
          "published_at": "2026-05-24T13:33:37Z"
        },
        {
          "tag": "v1.8.0",
          "kind": "minor",
          "published_at": "2026-05-16T12:49:11Z"
        },
        {
          "tag": "v1.7.1",
          "kind": "patch",
          "published_at": "2026-05-13T16:30:29Z"
        },
        {
          "tag": "v1.7.0",
          "kind": "minor",
          "published_at": "2026-05-13T15:43:25Z"
        },
        {
          "tag": "v1.6.2",
          "kind": "patch",
          "published_at": "2026-05-12T07:24:14Z"
        },
        {
          "tag": "v1.6.1",
          "kind": "patch",
          "published_at": "2026-05-12T07:21:56Z"
        },
        {
          "tag": "v1.6.0",
          "kind": "minor",
          "published_at": "2026-05-07T20:45:17Z"
        },
        {
          "tag": "v1.5.0",
          "kind": "minor",
          "published_at": "2026-05-07T19:01:42Z"
        },
        {
          "tag": "v1.4.0",
          "kind": "minor",
          "published_at": "2026-05-07T12:12:49Z"
        },
        {
          "tag": "v1.3.0",
          "kind": "minor",
          "published_at": "2026-05-06T11:22:20Z"
        },
        {
          "tag": "v1.2.0",
          "kind": "minor",
          "published_at": "2026-05-02T10:24:25Z"
        },
        {
          "tag": "v1.1.0",
          "kind": "minor",
          "published_at": "2026-05-01T11:48:35Z"
        },
        {
          "tag": "v1.0.3",
          "kind": "patch",
          "published_at": "2026-05-01T11:32:38Z"
        },
        {
          "tag": "v1.0.2",
          "kind": "patch",
          "published_at": "2026-05-01T11:31:34Z"
        },
        {
          "tag": "v1.0.1",
          "kind": "patch",
          "published_at": "2026-05-01T11:30:26Z"
        },
        {
          "tag": "v0.10.0",
          "kind": "minor",
          "published_at": "2026-04-24T11:12:09Z"
        },
        {
          "tag": "v0.9.3",
          "kind": "patch",
          "published_at": "2026-04-21T12:27:53Z"
        },
        {
          "tag": "v0.9.2",
          "kind": "patch",
          "published_at": "2026-04-21T09:02:01Z"
        },
        {
          "tag": "v0.9.1",
          "kind": "patch",
          "published_at": "2026-04-16T20:18:48Z"
        },
        {
          "tag": "v0.9.0",
          "kind": "minor",
          "published_at": "2026-04-16T20:03:15Z"
        },
        {
          "tag": "v0.8.0",
          "kind": "minor",
          "published_at": "2026-04-16T19:46:25Z"
        },
        {
          "tag": "v0.7.2",
          "kind": "patch",
          "published_at": "2026-04-16T19:20:50Z"
        },
        {
          "tag": "v0.7.1",
          "kind": "patch",
          "published_at": "2026-04-14T13:55:31Z"
        },
        {
          "tag": "v0.7.0",
          "kind": "minor",
          "published_at": "2026-04-10T14:41:20Z"
        },
        {
          "tag": "v0.6.0",
          "kind": "minor",
          "published_at": "2026-04-10T13:28:35Z"
        },
        {
          "tag": "v0.5.0",
          "kind": "minor",
          "published_at": "2026-04-09T14:21:55Z"
        },
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2026-04-09T08:59:25Z"
        },
        {
          "tag": "v0.3.0",
          "kind": "minor",
          "published_at": "2026-04-09T07:47:03Z"
        },
        {
          "tag": "v0.2.0",
          "kind": "minor",
          "published_at": "2026-03-30T08:30:09Z"
        },
        {
          "tag": "v0.1.3",
          "kind": "patch",
          "published_at": "2026-03-25T15:21:15Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "6c0f55443706ba27fda1db73f48655795cb151d9",
          "body": "test(encrypt): cover encryptPipeline, sealRaw and awaitAllOrAbort",
          "is_bot": false,
          "headline": "Merge pull request #117 from encryption4all/test/encrypt-pipeline",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-21T14:56:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9e821471ad440126cdb2c5f37331b41047343659",
          "body": "The encrypt/upload pipeline in src/crypto/encrypt.ts had no direct test\ncoverage. Add unit tests with sealStream/loadWasm and the Cryptify upload\nsink mocked:\n\n- awaitAllOrAbort: happy path (no abort), first-failure abort + re-throw\n  of the first error from either side, loser-rejection is swallowed\n[…]\nciphertext.\n\nawaitAllOrAbort is exported for direct testing; it is not re-exported from\nsrc/index.ts, so the package's public API is unchanged.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "test(encrypt): cover encryptPipeline, sealRaw and awaitAllOrAbort",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-17T17:09:23Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c7d21aae03cadcf4fbf8e09a7b6beed73d3d6e4f",
          "body": "fix: floor the seal policy timestamp so it is never in the future",
          "is_bot": false,
          "headline": "Merge pull request #116 from encryption4all/fix/seal-timestamp-floor",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-17T05:56:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f3169a6004a67b1d7bb749e083f18052e7155463",
          "body": "The PKG rejects a USK request whose timestamp is > now (\"chronology error\").\nThe seal timestamp was stamped with Math.round(Date.now()/1000), which can\nland up to ~1s ahead of the real time, so an encrypt→decrypt within the same\nsecond could fail. In normal usage the multi-second gap masks it; the e\n[…]\ne\nharness's headless flow (encryptionall/postguard-e2e) completes in <1s and\nsurfaced it.\n\nExtract nowSeconds() (floor) and use it for both seal timestamps. Adds unit\ntests pinning the floor behavior.",
          "is_bot": false,
          "headline": "fix: floor the seal policy timestamp so it is never in the future",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-17T05:50:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "854d5c06ea7c852e8ebb8517687d71f0349bfd51",
          "body": "feat: configurable email attribute types (emailAttributes option)",
          "is_bot": false,
          "headline": "Merge pull request #115 from encryption4all/feat/email-attribute",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-16T18:01:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7b1c6287bd8a0b56e53b096c56262603c7918e91",
          "body": "Review catch: the apiKey dispatch case dropped the configured attributes and\nfetchSigningKeysWithApiKey hardcoded the production type in its request body\n- under an override, an api-key sender would request the production type\nwhile recipient policies used the overridden one. Threaded through\n(dispa\n[…]\nst capturing the request body. Note: the PKG derives the\napi-key signing identity from the business database and ignores this body\ntoday; the configured type is sent anyway for wire-level consistency.",
          "is_bot": false,
          "headline": "fix: thread emailAttributes through the api-key signing path too",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-16T18:01:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b41fe228daf90c6a7bf831b6a63251fe19049642",
          "body": "Part 3 of encryption4all/postguard#236 (the pg-js leg). The pbdf email and\nemail-domain attribute types were hardcoded across recipient builders, key\nrequests, decryption hints, includeSender policies and all signing flows —\nmaking it impossible to run the real SDK in any test environment (pbdf\ncred\n[…]\ns are unaffected — all 212 existing tests pass untouched; 6\nnew tests pin the override and default behavior.\n\nCompanions: encryption4all/postguard#244 (PKG) and\nencryption4all/cryptify#193 (cryptify).",
          "is_bot": false,
          "headline": "feat: configurable email attribute types (emailAttributes option)",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-16T17:52:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f8bd16b154b79b92bb086c2c088f10e79babb7db",
          "body": "feat(sign): prepareSign() — pre-warm a Yivi session for one-tap app open on iOS",
          "is_bot": false,
          "headline": "Merge pull request #103 from encryption4all/feat/prepare-sign-prewarm",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-15T07:33:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c4a26c324fc32ae69543043be92a9379f5dee34f",
          "body": "docs: extend CLAUDE.md with migrated agent notes",
          "is_bot": false,
          "headline": "Merge pull request #104 from encryption4all/chore/add-claude-md",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-15T07:21:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "572c91a5f98c4d9ab4db20fe1fb8ae4866537b20",
          "body": "perf(chunker): fill output buffers from chunk views to cut per-chunk copies",
          "is_bot": false,
          "headline": "Merge pull request #111 from encryption4all/perf/chunker-zero-copy",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-15T07:15:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6181086d3b8986a54e543411fb5b7bbfa2a68a73",
          "body": "fix(download): cancel previous reader before retrying",
          "is_bot": false,
          "headline": "Merge pull request #113 from encryption4all/fix/108-download-reader-leak",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-15T07:11:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c457be42a94d231c673a675cd6f7dc318d9c0294",
          "body": "The pre* hooks run three generators (wasm-base64, yivi-css, version),\nnot two; version.ts is itself a gitignored build-time source.",
          "is_bot": true,
          "headline": "docs: correct generator count in CLAUDE.md build-pipeline note",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-15T07:00:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7d21ce6ffb51fa3fac00fd53895f61808726e973",
          "body": "The migrated 'Repo layout' note described integration.yml as running\n'typecheck, build, test on Node 24', which under-states the actual matrix\n(Node 22 and 24, plus Bun and Deno lanes) and every lane's smoke step. It\nalso contradicted the accurate description already present under 'Releases\nand CI'. Align the two.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "docs: correct integration.yml CI description in repo-layout note",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-15T06:55:46Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "6326ca81c7cb6c410e385205f54e60a3010af702",
          "body": "On the retrying download stream's `start()` source, each attempt opened a\nfresh fetch/reader but never released the previous one on a mid-stream\nerror. On a flapping connection every retry left a dangling response-body\nreader holding a lock on an abandoned stream.\n\nHoist `reader` so the `catch` can \n[…]\nping back to a fresh fetch. Add a\nfail-then-succeed regression test asserting the first attempt's reader is\ncancelled exactly once.\n\nFixes #108\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix(download): cancel previous reader before retrying",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-14T23:40:21Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b5de5f08a7e83e3475459c0db6c63540b15ba049",
          "body": "…copies\n\nChunker copied streamed data three times per emitted chunk: chunk.slice()\nallocated a copy of the slice, which was copied into a reused ArrayBuffer,\nwhich was then copied again into a fresh output buffer before enqueue. Over\nthe whole ciphertext stream on large uploads this is a large amoun\n[…]\nre unchanged.\n\nAdd tests covering data spanning a chunk boundary across multiple transform\ncalls and buffer independence across emitted chunks.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "perf(chunker): fill output buffers from chunk views to cut per-chunk …",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-14T23:39:29Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "7dee2640ec73acf379b9f297ffc50dab8f330201",
          "body": null,
          "is_bot": false,
          "headline": "docs: add agent & contributor notes (migrated from dobby memory)",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-14T18:46:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e8a84f79d825926165c1c1adede03699e1bcd92f",
          "body": "…app open\n\nOn iOS a Yivi Universal Link only opens the app when the navigation happens\ninside a genuine user gesture. If the signing session is started on tap (as\npart of encrypt()), the app deep-link URL doesn't exist yet, so the tap can't\nnavigate synchronously and falls back to Safari.\n\nprepareSi\n[…]\ns` field; when supplied, Sealed.getSigningKeys() uses it\ndirectly and never starts a second session (the internal pipeline already\nthreaded signingKeys). Adds AbortSignal support to the Yivi resolver.",
          "is_bot": false,
          "headline": "feat(sign): add prepareSign() to pre-warm a Yivi session for one-tap …",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-14T14:38:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b2f55ad9948d3c163e68d97830a6d2fe945ddbb6",
          "body": "fix(yivi): use SSE for session status, fall back to polling",
          "is_bot": false,
          "headline": "Merge pull request #102 from encryption4all/fix/yivi-sse-status-updates",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-14T14:12:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f02725a1330b805d3d94b2b0bfaaf393007e0a36",
          "body": "The Yivi session state config hardcoded `serverSentEvents: false`, which\nforced yivi-client's StatusListener into polling for every disclosure/\nsigning session. Enable the irmaserver's Server-Sent Events stream\n(/frontend/statusevents) instead — the same default yivi-client ships —\nso status updates\n[…]\nck to\npolling when the SSE connection can't be established.\n\nApplies to both the signing (resolveSigningKeysFromYivi) and decrypt\n(retrieveUSKViaYivi) flows.\n\nRefs encryption4all/postguard-website#317",
          "is_bot": false,
          "headline": "fix(yivi): use SSE for session status, fall back to polling",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-14T14:07:46Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "54dced1bbb1c5c52946335efc4db7cfe4850ee25",
          "body": "fix: escape quotes in attachment names and sanitize injected header names",
          "is_bot": false,
          "headline": "Merge pull request #99 from encryption4all/fix/97-mime-name-escaping",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-10T08:36:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e044d7ac3b37bfa16b3545cdd12b164a36de963e",
          "body": "fix: harden client-side JWT handling in the Yivi session path",
          "is_bot": false,
          "headline": "Merge pull request #101 from encryption4all/fix/98-jwt-hardening",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-06T13:43:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2a4f2f5b8caf98a5a5c6e73465e7008144b1dc2f",
          "body": "…ars-mime-headers\n\nfix: strip control characters from MIME header values",
          "is_bot": false,
          "headline": "Merge pull request #100 from encryption4all/security/strip-control-ch…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-06T13:38:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d0e47238eb6abdc62a9d9a2835053caaf03567a1",
          "body": "Yivi/IRMA session-result JWTs are decoded client-side without signature\nverification, so their claims must not be trusted verbatim.\n\n- New shared util src/util/jwt.ts -> decodeJwtPayloadUnsafe: structural-only\n  decode (3 non-empty segments, base64url + UTF-8), returns null on any\n  malformation; do\n[…]\nt-provided senderEmail wins over the JWT value.\n\nDefense-in-depth: the PKG server verifies the JWT signature before issuing keys.\n\nResolves #98\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: harden client-side JWT handling in the Yivi session path",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-05T23:24:59Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b4171400a0e9ba3bf143b5e2ab3b6b1affcce927",
          "body": "The comment claimed a bare CR was embedded, but no CR byte is present in\nany field; list the actual control bytes (NUL, SOH, backspace, VT, ESC,\nDEL) instead.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "test: fix control-char test comment to match embedded bytes",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-05T23:20:49Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "4c5618263fa2132164f42c4fa7ed57503231829e",
          "body": "sanitizeHeaderValue now removes C0 control characters and DEL from\nuser-supplied header values, in addition to collapsing CR/LF runs to a\nsingle space, so no control byte survives interpolation into the MIME\ntemplate. Tab is preserved as valid header whitespace.\n\nAdds regression tests for control-character stripping and tab\npreservation.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: strip control characters from MIME header values",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-05T23:10:19Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "5becc13d72802e783f8f9aaabfd06755558e2d77",
          "body": "…ames\n\nTwo related MIME header-injection gaps in src/email/mime.ts (GHSA-qmf2-7wp2-gm9x):\n\n- injectMimeHeaders interpolated header names (and values) from headersToInject\n  without stripping CR/LF, allowing extra header lines to be smuggled in. Both\n  name and value are now run through sanitizeHeade\n[…]\nnject additional parameters. Added\n  escapeQuotedStringParam (backslash + quote escaping after CR/LF folding).\n\nAdds regression tests for both.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: escape quotes in attachment names and sanitize injected header n…",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-04T23:06:54Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b5c4b19e632101935e273954173613355e80ff92",
          "body": "fix: sanitize user input in MIME construction",
          "is_bot": false,
          "headline": "Merge pull request #96 from encryption4all/fix/93-mime-sanitization",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-02T11:36:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7dc6253ddfcce2d81811aad18e7dd85d743521b9",
          "body": "fix(envelope): tighten validation of the websiteUrl option",
          "is_bot": false,
          "headline": "Merge pull request #95 from encryption4all/fix/94-validate-website-url",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-02T11:35:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "67b1e496197880a1e0760290116f1929e2eaf295",
          "body": "chore: update dependencies",
          "is_bot": false,
          "headline": "Merge pull request #92 from encryption4all/chore/update-dependencies",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-07-02T08:34:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2c884aa31e18743bf221b2f8ae830b127f04b0cc",
          "body": "Harden src/email/mime.ts against header injection:\n\n- Strip CR/LF runs from all user-supplied header values (from, to, cc,\n  subject, inReplyTo, references, attachment name/type) before they are\n  interpolated into the MIME template, collapsing them to a single space\n  so a crafted value cannot smug\n[…]\nme\n  containing metacharacters is matched literally rather than as a pattern.\n\nAdds tests/mime.test.ts covering both hardening paths.\n\nRefs #93\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: sanitize user input in MIME construction (GHSA-hvj8-v57h-f99m)",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-02T03:03:33Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "ab381a55317c61c852b05065c7c2c729bb715471",
          "body": "Interpolating options.websiteUrl verbatim into the generated email's\nhref/src attributes allowed javascript:/data: schemes and\nattribute-breaking strings to be injected into the HTML body. Validate\nthat the resolved URL is a well-formed absolute https: URL via new URL()\nand throw otherwise; re-serialize from origin + pathname so the parser's\npercent-encoding neutralizes any attribute-breaking characters.\n\nRefs GHSA-6q8p-8fxx-wc7f\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: validate caller-supplied websiteUrl before embedding in HTML",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-02T03:02:30Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "b7675779bf00bfcf5d821086dec2671948946ebe",
          "body": "Resolve all open dependency CVEs (undici, vite) flagged in #91.\n\n- undici bumped to 7.28.0 (top-level) and 6.27.0 (under\n  @actions/http-client), fixing GHSA-vmh5-mc38-953g, GHSA-vxpw-j846-p89q,\n  GHSA-hm92-r4w5-c3mj, GHSA-p88m-4jfj-68fv, GHSA-pr7r-676h-xcf6,\n  GHSA-35p6-xmwp-9g52, GHSA-g8m3-5g58-fq\n[…]\nm\n  package tarball's bundled deps).\n\nnpm audit now reports 0 vulnerabilities. typecheck, build and the full\nvitest suite (162 tests) all pass.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "chore: update dependencies to resolve undici and vite CVEs",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-07-01T22:12:26Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "f4debe00e35f7d8fe020619d04e4fbe9a3b94397",
          "body": "feat: report client version on every PKG and Cryptify request",
          "is_bot": false,
          "headline": "Merge pull request #90 from encryption4all/feat/client-version-header",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-19T12:08:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a11c139c2595e4c861f0afae40c2bdccb3b1cd9e",
          "body": "…build\n\nAddresses the two non-blocking review nits on #90:\n\n- detectHost() now recognises a Web Worker (WorkerGlobalScope/importScripts)\n  as host=browser. WASM crypto is commonly offloaded to a worker where\n  window/document are undefined, so that traffic was reported as unknown.\n  detectHost is no\n[…]\ntime `prepare` lifecycle\n  already regenerates version.ts from the bumped package.json and rebuilds,\n  so the exec build just doubled the work.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "fix: attribute Web Worker traffic as browser; drop redundant release …",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-06-19T11:56:11Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "261459cf59853ad34dffe4d4470dde730ace528e",
          "body": "The two new tests hard-coded host='node', which fails on the Deno/Bun\nintegration lanes where detectHost() correctly returns 'deno'/'bun'. Mirror\ndetectHost()'s ordering in the runtime assertion, and test comma-sanitisation\ndirectly via an exported sanitizeField helper instead of stubbing runtime\nglobals (which is order-sensitive under Deno). Implementation unchanged.",
          "is_bot": false,
          "headline": "test: make client-version tests runtime-agnostic",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-15T15:32:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7dfbe518b3f3074b7e90d3bd287ce3e987bb10b2",
          "body": "The SDK now stamps a 'host,host_version,pg-js,<version>' client-version header\n(reused from pg-pkg) onto every PKG and Cryptify request, so servers can attribute\ntraffic by SDK + version. A caller-supplied header (any casing) wins, so embedding\nhosts (e.g. the Outlook add-in sending pg4ol) keep thei\n[…]\n PostGuardBase (caller wins, case-insensitive).\n- Thread config.headers through the cryptify upload/download functions, which\n  previously hard-coded their headers and dropped config.headers entirely.",
          "is_bot": false,
          "headline": "feat: send X-POSTGUARD-CLIENT-VERSION on every request",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-15T15:06:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "70d435c2200d067caa73503e2c9a7028701bfc5b",
          "body": "…-identity\n\nfix(decrypt): surface verified private signing identity to recipients",
          "is_bot": false,
          "headline": "Merge pull request #89 from encryption4all/fix/expose-private-signing…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-08T12:47:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0f08840b08ccacf42d13ed70e998cabacc217050",
          "body": "Drop the rationale comment in front of the unseal-result capture (the PR\ndescription already carries the why), and give the unsealAndCollect test\ndistinguishing public attributes so it actually locks in that the\npost-unseal VerificationResult overrides the pre-unseal sender.",
          "is_bot": false,
          "headline": "fix(decrypt): address PR feedback on verified-identity capture",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-05T15:28:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d76b07a301012ea6f596e8da2564f74a3fbf304f",
          "body": "`unsealAndCollect` was discarding the value returned by\n`unsealer.unseal(...)`. pg-wasm's `StreamUnsealer.unseal` resolves with\nthe full `VerificationResult { public, private? }` derived from the\ninner IBS signature after decryption, so dropping it threw away every\nattribute the sender signed under \n[…]\neds to\nchange.\n\nA unit test for `unsealAndCollect` against a stub unsealer locks the\nbehavior in: it resolves `unseal()` with `{public, private}` and\nasserts the returned `sender.private.con` matches.",
          "is_bot": false,
          "headline": "fix(decrypt): surface verified private signing identity to recipients",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-05T13:10:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "44e52504c003f77de9ad251855598293ec099621",
          "body": "…n-test\n\nfix(upload): pin onUploadInit acceptance with a regression test",
          "is_bot": false,
          "headline": "Merge pull request #88 from encryption4all/fix/onuploadinit-regressio…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-03T09:48:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a97dd9f7558f364aed2ef2b451bda5da3cd4f100",
          "body": "A prior runtime validator hand-maintained an allowlist of upload keys\nand started rejecting `onUploadInit` when the type was extended but\nthe allowlist wasn't. The validator was removed in #87, but that\nlanded under a `refactor:` prefix so semantic-release didn't cut a\nrelease and consumers on @e4a/\n[…]\nat asserts\n`sealed.upload({ onUploadInit })` does not throw \"unknown option\" —\npinning the contract so any future attempt to reintroduce a\nhand-maintained allowlist can't silently re-break the option.",
          "is_bot": false,
          "headline": "fix(upload): pin onUploadInit acceptance with a regression test",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-03T09:32:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "10261087db988e8190365e7683159cc496978e0f",
          "body": "…ts-onUploadInit\n\nrefactor(upload): remove runtime option validator, trust the types",
          "is_bot": false,
          "headline": "Merge pull request #87 from encryption4all/fix/upload-validator-accep…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-03T09:25:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f24762cdf850018990b33ebf752afbb096f9dd62",
          "body": "`validateUploadOptions` duplicated the shape of `UploadOptions` as a\nhand-maintained set of allowed keys + per-key value-type checks. The\ntype already encodes all of that — and when we recently added\n`onUploadInit` to `UploadOptions` we updated the type but forgot the\nvalidator, so callers passing t\n[…]\nfriendly compile-time error, and untyped JS callers\nwill get a downstream failure that's no worse than what most JS\nlibraries provide. No public API change; the deleted code path was\npurely defensive.",
          "is_bot": false,
          "headline": "refactor(upload): remove runtime option validator, trust the types",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-03T09:24:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d3a716870397fa1fc1bce427223c51ec10ab907d",
          "body": "…autounzip\n\nfeat(decrypt): onDownloadProgress callback + auto-unzip files",
          "is_bot": false,
          "headline": "Merge pull request #86 from encryption4all/feat/decrypt-progress-and-…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T10:51:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c99027abd3f0aae9455455829c1f496bc545ba64",
          "body": "…sanitize, concurrency, tests\n\n- ProgressPipe now buffers the latest state so a callback attached\n  after bytes have flowed still receives one event. Fixes small-\n  payload case where inspect() drains the whole stream before\n  decrypt() can attach onDownloadProgress.\n- Restore DecryptFileResult.blob\n[…]\n-entry,\n  directory-filter, empty, and ordering-under-concurrency cases\n- api.test.ts: Content-Length parsing — numeric, 0, non-numeric, missing\n- download.test.ts: sanitizeDownloadFilename edge cases",
          "is_bot": false,
          "headline": "fix(decrypt): address review — small-payload progress, escape hatch, …",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T09:51:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7fb2083905e08f7fc91bbb78021be0e0b28d7af0",
          "body": "Lets consumers reference the package via `github:org/repo#branch` URLs\n(needed for cross-repo PRs where the website branch wants to test\nagainst an unpublished pg-js branch). npm installs devDependencies and\nruns prepare when fetching a git dep, building dist/ before the\npackage is wired into the consumer.",
          "is_bot": false,
          "headline": "chore: add prepare script so git-installs build automatically",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T09:31:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a4a4eeb4b9fa15316f4249fccdd1fd98087f2c8d",
          "body": "Adds an onDownloadProgress callback to DecryptInput so consumers can\nrender a real progress bar during decrypt(). The download stream is\ncreated in inspect() but bytes flow during decrypt(); a mutable-\ncallback ProgressPipe bridges this gap. Total size comes from\nContent-Length; when absent the call\n[…]\nntry shortcut (raw-data round trip) is\npreserved.\n\nBREAKING CHANGE: DecryptFileResult.files is now Array<{name, blob}>\n(was string[]); DecryptFileResult.blob is removed; download() takes\nno arguments.",
          "is_bot": false,
          "headline": "feat(decrypt): add onDownloadProgress + auto-unzip files",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T09:25:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "df02f3021a084d950e4c5050826c2c0a2c494020",
          "body": "ci: enable npm cache in setup-node to speed up Integration / Delivery",
          "is_bot": false,
          "headline": "Merge pull request #85 from encryption4all/ci/npm-cache-setup-node",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T08:20:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b3a5225d5dbf4bc0e2ea7d0b4a8f83df565c89ca",
          "body": "Add cache: 'npm' to all actions/setup-node@v6 steps in integration.yml\nand delivery.yml so npm ci benefits from package-lock.json-keyed caching.",
          "is_bot": false,
          "headline": "ci: enable npm cache in setup-node for faster installs",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:52:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1e16f117d5077948fc64612747e7569461b3922a",
          "body": "…utes\n\nfeat(sign): accept Yivi condiscon in pg.sign.yivi attributes",
          "is_bot": false,
          "headline": "Merge pull request #78 from encryption4all/feat/condiscon-sign-attrib…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:45:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f532af8c43d44d85d5f1d8843f524e49df6ee9e4",
          "body": "- Add docstring to AttrConItem hinting at Array.isArray narrowing\n- Document that callers must not re-request the email attribute\n- Add test for empty-inner-array optional-discon convention",
          "is_bot": false,
          "headline": "fix: address dobby review comments",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:40:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a1dda70177a039c31509fb1719e9c21d9f77a52b",
          "body": "…cause\n\nfix: preserve original error as cause in unsealAndCollect",
          "is_bot": false,
          "headline": "Merge pull request #84 from encryption4all/fix/preserve-unseal-error-…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:35:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5e7059e41c7866c19c5effc8c4d3ab08d98adcb3",
          "body": "perf: collect sealRaw output as chunks, single-allocate at end",
          "is_bot": false,
          "headline": "Merge pull request #83 from encryption4all/perf/sealraw-chunk-collect",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:34:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "291bf601e6319364918e7d1c914599d112e0f65d",
          "body": "…error\n\nfix(envelope): surface tier-3 upload failures instead of broken fallback",
          "is_bot": false,
          "headline": "Merge pull request #82 from encryption4all/fix/envelope-tier3-upload-…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-02T07:33:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ce41f9b107d7545c5026040a999e1058381e044b",
          "body": "unsealAndCollect swallowed every error from `unsealer.unseal()` and\nre-threw `IdentityMismatchError`, masking transient failures, aborts,\nand WASM bugs. Forward `ErrorOptions` through `DecryptionError` and\n`IdentityMismatchError` so the original error is reachable via\n`.cause`, and let `AbortError` propagate unchanged.\n\nCloses #80",
          "is_bot": true,
          "headline": "fix: preserve original error as cause in unsealAndCollect",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-06-01T23:39:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "69b270e40bf94b98b4c598735b953856932c4bcf",
          "body": "sealRaw was reallocating and copying the entire prior buffer on every\nwrite — O(N²·c) bytes copied for N chunks of average size c. Mirror the\ndecrypt.ts:218-224 pattern: collect chunks during streaming, then\nallocate once and copy each into place. Output bytes unchanged.\n\nCloses #81",
          "is_bot": true,
          "headline": "perf: collect sealRaw output as chunks, single-allocate at end",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-06-01T23:38:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d728838eead306050f2d1f94af6eb86deef66928",
          "body": "…broken fallback\n\nTier 3 has no local attachment, so swallowing a Cryptify upload rejection produced an envelope whose manual-upload body pointed the recipient at a file that does not exist. Re-throw the error on tier 3; tier 2 still falls through to manual-upload (attachment is the fallback) but now warns instead of being fully silent.\n\nCloses #79",
          "is_bot": true,
          "headline": "fix(envelope): surface tier-3 upload failures instead of producing a …",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-06-01T23:38:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fbe1dec13061fc62faef54f2fb0c592684e5c615",
          "body": "Extend `YiviSign.attributes` from `AttrReq[]` to `AttrConItem[]`, where\neach entry is either a single attribute (legacy flat shape — keep\n`optional: true` for the skip-one case) or a Yivi\ndisjunction-of-conjunctions `AttrReq[][]`. An empty inner array marks\nthe discon as optional, mirroring Yivi nat\n[…]\nr, additive bump.\n\nTests: new `buildStartRequestBody` block in `tests/postguard.test.ts`\ncovers the no-attr, email-bound, legacy-flat-with-optional, and\nmixed-Single/Discon cases. `npm test`: 115/115.",
          "is_bot": false,
          "headline": "feat(sign): accept Yivi condiscon in pg.sign.yivi attributes",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-06-01T13:40:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e11942da25a6470c76be42f044566deca5a916ff",
          "body": "…-notice\n\nfeat(upload): info notice when notify is unset on first upload",
          "is_bot": false,
          "headline": "Merge pull request #77 from encryption4all/feat/upload-silent-default…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:44:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "61b4459270d5ca36a553358213bf5edcf7301502",
          "body": "Closes the one failure mode neither TypeScript nor validateUploadOptions\ncan catch: a caller who simply forgets to set `notify` and silently gets\nno recipient email. `notify` is optional in `UploadOptions`, so leaving\nit out type-checks cleanly — the user only finds out via a support\nticket that \"th\n[…]\nt) so long-running\nprocesses don't flood. Suppressed by passing `notify` explicitly:\n`{ notify: { recipients: true } }` to email, or\n`{ notify: { recipients: false } }` to keep silent intent explicit.",
          "is_bot": false,
          "headline": "feat(upload): info notice when notify is unset on first upload",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:42:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b6d972601de45fd51a5094f39819fe788e992e70",
          "body": "…upport\n\nfeat(runtime): support Node 20.3+, Bun, and Deno for encrypt + upload",
          "is_bot": false,
          "headline": "Merge pull request #76 from encryption4all/feat/non-browser-runtime-s…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:32:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c3f3e78697455c6ba6abf69704a506fe1af189c0",
          "body": "tsdown@0.22 requires Node ^22.18 || >=24; it can't build on Node 20.\nSince we don't intend to claim Node 20 support, drop it from the matrix\nand bump engines.node to >=22 to match. README and CLAUDE.md updated to\nreflect Node 22 as the floor.",
          "is_bot": false,
          "headline": "ci: drop Node 20 from matrix, require Node 22+",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:23:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0f8b2080bf71cfffc65ce35e63eb37e8182f62b6",
          "body": "Addresses inline comments from dobby-coder's review:\n\n- src/util/zip.ts: narrow the conflux self shim to save/restore around\n  the dynamic import instead of permanently mutating globalThis.self.\n  Avoids surprising downstream libraries that use `typeof self !==\n  'undefined'` as a browser-detection \n[…]\nns surface here as a CI failure\n  on this SDK rather than confused for upstream breakage.\n\n- README.md: documents the minimum Bun (1.0.16+) and Deno (1.39+)\n  versions — both gated by AbortSignal.any.",
          "is_bot": false,
          "headline": "refactor(runtime): apply review feedback from #76",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T13:04:49Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "107870acfef70baf59ea8647b087f5e028cb2526",
          "body": "README gets a \"Server-side usage\" section listing what works (encrypt +\nupload via sign.apiKey or sign.session), what's browser-only (sign.yivi,\nresult.download), and how to run scripts/smoke.mjs.\n\nCLAUDE.md gets a runtime-support section plus notes on the two\nnon-obvious gotchas (FileList guard, conflux self shim) so future\ncontributors don't accidentally regress them.",
          "is_bot": false,
          "headline": "docs: document Node, Bun, and Deno runtime support",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:46:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c2cbf0c9239ffbe35d26639710de546ec694ca87",
          "body": "Adds three CI jobs to integration.yml: Node (matrix over 20/22/24), Bun,\nand Deno. Each lane builds the dist and runs scripts/smoke.mjs in dry\nmode, which loads the dist, exercises createZipReadable, and verifies\nglobal availability — catches both bugs fixed in the previous commit\nwithout needing live credentials.\n\nThe smoke script can also do a full live upload when PG_API_KEY is set;\nuseful for one-off manual verification.",
          "is_bot": false,
          "headline": "ci: matrix-test Node 20/22/24, Bun, and Deno with smoke script",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:46:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2adff7ae11d0de16f6d2367518791f3032ce6ad0",
          "body": "Two bugs prevented the SDK from working in non-browser runtimes:\n\n  1. `instanceof FileList` in sealed.ts throws ReferenceError when\n     FileList is not a global. FileList is browser-only; Node, Bun, and\n     Deno don't have it. Now typeof-guarded.\n\n  2. `@transcend-io/conflux/dist/esm/bigint.js` r\n[…]\nlower bound is required\nfor AbortSignal.any, which the encrypt pipeline uses.\n\nVerified end-to-end against staging.postguard.eu under Node 26, Bun\n1.3.14, and Deno 2.8.0 (real UUID returned for each).",
          "is_bot": false,
          "headline": "feat(runtime): support Node 20.3+, Bun, and Deno for encrypt + upload",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:46:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "89b07223ca5600097020ee9356f468e6ea95419a",
          "body": "Captures the prebuild generator step (base64 WASM + wasm-bindgen shim\npatch), the lazy builder surface, and the Conventional Commits\nrequirement so future contributors don't have to reverse-engineer them.",
          "is_bot": false,
          "headline": "docs: add CLAUDE.md with build, architecture, and pg-wasm patching notes",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:44:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3b9c2f20ebe01b9fc436aaad9e709ba8cc6c356b",
          "body": "Surfaces the most common misconfigurations (boolean notify, top-level\nrecipients, typos in notify keys, non-object opts) as synchronous\nTypeErrors with messages that point at the correct shape. Previously\nthese silently degraded to \"no notification email sent\" because every\nfield coalesced to undefined and Cryptify defaulted to silent.",
          "is_bot": false,
          "headline": "feat(upload): validate upload options shape with clear errors",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-24T12:43:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3af134536d127d14ad1866c33e81b44b68feae11",
          "body": "chore: bump vitest to 4.1.7",
          "is_bot": false,
          "headline": "Merge pull request #75 from encryption4all/chore/vitest-4.1.7",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-21T11:55:34Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2dabb9351eac9577a43bb89d7af8ef1a5514397a",
          "body": null,
          "is_bot": true,
          "headline": "chore: bump vitest to 4.1.7",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-20T22:20:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "39a877af5275796e53390fb56131050c6bd47629",
          "body": "chore: update dependencies",
          "is_bot": false,
          "headline": "Merge pull request #73 from encryption4all/chore/update-deps-2026-05-16",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-17T15:59:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c6905cfa83e24601b15ad725ab5a9e0d917e0af2",
          "body": "- @e4a/pg-wasm 0.6.0 -> 0.6.1\n- @privacybydesign/yivi-client 1.0.0-beta.6 -> 1.0.0\n- @privacybydesign/yivi-core 1.0.0-beta.6 -> 1.0.0\n- @privacybydesign/yivi-css 1.0.0-beta.7 -> 1.0.1\n- @privacybydesign/yivi-web 1.0.0-beta.6 -> 1.0.1\n\nRefs #72",
          "is_bot": true,
          "headline": "chore: update dependencies",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-16T22:18:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dcdd6591f58976364f8220f4cedbe86d0e2bce3b",
          "body": "feat(upload): onUploadInit callback exposes uuid + recoveryToken pre-chunk",
          "is_bot": false,
          "headline": "Merge pull request #71 from encryption4all/feat/upload-init-callback",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-16T12:48:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b8a483ab5d9dc72b20eec89eab6dc8c8da5032af",
          "body": "…chunk\n\nAdds `onUploadInit?: (info: { uuid: string; recoveryToken: string }) => void`\nto `UploadOptions` and `CreateEnvelopeOptions`. Fires once, synchronously\nafter `upload_init` resolves and before the first chunk PUT, so callers\n(Outlook/Thunderbird addons) can persist `{uuid, recoveryToken}` to \n[…]\nam, and\nforwarded through createEnvelope.\n\nCloses #68\nRefs encryption4all/postguard-tb-addon#103\nRefs encryption4all/postguard-outlook-addon#82\n\nCo-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>",
          "is_bot": true,
          "headline": "feat(upload): onUploadInit callback exposes uuid + recoveryToken pre-…",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-16T12:43:56Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "9453662c37e327d1d854a77a12f9ab008ae54dde",
          "body": "chore: bump @e4a/pg-wasm to 0.6.0",
          "is_bot": false,
          "headline": "Merge pull request #70 from encryption4all/chore/pg-wasm-0.6.0",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-16T10:24:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eb457cac640c13d4d33b2767cfcf951a8875badc",
          "body": "Tracks postguard pg-core-v0.6.0. Public TypeScript API surface is unchanged; only internal wasm-export symbol order shifted and ReadableStreamReaderMode became exported.\n\nCloses #69",
          "is_bot": true,
          "headline": "chore: bump @e4a/pg-wasm to 0.6.0",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-13T22:14:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "146a7ab70ea8acc6071a4c773a8ae467c1c391a9",
          "body": "fix: unwrap data.bin from zip in Opened.decrypt uuid mode",
          "is_bot": false,
          "headline": "Merge pull request #67 from encryption4all/fix/data-bin-unwrap-39",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-13T16:29:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "959a0c2526aa8cee5ee4273635885953d864aa59",
          "body": "Restores symmetry with Sealed.upload() data: mode, which wraps raw bytes\nas a single-entry zip named data.bin. When Opened.decrypt() sees a uuid\nresult whose central directory is exactly ['data.bin'], it now returns\nDecryptDataResult { plaintext, sender } instead of forcing consumers to\nwalk the zip\n[…]\nven (conflux's streaming\nwriter leaves compressedSize=0 in the LFH, so LFH-only walking fails)\nand supports stored (method 0) and deflate (method 8) via\nDecompressionStream('deflate-raw').\n\nCloses #39",
          "is_bot": true,
          "headline": "fix: unwrap data.bin from zip in Opened.decrypt uuid mode",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-13T16:22:04Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "afc9b71ad518127f8bc60bd7923cc8d6e6637c45",
          "body": "chore: update vitest to 4.1.6",
          "is_bot": false,
          "headline": "Merge pull request #64 from encryption4all/chore/vitest-4.1.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-13T15:53:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6205fc309aaf954e82937beae723912812604f2e",
          "body": "…y-token\n\nfeat(api): capture recovery_token and add resumeUpload for cross-restart resume",
          "is_bot": false,
          "headline": "Merge pull request #66 from encryption4all/feat/upload-resume-recover…",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-13T15:42:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3838012e3a1883ae295e3baef404def79b951caf",
          "body": "…art resume\n\nWires the cross-refresh-resume protocol on the SDK side (cryptify#148):\n\n- initUpload now reads the snake-case recovery_token field from the JSON\n  body and returns it on FileState as recoveryToken.\n- New resumeUpload(cryptifyUrl, uuid, recoveryToken) calls\n  GET /fileupload/{uuid}/stat\n[…]\ntoken cases per cryptify's\n  info-hiding behaviour).\n- FileState and resumeUpload re-exported from the package root so\n  consumers (Office addon, Thunderbird, website) can own persistence.\n\nCloses #65",
          "is_bot": true,
          "headline": "feat(api): capture recovery_token and add resumeUpload for cross-rest…",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-13T14:55:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e07f68e0b1870eaa7279e0d3fb3d636076db2b8b",
          "body": "Patch bump from 4.1.5 → 4.1.6. Closes #63.",
          "is_bot": true,
          "headline": "chore: update vitest to 4.1.6",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-12T22:17:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4b78ab4dadc50e9df1578db6f8080abeba07eefd",
          "body": "fix(decrypt-session): bound JWT cache and sweep expired entries on write",
          "is_bot": false,
          "headline": "Merge pull request #61 from encryption4all/fix/jwt-cache-bound-sweep",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-12T07:23:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a8a9a25ddbad8f76ab04f65ee453b5730ff6a9df",
          "body": "perf(envelope): skip base64 of full ciphertext for tier 2/3",
          "is_bot": false,
          "headline": "Merge pull request #62 from encryption4all/fix/skip-base64-tier-2-3",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-12T07:20:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "53a6db6c42c22c0537149750cf27bb7453ed4544",
          "body": "Tier 2/3 envelopes never embed base64 in the body — they use Cryptify\nupload URLs. Computing the full base64 string just to feed pickTier\nwasted CPU and a ciphertext-sized intermediate String (~33% larger\nthan the bytes themselves) that was immediately discarded.\n\nDerive the base64 length arithmetic\n[…]\nd only call uint8ArrayToBase64 inside the tier-1 branch.\nAlso replaces the char-by-char btoa loop with a chunked\nString.fromCharCode(...subarray) accumulator for the remaining tier-1\npath.\n\nCloses #58",
          "is_bot": true,
          "headline": "perf(envelope): skip base64 of full ciphertext for tier 2/3",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-11T23:39:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f5986dd19a25f5bd674ed046ae91695549c9ae97",
          "body": "Cap jwtCache at 100 entries with LRU eviction so long-lived SPA sessions\nthat decrypt for many recipients can't grow the Map indefinitely. On every\ncacheJwt write, sweep entries past their exp (with the same 30s margin used\non read) so expired-but-unread keys don't linger.\n\nCloses #59",
          "is_bot": true,
          "headline": "fix(decrypt-session): bound JWT cache and sweep expired entries on write",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-11T23:39:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cd3b05d16e5964cb2681d28e30637e68d243ba8a",
          "body": "chore: bump tsdown to 0.22.0 and yivi-css to 1.0.0-beta.7",
          "is_bot": false,
          "headline": "Merge pull request #57 from encryption4all/chore/bump-deps-2026-05-10",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-10T22:37:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2e6ad1abf96d19154448c0ae494e5fd87985b8a5",
          "body": null,
          "is_bot": true,
          "headline": "chore: bump tsdown to 0.22.0 and yivi-css to 1.0.0-beta.7",
          "author_name": "dobby-yivi-agent[bot]",
          "author_login": "dobby-coder[bot]",
          "committed_at": "2026-05-10T22:19:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a60716e0b4eaaed0f3763a2eebbcf6c39fc0560d",
          "body": "feat: release with @privacybydesign/yivi-* 1.0.0-beta.6",
          "is_bot": false,
          "headline": "Merge pull request #54 from encryption4all/chore/release-yivi-beta.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T20:44:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d282facf84bb1d1669c7c38f3bee9deda06317d8",
          "body": null,
          "is_bot": false,
          "headline": "feat: release with @privacybydesign/yivi-* 1.0.0-beta.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T20:43:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "837382d71df3438efb7c42b78ec5b7065bc70f68",
          "body": "chore: bump @privacybydesign/yivi-* to 1.0.0-beta.6",
          "is_bot": false,
          "headline": "Merge pull request #53 from encryption4all/chore/yivi-1.0.0-beta.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T20:41:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1ace739a67a87f547369e926b66adc4313d0c5e7",
          "body": null,
          "is_bot": false,
          "headline": "chore: bump @privacybydesign/yivi-* to 1.0.0-beta.6",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T20:40:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "95af57cc8bfb42d42baf9ac04c5293c4b9b1abaa",
          "body": "feat(download): resume via Range header on transient stream failures",
          "is_bot": false,
          "headline": "Merge pull request #52 from encryption4all/feat/resumable-downloads",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T19:00:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e9f60c44b27f10b035905e221a188aed53332ea5",
          "body": "Cryptify's `FileServer` already supports HTTP Range, so a stream-level\nfailure mid-download (network drop, idle timeout) can now resume from\nthe byte offset reached rather than restarting from zero. The consumer\nsees a single contiguous stream regardless of how many internal retries\nhappened.\n\nImple\n[…]\nqueued\nchunks, so the test-only `streamThenError` is now pull-based to\nguarantee chunks are delivered before the error fires.\n\n72 tests pass; type-check clean.\n\nCloses #48.\nRefs #117, #136, #145, #47.",
          "is_bot": false,
          "headline": "feat(download): resume via Range header on transient stream failures",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:44:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e55302aab22e429917fb57704331af80da233026",
          "body": "ci: add semantic PR title check workflow",
          "is_bot": false,
          "headline": "Merge pull request #51 from encryption4all/ci/semantic-pr-title",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:38:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "629fff28fd173b4737601bd8dd755cfd93f8f73b",
          "body": "Closes #49",
          "is_bot": false,
          "headline": "ci: add semantic PR title check workflow",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:36:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3b0f0dd871b7bdd25e652ce75b6c74da010e7e59",
          "body": "chore(deps): bump @privacybydesign/yivi-* to 1.0.0-beta.5",
          "is_bot": false,
          "headline": "Merge pull request #50 from encryption4all/chore/yivi-beta-5",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:18:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9d4f49e7d71b49311f4541c6cc3edc1bc3f46b17",
          "body": null,
          "is_bot": false,
          "headline": "chore(deps): bump @privacybydesign/yivi-* to 1.0.0-beta.5",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T18:14:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3f52a9a7eb25ebf0c7b6555a0f7897219fd7b41d",
          "body": "feat(upload): retry chunks and downloads with exponential backoff",
          "is_bot": false,
          "headline": "Merge pull request #47 from encryption4all/feat/upload-retry-backoff",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T12:11:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4081e1bdf44e0dd8d27783a85dce0ea3c9058bdc",
          "body": "Three small follow-ups from @dobby-coder's review:\n\n1. Flatten `throwSessionExpiredOrNetworkError` — replace the\n   throw-inside-try / catch-and-rethrow pattern with a flatter\n   parse-then-decide shape. Same behaviour, no `instanceof` re-check\n   needed in the catch.\n\n2. Document `downloadTimeoutMs\n[…]\nIDE go-to-definition. No runtime change.\n\n`AbortSignal.any` is left as-is — it's already used in\n`src/crypto/encrypt.ts:45`, so this PR doesn't introduce a new\nbrowser-support floor.\n\nRefs #47 review.",
          "is_bot": false,
          "headline": "refactor(retry): apply review feedback from #47",
          "author_name": "Ruben Hensen",
          "author_login": "rubenhensen",
          "committed_at": "2026-05-07T11:46:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        }
      ],
      "releases_count": 49,
      "commits_last_year": 210,
      "latest_release_at": "2026-07-17T05:57:23Z",
      "latest_release_tag": "v2.3.1",
      "releases_from_tags": false,
      "days_since_last_push": 0,
      "active_weeks_last_year": 16,
      "days_since_latest_release": 4,
      "mean_days_between_releases": 1.6
    },
    "community": {
      "has_readme": true,
      "has_license": false,
      "has_description": false,
      "has_contributing": false,
      "health_percentage": 25,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "@e4a/pg-js",
          "exists": true,
          "license": "MIT",
          "keywords": [
            "postguard",
            "ibe",
            "encryption",
            "identity-based-encryption",
            "yivi",
            "irma"
          ],
          "ecosystem": "npm",
          "matches_repo": true,
          "registry_url": "https://www.npmjs.com/package/@e4a/pg-js",
          "is_deprecated": false,
          "latest_version": "2.3.1",
          "repository_url": "https://github.com/encryption4all/postguard-js",
          "versions_count": 51,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": 3,
          "monthly_downloads": 4063,
          "first_published_at": "2026-03-25T14:29:26.192000Z",
          "latest_published_at": "2026-07-17T05:57:22.376000Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 4
        }
      ]
    },
    "popularity": {
      "forks": 0,
      "stars": 0,
      "watchers": 0,
      "fork_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_forks": 0
      },
      "star_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_stars": 0
      },
      "open_issues_and_prs": 6
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [
        "tsconfig.json"
      ],
      "toolchain_manifests": [],
      "largest_source_bytes": 41642,
      "source_files_sampled": 60,
      "oversized_source_files": 0,
      "agent_instruction_files": [
        "CLAUDE.md"
      ],
      "agent_instruction_max_bytes": 10266
    },
    "dependencies": {
      "manifests": [
        "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": "@e4a/pg-wasm",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^0.6.1"
        },
        {
          "name": "@privacybydesign/yivi-client",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.0.0"
        },
        {
          "name": "@privacybydesign/yivi-core",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.0.0"
        },
        {
          "name": "@privacybydesign/yivi-css",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.0.1"
        },
        {
          "name": "@privacybydesign/yivi-web",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^1.0.1"
        },
        {
          "name": "@transcend-io/conflux",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^6.1.3"
        }
      ],
      "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": 2,
        "merged_prs": 75,
        "open_issues": 4,
        "closed_ratio": 0.897,
        "closed_issues": 35,
        "closed_unmerged_prs": 1
      },
      "bus_factor": 1,
      "bot_contributors": 1,
      "top_contributors": [
        {
          "type": "User",
          "login": "rubenhensen",
          "commits": 177,
          "avatar_url": "https://avatars.githubusercontent.com/u/17710718?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "delivery.yml",
        "integration.yml",
        "pr-title.yml"
      ],
      "has_docs_dir": false,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "package-lock.json"
      ],
      "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": 5,
            "reason": "branch protection is not maximal on development and all release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
            "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": 8,
            "reason": "Found 4/5 approved changesets -- score normalized to 8",
            "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": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 0,
            "reason": "license file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 10,
            "reason": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 5,
            "reason": "dependency not pinned by hash detected -- score normalized to 5",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "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": 9,
            "reason": "1 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "6c0f55443706ba27fda1db73f48655795cb151d9",
        "ran_at": "2026-07-22T02:15:14Z",
        "aggregate_score": 5.3,
        "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-21T14:57:55Z",
      "oldest_open_prs": [
        {
          "number": 112,
          "created_at": "2026-07-14T23:40:01Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 114,
          "created_at": "2026-07-14T23:41:39Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ],
      "last_merged_pr_at": "2026-07-21T14:56:49Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": [
        {
          "number": 94,
          "created_at": "2026-07-01T23:13:54Z",
          "last_comment_at": "2026-07-02T03:55:34Z",
          "last_comment_author": "dobby-coder"
        },
        {
          "number": 105,
          "created_at": "2026-07-14T23:36:01Z",
          "last_comment_at": "2026-07-14T23:37:02Z",
          "last_comment_author": "dobby-coder"
        },
        {
          "number": 107,
          "created_at": "2026-07-14T23:36:23Z",
          "last_comment_at": "2026-07-14T23:37:04Z",
          "last_comment_author": "dobby-coder"
        },
        {
          "number": 110,
          "created_at": "2026-07-14T23:36:50Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ]
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/encryption4all/postguard-js",
    "host": "github.com",
    "name": "postguard-js",
    "owner": "encryption4all"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 57,
      "inputs": {
        "security": 53,
        "vitality": 85,
        "community": 24,
        "governance": 60,
        "engineering": 57
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "excellent",
        "name": "Vitality",
        "value": 85,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "good",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 75,
            "inputs": {
              "commits_last_year": 210,
              "human_commit_share": 0.74,
              "days_since_last_push": 0,
              "active_weeks_last_year": 16
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 0 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "16/52 weeks with commits",
                "points": 11.1,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 16
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "210 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 210
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "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": 100,
            "inputs": {
              "releases_count": 49,
              "latest_release_tag": "v2.3.1",
              "releases_from_tags": false,
              "days_since_latest_release": 4,
              "mean_days_between_releases": 1.6
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "49 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 49
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 4 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 4
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~1.6 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 1.6
                    }
                  }
                ],
                "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": "critical",
        "name": "Community & Adoption",
        "value": 24,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 1,
            "inputs": {
              "forks": 0,
              "stars": 0,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "0 stars",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "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": "critical",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 25,
            "inputs": {
              "has_readme": true,
              "has_license": false,
              "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": "no license file detected",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "license_absent",
                    "params": {}
                  }
                ],
                "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": [
                "@e4a/pg-js"
              ],
              "dependents": null,
              "ecosystems": "npm",
              "total_downloads": null,
              "monthly_downloads": 4063
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "4,063 downloads/month across npm",
                "points": 48.1,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 4063,
                      "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": "moderate",
        "name": "Sustainability & Governance",
        "value": 60,
        "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": "excellent",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 92,
            "inputs": {
              "merged_prs": 75,
              "open_issues": 4,
              "closed_issues": 35,
              "issue_closed_ratio": 0.897,
              "closed_unmerged_prs": 1
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "90% of issues closed",
                "points": 41.9,
                "status": "partial",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 90
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "75/76 decided PRs merged",
                "points": 37.7,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 75,
                      "decided": 76
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 4/5 approved changesets -- score normalized to 8",
                "points": 12,
                "status": "partial",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "moderate",
            "name": "Ownership & stewardship",
            "note": null,
            "notes": [],
            "value": 56,
            "inputs": {
              "followers": 6,
              "owner_type": "Organization",
              "is_verified": null,
              "owner_login": "encryption4all",
              "public_repos": 16,
              "account_age_days": 1992
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "organization-owned",
                "points": 30,
                "status": "met",
                "details": [
                  {
                    "code": "owner_organization",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "6 followers of encryption4all",
                "points": 6.1,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 6,
                      "login": "encryption4all"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "16 public repos, account ~5 yr old",
                "points": 19.9,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 16
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 5
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "@e4a/pg-js"
              ],
              "ecosystems": "npm",
              "any_deprecated": false,
              "min_days_since_publish": 4
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on npm",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 4 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 4
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "51 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 51
                    }
                  }
                ],
                "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": "moderate",
        "name": "Engineering Quality",
        "value": 57,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 68,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": false,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "3 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "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": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "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": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "at_risk",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "topics": [],
              "has_wiki": true,
              "homepage": null,
              "has_readme": true,
              "has_docs_dir": false,
              "has_description": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "moderate",
        "name": "Security",
        "value": 53,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "moderate",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 53,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 17,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 1,
              "scorecard_aggregate": 5.3
            },
            "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 is not maximal on development and all release branches",
                "points": 3.8,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "14 out of 14 merged PRs checked by a CI test -- score normalized to 10",
                "points": 2.5,
                "status": "met",
                "details": [],
                "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 4/5 approved changesets -- score normalized to 8",
                "points": 6,
                "status": "partial",
                "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 not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 5",
                "points": 2.5,
                "status": "partial",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "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": "1 existing vulnerabilities detected",
                "points": 6.8,
                "status": "partial",
                "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": 1
            },
            "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": "good",
        "name": "AI Readiness",
        "value": 75,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "excellent",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 85,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 1,
              "agent_instruction_files": [
                "CLAUDE.md"
              ],
              "agent_instruction_max_bytes": 10266
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "CLAUDE.md",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "CLAUDE.md"
                    }
                  }
                ],
                "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": "74 of 74 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 74,
                      "sampled": 74
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 58,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "package-lock.json"
              ],
              "has_dockerfile": false,
              "typed_language": true,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [
                "tsconfig.json"
              ],
              "agent_commit_share": 0.13,
              "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": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "tsconfig.json",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "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": "13 of the last 100 commits agent-authored or agent-credited",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "agent_authored_commits",
                    "params": {
                      "count": 13,
                      "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 5",
                "points": 5,
                "status": "partial",
                "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": 41642,
              "source_files_sampled": 60,
              "oversized_source_files": 0
            },
            "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": "0/60 source files over 60KB",
                "points": 55,
                "status": "met",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 60,
                      "oversized": 0
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          }
        ],
        "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": [
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
    "deps.dev does not index npm:@e4a/pg-js@2.3.1; advisories assessed against the repository dependency graph instead"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-22T02:15:30.722115Z",
  "schema_version": "0.26.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/e/encryption4all/postguard-js.svg",
  "full_name": "encryption4all/postguard-js",
  "license_state": "absent",
  "license_spdx": null
}

Scores are signals, not warranties. They reflect publicly visible practices on GitHub — not a code audit, and not a security guarantee.

Missing data is excluded and weights renormalized, never scored as zero. Methodology is versioned and open: metrics v1.13.0, schema v0.26.0 — full methodology · metrics wiki.

How one result sits in the wider record: aggregate statisticsnpm.