pf-bridge — keep project files in sync

pf-bridge keeps every project file in sync with your projectfile document. A projectfile is the single source of truth for a project — identity, people, links, and tool config — and pf-bridge copies that data out into the files each tool already reads (README.md, package.json, CITATION.cff, forge settings) and reads two-way files back in.

Step Tool What it does
1. Read / write pf-cli get, set, add, del, validate, convert
2. Sync files pf-bridge (this page) package.json, CITATION.cff, README.md, …
3. Lower to CI pf-ci org.projectfile.ci → workflow YAML

Release binary

Latest release 4.3.0. Published for 5 platforms: macOS, Linux.

Terminal window
base=https://kiota.ch/projectfile/bridge/releases/download/v4.3.0curl --fail --location --output ~/.local/bin/pf-bridge \    "$base/pf-bridge-$(uname -s | tr A-Z a-z)-$(uname -m)"chmod +x ~/.local/bin/pf-bridge
PlatformAsset
macOS / ARM64pf-bridge-darwin-arm64
macOS / x86-64pf-bridge-darwin-x86_64

Verify what you downloaded

Every binary ships a detached signature beside it. Confirm it with the maintainer's release key, which the keyserver serves:

Terminal window
gpg --keyserver hkps://keys.openpgp.org --recv-keys 8F89CBF7C8D51F8BEC7533C00925F8F79EBD3257curl --fail --location --output pf-bridge.asc \    "https://kiota.ch/projectfile/bridge/releases/download/v4.3.0/pf-bridge-$(uname -s | tr A-Z a-z)-$(uname -m).asc"gpg --verify pf-bridge.asc ~/.local/bin/pf-bridge

Container image

Run the tool without installing it. The alias makes every command on this site work unchanged against your working directory:

ghcr.io/projectfile-org/bridge — linux/amd64, linux/arm64, linux/riscv64

Terminal window
docker pull ghcr.io/projectfile-org/bridge:4.3.0alias pf-bridge='docker run --rm --user "$(id -u):$(id -g)" --group-add 0 \    --volume "$PWD:/app/ws" --workdir /app/ws ghcr.io/projectfile-org/bridge:4.3.0 pf-bridge'

damianbuho/projectfile-bridge — linux/amd64

Terminal window
docker pull damianbuho/projectfile-bridge:4.3.0alias pf-bridge='docker run --rm --user "$(id -u):$(id -g)" --group-add 0 \    --volume "$PWD:/app/ws" --workdir /app/ws damianbuho/projectfile-bridge:4.3.0 pf-bridge'

kiota.ch/projectfile/bridge — linux/amd64

Terminal window
docker pull kiota.ch/projectfile/bridge:4.3.0alias pf-bridge='docker run --rm --user "$(id -u):$(id -g)" --group-add 0 \    --volume "$PWD:/app/ws" --workdir /app/ws kiota.ch/projectfile/bridge:4.3.0 pf-bridge'

Stable releases also publish X.Y.Z, X.Y and X tags — pull the precision you want to pin.

Uninstall

Terminal window
rm -f ~/.local/bin/pf-bridge          # release binarydocker image rm ghcr.io/projectfile-org/bridge   # container image
pf-bridge <bridge> [args]   run one bridge (e.g. readme, npm, forge)pf-bridge all [flags]       update every file from the projectfile [rw]pf-bridge check [flags]     verify declared files match, write nothing [ro]pf-bridge to all            write projectfile out to every file [rw]pf-bridge from all          read every two-way file back in [rw]pf-bridge --list            list installed bridgespf-bridge completion SHELL  print completion script (bash|zsh|fish)

[rw] updates files, [ro] only reads or compares. check without names covers the bridges this project declares; check <name>… narrows it, and check --all covers every installed bridge instead.

Direction is a positional preposition. pf-bridge <name> to writes projectfile out to the external file and is authoritative; pf-bridge <name> from reads the external file back in; without either, the sync converges — projectfile values fill and overwrite, and fields projectfile lacks keep the file’s value. Derive-only bridges always resolve to to.

Terminal window
pf-bridge readme                # converge — neither side is clearedpf-bridge readme to              # write   — projectfile winspf-bridge npm from              # read    — package.json winspf-bridge all                    # update every declared filepf-bridge check --all            # report drift, write nothing

Two-way bridges ([rw]) round-trip: the newer side is authoritative and the other side gap-fills. Derive-only bridges ([ro]) are written from the projectfile and never read back. The table below is pf-bridge --list itself, so an installed bridge reaches the page without a hand edit.

