// dependency interception · v0.6.4 · multi-ecosystem
§ 02 Docs npm packages · Go modules · one engine

One tool, two ecosystems.

phi auto-detects the ecosystem from your cwd: package.json → npm mode, go.mod or go.work → Go mode. Each tab below documents that ecosystem's surface; the security model, lockfile schema, and trust-override flags are identical across both.

Quick start (npm)

In a project with a package.json:

phi install

phi resolves the full transitive tree and scans every package before extraction. SAFE packages install first. REVIEW packages install after approval (or automatically with --yes), and accepted REVIEW packages recorded in phi.lock are not re-prompted on later installs. BLOCKED packages are skipped by default and only materialized with --force. Unresolvable packages are listed under skipped in phi-report.json while the rest of the install continues.

To add a new dep:

phi install chalk@^5.0.0

Updates the dependencies field, scans, installs.

To audit without installing:

phi audit

Commands

Command Aliases Behavior
phi init Create package.json + .gitignore + README.md. --yes for non-interactive; --force to overwrite.
phi create <fw> <name> Scaffold a new project. fw: react, next, express, fastify, nest. Pass-through args after --. See § 02.5.
phi install [pkg…] i, a Best-effort scan and install. Args are added to package.json (or to devDependencies with --save-dev). SAFE packages install even when another requested package cannot resolve; REVIEW/BLOCKED packages are gated after the safe extraction step.
phi update [pkg…] u Re-resolve and install fresh, ignoring phi.lock.
phi ci Production install for non-interactive environments (Docker, GitHub Actions). Sugar for phi install --frozen-lockfile --yes. See § 02.8.
phi remove <pkg…> rm Drop from package.json, prune from lockfile + node_modules.
phi audit Resolve + scan, no extraction. Writes phi-report.json.
phi audit fix [--apply|--force] Propose fixes for fixable issues. Preview by default; --apply writes safe fixes to package.json; --force allows breaking changes (replacement packages, major-version bumps). See § 02.6.
phi check verify Verify package.json, phi.lock, and node_modules. Detects lockfile drift, missing packages, and changed installed files when the cached tarball is available.
phi do <script> [args…] d Run a script from package.json with node_modules/.bin on PATH. Pre/post hooks honored. --filter <glob> (repeatable) runs the script in every matched workspace with a per-workspace header. See § 02.15.
phi exec <pkg>[@<ver>] [args…] x Run a binary; auto-fetch and scan like npx if not in node_modules/.bin. Flags: -p <pkg>, --no-install, -y, --rescan, -f. See § 02.7.
phi dev | build | start | … Direct shortcuts to phi do <name>. Available: dev, build, start, test, lint, preview, prod.
phi outdated Show direct deps with newer versions available, color-coded by major bump. --filter <glob> for per-workspace tables in monorepos. Live spinner during packument checks. See § 02.13.
phi upgrade-interactive ui Walk every outdated direct dep with a per-package y/n/a/q prompt. --allow-major to include major bumps; --yes for non-interactive bulk upgrades. Preserves the existing range prefix (^, ~, exact). See § 02.13.
phi view <pkg>[@<ver>] info Packument inspector with a security scan by default — runs phi's detectors + OSV check on the resolved tarball and prints score / verdict / detections / advisories alongside the standard metadata. --no-scan for the fast metadata-only path; field-targeted queries (phi view react license) auto-skip the scan. --json includes a "scan" object. See § 02.10.
phi ls [pkg] list Print the dependency tree from phi.lock in npm-style ASCII. --depth N caps depth; --prod drops devDependencies; --json emits a stable JSON shape; bare [pkg] prunes to only branches that reach a match. See § 02.11.
phi why <pkg> Print every dep chain from a root that reaches <pkg>.
phi link [pkg] Global package linking with Windows directory junctions (no admin required). In a package directory: registers it under ~/.config/phi/links/<name>. In a consumer directory: phi link <name> creates node_modules/<name> pointing at the registered target. --list shows registered links. See § 02.12.
phi unlink [pkg] Reverse either side based on cwd: in a registered package directory, drops the global entry; in a consumer with a link present, removes the symlink/junction.
phi pkg get|set|delete|fix Read and write package.json with dot-path semantics. Preserves indent style, BOM, and key order on round-trip. Conservative coercion: true/false/null/literal {}/[] become JSON; bare numeric strings stay strings (so engines.node = "20" round-trips cleanly). See § 02.14.
phi config get|set|delete|list ls Read and write ~/.config/phi/config (or %APPDATA%\phi\config on Windows) with comment and blank-line preservation. Secret-shaped keys (containing token, password, secret, _authtoken) are auto-redacted when stdout is a terminal; non-tty pipes get raw values. See § 02.14.
phi cache stat Prints both cache sections — the tarball cache (reused across installs) and the stage cache ($XDG_CACHE_HOME/phi/runs/, populated by phi x and similar one-shot binaries).
phi cache clean Prune the cache. --older-than 30d default; --all wipes everything; --list prints contents without deleting; --runs targets the stage cache instead of tarballs; --all-caches applies the prune to both in one pass.
phi self-update Update phi itself to the latest GitHub release. --check just reports availability; --version v0.X.Y pins; --yes skips the confirmation prompt. Distinct from phi update which updates project dependencies.
phi version Print version, e.g. phi 0.6.4.
phi help Print available commands and flags.

