Public record
Software health reportschema 0.27.0 · metrics 1.13.0 · 2026-07-24 12:30 UTC

zaclummys / sf-plugin-permission-sets

Declarative, GitOps-style management of permission set assignments for Salesforce orgs.

TypeScript · JavaScriptBSD-3-Clause★ 1 star⑂ 0 forkssince Jun 2026View on GitHub ↗

zaclummys/sf-plugin-permission-sets holds a health index of 57 out of 100, placing it in the Moderate band. It scores highest on AI Readiness (82/100) and lowest on Community & Adoption (34/100). It was last updated 5 days ago. 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

Isaac FerreiraPersonal account
20 followers28 public repossince Mar 2016@gympass

This repository is owned by a personal account. A single-owner project carries more continuity risk than an organization-backed one.

Package ecosystems

Metrics by category

Vitality

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

74Good · 22% of overall
How it's scored
36/36Push recency — last push 5 days ago
2.8/36Commit cadence — 4/52 weeks with commits
18/18Commit volume — 123 commits in the last year
0/10OpenSSF Scorecard: Maintained — project was created within the last 90 days. Please review its contents carefully
Inputs used
commits_last_year123
human_commit_share0.98
days_since_last_push5
active_weeks_last_year4

Release discipline

100Excellent
How it's scored
27/27Ships releases — 5 releases published
36/36Release recency — latest release 5 days ago
27/27Release cadence — a release every ~5 days
0/10OpenSSF Scorecard: Signed-Releases — no data
Inputs used
releases_count5
latest_release_tagv0.4.0
releases_from_tagsno
days_since_latest_release5
mean_days_between_releases5
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?

34At risk · 18% of overall
How it's scored
0/60Stars — 1 stars
0/25Forks — 0 forks
0/15Watchers — 0 watchers
Inputs used
forks0
stars1
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
How it's scored
22.5/22.5README
22.5/22.5License — recognized license (BSD-3-Clause)
0/18CONTRIBUTING guide
0/13.5Code of conduct
0/7.2Issue template
0/6.3PR template
Inputs used
has_readmeyes
has_licenseyes
has_contributingno
has_issue_templateno
has_code_of_conductno
has_pull_request_templateno
How it's scored
52.5/80Monthly downloads — 8,679 downloads/month across npm
0/20Registry dependents — not reported by this ecosystem
Inputs used
packagessf-plugin-permission-sets
dependents
ecosystemsnpm
total_downloads
monthly_downloads8,679
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?

42At risk · 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
3/10OpenSSF Scorecard: Contributors — project has 1 contributing companies or organizations -- score normalized to 3
Inputs used
bus_factor1
contributors_sampled1
top_contributor_share1
How it's scored
0/46.8Issue resolution — no issues or no data
9.6/38.3PR acceptance — 3/12 decided PRs merged
0/15OpenSSF Scorecard: Code-Review — Found 0/27 approved changesets -- score normalized to 0
Inputs used
merged_prs3
open_issues0
closed_issues0
issue_closed_ratio
closed_unmerged_prs9
Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.
How it's scored
10/30Ownership backing — personal (user) account
0/20Verified domain — not applicable to user accounts
9.5/25Owner reach — 20 followers of zaclummys
22.6/25Track record — 28 public repos, account ~10 yr old
Inputs used
followers20
owner_typeUser
is_verified
owner_loginzaclummys
public_repos28
account_age_days3,781
Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.
How it's scored
25/25Published & resolvable — 1 package(s) on npm
35/35Publish recency — latest publish 5 days ago
20/20Version history — 57 published versions
20/20Not deprecated — active, not deprecated or yanked
Inputs used
packagessf-plugin-permission-sets
ecosystemsnpm
any_deprecatedno
min_days_since_publish5

Engineering Quality

Are baseline engineering and documentation practices in place?

74Good · 20% of overall
How it's scored
24/24CI workflows — 2 workflow(s)
24/24Tests present
16/16Linter config — eslint.config.js
0/9.6Pre-commit hooks
6.4/6.4.editorconfig
20/20OpenSSF Scorecard: CI-Tests — 1 out of 1 merged PRs checked by a CI test -- score normalized to 10
Inputs used
has_ciyes
has_testsyes
has_editorconfigyes
has_linter_configyes
has_precommit_configno

Documentation

50Moderate
How it's scored
30/30README
0/25Documentation directory
0/15Documentation / homepage site
10/10Repository description
10/10Topics — 5 topics
0/10Wiki
Inputs used
topicsaccess-management, gitops, permission-sets, salesforce, sfdx-plugin
has_wikino
homepage
has_readmeyes
has_docs_dirno
has_descriptionyes

Security

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

60Moderate · 16% of overall
How it's scored
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
2.5/2.5CI-Tests — 1 out of 1 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
0/7.5Code-Review — Found 0/27 approved changesets -- score normalized to 0
0.8/2.5Contributors — project has 1 contributing companies or organizations -- score normalized to 3
10/10Dangerous-Workflow — no dangerous workflow patterns detected
7.5/7.5Dependency-Update-Tool — update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5License — license file detected
0/7.5Maintained — project was created within the last 90 days. Please review its contents carefully
5/5Packaging — packaging workflow detected
1/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 2
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
5/5Security-Policy — security policy file 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
Excluded from scoring (no data or not applicable): signed_releases. Remaining weights renormalized.
How it's scored
35/35Direct dependencies free of known advisories — no direct dependency carries a known advisory
25/25Indirect dependencies free of known advisories — no indirect dependency carries a known advisory
0/40No advisories left outstanding — no advisory carries a publication date
Inputs used
sourceosv
advisories0
affected_packages0
assessed_packages288
unassessed_packages0
affected_by_severitynone
direct_affected_packages0
Excluded from scoring (no data or not applicable): No advisories left outstanding. Remaining weights renormalized. Matched the npm:sf-plugin-permission-sets@0.4.0 runtime dependency closure — what installing the published package pulls in — 288 packages. Reachability is not analyzed.

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.

82Good · 0% of overall
How it's scored
45/45Agent instructions — CLAUDE.md
0/15Machine-readable docs (llms.txt)
40/40Legible commit history — 87 of 98 human commits state their intent (structured subject or explanatory body)
Inputs used
has_llms_txtno
legible_history_share0.888
agent_instruction_filesCLAUDE.md
agent_instruction_max_bytes6,263
How it's scored
0/18One-command bootstrap
22/22Automated tests
11/11Lint / format config — eslint.config.js
11/11Static type checking — tsconfig.json
10/10Reproducible environment — lockfile
10/10Demonstrated agent practice — 84 of the last 100 commits agent-authored or agent-credited
8/8Automated maintenance — 2 of the last 100 commits are automated dependency updates
2/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 2
Inputs used
has_nixno
has_testsyes
lockfilespackage-lock.json
has_dockerfileno
typed_languageyes
bootstrap_files
has_devcontainerno
has_linter_configyes
typecheck_configstsconfig.json
agent_commit_share0.84
toolchain_manifests
dependency_bot_commit_share0.02
How it's scored
45/45Type-checkable code — TypeScript (statically typed)
55/55Manageable file sizes — 0/41 source files over 60KB
Inputs used
primary_languageTypeScript
largest_source_bytes17,998
source_files_sampled41
oversized_source_files0

