IMPROVE YOUR SCORE
This guide shows you how to raise an extension's quality score. Each section addresses one factor from the scorecard rubric. Work through the sections for factors your extension does not currently earn.
Before you start
Open the extension's detail page and look at the factor breakdown. Everything marked "earned" can be skipped — only the gaps are worth your time.
If you change a per-version factor (README content, JSDoc coverage), you will
need to either publish a new version or click "Rescore" on the owner panel.
Changes to per-package fields (description, platforms, repository) take
effect immediately; no rescore needed.
Ship a README
Worth: 2 points.
Create a README.md at the root of your extension and list it under
additionalFiles in the manifest:
additionalFiles:
- README.mdThe scorer accepts the file either at the extension root or under files/ in
the packaged tarball. Keeping it at repo root also means GitHub (and the other
allowlisted hosts) render it on the repository page.
The analyzer accepts README.md, README.MD, readme.md, and Readme.md. Any
other casing (for example ReadMe.md) is not detected.
The entry must be a bare basename. Each additionalFiles entry is copied
verbatim as the archive sub-path under extension/files/, and the analyzer does
not recurse — additionalFiles: [docs/README.md] lands at
extension/files/docs/README.md and earns zero. If your repository keeps each
extension self-contained in its own subdirectory (manifest beside README), opt
into paths.base: manifest so the bare-basename entry resolves against the
manifest's own directory.
Add a code example
Worth: 1 point.
Include at least one fenced code block in the README that shows the extension in use:
```ts
import { validate } from "@mycollective/my-ext";
validate({ region: "us-east-1" });
```Any fenced block counts. A realistic usage snippet is more useful to readers than a trivial one.
Make the README substantive
Worth: 1 point.
The README must be at least 500 characters and contain at least two fenced code blocks. A common shape is one block showing how to invoke the extension and a second showing the expected output or a workflow snippet that consumes it.
Any README with a short intro and two real examples clears the threshold.
Document your exports
Worth: 1 point.
At least 80% of symbols exported from your entry points must carry a JSDoc comment. One sentence each is enough:
/** Validate the behavior's inputs against the schema. */
export function validate(input: Input): ValidationResult {
// ...
}Only exported symbols are counted. Internal helpers that are not re-exported from the entry point do not affect the percentage.
If you are close to the threshold, favour documenting the symbols that consumers will actually call — public types, factory functions, and the main entry function — rather than spreading comments thin across everything.
Slow types need no fix
Worth: 1 point, already earned.
The fast-check factor is deprecated. Every analysed version earns it, so there
is nothing to fix: you do not need to run deno doc --lint, add return types to
exports, or rewrite exported zod schemas and z.infer types for it.
Fix imports the scorer cannot resolve
If the scorecard says deno doc could not analyse this version's source, the
documentation factors score 0 until every listed import resolves.
swamp extension quality reports the same failure before you publish. An import
that resolves locally only through your Deno cache or registry credentials fails
on the registry; run swamp extension quality manifest.yaml --isolated to check
with an empty cache and no credentials, as the registry does.
Give each bare import an explicit specifier. Either resolve it through the project's Deno config (
deno.jsonordeno.jsoncimports, animportMapfile, or a scope), which push writes in its place in the shipped source, or write the specifier in the source.swamp extension pushwarns about each one that ships as written asbare-specifiers:{ "imports": { "zod": "npm:zod@4" } }
// ships as npm:zod@4 with the entry above; without it the registry cannot resolve it import { z } from "zod";
In a
package.jsonproject, declare the package as an npm registry dependency and runnpm install: push ships the import pinned to the installed version. Afile:,link:,workspace:, git or URL dependency ships as written and also costs the dependency-trust factor; write annpm:orjsr:specifier in the source instead.For a module listed as "not in the published archive", check that it sits where the import expects it relative to the importing file. If it does and
swamp extension pushstill leaves it out, report it withswamp issue bug.For a host the scorer may not fetch from, import the package from
npm:orjsr:instead. The scorer also fetches fromdeno.land,esm.sh,cdn.jsdelivr.netandraw.githubusercontent.com, but a URL import from any host costs the dependency-trust factor; see Pass the dependency trust audit.Publish a new version.
Fill in the description
Worth: 1 point.
Set the description field in manifest.yaml:
description: "Manage AWS S3 buckets — create, upload, invalidate, delete."One sentence is fine; a description of only spaces or line breaks does not count. This is a per-package field — editing it on the owner panel updates the score instantly, no republish required.
Declare platforms
Worth: 2 points.
Set the platforms field in manifest.yaml:
platforms:
- linux-x86_64
- darwin-aarch64
- linux-aarch64Or leave it empty:
platforms: []An empty array earns the full 2 points. Any explicit platform list — even a single entry — also earns the full 2 points. Platform breadth is not a quality signal; what matters is that the manifest honestly declares what the extension supports.
Registry search matches the platforms you list. An extension with an empty list appears only when no platform filter is set, so list every platform you want it found under.
Declare a license
Worth: 2 points, one for each of these. A license is optional; nothing is refused or hidden without one.
License declared: set
license:inmanifest.yamlto an SPDX expression. Only SPDX ids count. An invalid expression is reported as a warning at push and earns nothing.license: MIT
LICENSE file included: ship the license text in a non-empty license file. The analyzer looks at the extension root and under
files/for any of these filenames:LICENSE,LICENSE.md,LICENSE.txtLicense,License.md,License.txtlicense,license.md,license.txtCOPYING,COPYING.md,COPYING.txt
List it under
additionalFiles:additionalFiles: - README.md - LICENSE
The entry must be a bare basename — the same rule that applies to README. A nested entry like
additionalFiles: [legal/LICENSE]lands atextension/files/legal/LICENSEand earns zero. Usepaths.base: manifestif your LICENSE sits beside a per-directory manifest.
Both are read from the version, so they change with a new version. See Declare an Extension License.
Link a verified repository
Worth: 2 points (server-verified for a public repository; provisional in the CLI until publish).
Only the registry can verify a public repository. When the CLI expects the
registry to award the factor, the CLI marks it as provisional, shown with a
~ marker: 0 points locally, still counted in the divisor. An extension that
earns everything else scores 87% (Grade B) locally, and the rubric line adds
100% (A) if the registry verifies them. The full 2 points are awarded
server-side on publish.
Set the repository field in manifest.yaml to a public repository on an
allowlisted host:
repository: https://github.com/mycollective/swamp-s3Allowlisted hosts are github.com, gitlab.com, codeberg.org, and
bitbucket.org. Verification runs automatically within a few seconds of publish
and re-runs when the URL changes. A verified result is cached for 7 days and an
unverified one for an hour. A successful check applies to every extension whose
repository is set to the same URL — if several of your extensions live in one
repo, publishing or rescoring any one of them verifies the rest.
Private or self-hosted source
If your collective publishes private extensions from a self-hosted instance (GitHub Enterprise, self-hosted GitLab, Gitea, Forgejo) or from private repositories, ask Swamp Club to mark the collective for private source hosting. Every private extension the collective owns earns the 2 points, and the scorecard reads Private repository (collective attested). A public extension earns them only through a verified public repository.
swamp extension quality scores the factor earned with that label for an
extension that will publish private, whatever its repository URL, so a local
100% gate passes. It needs a login to ask Swamp Club about the collective. To
publish a new extension private, set visibility: private in manifest.yaml,
or publish it to a private namespace. A republish keeps the visibility already
published. See
Publishing § quality for how
the CLI decides.
If you have set the URL but the factor is not earned, check that:
- The URL points to a public repository on one of the four allowlisted hosts, or the extension is private and your collective has private source hosting.
- When the remediation says private source hosting could not be checked, you are
logged in (
swamp auth login) and can reach Swamp Club. - The URL uses
https. - The owner and repository path are correct — GitLab nested group paths must resolve to a real project.
Pass the dependency trust audit
Worth: 2 points.
The scorer extracts every npm: and jsr: import from your code and audits
each at the version that ships. Test files are not audited, and an import in a
comment is not a dependency. A blocker or a dependency that was not audited
costs both points; a loose specifier costs one.
Pin exact versions or commit a lock. An unpinned, range or dist-tag
specifier that no lock fixed costs one of the two points, however many there
are. Either write npm:express@4.21.1 instead of npm:express@^4.18.0, or
commit the deno.lock beside your deno.json; in a package.json project,
commit a package-lock.json that matches node_modules. A lock also fixes the
transitive versions the bundle holds. npm:zod@4 is the exception; zod is not
audited.
Replace code from outside npm and JSR. A URL import, a git dependency or a
local file: dependency costs both points: no registry audit covers it. Publish
the code as an npm: or jsr: package and import that instead, or move it into
the repository and import it relatively.
Publish with swamp extension push. Push writes every import at the exact
version it bundled. A source edited by hand after push, so that it writes a
range or a version dependency-audit.json does not list, is not audited.
Fix a module that does not parse. Its imports cannot be read, so the factor is withheld until it parses.
Fix blockers first — each costs both points:
- Replace deprecated packages with their maintained successors, and move a pin to a deprecated version onto one that is not deprecated. A deprecated pin only warns at push, but it costs both points.
- Update packages that have HIGH or CRITICAL severity advisories on OSV.dev. If no patched version exists, switch to an alternative package.
- Fix a
jsr:import whose package or version does not exist: check the name and the version against the package's page on jsr.io. - Move a
jsr:import off a yanked version onto one that is not yanked. A yanked version only warns at push, but it costs both points.
The other warnings cost no points, but swamp extension quality reports
them so you can assess the risk:
- MEDIUM severity advisories.
- A license that is not an SPDX expression of allowlisted licenses (MIT,
Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD, MPL-2.0, Unlicense,
CC0-1.0), or no license at all.
MIT OR Apache-2.0passes. - Fewer than 1,000 weekly downloads on npm.
- Last published more than 24 months ago.
- No maintainers listed on the npm registry.
Rescore a dependency that was not audited. When the npm registry, OSV.dev or
JSR lookup for a dependency fails, the scorecard lists it as not audited and the
factor is withheld, the same as a blocker. swamp extension quality marks it
? with not audited: and the failed lookup. Once npm, OSV.dev and JSR are
reachable, press Rescore on the extension page, or run
swamp extension quality again locally. If the reason is HTTP 404 from the
npm registry, check the package name in the import — npm does not know it.
If the reason is OSV not queried (no published version matches <spec>),
rescoring does not help: no version of the package satisfies the specifier.
Change the specifier to a version the package has published, then publish a new
version.
If your extension has no npm: or jsr: dependencies, no code from outside npm
and JSR, and every module parses, the factor is earned automatically.
After fixing blockers, publish a new version or click "Rescore" on the owner panel.
Keep secrets out of the docs
Worth: 1 point.
The extension page lists each secret-unsafe-docs finding under Docs keep
secrets in a vault, with its file and line. swamp extension quality reports
the same findings before you publish.
Store the secret with a prompt. Replace a command that puts the value on the command line with one that prompts for it:
swamp vault put my-vault api-tokenPass the secret as a vault expression. Replace a literal, a placeholder, or
vault:// syntax with a ${{ ... }} expression:
swamp behavior create @acme/artifactory my-artifactory \
--global-arg 'token=${{ vault.get(my-vault, api-token) }}'Use swamp vault put, not swamp vault set.
Remove real credentials. Replace a token, key or password in the docs with a
placeholder of one repeated character (glpat-xxxxxxxxxxxxxxxxxxxx), or a URL
on an example.com host.
Mark secret arguments sensitive. An argument named like a secret is treated
as one unless its schema declares it. Declare the argument, and mark it
.meta({ sensitive: true }) when it holds a secret.
Split a Markdown file over 1 MiB. A file that size is not read, and docs the scorer has not read do not earn the factor.
Accept a line that shows the wrong form on purpose. For a line inside a
fenced code block, add a quality.yaml entry beside manifest.yaml naming the
file and line:
version: 1
accept:
- rule: secret-unsafe-docs
file: README.md
line: 42For a manifest description finding, add an entry naming manifest.yaml. See
Publishing § quality for the
other acceptance forms.
Publish a new version. A rescore reads the acceptances the published version was pushed with, so an acceptance added after publishing counts only from the next version.
Troubleshooting: "My score did not move"
The most common cause is the tarball layout. The Swamp CLI packages a published extension like this:
extension/
manifest.yaml
models/ ← behavior files land here
your_behavior.ts
files/ ← additionalFiles land here
README.md
LICENSEFiles you list under additionalFiles end up under files/ in the archive. The
scorer looks for README.md and license files at both the extension root and
under files/, so either layout works — but if your local filesystem has a
README.md that is not listed in additionalFiles, it will not be in the
tarball at all.
The lookups at both locations are exact — the analyzer does not recurse into
subdirectories under files/. Each additionalFiles entry is copied verbatim
as the archive sub-path, so a nested entry like
additionalFiles: [docs/README.md] lands at extension/files/docs/README.md
and earns zero. The fix is almost always to rewrite the entry as a bare
basename. For per-extension-subdir layouts, opt into paths.base: manifest. An
entry must name a file: listing a directory such as docs stops quality and
push with Additional file is a directory: docs (...), so list each file you
want shipped.
If has-readme is reporting 0/2 and you are sure you shipped one:
- Confirm the manifest lists
README.mdunderadditionalFiles. - Confirm the entry is a bare basename, not a nested path like
docs/README.md. - Republish, or click "Rescore" on the owner panel.
- Recheck the detail page — the factor should update within a few seconds.
Apply the same steps for license-file if the license file factor is not
earning.
Rescore after edits
Per-version factors (has-readme, readme-example, rich-readme,
symbols-docs, fast-check, has-license, license-file, dependency-trust,
secret-safe-docs) are only re-evaluated when a new version is published or
when the owner clicks "Rescore" on the detail page. Editing per-package fields
on the owner panel does not retrigger per-version analysis.
Per-package factors (description, platforms) update instantly on edit.
The repository-verified factor re-runs immediately when the URL changes.
Otherwise it re-checks at the next publish or rescore, but only if the stored
result is more than 7 days old (one hour for an unverified result). A successful
check fans out to every extension sharing the same repository URL, so one
publish or rescore heals all siblings in a shared repo. There is no separate
periodic sweep — if nothing triggers a scoring event for any extension on the
URL, a stale cache stays put.
Related
- Extension Scorecard Rubric — exact factors, weights, and grade thresholds.
- About the Extension Scorecard — what the score measures and why it exists.
- Extension Manifest — YAML schema for
description,repository,platforms, andadditionalFiles. - Create and Publish — full publish workflow.