Auto-fixing with phi audit fix

New in v0.2.3. Propose actionable fixes for direct dependencies. Three sources, in order of confidence:

  1. Typosquats → swap to the popular package name (loadshlodash). Always safe — different package, same intent.
  2. Advisory bumps → upgrade to the OSV-documented fixed in version. Same-major bumps are safe; cross-major bumps are flagged as breaking.
  3. Deprecated packages → swap to the curated successor (vm2isolated-vm, requestundici, node-uuiduuid, …). Always breaking — public API differs.

Three modes:

phi audit fix              # preview only — no filesystem changes
phi audit fix --apply      # write SAFE fixes to package.json
phi audit fix --force      # write EVERY fix, including breaking changes

After applying, run phi install to materialize the new versions.

How phi differs from npm audit fix: phi handles typosquats and deprecations (not just CVE bumps); phi shows the proposal before applying; phi explicitly distinguishes safe-to-apply fixes from breaking changes — they're rendered in separate color-coded groups.

One-shot binaries with phi x (npx-equivalent)

New in v0.3.0. phi x (the alias for phi exec) is now a drop-in replacement for npx: if the bin isn't already in node_modules/.bin, phi resolves the providing package, runs every detector + the OSV layer over the transitive tree, then runs the bin from a per-version cache. Repeat invocations of the same resolved version skip the scan and run instantly. The user's node_modules is never touched.

phi x cowsay "hello phi"           # fetch + scan + run, like npx
phi x prettier --write src/        # args after the pkg go to the bin verbatim
phi x cowsay@1.5.0                 # pin a version (always fetches)
phi x -p typescript tsc --version  # bin name (tsc) ≠ package name (typescript)
phi x -y create-react-app my-app   # auto-approve review verdicts (CI / scaffold)
phi x --no-install eslint .        # strict-local: fail if not already installed
phi x --rescan cowsay              # invalidate the cached scan, redo it
Flag Behavior
-p, --package <pkg> Package name when bin name differs (npx parity for typescripttsc).
--no-install Strict-local: fail if the bin isn't already in node_modules/.bin. The pre-v0.3.0 behavior.
-y, --yes Auto-approve review-verdict packages instead of prompting. Blocked still aborts unless --force.
--rescan Invalidate the cached scan-passed marker for the resolved version, refetch + rescan.
-f, --force Override blocked verdicts and proceed anyway. Loud warning printed.

Cache layout:

$UserCacheDir/phi/run/<name>@<version>/
  node_modules/<name>/…           # extracted package + transitive deps
  node_modules/.bin/<bin-name>     # shim, on PATH for the bin's lifetime
  .phi-scan-passed                 # marker; presence means "trusted, run"
How phi x differs from npx: every package goes through phi's full scanner + advisory pipeline before the bin runs — the same gate that protects phi install. Lifecycle scripts stay off (npx runs them by default). Blocked verdicts abort unless --force; review verdicts prompt unless -y. Pinned versions and scoped packages match npx semantics; git/GitHub/tarball URLs are not yet supported (registry-only for now).

Production installs with phi ci

New in v0.4.1. Non-interactive environments (Docker builds, GitHub Actions) have no stdin for phi's REVIEW prompt — without help, an install hangs forever. phi ci solves this without losing the audit:

phi ci                          # production: --frozen-lockfile + --yes preset
phi ci --omit=dev               # also skip devDependencies (npm parity)
phi ci --force                  # explicit override for blocked verdicts
phi ci --no-advisories          # offline / air-gapped builds

phi ci is sugar for phi install --frozen-lockfile --yes. The frozen lockfile is the audit record — any package in phi.lock was already reviewed and approved by a human during their local phi install. Prod can therefore auto-approve REVIEW verdicts without a prompt. BLOCKED verdicts still abort unless --force is explicit. A missing or drifted lockfile errors clearly (phi.lock not found / phi.lock is out of sync with package.json) — there's no silent fallback to a fresh resolve.