Key facts

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

Data collection warnings

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

More detail

OpenSSF Scorecard 5.0 / 10
5.0aggregate

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-24 12:30 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
10CI-Tests1 out of 1 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/27 approved changesets -- score normalized to 0
3Contributorsproject has 1 contributing companies or organizations -- score normalized to 3
10Dangerous-Workflowno dangerous workflow patterns detected
10Dependency-Update-Toolupdate tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
0Maintainedproject was created within the last 90 days. Please review its contents carefully
10Packagingpackaging workflow detected
2Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 2
0SASTSAST tool is not run on all commits -- score normalized to 0
10Security-Policysecurity policy file detected
n/aSigned-Releasesno releases found
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
9Vulnerabilities1 existing vulnerabilities detected
Direct dependencies 6
RegistryPackageVersion constraintManifest
npm@oclif/core^4package.json
npm@salesforce/core^8package.json
npm@salesforce/sf-plugins-core^12package.json
npmglobby^16package.json
npmyaml^2package.json
npmzod^4package.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

Dependency advisories 0

Installing npm:sf-plugin-permission-sets@0.4.0 pulls in 288 packages, direct and transitive: 0 carry known advisories, of which 0 are direct dependencies.

No known advisories affect the assessed dependencies.

An advisory means the version recorded in the dependency graph falls inside an advisory’s affected range. Reachability is not analysed, and the graph includes development and test pins — a finding may concern tooling rather than shipped software.

