Advanced
Architecture
How SCAL-P's components fit together — CLI routing, policy engine, trust scoring, hashing, lockfile management, and reporting.
SCAL-P is not a daemon, not a proxy, not a plugin. It's a CLI tool that wraps your package manager. Every invocation is stateless: load policy, resolve, evaluate, act, log, exit.
Package structure
cmd/scalp/main.go # entrypoint → cli.Run()
internal/
├── cli/ # command routing (install, audit, ci, verify, checksum, stage, policy)
├── policy/ # policy loading, evaluation, enforcement
├── lockfile/ # .scalp/lockfile.json management + hash verification
├── hash/ # SHA-512 hashing (directory, single file, raw bytes)
├── trust/ # trust score engine, cache, npm API client
├── reporter/ # JSON, Markdown, and SARIF 2.1.0 reports
├── audit/ # NDJSON audit logger
├── ctxutil/ # context helpers
├── pkgmanager/ # PackageManager interface + registry
├── npm/ # npm adapter
├── pnpm/ # pnpm adapter
├── yarn/ # yarn (Berry) adapter
├── bun/ # bun adapter
└── version/ # build-time version injectionData flow (guarded install)
Data flow (stage verify)
Component diagram
Package manager adapter
Every package manager implements the same interface:
PackageManager
├── Name() string
├── Resolve(ctx, args...) error
├── ParseLockfile(ctx) ([]PackageNode, error)
├── Install(ctx, args...) error
├── GetTree(ctx) (DependencyTree, error)
└── LocalPath(name) stringCurrently implemented: npm, pnpm, yarn (Berry v2+), bun. The registry in internal/pkgmanager/registry.go maps names to constructors.
To add a new package manager: implement the interface, register it in init(), done.
Trust score pipeline
CI command flow
File layout
.scalp/
├── policy.json ← your policy (checked in)
├── policy.schema.json ← JSON Schema (checked in)
├── lockfile.json ← auto-generated hashes (recommended to commit)
├── ci-report.json ← last CI run (never commit)
├── cache/
│ └── trust.json ← download counts, CVEs (TTL 7 days)
└── audit.log ← append-only event log (never commit)Design constraints
- Zero external Go dependencies — stdlib only. No framework, no SDK, no ORM.
- No daemon — every invocation is stateless. Load, evaluate, act, exit.
- Offline-first — network is a cache amplifier, not a requirement.
- Deterministic — same inputs (policy, lockfile, cache) produce the same outputs.