Composable primitives

phi ci is built on two new flags that work standalone too:

Flag Behavior
-y, --yes Auto-approve REVIEW verdicts instead of prompting. BLOCKED still aborts unless --force. Works on phi install, phi update, phi ci.
--omit=dev Skip devDependencies during resolution — npm parity. Useful for prod-deploy bundles where test runners, linters, type defs aren't needed. Both --omit=dev and --omit dev accepted.

Docker / GitHub Actions build commands

# Dockerfile
COPY package.json phi.lock ./
RUN phi ci --omit=dev

# GitHub Actions
- run: phi ci --omit=dev
Why --frozen-lockfile is the right default for prod: the lockfile pins exact resolved versions and integrity hashes. Without it, a transient registry change could ship a different tree to prod than dev tested against. With it, prod refuses to install anything the dev hasn't already seen and approved.

Security inspection with phi view

New in v0.5.0; scan-by-default in v0.5.1. Pointed at any public package, phi view fetches the packument and the tarball, runs phi's detector pipeline, queries OSV for known advisories, and prints the human metadata block alongside a security: section showing score / verdict / detections / advisories. Same engine phi install and phi audit use — so you get the same verdict you'd get if you installed the package, before you add it to package.json.

phi view react                       # latest version, full security scan
phi view react@18.2.0                # version-pinned scan
phi view lodash@4.17.10              # known-vuln older version → BLOCKED + 9 GHSA hits
phi view react --json                # JSON output, including the "scan" object
phi view react --no-scan             # fast metadata-only path (no tarball fetch)
phi view react license               # one field via gjson dotpath (auto-skips scan)
phi view react 'versions[0]'         # array index queries

Sample output (truncated) — a vulnerable older lodash:

lodash@4.17.10
A modern JavaScript utility library delivering modularity, performance, & extras.

  license:      MIT
  homepage:     https://lodash.com/
  published:    2018-04-24 (8 years ago)
  versions:     117 total · latest 4.18.1
  tarball:      https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz

security:
  score:        100/100
  verdict:      BLOCKED
  files scanned: 1047
  advisories (9):
    - [CRITICAL] GHSA-jf85-cpcp-j695 — Prototype Pollution in lodash (fix: 4.17.12)
    - [HIGH] GHSA-35jh-r3h4-6jhm — Command Injection in lodash (fix: 4.17.21)
    - [HIGH] GHSA-4xc9-xhrj-v574 — Prototype Pollution in lodash (fix: 4.17.11)
    - [HIGH] GHSA-p6mc-m468-83gw — Prototype Pollution in lodash (fix: 4.17.19)
    - [HIGH] GHSA-r5fr-rjxr-66jc — Code Injection via _.template (fix: 4.18.0)
    - [MODERATE] GHSA-29mw-wpgm-hmr9 — ReDoS (fix: 4.17.21)
    - [MODERATE] GHSA-f23m-r3pf-42rh — Prototype Pollution via array path (fix: 4.18.0)
    - [MODERATE] GHSA-x5rq-j2xg-h7qm — ReDoS (fix: 4.17.11)
    - [MODERATE] GHSA-xxjr-mmjv-4gpg — Prototype Pollution in _.unset/_.omit (fix: 4.17.23)
Why the scan runs by default: the security story is the point of using phi over npm view. Showing tarball URL and license without showing the verdict on those bytes would undersell the tool. The fast metadata path is one flag away (--no-scan) for scripting, and the field-targeted form auto-skips the scan because pipelines rarely need it.

Inspecting trees with phi ls

New in v0.5.0. npm-style ASCII dependency tree, sourced from phi.lock (falls back to a fresh resolve when no lockfile exists).

phi ls                    # full tree, all direct deps
phi ls --depth 0          # direct only (no transitives)
phi ls --depth 1          # direct + one level
phi ls --prod             # drop devDependencies
phi ls lodash             # prune to branches that reach lodash
phi ls --json             # stable JSON shape for scripts

Honors NO_COLOR for plain ASCII output (no bold escapes) — useful when piping to less or capturing to a file.

Global linking with phi link

New in v0.5.0. Two-step npm link-style flow, but cross-platform without admin: macOS / Linux get real symbolic links; Windows gets directory junctions (mklink /J) so no developer mode or elevation is required.

# 1. In the package being developed
cd ~/work/my-lib
phi link                  # registers ~/.config/phi/links/my-lib → cwd