BridgeWhat it doesMode
cacheManage the local SPDX and include cachecommand
forgePush projectfile metadata out to the repository forgecommand
initScaffold a new projectfile document (prefer pf-cli init)command
release-notesRender release notes from gitcommand
scanRun scanners to refresh projectfile metadata from filesystem signalscommand
composercomposer.json — two-way synctwo-way
npmpackage.json — two-way synctwo-way
pyprojectpyproject.toml — two-way synctwo-way
shardshard.yml — two-way synctwo-way
cffCITATION.cff — two-way synctwo-way
codeownersCODEOWNERS — two-way synctwo-way
fundingFUNDING.yml — one-way renderone-way
fundingjsonfunding.json — one-way renderone-way
ai-policyAI_POLICY.md — one-way renderone-way
cocCODE_OF_CONDUCT.md — one-way renderone-way
contributingCONTRIBUTING.md — one-way renderone-way
deiDEI.md — one-way renderone-way
fragmentsFEATURES.md, ROADMAP.md, … assembled from docs/<name>.d fragments (one-way render)one-way
readmeREADME.md — one-way renderone-way
securitySECURITY.md — one-way renderone-way
supportSUPPORT.md — one-way renderone-way
browserslist.browserslistrc — one-way renderone-way
gitattributes.gitattributes — one-way renderone-way
ignore.claudeignore, .containerignore, .dockerignore, .fdignore, .gitignore, .npmignore, .textlintignore — one-way renderone-way
licenseLICENSE + LICENSES/<spdx>.txt (one-way render, always overwrite)one-way
releaserc.releaserc.yaml — one-way renderone-way
vulnerabilities.grype.yaml, .trivyignore, audit-ci.jsonc, osv-scanner.toml — one-way renderone-way
yamllint.yamllint — one-way renderone-way

28 commands — the listing pf-bridge v4.3.0 reported when the page was built.

fragments assembles its files from docs/<name>.d fragments rather than a single template. ignore covers .claudeignore, .containerignore, .dockerignore, .fdignore, .gitignore, .npmignore and .textlintignore.

Every bridge accepts these, and every one defaults from its PF_BRIDGE_* environment variable:

Flag Description
--check report drift instead of writing — the sync gate; implies --dry-run
--dry-run show what would change without writing
--fail-on-drift under --check, exit non-zero on drift instead of warning
--force overwrite even if the file lacks the marker
--create-all create target files even if they do not exist
--no-diff under --check, report drift without the unified diff
--preview print the rendered file(s) to stdout instead of writing (renderers)
--offline refuse all network fetches; use embedded and cached data only
--fail-on abort includes at error (default) or warning severity
--ignore-user-config run as if no personal config existed
--sorted write YAML keys in sorted order; disables canvas round-trip preservation
--timeout <dur> per-attempt network timeout; retries and backoff are built in (10s)
-q, --quiet suppress info and decision-trace output
-v, --verbose show operational log lines (file detection, includes, locks)

Subcommands that are not bridges:

Run scanners to refresh projectfile metadata from filesystem signals. Without arguments it opens an interactive picker.

pf-bridge scan [scanner...] [directory] [flags]
Scanner What it does
all run every registered scanner
git authors, remotes and dates — expands to git-authors, git-remotes, git-dates
git-authors extract authors from git history
git-remotes extract origin URL from git
git-dates extract the root-commit date from git
stacks walk the tree, apply the built-in marker rules, merge tags into [technologies]
forge materialise org.projectfile.forge templates into links[] and repositories[]
sinks derive one package-registry link per public registry in org.projectfile.sinks

Stack-specific flags only apply when stacks is in the scanner list: --check (warn about tags with no filesystem evidence, read-only), --strict (same, but stale tags exit non-zero), --prune (remove stale tags in place).

Terminal window
pf-bridge scan git stackspf-bridge scan stacks --checkpf-bridge scan stacks --prune

Push projectfile metadata out to the repository forge, for every non-archive entry in repositories[].

pf-bridge forge push [directory]pf-bridge forge list [directory]     # repositories and whether a token is available

Supported forges: GitHub, GitLab, Forgejo/Gitea. Tokens are read from GITHUB_TOKEN, GITLAB_TOKEN, FORGEJO_TOKEN, or PF_FORGE_TOKEN_<HOST> for self-hosted instances. Self-hosted instances on bare hostnames are not auto-detected; declare them under org.projectfile.forge.kinds:

[org.projectfile.forge.kinds]"code.example.com" = "forgejo"

Render release notes from git.

pf-bridge release-notes renderpf-bridge release-notes publish

Scaffold a new projectfile document with full detection from sources (package.json, CITATION.cff, …) plus scanner gap-fill. pf-cli init delegates here, so prefer that entry point.

Keep a local copy of SPDX license texts and HTTP includes, so LICENSE renders and projectfile reads work offline once warmed. Shared with pf-cli and pf-ci — a purge here clears it for all three.

pf-bridge cache warmpf-bridge cache statuspf-bridge cache purge

pf-bridge check compares what the declared files would become against what is on disk and writes nothing. Drift warns by default; --fail-on-drift makes it fatal, which is the form a pipeline wants.

Terminal window
pf-bridge check --all --fail-on-drift

.onion.i2p