Raw JSON report machine-readable
{
  "data": {
    "repo": {
      "topics": [
        "access-management",
        "gitops",
        "permission-sets",
        "salesforce",
        "sfdx-plugin"
      ],
      "is_fork": false,
      "size_kb": 330,
      "has_wiki": false,
      "homepage": null,
      "languages": {
        "Batchfile": 117,
        "JavaScript": 31044,
        "TypeScript": 92386
      },
      "pushed_at": "2026-07-18T23:31:44Z",
      "created_at": "2026-06-28T17:53:02Z",
      "owner_type": "User",
      "updated_at": "2026-07-18T23:31:48Z",
      "description": "Declarative, GitOps-style management of permission set assignments for Salesforce orgs.",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "BSD-3-Clause",
      "default_branch": "main",
      "license_spdx_raw": "BSD-3-Clause",
      "primary_language": "TypeScript",
      "significant_languages": [
        "TypeScript",
        "JavaScript"
      ]
    },
    "owner": {
      "blog": null,
      "name": "Isaac Ferreira",
      "type": "User",
      "login": "zaclummys",
      "company": "@gympass ",
      "location": "Niterói, RJ",
      "followers": 20,
      "avatar_url": "https://avatars.githubusercontent.com/u/17896485?v=4",
      "created_at": "2016-03-17T05:59:52Z",
      "is_verified": null,
      "public_repos": 28,
      "account_age_days": 3781
    },
    "license": {
      "state": "standard",
      "spdx_id": "BSD-3-Clause",
      "raw_spdx": "BSD-3-Clause",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2026-07-18T19:13:11Z"
        },
        {
          "tag": "v0.3.1",
          "kind": "patch",
          "published_at": "2026-07-17T19:49:33Z"
        },
        {
          "tag": "v0.3.0",
          "kind": "minor",
          "published_at": "2026-07-04T21:30:28Z"
        },
        {
          "tag": "v0.2.0",
          "kind": "minor",
          "published_at": "2026-06-29T19:55:18Z"
        },
        {
          "tag": "v0.1.0",
          "kind": "minor",
          "published_at": "2026-06-28T21:10:29Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "41e66be1eed3fad7344af0c8333e8b58cb822361",
          "body": null,
          "is_bot": false,
          "headline": "Simplify methods",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T23:31:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "35c1908b65f14e1dea2c5dc7aea9d41fd2d73fba",
          "body": "…ames\n\nMove every inline SELECT into a dedicated, self-contained query builder\n(buildMembershipQuery, buildCurrentMembershipQuery, buildLicenseQuery,\nbuildCurrentLicenseQuery, buildUserQuery, buildTargetQuery), each owning\nits own WHERE construction so the adapter methods pass domain inputs, not\nSOQ\n[…]\nnments -> listCurrentAssignments (and its private membership\nand license readers), plus soqlLiteral, inList, outcomeOf, and the batch\nbuilders.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: extract SOQL builders and give methods and functions verb n…",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T23:08:35Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "2eead214bd66c194345cae4e47c0aec8752a0258",
          "body": "… sites\n\nRename membershipKindTarget to classifyMembership and narrow its return\nto the two kinds it can produce (never permissionSetLicense). Destructure\n{ kind, target } in each mapper and list every field explicitly, instead\nof spreading a fresh call result into the middle of the object literal.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: rename membership classifier and destructure it at the call…",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:51:39Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "ec098fd604a725cec635ac7ee57f00e619d2757e",
          "body": null,
          "is_bot": false,
          "headline": "Add concurrency control",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:47:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "65dde384cfde11e953fca65d6130ba043c7aaac4",
          "body": "Collapse the duplicated object-shaped ternaries in the membership\nmappers into a shared membershipKindTarget helper, and pull the nested\nternary in formatFindings into a locationPrefix helper.\n\nRepresent \"no expiration\" uniformly as null instead of an absent/optional\nkey: expiration is now a require\n[…]\nough diff, report,\nserialize, and normalize. This removes the null-vs-undefined mismatch\nthat could have surfaced a value as a spurious update.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: make assignment expiration an always-present string | null",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:45:42Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "532913921e7bf80d38a3718a773bc0d2242ef878",
          "body": null,
          "is_bot": false,
          "headline": "Increase vitest timeout",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:32:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bfcd8d1d7bb2bfc017dbea906880c8373cd85e57",
          "body": null,
          "is_bot": false,
          "headline": "Fix ternary connection org client",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:32:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "306f7899a43daeef122d6416110631b457ac486f",
          "body": null,
          "is_bot": false,
          "headline": "Fix ternary in finding",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:32:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7afda633da7aae61ddfaa5ba95659efc9a876908",
          "body": "Remove the saved-plan artifact (plan --out, apply --plan)",
          "is_bot": false,
          "headline": "Merge pull request #12 from zaclummys/remove-saved-plan-feature",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:17:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "001a573d3d55941c4f2b468b2441754820e6ff14",
          "body": "Without the saved-plan injection there is no deterministic, org-agnostic\nway to force an in-scope removal, so this case cannot run reliably as a\nblack-box real-org test. Remove it along with its now-unused imports.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: drop the --json deletion-refusal case",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:15:08Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "5690ddb073fe2f247708563c162b02463698ef03",
          "body": "Dropping a whole user left zero removals: the apply diff only fetches\nassignments for targets referenced in the file, so a dropped user's\ntargets fall out of the managed set and are never seen as removable.\n\nRelocate one target to another user instead. The target stays\nreferenced (still managed), so the original membership becomes a removal\nsync acts on, reaching the confirmation gate the test asserts on.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: force an in-scope removal for the --json refusal case",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:12:51Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "9b981495ee3a39dce1ed1f84a01a7d3582e2c6f8",
          "body": "The saved-plan round-trip was the sole cause of apply's two-source\nbifurcation, and the sf ecosystem rarely wires up the CI artifact\nhand-off it needs (the README's own recommended workflow never used\nit). Dropping it collapses apply to a single --file source.\n\nRemove:\n- plan --out and the core/plan\n[…]\nrvices barrel exports\n\napply now recomputes from the files every run; run plan shortly before\napply so the preview reflects what apply will do.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat: remove the saved-plan artifact (plan --out, apply --plan)",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T22:05:36Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "f45d882bd1ea8c242b5840d71bcdfd34c2d0402b",
          "body": "A --json run that hits the delete-confirmation gate throws error.promptInJson,\nwhich SfCommand serializes into the JSON envelope on stdout. stderr only carries\nsf's linked-ESM-module notice, so the message assertion belonged on stdout.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: assert apply's --json refusal on stdout, not stderr",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T21:32:40Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "d1a3ab443f3ab1b08f684f727b87dbc2f2eacf74",
          "body": "…id-input handling\n\nAdds a non-dry-run apply round trip, the JSON-mode delete-confirmation\nrefusal, schema/malformed fixture coverage for plan/apply/validate\n(previously only exercised by check), and export's --help, invalid\n--kind, and positive --user match cases.\n\nCo-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: cover apply's real-mutation and confirm-prompt paths, and inval…",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T21:19:19Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "67cc13a11b8cffd093a70fd560e9375746e73cc8",
          "body": "Match the versions the README now recommends. Node pin (24) and the rest\nof the pipeline are unchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ci: bump actions/checkout and actions/setup-node to v7",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T19:15:21Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "7a3ce9da57a0c37131dd9aa5e6e71df122a245ec",
          "body": "Space out the workflow YAML, drop the paths filter and the inline\ncomment, and rename the auth secret to SFDX_AUTH_URL consistently in\nboth the workflow and the prose.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: tidy GitHub Actions example (spacing, secret name, triggers)",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T19:12:56Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "57e75e8deb158e89293898d83fde9a6e18b65fbd",
          "body": "Pin actions/checkout and actions/setup-node to v7 (current latest).\nAuthenticate by piping the secret into `sf org login sfdx-url\n--sfdx-url-stdin`, which drops the separate auth-file write step and\navoids writing the secret to disk.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: bump GitHub Actions to v7 and auth via --sfdx-url-stdin",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T19:09:39Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "12bd49a183b4ae0bf17817d983e03716db0a00d0",
          "body": "Give each run step a descriptive name so the workflow logs read clearly.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: name the run steps in the GitHub Actions examples",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T19:08:12Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "c3928d7923c35ce54b332db3aa3e5767a67a617b",
          "body": "Relocate the section (and its TOC entry) below Commands, so readers meet\nthe commands first. Scope the check workflow to pull requests targeting\nmain.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: move GitHub Actions after Commands, scope check to PRs into main",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T19:07:19Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "c01d2dacdb40a18372c0149f34cc247bf6e412b6",
          "body": "Two copy-paste workflows: check every pull request with no org, and\napply on merge to main using a stored SF_AUTH_URL secret. Notes how to\nget the auth URL and how to add a plan step for PR diffs.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: add a dead-simple GitHub Actions section",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T19:05:01Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "b6e72085d52d17f09e180c0796bd83e534a32895",
          "body": "We only ship `latest` (tagged releases) and `dev` (main snapshots), so\nthe `next` prerelease channel was never used. Remove its row and note\nfrom the README and the hyphenated-tag case from the publish workflow;\ntagged releases now always publish to `latest`.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "build: drop the unused `next` dist-tag",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T19:03:11Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "cca341ff1e07d0f5e9d5748ee8e0d0741d380ac8",
          "body": "- Node.js floor is 20+, not 18+ (matches package.json engines).\n- --file describes check/validate/plan/apply; note export's -f is output.\n- Add the mode and plan-file core modules to the architecture table.\n- Note plan --out / apply --plan in the pipeline description.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: fix README staleness after recent features",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T19:01:27Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "d6f77d2baa0fe75a321f7aee4ecc194079faf5d6",
          "body": "generatedAt was informational only: nothing in apply used it, and we do\nno staleness checks (the org-id and version guards cover safety). It also\nmade plan --out non-deterministic, at odds with the rest of the plugin.\nRemove the field, the timestamp in the apply header, and the schema entry.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor(plan): drop generatedAt from the saved plan",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:58:49Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "5333897894eb97f7ea7a8ff89126f29f631f6068",
          "body": "The saved-plan suite spanned two commands. Split it so test/plugin has\none file per command: the plan --out coverage moves to plan.test.js, the\napply --plan round-trip and guard cases move to apply.test.js, and\nsaved-plan.test.js is removed.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: one file per command, fold saved-plan into plan and apply",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:54:57Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "6b56cc1e2510247e50d44b3f95b99ba9b4141706",
          "body": "The file split `sf ps validate` into two identically-titled describe\nblocks along the old online/offline line. With that distinction gone,\nfold them into a single describe; the round-trip comment now annotates\nthe first real-org test instead of the second block.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(validate): merge the two validate describe blocks into one",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:51:12Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "bfe83be7032bf1612dedb81f8fed04add0e55357",
          "body": "There is no \"online\" vs \"offline\" concept in this plugin. Replace every\nmention with concrete phrasing (needs an org vs does not) across service\nand core doc comments, the README, command message descriptions, and test\ndescribe titles. Rename the `online` local in ValidateService to\n`resolved`. No behavior change.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: drop the online/offline distinction",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:49:51Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "ab1b60c739e87ae5db3fb6a49bcd4e0154b84673",
          "body": "`plan --out <file>` freezes the resolved, mode-scoped change set to a\nplan file; `apply --plan <file>` runs it verbatim, with no re-read,\nre-resolve, or re-diff. What you review is exactly what applies.\n\ncore: new plan-file module (SavedPlan type, zod schema, serialize/parse,\nversion) and mode modul\n[…]\nt), which\nthe README documents. README updated first per repo convention. Adds\nblack-box tests for the round-trip and every guard.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(plan,apply): saved-plan artifact (plan --out, apply --plan)",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:44:44Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c5e6a8c202160f73f86aa95b6f51a815ba021f7e",
          "body": "Adds offline checks (unresolvable org, --help) and online round-trip\ntests against PS_TARGET_ORG: export the org, validate the snapshot\nback against it, and assert no findings plus a valid --json envelope.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(validate): cover ps validate",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:32:00Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "5000ad672c921dc444d449f036129f835026f9f7",
          "body": "globby@16 requires Node >=20, so the previous >=18.0.0 floor was\nnever actually runnable. Node 18 is EOL as of April 2025.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "build: raise engines floor to Node 20 to match globby",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:31:59Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "6209a484eebb49d2202d3bd684ebd5692588d8cc",
          "body": "Add BSD-3-Clause LICENSE.md matching the declared license field, replace\nthe Salesforce-internal vuln boilerplate in SECURITY.md with a real policy\nrouted to this repo, and add homepage and bugs URLs to package.json.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "chore: add license file, real security policy, and npm metadata",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:19:34Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "05a1e3aec14a899ef496e0ff13c4656e95225d2c",
          "body": "The summary prints to stdout in file mode and is absent in stdout mode,\nnot \"on stderr\" as previously written. Clarify that under --json the\ncontent field appears only when --output-file is omitted.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(export): correct stdout/stderr description for stdout mode",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:17:21Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "b3e8254822378312e5e15fa73246296e695cee51",
          "body": "Make --output-file optional. Without it, export writes the YAML document\nto stdout, byte-for-byte identical to the file it would have written, so\nit pipes and diffs cleanly (sf ps export -o prod | diff - snapshot.yml).\nWarnings and the summary stay on stderr, keeping stdout pure YAML. Under\n--json t\n[…]\nME updated first per repo convention. Adds black-box tests for the\nstdout write, file/stdout parity, and the --json content field.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(export): write to stdout when --output-file is omitted",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:16:12Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "bc6e769f0d61b951b5bad7f28c2433383c1b2769",
          "body": "Every other required flag already had a short alias (-f for --file, -o\nfor --target-org). --output-file on export was the only required flag\nwithout one. Use -f, consistent with the plugin's \"f = file\" convention\n(export has no input --file flag to collide with).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(export): add -f short alias for --output-file",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:09:12Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "3a12bdb7186ccbcc4b7c0a6a616fe46609bc9a19",
          "body": "Add optional --user (repeatable, exact username) and --kind (repeatable,\nYAML scope keys) flags to `sf ps export`. Values within a flag union,\n--user and --kind intersect. A requested --user with no in-scope\nassignments warns and continues instead of writing a silent empty file.\n\nThe filter is a pur\n[…]\ny scoping next.\n\nREADME updated first per repo convention. Adds black-box tests for\n--kind scoping and the unmatched-user warning.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(export): scope exports with --user and --kind",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:01:42Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "09e0bd4a41a1499707b5006f35a1c20a0b15ed25",
          "body": "Treat the plugin as a product: weigh developer and user experience in\nevery change, design for real usage (scoping, safe destructive actions,\nclear output), and call out UX and DX gaps proactively.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: add product mindset (DX & UX) convention to CLAUDE.md",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T18:01:33Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "8a32e0f64c9a7571f05492cc42949eae6ca6b6a1",
          "body": null,
          "is_bot": false,
          "headline": "Break line",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:42:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1a97f5cca7df3da1bf434b7f68323b30aa6f5c71",
          "body": null,
          "is_bot": false,
          "headline": "Update global",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:42:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0cf8cda1773c757ba93bdfa346979da7c40ac438",
          "body": null,
          "is_bot": false,
          "headline": "Update packages",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:42:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9d8d4227dbbd8bdcc120957fe165aa58a3954a78",
          "body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "style: reindent eslint config to 4 spaces, break long lines",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:35:43Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "0465bce3855993280072eeca9db4596cbca1b13d",
          "body": "Convert the remaining .then callbacks in the org client and the\nresolution/validate services to async/await, keeping parallelism by\npushing promises from async helpers and async map callbacks into the\nPromise.all arrays.\n\nEnforce the conventions in eslint.config.js: a no-restricted-syntax\nselector b\n[…]\nnested-callbacks, max-statements, and max-lines-per-function caps\nset just above today's largest functions to block future growth.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: convert .then to await, add ESLint style guards",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:34:50Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "6ec4e832fd3212f9575a7a5efc6949b0c305f25a",
          "body": "getMessage tokens accept number and every placeholder is %s, so String()\nwrapping was a no-op. Pass numbers directly and drop the now-redundant\naffected alias in countsLine.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: drop redundant String() around getMessage tokens",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:24:02Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "7332b5fab0535d67c3d23dc99b46e1500d45dd6e",
          "body": "Extend the named-message-method convention to this.error and this.confirm:\nerrorInvalid, errorMaxDeletes, errorFailed, and confirmDelete. jsonEnabled\nguards stay at the call site; messages.createError stays inline.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: wrap command errors and confirms in named methods",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:21:49Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "05303d9965dbf665e6440937d805c10376a39747",
          "body": "Every this.log(messages.getMessage(...)) call across the commands now goes\nthrough a named private method (logHeaderTitle, logHeaderOrg, logSummaryCounts,\n...) that takes raw token values. this.error/confirm paths are unchanged. The\nexport 'success' message is logExportSuccess to avoid SfCommand.logSuccess.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: wrap command messages in named log methods",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:19:05Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "0c5afb3eea49c2f9450e08f4e0ead9d9d7bbac99",
          "body": "Enable the complexity rule (max 10) and split the four functions that\nexceeded it into cohesive helpers:\n- report.formatDiff -> collectBuckets + renderBucket\n- apply service run -> planForMode\n- plan command run -> logPlan\n- apply command run -> reportOutcome (ApplyResult now via the barrel)\n\nBehavior is unchanged. tsc and lint pass clean.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: cap cyclomatic complexity at 10 via eslint",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:11:57Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "c97cbea66bfda6c73b38111953c4f85f85fe7e0e",
          "body": "Replace the sequential await-in-loop read with a parallel Promise.all over\nthe files, so the eslint-disable directive is no longer needed. Order and\ndedup semantics are unchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: read files in parallel, drop no-await-in-loop disable",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:02:08Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "a532175965195ec11a9bcffd6a2b9cd5b7ecde51",
          "body": "…lint\n\nAdd no-restricted-imports per-directory overrides (core purity, layer\ndirection, barrel-only cross-dir imports), id-length, and a\nno-restricted-syntax ban on === undefined. Existing code passes clean.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ci: enforce layering, barrels, id-length, and no-=== undefined via es…",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T17:00:22Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "ef17588d0c9650576d429c64c532e69bf2242dfb",
          "body": "Array literals built from more than one element (values or spreads) now go\nmultiline with a trailing comma. Enum-style literal lists and lookup-table\ntuple rows stay inline as single logical units.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "style: put multi-element array literals one per line",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:56:20Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "cb13e02f89518c7554222034d22bcfcc82a60d3f",
          "body": "PlanInput only wrapped a single mode field, so run() now takes mode directly.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: pass plan mode directly, drop PlanInput wrapper",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:51:44Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "2fe9296bc78a33811c6569bf278c51041fba6914",
          "body": "Avoid call + immediate property access (countFindings(x).errors); bind the\ncounts to a variable first. Broaden the CLAUDE.md rule to cover plain calls.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: bind countFindings result before member access",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:49:20Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "68ab3f56e25959c7827d7ceda21c1ed10aaf7cca",
          "body": "fileSchema, userEntrySchema, ReportOptions, FindingLevel, and FindingCode\nare used only within their own file, so they no longer carry export per the\nno-internal-exports convention.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: drop export on internal-only core helpers",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:47:56Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "09f24d9a8cdd8f2b92dac88803be8e88dd49fd90",
          "body": "Capture the layering, barrel, service DI, code-style, testing, and\nworkflow guidelines established so far.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: add CLAUDE.md with project conventions",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:45:23Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "a2d1b33c009bed92c1ccab11b8482e21f7f52fdc",
          "body": "Give the apply deletion-approval callback a named type alias so the\nport reads like the other injected dependencies at both ends.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: name the ConfirmDeletions port",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:41:06Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "239bfc95eaadd7e5c301fd6259455ec51aa81982",
          "body": "Service constructors now take only injected collaborators; per-invocation\ninputs (files, mode/config, assignments) move to run() parameters.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: inject only dependencies into service constructors",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:38:18Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "5f177acb7ff6f93ba0919f2103850ec428bf9d2a",
          "body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "style: group external and local imports in ps commands",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:34:05Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "4a0803289c9eec92434bd87a9922a47dea46a22f",
          "body": "Route external importers of src/adapters/ and src/services/adapters/\nthrough index.js barrels that re-export only the used symbol.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: add adapters barrels for imports",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:33:22Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "0c94d145a36a17580c798aeb3bbdfc36216a03d1",
          "body": "Route external importers through index.js barrels that re-export only\nthe symbols used outside each layer, consolidating multiple per-file\nimports into one.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: add core/ and services/ barrels for imports",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-18T16:31:27Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "14d10acf782d9dae7e2b8ec5b1bdd1fa3472e94d",
          "body": null,
          "is_bot": false,
          "headline": "Improve plan output",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-15T20:53:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c8e676856a12f3eea209f8933d9a2dcfc4f17fcc",
          "body": "The login output is not consumed, so the human-readable form is fine.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ci: drop --json from the sfdx-url login",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T21:27:21Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "35e5680dfdaa92c8c5941c5c3fb7c6ec74fd8101",
          "body": "The publish workflow calls ci.yml via workflow_call as its release gate, but reusable workflows do not inherit the caller's secrets, so the test job's auth step got an empty SFDX_AUTH_URL and would fail on push-to-main and release. ci.yml now declares the secret under on.workflow_call.secrets and publish.yml passes it through explicitly.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ci: pass SFDX_AUTH_URL to the reusable ci workflow",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T21:25:30Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "10fab41ba7edfc10d547c834be70b5631c1a08f5",
          "body": "The test:only/test split existed to keep lint out of the test run for CI job parallelism. Renaming test:only to test (with no lint dependency) keeps that benefit with one fewer script: `npm test` compiles and runs vitest, while lint stays in the build job. CI's test job now runs `npm test`; the parallel build/test jobs are unchanged.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: rename test:only to test, drop its lint dependency",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T21:22:24Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "7279f03280810ed43a720f36914c3ef74b48d7f4",
          "body": "vitest now runs every test concurrently (sequence.concurrent), bounded by maxWorkers and maxConcurrency so the many `sf` subprocesses do not thrash the machine and trip the 30s timeout. The online export test writes a unique OS temp file per case (the OS reclaims it, no cleanup), so the concurrent cases never collide. projectRoot is demoted to a local const since nothing imports it anymore.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: run the whole suite concurrently with per-test temp files",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T21:15:18Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "2a6c5de3cadc70a61263ccafef9520a710a8e068",
          "body": "globalSetup now runs sf with cwd at the repo root and links/unlinks the plugin with `.` instead of an absolute path and plugin name. It always links (dropped the already-linked short-circuit) and unlinks on teardown. --no-install stays: without it `sf plugins link` runs a production install that prunes devDependencies and breaks the test run.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: link plugin from repo root via `sf plugins link .`",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T20:36:53Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "c827ae69f52d574998d5f353455274471a6633dd",
          "body": "A black-box suite drives `sf ps export` against a real org and re-checks its output offline (export then check round-trip). The harness receives the org via PS_TARGET_ORG instead of authenticating: the caller (a local logged-in org, or the CI login step) provides it. global-setup.js loads a gitignored .env so PS_TARGET_ORG can live there locally, and CI authenticates from the SFDX_AUTH_URL secret and hands the alias to the tests.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: add real-org export test targeting PS_TARGET_ORG",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T20:27:08Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "653b467ac5b8e53777e61e40bc6195ded46ff1cc",
          "body": "The plugin only touches stable standard objects (PermissionSetAssignment,\nPermissionSetLicenseAssign, PermissionSet, PermissionSetGroup, User), whose\nshape and Collections-API behavior do not vary meaningfully across API\nversions, so pinning one bought no real reproducibility. Drop the flag from\napp\n[…]\n connection now uses the org's default\nAPI version. Removing it while on 0.x is the low-regret direction: adding it\nback later is non-breaking.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat!: remove --api-version flag from org commands",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T20:21:51Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "35ee1857ee1c2991319a1dee498e9bce6ee97652",
          "body": "The --watch/-w flag was documented for check, validate, and plan but no\nsuch flag exists in any command. Remove the USAGE tokens, FLAGS entries,\nand the explanatory paragraph so the docs match the implemented commands.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: remove undocumented --watch flag from README",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T20:08:11Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "0e20da54e3363bd311c4d60f964e835e7f30151d",
          "body": null,
          "is_bot": false,
          "headline": "Ignore env files",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T19:58:01Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "42fd0ca7731e314c041d4088f2adbf1daabdc71f",
          "body": null,
          "is_bot": false,
          "headline": "Update readme",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T19:57:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eece7aa44f12ccbb252385d6f808ce83b029f0d9",
          "body": "Replace org-required.test.js (which spanned plan, apply, and topic help) with\nper-command files, matching the one-file-per-command layout check.test.js\nalready used.\n\n- apply.test.js: invalid --mode, negative --max-deletes, --help.\n- plan.test.js: clean failure when the org can't be resolved, --help\n[…]\n gains its own --help test (asserts --file).\n- Drop the redundant `ps --help` topic test; each command's --help now\n  proves the command loads.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: split plugin tests one file per command",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T19:51:44Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "e3ef14d5020e74305c342899a81719b68a58b197",
          "body": "Remove the --strict assertion from the check suite temporarily; the sibling\nnon-strict test keeps warning coverage.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: drop the ps check --strict case for now",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T19:46:52Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "c59eed4298d7a34d5897d153c8b4d8c29ca7b972",
          "body": "Introduce a vitest suite that exercises the plugin the way a user runs it:\nspawn `sf ps ...` via execa and assert on stdout, stderr, and exit codes.\nOffline only, against local YAML fixtures, so it needs no org or secrets.\n\n- test/plugin/check.test.js: the fully-offline `ps check` (valid, warnings,\n\n[…]\nion so sf loads compiled lib.\n- Add execa; vitest config moved to JS; test:only now compiles first (wireit).\n- CI test job installs the sf CLI.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: add offline black-box plugin tests driving sf ps",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T19:43:35Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "afa3b6de13f0922354811cc47c79101f990b60e9",
          "body": "…ce-core-8.31.5\n\nfix(deps): Bump @salesforce/core from 8.6.4 to 8.31.5",
          "is_bot": false,
          "headline": "Merge pull request #5 from zaclummys/dependabot-npm_and_yarn-salesfor…",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T19:41:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bc5891aa9b0640af1163472aacc5f51fb66be60c",
          "body": "…re-4.11.14\n\nfix(deps): Bump @oclif/core from 4.0.31 to 4.11.14",
          "is_bot": false,
          "headline": "Merge pull request #6 from zaclummys/dependabot-npm_and_yarn-oclif-co…",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-04T19:41:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "58fe8f68bdc04b6b32a26ba020f6311615094e66",
          "body": "Bumps [@oclif/core](https://github.com/oclif/core) from 4.0.31 to 4.11.14.\n- [Release notes](https://github.com/oclif/core/releases)\n- [Changelog](https://github.com/oclif/core/blob/main/CHANGELOG.md)\n- [Commits](https://github.com/oclif/core/compare/4.0.31...4.11.14)\n\n---\nupdated-dependencies:\n- dependency-name: \"@oclif/core\"\n  dependency-version: 4.11.14\n  dependency-type: direct:production\n  update-type: version-update:semver-minor\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
          "is_bot": true,
          "headline": "fix(deps): Bump @oclif/core from 4.0.31 to 4.11.14",
          "author_name": "dependabot[bot]",
          "author_login": "dependabot[bot]",
          "committed_at": "2026-07-04T18:43:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8fb0c99194b6286a62a85cb8688606a3b408a28b",
          "body": "Bumps [@salesforce/core](https://github.com/forcedotcom/sfdx-core) from 8.6.4 to 8.31.5.\n- [Release notes](https://github.com/forcedotcom/sfdx-core/releases)\n- [Changelog](https://github.com/forcedotcom/sfdx-core/blob/main/CHANGELOG.md)\n- [Commits](https://github.com/forcedotcom/sfdx-core/compare/8.\n[…]\nependency-name: \"@salesforce/core\"\n  dependency-version: 8.31.5\n  dependency-type: direct:production\n  update-type: version-update:semver-minor\n...\n\nSigned-off-by: dependabot[bot] <support@github.com>",
          "is_bot": true,
          "headline": "fix(deps): Bump @salesforce/core from 8.6.4 to 8.31.5",
          "author_name": "dependabot[bot]",
          "author_login": "dependabot[bot]",
          "committed_at": "2026-07-04T18:43:01Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e9fbc1af19cc50922d8c4523f3a51e633ea1fd17",
          "body": "v4 targets the deprecated Node 20 runtime, which GitHub was forcing\nonto Node 24 with a warning annotation. v5 targets Node 24 natively.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ci: bump checkout and setup-node to v5",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-01T18:20:21Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "537ee8bc5c7f8a97f6b9e6495b16e2115f363abf",
          "body": "CI build broke because ESLint 10 needs jiti to load the TypeScript\neslint.config.ts, and jiti was never in the lockfile. Rename the\nconfig to plain-JS eslint.config.js (no jiti needed) and add the\nglobals and typescript-eslint packages it imports as devDependencies.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "build: convert eslint config to js and declare its deps",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-01T18:17:13Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "e7a4673489b6e425274bcb39c96e0bf5ede8094a",
          "body": "The repo switched to npm (package-lock.json), so update the CI and\npublish workflows to match: npm cache, npm ci, and npm run scripts.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "ci: use npm instead of yarn in workflows",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-01T18:12:22Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "0570a864ed703e39d05878f079608c624eb1efa8",
          "body": "Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "chore: switch to npm, add wireit, inline tsconfig",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-01T18:08:45Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "ec5d55690bf77a695f549a6aa3a59c46c5bc018c",
          "body": "…onfig\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "chore: remove husky, dev-scripts, and oclif; migrate to flat ESLint c…",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-01T18:05:00Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "54723d44facade4444a0540a983356e03352b8c0",
          "body": "…ogic\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: extract ResolutionService to eliminate duplicated resolve l…",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-01T17:44:02Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "fcb0c9275f91a009cf481f09d829701f14637d18",
          "body": "Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: prefer two loops over one across core and services",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-07-01T17:35:45Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "58d496cd53f35dae9a87e8be95bd8888490b0782",
          "body": "A run now performs three operations, not two. Add an \"Updates\nexpirations\" column to the mode table and note that updates ride with\nthe additive half, never revoking access.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: reflect expiration updates in the Modes section",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T20:01:00Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "85dc7bd8557427170f970ca43fc5cf82815d2d8c",
          "body": "A real export (permissions.yml with live org usernames and ids) is easy\nto stage by accident. Ignore /permissions.yml and /permissions/ so local\nscratch never lands in the repo.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "chore: gitignore local permission files",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T19:53:06Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "a14940d23cb9ec2d0403b969060ae1299c069b0c",
          "body": "Use truthiness checks instead of explicit undefined comparisons:\n!list in normalize and !left || !right in diff, where the values are\nnever other-falsy. parse keeps the null-or-undefined distinction with\n== null so a scalar document still reaches schema validation.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: drop === undefined in favor of !x / == null",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T19:52:34Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "d89e5cad45e6d3e04f60697ce298603909a48e0c",
          "body": "Give each finding code a named constructor in finding.ts (yamlError,\nschemaError, dupTargetWarning, userNotFoundError, and so on) that owns\nits code and wording, and make the generic error() and warning()\nprivate to the module. Call sites in parse, schema, normalize, resolve,\nand load now build findings by name instead of by code and string.\nnoFilesError moves here too, alongside the rest.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: name the finding constructors, hide error/warning",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T19:47:12Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "4d636d5f46dd07319e5a30133f06b69f356cb91f",
          "body": "Permission set and permission set group entries accept an optional\nexpiration (an ISO 8601 datetime) in a new object form alongside the\nexisting string form. Permission set licenses cannot expire, so the\nobject form is rejected there.\n\nThe diff gains a toUpdate bucket. A changed expiration on an\nalr\n[…]\nxpiration in the org.\n\nAlso remove the ps info command and extract the NO_FILES finding into\na noFilesError(patterns) constructor.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat: support permission set assignment expiration",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T19:41:19Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "0bc0bcb65ea9f288dbe34601090af3a57c0fc624",
          "body": "Read-only preview of the apply reconcile: load, resolve, fetch current\nstate, and diff, stopping before any DML. Shows the full change set\n(adds and would-be removes) regardless of mode, surfacing what the\nchosen mode won't act on as drift.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat: add ps plan command",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T19:10:37Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "bc7dbcd16c1f9303358d1fb268103bfdd276aa2e",
          "body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: move OrgClient port to services/adapters",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T19:10:25Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "9d0435c705062b2e74eb6d60a66e670aff373f1e",
          "body": "Align the npm description with the GitHub About blurb and replace the\ngeneric scaffold keywords with topical, search-friendly terms\n(permission sets, access management, user provisioning, GitOps).\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "chore: sharpen npm description and keywords for discoverability",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T18:05:14Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "f9de23244de1afb68c2191b943e22099c21b9129",
          "body": "Reconcile a target org to the YAML: load, resolve every reference to an\norg id, diff against current state, then add and/or remove per --mode\n(additive, destructive, sync). Deletions are capped by --max-deletes and\ngated by a confirmation; --dry-run previews the plan. DML runs through\nthe sObject Co\n[…]\nthe format/count helpers\npulled out of report and model. Findings are now built through the\nfactories rather than inline literals.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat: add ps apply command",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T16:53:22Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "85b2b14fdb8ca699834767f5c08d6e956927982f",
          "body": "Snapshot an org's current permission set, group, and license\nassignments to a single user-keyed YAML file that round-trips back\nthrough check, validate, plan, and apply.\n\nAdds a serialize core module (the inverse of normalize), a\nlistAssignments port method implemented with SOQL in the adapter, and\nthe ExportService that fetches, serializes, and writes the file.\nProfile-owned permission sets and inactive users are skipped.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat: add ps export command",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T16:23:47Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "3504b623f4ebccea3e70570ac081bbf96195cbdb",
          "body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: place Validations after Modes in README",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T15:16:57Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "31e7757e7175d1806798c125883bb968e40b4101",
          "body": "Add a stability badge, an under-development callout, and an explicit\n0.x policy note in the Versioning section, and fill the empty npm\ndescription, so consumers know to expect breaking changes until v1.0.0.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: flag 0.x stability and breaking-change policy",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-29T13:14:15Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "29d9e79935cb73494362927c5e9f09d4de31cded",
          "body": "Rename the SCREAMING_SNAKE module-level constants to camelCase (kindLabels,\nkinds, targetObjects, kindKeys). In countFindings, bind the filtered arrays\nto locals instead of reading .length directly off the filter call.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: rename module constants to camelCase",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-28T22:57:24Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "5d7a05743ae9781a8e7550652034aa0e560535e1",
          "body": "The OrgClient port is declared in services (not adapters), and resolve no\nlonger plans SOQL: the adapter owns it. Update the architecture section and\nthe resolve module row to match.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: correct architecture for services-owned port and SOQL location",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-28T22:32:56Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "4e4f5f61acb1f8c28771f0b082219f71306f7b9c",
          "body": "A port is owned by its consumer, not by the adapter that implements it\n(dependency inversion). OrgClient now lives in services/org-client.ts;\nConnectionOrgClient (renamed from adapters/org-client.ts) implements it.\nServices no longer import adapters; the adapter depends inward on the\nservices-owned port.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: move OrgClient port from adapters to services",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-28T22:32:51Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "9e5c85133ac02a81946ba416c43fbf4209c1d2c2",
          "body": "Introduce an adapters layer so core stays persistence-ignorant. The\nOrgClient port exposes domain lookups (findUsers, findTargets) and\nConnectionOrgClient owns all Salesforce detail: the SOQL, the\nkind-to-SObject/field map, autoFetchQuery, and mapping records to domain\nshapes. core/resolve now holds\n[…]\n with no SOQL or SObject/field names. The service depends on\nthe port, not @salesforce/core, so it unit-tests with a trivial fake.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor: move org I/O behind an OrgClient adapter port",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-28T22:20:20Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "6d7ef166a72339e78f00e69ffc4f8a1f791fc5ef",
          "body": "Use interface for class-implemented contracts (e.g. the OrgClient port) and\ntype for data shapes, so neither is forced. The salesforce config defaults\nthe rule to \"type\"; turn it off and let the author choose by context.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "chore: disable consistent-type-definitions lint rule",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-28T22:20:16Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "d853d640db0c9bc0242cd8a283ddb2f83ddf4dc4",
          "body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: document adapters layer and resolve module in architecture",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-28T22:17:48Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "48dea5a3e9dde7fb066bf05382652afa1b3af803",
          "body": "Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: drop inline example from duplicate-target validation row",
          "author_name": "Isaac Ferreira",
          "author_login": "zaclummys",
          "committed_at": "2026-06-28T22:03:36Z",
          "body_truncated": false,
          "is_coding_agent": true
        }
      ],
      "releases_count": 5,
      "commits_last_year": 123,
      "latest_release_at": "2026-07-18T19:13:11Z",
      "latest_release_tag": "v0.4.0",
      "releases_from_tags": false,
      "days_since_last_push": 5,
      "active_weeks_last_year": 4,
      "days_since_latest_release": 5,
      "mean_days_between_releases": 5
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": false,
      "health_percentage": 57,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "sf-plugin-permission-sets",
          "exists": true,
          "license": "BSD-3-Clause",
          "keywords": [
            "salesforce",
            "sf-plugin",
            "sfdx-plugin",
            "permission-sets",
            "permission-set-assignment",
            "permission-set-group",
            "access-management",
            "user-provisioning",
            "gitops",
            "declarative"
          ],
          "ecosystem": "npm",
          "matches_repo": true,
          "registry_url": "https://www.npmjs.com/package/sf-plugin-permission-sets",
          "is_deprecated": false,
          "latest_version": "0.4.0",
          "repository_url": "https://github.com/zaclummys/sf-plugin-permission-sets",
          "versions_count": 57,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": 1,
          "monthly_downloads": 8679,
          "first_published_at": "2026-06-28T18:37:56.916000Z",
          "latest_published_at": "2026-07-18T19:15:07.281000Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 5
        }
      ]
    },
    "popularity": {
      "forks": 0,
      "stars": 1,
      "watchers": 0,
      "fork_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_forks": 0
      },
      "star_history": null,
      "open_issues_and_prs": 0
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [],
      "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": 17998,
      "source_files_sampled": 41,
      "oversized_source_files": 0,
      "agent_instruction_files": [
        "CLAUDE.md"
      ],
      "agent_instruction_max_bytes": 6263
    },
    "dependencies": {
      "manifests": [
        "package.json"
      ],
      "advisories": {
        "error": null,
        "scope": "published_package",
        "source": "osv",
        "findings": [],
        "collected": true,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 288,
        "malicious_count": 0,
        "assessed_package": "npm:sf-plugin-permission-sets@0.4.0",
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "npm"
      ],
      "dependencies": [
        {
          "name": "@oclif/core",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^4"
        },
        {
          "name": "@salesforce/core",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^8"
        },
        {
          "name": "@salesforce/sf-plugins-core",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^12"
        },
        {
          "name": "globby",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^16"
        },
        {
          "name": "yaml",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^2"
        },
        {
          "name": "zod",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "^4"
        }
      ],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 3,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 9
      },
      "bus_factor": 1,
      "bot_contributors": 1,
      "top_contributors": [
        {
          "type": "User",
          "login": "zaclummys",
          "commits": 121,
          "avatar_url": "https://avatars.githubusercontent.com/u/17896485?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "publish.yml"
      ],
      "has_docs_dir": false,
      "linter_configs": [
        "eslint.config.js"
      ],
      "has_editorconfig": true,
      "has_linter_config": true,
      "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": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "1 out of 1 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": 0,
            "reason": "Found 0/27 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 3,
            "reason": "project has 1 contributing companies or organizations -- score normalized to 3",
            "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": 10,
            "reason": "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": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 0,
            "reason": "project was created within the last 90 days. Please review its contents carefully",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 2,
            "reason": "dependency not pinned by hash detected -- score normalized to 2",
            "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": 10,
            "reason": "security policy file 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": "41e66be1eed3fad7344af0c8333e8b58cb822361",
        "ran_at": "2026-07-24T12:30:07Z",
        "aggregate_score": 5,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": true,
      "has_dependabot_config": true
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-18T23:34:33Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": "2026-07-18T22:17:03Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/zaclummys/sf-plugin-permission-sets",
    "host": "github.com",
    "name": "sf-plugin-permission-sets",
    "owner": "zaclummys"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 57,
      "inputs": {
        "security": 60,
        "vitality": 74,
        "community": 34,
        "governance": 42,
        "engineering": 74
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 74,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 57,
            "inputs": {
              "commits_last_year": 123,
              "human_commit_share": 0.98,
              "days_since_last_push": 5,
              "active_weeks_last_year": 4
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 5 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "4/52 weeks with commits",
                "points": 2.8,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 4
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "123 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 123
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "releases_count": 5,
              "latest_release_tag": "v0.4.0",
              "releases_from_tags": false,
              "days_since_latest_release": 5,
              "mean_days_between_releases": 5
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "5 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 5
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 5 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~5 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 5
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "at_risk",
        "name": "Community & Adoption",
        "value": 34,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 1,
            "inputs": {
              "forks": 0,
              "stars": 1,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "1 stars",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "0 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "moderate",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (BSD-3-Clause)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "BSD-3-Clause"
                    }
                  }
                ],
                "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": 66,
            "inputs": {
              "packages": [
                "sf-plugin-permission-sets"
              ],
              "dependents": null,
              "ecosystems": "npm",
              "total_downloads": null,
              "monthly_downloads": 8679
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "8,679 downloads/month across npm",
                "points": 52.5,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 8679,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "at_risk",
        "name": "Sustainability & Governance",
        "value": 42,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 13,
            "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 1 contributing companies or organizations -- score normalized to 3",
                "points": 3,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "critical",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 18,
            "inputs": {
              "merged_prs": 3,
              "open_issues": 0,
              "closed_issues": 0,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 9
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "no issues or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_issues_or_data",
                    "params": {}
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "3/12 decided PRs merged",
                "points": 9.6,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 3,
                      "decided": 12
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/27 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "moderate",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 53,
            "inputs": {
              "followers": 20,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "zaclummys",
              "public_repos": 28,
              "account_age_days": 3781
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "20 followers of zaclummys",
                "points": 9.5,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 20,
                      "login": "zaclummys"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "28 public repos, account ~10 yr old",
                "points": 22.6,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 28
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 10
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "sf-plugin-permission-sets"
              ],
              "ecosystems": "npm",
              "any_deprecated": false,
              "min_days_since_publish": 5
            },
            "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 5 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "57 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 57
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "good",
        "name": "Engineering Quality",
        "value": 74,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "excellent",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": true,
              "has_linter_config": true,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "2 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 2
                    }
                  }
                ],
                "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": "eslint.config.js",
                "points": 16,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "eslint.config.js"
                    }
                  }
                ],
                "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": 6.4,
                "status": "met",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "1 out of 1 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "moderate",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "topics": [
                "access-management",
                "gitops",
                "permission-sets",
                "salesforce",
                "sfdx-plugin"
              ],
              "has_wiki": false,
              "homepage": null,
              "has_readme": true,
              "has_docs_dir": false,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 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": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": "5 topics",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "topics_count",
                    "params": {
                      "count": 5
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "moderate",
        "name": "Security",
        "value": 60,
        "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": 50,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 17,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 1,
              "scorecard_aggregate": 5
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "1 out of 1 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 0/27 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 1 contributing companies or organizations -- score normalized to 3",
                "points": 0.8,
                "status": "partial",
                "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": "update tool detected",
                "points": 7.5,
                "status": "met",
                "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 detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow 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 2",
                "points": 1,
                "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 detected",
                "points": 5,
                "status": "met",
                "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": "dependency_advisories",
            "band": "excellent",
            "name": "Dependency advisories",
            "note": "Excluded from scoring (no data or not applicable): No advisories left outstanding. Remaining weights renormalized. Matched the npm:sf-plugin-permission-sets@0.4.0 runtime dependency closure — what installing the published package pulls in — 288 packages. Reachability is not analyzed.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "no_advisories_left_outstanding"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              },
              {
                "code": "advisories_scope_published",
                "params": {
                  "package": "npm:sf-plugin-permission-sets@0.4.0",
                  "assessed": 288
                }
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "source": "osv",
              "advisories": 0,
              "affected_packages": 0,
              "assessed_packages": 288,
              "unassessed_packages": 0,
              "affected_by_severity": "none",
              "direct_affected_packages": 0
            },
            "components": [
              {
                "key": "direct_dependencies_free_of_known_advisories",
                "name": "Direct dependencies free of known advisories",
                "detail": "no direct dependency carries a known advisory",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "no_direct_advisories",
                    "params": {}
                  }
                ],
                "max_points": 35
              },
              {
                "key": "indirect_dependencies_free_of_known_advisories",
                "name": "Indirect dependencies free of known advisories",
                "detail": "no indirect dependency carries a known advisory",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "no_indirect_advisories",
                    "params": {}
                  }
                ],
                "max_points": 25
              },
              {
                "key": "no_advisories_left_outstanding",
                "name": "No advisories left outstanding",
                "detail": "no advisory carries a publication date",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "advisories_no_publication_date",
                    "params": {}
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "malicious_dependencies",
            "band": "excellent",
            "name": "Malicious dependencies",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "source": "osv",
              "meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
              "packages": [],
              "red_flag": false,
              "assessed_packages": 288,
              "malicious_packages": 0,
              "direct_malicious_packages": 0,
              "withdrawn_malicious_packages": 0,
              "installable_malicious_packages": 0
            },
            "components": [
              {
                "key": "no_dependency_reported_as_a_malicious_package",
                "name": "No dependency reported as a malicious package",
                "detail": "no dependency is reported as a malicious package",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "no_malicious_dependencies",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "excellent",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "review_only_matches": 0,
              "assessed_self_published_locations": 2
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "good",
        "name": "AI Readiness",
        "value": 82,
        "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": 0.888,
              "agent_instruction_files": [
                "CLAUDE.md"
              ],
              "agent_instruction_max_bytes": 6263
            },
            "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": "87 of 98 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 87,
                      "sampled": 98
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "good",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 74,
            "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": true,
              "typecheck_configs": [
                "tsconfig.json"
              ],
              "agent_commit_share": 0.84,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0.02
            },
            "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": "eslint.config.js",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "eslint.config.js"
                    }
                  }
                ],
                "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": "84 of the last 100 commits agent-authored or agent-credited",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "agent_authored_commits",
                    "params": {
                      "count": 84,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "2 of the last 100 commits are automated dependency updates",
                "points": 8,
                "status": "met",
                "details": [
                  {
                    "code": "dependency_bot_commits",
                    "params": {
                      "count": 2,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 2",
                "points": 2,
                "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": 17998,
              "source_files_sampled": 41,
              "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/41 source files over 60KB",
                "points": 55,
                "status": "met",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 41,
                      "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": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-24T12:30:14.842345Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/z/zaclummys/sf-plugin-permission-sets.svg",
  "full_name": "zaclummys/sf-plugin-permission-sets",
  "license_state": "standard",
  "license_spdx": "BSD-3-Clause"
}

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.27.0 — full methodology · metrics wiki.

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