# 2. In the consumer
cd ~/work/some-app
phi link my-lib           # creates node_modules/my-lib → ~/work/my-lib

phi link --list           # show registered links and their targets
phi unlink my-lib         # consumer side: drop node_modules/my-lib
cd ~/work/my-lib && phi unlink   # package side: drop the global entry

Storage: ~/.config/phi/links/<name> on Linux/macOS, %APPDATA%\phi\links\<name> on Windows. Scoped names round-trip through one extra directory level (links/@org/utils).

Outdated reports & interactive upgrades

New in v0.5.0; live spinner in v0.5.1. phi outdated queries every direct dep against the registry's latest dist-tag and prints a color-coded table — yellow for minor / patch bumps, red for major. Workspace siblings declared with workspace: are stripped from the table (they don't have a registry version to be "behind").

phi outdated                          # table for root direct deps
phi outdated --filter '@org/*'        # per-workspace tables, filtered
phi outdated --filter './apps/**'     # filter by workspace path

phi upgrade-interactive (alias phi ui) walks each outdated package and asks [y]es / [n]o / [a]ll / [q]uit. The existing range prefix is preserved when rewriting package.json"^1.2.0" + new latest 1.3.0 becomes "^1.3.0", not a bare exact pin.

phi upgrade-interactive               # walk minor/patch bumps with prompts
phi ui --allow-major                  # also propose major-version bumps
phi ui --yes                          # accept every proposal (CI-safe)
phi ui --allow-major --yes            # upgrade everything to latest

Editing package.json and config

New in v0.5.0. Two scriptable round-trippers replace shell sed/awk:

phi pkg — edit package.json

phi pkg get name                       # → "monorepo"
phi pkg get dependencies.lodash        # → "^4.17.20"
phi pkg set scripts.lint "eslint ."    # add or replace a script
phi pkg set engines.node "20"          # stays the STRING "20", not coerced
phi pkg set private true               # JSON true (boolean)
phi pkg delete scripts.lint
phi pkg fix                            # sort deps, dedupe dev→prod

Preserves indent style, leading BOM, trailing newline, and key order on round-trip. Conservative coercion: only true / false / null and literal {} / [] parse as JSON. npm pkg parity for the common flows.

phi config — edit ~/.config/phi/config

phi config set registry "https://registry.npmjs.org/"
phi config set //npm.pkg.github.com/:_authToken "ghp_..."
phi config get registry
phi config list                        # comments preserved; secrets redacted on tty
phi config delete //npm.pkg.github.com/:_authToken

Secret-shaped keys (containing token, password, secret, _authtoken) are auto-redacted in phi config list when stdout is a terminal; piping to another command gets raw values for scripting. Comments and blank lines survive round-trips so hand-edited files stay readable.

Workspace selectors with --filter

New in v0.5.0. pnpm-style filter syntax for running a script in a subset of a monorepo. Accepted by phi do and phi outdated.

Selector Matches
@org/api Exact workspace name.
@org/* Name glob across one segment.
./apps/** Path glob, relative to cwd. Matches against the workspace's resolved directory.
!@org/api Negation. Combine with positive selectors to exclude.
phi do build --filter '@org/*'                       # build every @org/* workspace
phi do build --filter '@org/*' --filter '!@org/web'  # everything except @org/web
phi do test --filter './apps/**'                     # only workspaces under apps/

# Each match runs in its own dir with a header:
# === @org/api (1/3) ===
# === @org/billing (2/3) ===
# === @org/queue (3/3) ===

Missing scripts in a filter run are skipped silently (npm parity) — so phi do test --filter '@org/*' works even when only a subset of workspaces have a test script.

Scaffolding with phi create

Bootstrap a new project end-to-end. The scaffolder package itself is fetched and audited through phi's safe pipeline (lifecycle scripts off) before its binary runs in your current directory. The temp install is wiped after one use.

Framework Underlying scaffolder Output
phi create react <name> create-vite (template react-ts) React + Vite + TypeScript
phi create next <name> create-next-app (with --skip-install) Next.js (App Router)
phi create express <name> built-in template (no network fetch) Minimal Express server, embedded in the phi binary via embed.FS
phi create fastify <name> fastify-cli generate Fastify HTTP server
phi create nest <name> @nestjs/cli new (with --skip-install) NestJS application

List available frameworks at any time:

phi create

Pass-through flags after -- go straight to the scaffolder. User flags override phi's defaults on flag-name collisions:

phi create react my-app
phi create express my-server
phi create next my-site -- --typescript --app --no-tailwind
phi create nest my-api -- --package-manager npm

Why --skip-install by default for next and nest? Both scaffolders auto-run npm install after writing files, which would bypass phi's safe pipeline for the project's actual dependencies. Phi disables it so the user runs phi install in the new directory instead, getting all transitive deps scanned.

Security note: the scaffolder package's transitive deps are fetched and scanned, but REVIEW prompts are auto-approved during the scaffold (their use is ephemeral — the temp dir is wiped after the scaffolder runs once). BLOCKED packages still abort the scaffold. Your project's own deps go through the normal interactive review when you run phi install afterwards.

Flags

Flag Effect
--allow-scripts a,b Run lifecycle scripts only for the named packages. Default: never run.
--frozen-lockfile Require phi.lock to exactly cover package.json. CI mode.
--no-lockfile Ignore phi.lock and resolve fresh.
--no-advisories Skip OSV vulnerability database query (offline mode).
--json Emit phi-report.json to stdout, suppress UI. Non-interactive mode skips unapproved REVIEW packages while keeping them in the report.
--save-dev / -D Write to devDependencies, moving from dependencies if needed.
--save-peer Write to peerDependencies.
--save-exact / -E Pin without caret prefix.
--force / -f Install BLOCKED verdicts that are skipped by default. The scan still runs and phi-report.json is still written — phi's audit trail is preserved, you've just chosen to install regardless. Implies auto-approval of REVIEW packages. Use case: a known-trusted package that phi has flagged (e.g. a library that uses eval() internally for a documented private method).
--omit=dev,optional,peer Skip dependency classes during resolution (npm parity). Comma-separated values accepted in one flag. dev drops devDependencies at the root and in every workspace. optional / peer are accepted as no-ops because phi already skips those during install resolution — the flags exist so the same install command works across npm, yarn, pnpm, and phi.
--filter <selector> Workspace selector for phi do and phi outdated. Repeatable. See § 02.15.

Environment variables

CI and Dockerfile users often can't thread flags through every call site, so the most useful flags also resolve from environment variables. Flag values still win if both are set.

Variable Equivalent
PHI_CI=1 Translates phi install into phi ci (frozen lockfile + auto-approve REVIEW).
PHI_YES=1 Same as --yes.
PHI_OMIT=dev,optional,peer Same as --omit=dev,optional,peer.
PHI_FROZEN_LOCKFILE=1 Same as --frozen-lockfile.
PHI_FORCE=1 Same as --force. Use sparingly — bypasses BLOCKED verdicts globally.
PHI_REGISTRY_TIMEOUT=5m Override npm registry HTTP timeout for slow networks.
PHI_REGISTRY_ATTEMPTS=5 Override registry retry attempts for packument/tarball fetches.
PHI_REGISTRY_RETRY_DELAY=1s Override the first registry retry delay; later retries back off.

Lockfile & cache

phi.lock is generated on every install. Format mirrors npm's package-lock.json shape with extra score and verdict fields per entry. When present and it covers package.json, phi reuses it without re-resolving.

Cache: Tarballs cache at $XDG_CACHE_HOME/phi/tarballs/ (Linux/macOS) or %LOCALAPPDATA%\phi\tarballs\ (Windows), keyed by sha512 integrity. Repeat installs are near-instant.

Workspaces

If your root package.json declares a workspaces field, phi aggregates dependencies from every workspace package, installs the union into the root node_modules/, and links each workspace into node_modules/<workspace-name> as a junction (Windows) or symlink (Unix).

{
  "workspaces": ["packages/*"]
}

Both array form and {packages: [...]} object form are supported. Sibling references ("@org/utils": "*" from inside @org/app) bypass the registry — they resolve through the link.

The workspace: protocol (v0.5.0)

For unambiguous sibling references that won't accidentally fall through to the registry, phi supports the same workspace: protocol pnpm and yarn-berry use:

Spec Behavior
"workspace:*" Match any version of the sibling workspace. Never produces a version warning.
"workspace:^1.0.0" / "workspace:~1.0.0" Match a sibling whose version satisfies the semver range. A version mismatch produces a single consolidated warning per workspace — no silent fallthrough to the registry.
"workspace:1.2.3" Exact version match against the sibling.
"workspace:./packages/lib" / "workspace:../lib" Path-relative form (yarn-berry parity). Resolves relative to the declaring workspace's directory.

A missing sibling for any workspace: spec is an explicit warning — phi does not fall through to npm and fetch a package with a colliding name from the public registry, which would otherwise be a stealth supply-chain risk in a monorepo.

Filtering and scripting

phi do --filter and phi outdated --filter let you scope a command to a subset of workspaces. See § 02.15 for the full selector syntax. phi link works inside or outside a monorepo and is unrelated to workspace: resolution — use it when the package being linked lives outside the repo.

Private registries (.npmrc)

phi reads .npmrc from $HOME and the project root (project wins on conflict). Supported settings:

//npm.pkg.github.com/:_authToken=${GITHUB_PAT}
@my-org:registry=https://npm.pkg.github.com/

CI integration

# GitHub Actions
- run: phi install --frozen-lockfile
- run: phi audit --json > phi-report.json
- run: jq '.summary.blocked == 0 and .summary.review == 0' phi-report.json

--frozen-lockfile requires phi.lock to exactly cover package.json; non-zero exit on drift. --json emits the full scan report to stdout and exits non-zero on any blocked or review-flagged package.

Quick start (Go modules)

phi switches to Go mode when the current directory (or any ancestor) contains a go.mod or go.work. The same command names you already know — install, audit, outdated, view, build — route to the Go pipeline automatically.

In an existing Go project:

phi install

What happens:

  1. phi parses go.mod + go.sum. If go.sum doesn't cover go.mod, phi fails loudly and asks you to run go mod tidy first.
  2. go mod download runs into a phi-controlled staging directory (~/.cache/phi/go-staging/<run-id>) via the GOMODCACHE=<staging> env override. Bytes do not land in your real $GOMODCACHE yet.
  3. Each fetched .zip is h1-verified against go.sum (constant-time compare via golang.org/x/mod/sumdb/dirhash), then scanned by phi's detector pipeline + OSV.
  4. All-or-nothing gate. If any module is BLOCKED, phi refuses to materialize its bytes; phi.lock still writes (audit trail). Approved modules get atomically moved into the real $GOMODCACHE per-file.
  5. go build, go test, and the rest of the toolchain see a normal cache and proceed as usual.

To start a brand new module from scratch:

phi init --go github.com/you/myproject
# creates go.mod, .gitignore (with Go-tuned entries), README.md

Commands

Command Aliases (Go-toolchain dialect) Behavior
phi init --go <module-path> phi mod init Run go mod init <path>, write a Go-tuned .gitignore and a starter README.md. --force overwrites pre-existing files; --no-readme skips the README.
phi install phi mod tidy · phi mod download Hybrid replace pipeline: go mod download → staging → scan → materialize. Writes phi.lock.
phi install <mod>[@<ver>] ... phi get <mod>[@<ver>] Shells out to go get to add the modules to go.mod, then runs the install pipeline to scan + materialize the resulting tree. Accepts SHAs: phi install github.com/foo/bar@abc1234 resolves to a pseudo-version.
phi install <pkg>@<ver> (no go.mod) Binary install. When run outside a Go project, phi treats this as the analog of go install foo@latest: fetches into staging, scans, then runs go install for real. Flags: --force, --allow <pattern>, --no-advisories, --gobin <dir>. See § 06.
phi update [mod...] phi get -u [mod...] go get -u with phi's scan + lock on the new versions.
phi remove <mod> phi mod edit -droprequire <mod> Drop a require + run go mod tidy. Does not automatically re-scan; run phi install afterwards if you want the new lockfile state.
phi audit phi mod verify Read-only scan over go.sum. Reports transparency-log (sum.golang.org) status in the summary footer.
phi audit fix [--apply [--force]] Per-direct-dep OSV query. Splits proposed upgrades into safe (same-major) and breaking (cross-major). --apply rewrites go.mod via go get; --force includes breaking bumps. See § 04.
phi outdated phi list -m -u all Table of direct + indirect modules with newer versions. Major bumps in red, minor/patch in yellow, indirect deps in muted text.
phi upgrade-interactive (alias phi ui) Walks each outdated module, prompts y/n/a/q. Selected upgrades batched into a single go get call. --allow-major includes major bumps; --yes for non-interactive bulk upgrades.
phi ls [filter] phi list -m all Flat module list with // indirect markers. Filter substring narrows the output; --direct drops indirects; --json emits a stable shape.
phi view <mod>[@<ver>] Metadata + on-demand security scan. Shows version list, publish time, replace target if any, deprecation / retraction status. --no-scan for the fast metadata-only path; --json for the structured shape.
phi why <mod> phi mod why <mod> Print the import chain that pulls a module into your tree.
phi doc [pkg [symbol]] go doc with a phi-verdict footer when the queried package is in your dep tree. Pure passthrough for stdlib lookups.
phi build [args] · phi test · phi run · phi vet · phi fmt phi go <subcmd> Toolchain passthroughs with a phi.lock freshness check upfront. PHI_STRICT_BUILD=1 or --strict turns the warning into a refusal. See § 07 for --cross.
phi replace <old> <new>[@<ver>] phi go mod edit -replace=... Write a replace directive. Fires the replace-with-fork detector if the redirect crosses VCS orgs. phi replace --list shows current directives. See § 09.
phi unreplace <mod> phi go mod edit -dropreplace=... Drop a replace directive.
phi mod vendor go mod vendor Materialize vendor/ from already-scanned modules in $GOMODCACHE. Useful for offline / reproducible / air-gapped builds.
phi go <anything> Universal Go toolchain escape hatch. Forwards every argument to go with the user's environment intact.
phi cache stat · clean Reports four caches: npm tarballs, phi-x run stages, Go verdict cache, Go's $GOMODCACHE. clean --verdicts / --gomodcache / --all-caches. See § 11.

Security model

The architectural promise is the same as on the npm side: bytes never reach your real cache before phi has a verdict on them. Implementation differs because Go's threat model is different from npm's.

npmGo
When can hostile code run? postinstall at install init() at program start; //go:generate during go generate; cgo at build
Are package bytes themselves dangerous? Tarball can include auto-running scripts Zip is inert — source code only
What does phi quarantine? Extraction into node_modules/ Materialization into $GOMODCACHE
What's the CI gate? phi ci (frozen lockfile, non-interactive) phi audit --strict (refuses any un-overridden BLOCKED entry)
Audit trail invariant: phi.lock writes on every run, including when there are BLOCKED entries (which are recorded with the verdict reason but not materialized). CI tooling reads phi.lock as the truth surface — never assumes its absence means "nothing was scanned."

Detectors (Go-specific)

Six per-module detectors fire as the analyzer walks each module's source tree. One project-level detector fires on your own go.mod.

DetectorPatternSeverity
go-init-network init() calls net. or net/http. — outbound HTTP/DNS on import HIGH
go-init-fs-write init() writes to the filesystem outside os.TempDir() HIGH
go-cgo-shellexec import "C" alongside os/exec.Command or syscall.Exec in the same file CRITICAL
go-build-tag-gated Restrictive //go:build directive (3+ tokens or negation) wrapping network/exec/fs calls MEDIUM
go-go-generate-curl //go:generate shells out to curl / wget / arbitrary URLs HIGH
go-unsafe-import Use of the unsafe package LOW (signal, not blocker)
go-replace-with-fork (project-level) Your go.mod has a replace directive pointing to a different VCS org than the original MEDIUM

Per-file 5 MiB cap, per-module 10s wallclock timeout, parser panic recovery. The analyzer reads zips via archive/zip without extracting to disk.

Trust overrides

Three escalating escape hatches when phi flags a module you've decided to ship anyway. Same shapes as the npm side.

--force — single-shot blanket override

phi install --force
# All BLOCKED entries admitted, with `forceAllowed: true`,
# timestamp, and reason recorded per entry in phi.lock.

--allow <pattern> — scoped per-install

phi install --allow github.com/sirupsen/logrus
phi install --allow 'github.com/sirupsen/*'
phi install --allow github.com/sirupsen/logrus@v1.9.3

.phi-allow — persistent project-level

Commit this to your repo, like .gitignore:

# .phi-allow
github.com/sirupsen/logrus        # uses init() for log-level setup
github.com/spf13/cobra@v1.*        # cgo for terminal detection
github.com/foo/bar                 # FALSE POSITIVE: $HOME read, not network

phi reads .phi-allow at the start of every gate check. Each line is <pattern> # <reason>; comments after the pattern are retained for audit logs. Precedence: --force > --allow > .phi-allow.

Binary install (Go)

When you run phi install <pkg>@<ver> outside a Go project, phi treats it as a global tool install (the analog of go install foo@latest) and runs the scan-then-install flow:

# Outside any go.mod directory:
phi install golang.org/x/tools/cmd/goimports@latest
phi install github.com/cosmtrek/air@v1.49.0
phi install --gobin ~/bin golang.org/x/perf/cmd/benchstat@latest

Pipeline:

  1. go list -m resolves the version (handles @latest, SHAs, pseudo-versions)
  2. Module containing the binary is fetched into staging
  3. Analyzer + OSV scan
  4. Gate (BLOCKED → refuse unless --force / --allow)
  5. go install <pkg>@<resolved-ver> runs for real, writing the binary to $GOBIN (or wherever --gobin points)

Build & toolchain passthroughs

phi build / test / run / vet / fmt forward to the matching go subcommand. Before doing so, phi checks phi.lock freshness vs go.sum:

Default is warn-and-proceed. To make warnings fatal:

phi build --strict
PHI_STRICT_BUILD=1 phi build  ./...

Cross-compile sugar

--cross GOOS[/GOARCH] sets the corresponding env vars for this invocation only (no leak to your shell):

phi build --cross linux/arm64 -o myapp-arm64
phi build --cross darwin/amd64 ./cmd/server
phi build --cross windows -o myapp.exe

Universal escape hatch

Anything phi doesn't wrap natively, hand to phi go <anything>:

phi go env GOMODCACHE
phi go tool pprof cpu.prof
phi go bug

Go workspaces — go.work

phi detects Go workspaces by walking from cwd up to the filesystem root looking for a go.work file. When found, phi install runs the pipeline for every use directive in source order, writing a per-member phi.lock. The aggregate verdict summary prints at the end.

# In a workspace root containing go.work:
phi install                # walks every member module
# Each member gets its own phi.lock alongside its go.sum.

use directives that escape the workspace root (e.g. use ../outside) produce a review warning but don't abort — they're allowed by Go but unusual enough to flag.

Adding new modules to a specific member still requires you to be in that member's directory (so go get knows which go.mod to edit):

cd packages/api
phi install github.com/gin-gonic/gin@v1.9.1

Replace directives

replace in go.mod redirects a require to a different module path or local directory. phi wraps the toolchain commands:

phi replace --list                                # show current replaces
phi replace github.com/foo/bar github.com/myorg/bar@v1.2.3
phi replace github.com/foo/bar ../local-fork      # local replacement
phi unreplace github.com/foo/bar                  # drop the directive

On write, the replace-with-fork detector fires if the redirect crosses VCS orgs (e.g. github.com/foo/bargithub.com/quux/bar) — not blocking, but surfaced as a review warning. Local-path replaces are assumed intentional (you wrote the path yourself).

Lockfile, audit trail, CI gating

Go-mode phi.lock uses the v3 schema with ecosystem: "go" and per-entry source.type = "go-module". Each entry records:

CI gating

In CI, phi audit --strict is the canonical gate. It exits non-zero if any BLOCKED entry in phi.lock lacks an override.

# GitHub Actions example
- run: phi install
- run: phi audit --strict
- run: go build ./...
- run: go test ./...

Cache management

phi cache stat reports every cache phi touches:

$ phi cache stat
Tarball cache (npm):  $LOCALAPPDATA/phi/tarballs
  entries: 1182
  size:    134.7 MiB
Run stages (phi x):   $LOCALAPPDATA/phi/run
  files: 0
  size:  0 B
Verdict cache (Go):   $LOCALAPPDATA/phi/verdicts/go
  entries: 28
  size:    8.4 KiB
Go module cache:      ~/go/pkg/mod
  size:  507.8 MiB  (managed by `go`)

phi cache clean takes target flags. The verdict cache is content-addressed by h1 hash, so wiping it is always safe — phi just re-scans next install (which fills it back up).

phi cache clean --verdicts                # wipe phi's verdict cache
phi cache clean --gomodcache              # shells out to `go clean -modcache`
phi cache clean --all-caches              # tarballs + runs + verdicts (not GOMODCACHE)
phi cache clean --older-than 14d          # default target: tarballs

Environment variables

VariableEffect
PHI_ECOSYSTEM=go|npm Force ecosystem; useful when both go.mod and package.json exist in the same dir.
PHI_STRICT_BUILD=1 phi build refuses on stale phi.lock instead of warning.
GOPROXY · GOPRIVATE · GOSUMDB · GOFLAGS Honored transitively — phi shells out to go which respects them.
GOBIN Where phi install <bin>@<ver> writes the binary. Defaults to $GOPATH/bin or ~/go/bin.

sum.golang.org transparency

phi delegates module fetching to the Go toolchain, which performs the transparency-log cross-check against sum.golang.org by default. We never independently re-verify — duplicating Google's reference implementation would risk divergence.

phi audit's footer surfaces the resolved state:

audit: 47 modules scanned · safe=46 review=1 blocked=0 (3.2s)
        transparency: ON via sum.golang.org

When GOSUMDB=off, GONOSUMCHECK=1, or -insecure appears in GOFLAGS, the footer reads transparency: OFF with the reason. phi.lock's audit trail is still complete; the user just knows the public log wasn't consulted.