This document is for contributors working on the SpecDD CLI codebase.
For CLI usage, see README.md. For source-adjacent requirements, read the relevant .sdd specs before editing code.
- Node.js 22 or newer.
- Yarn 1.x. The project currently declares
yarn@1.22.22.
Install dependencies:
yarn installThe CLI is organized as a small layered application. The entrypoint owns process concerns, the container owns object wiring, commands translate CLI input into service calls, and services own application behavior.
main.ts
-> Container
-> commands
-> services
-> infrastructure adapters
src/main.ts exposes the Main class and contains the self-running package entrypoint. It builds the root specdd
command from command instances exposed by Container.
Main is intentionally testable:
run(argv)parses caller-provided argv values.- Commander exits are overridden so tests can assert behavior without process exits.
selfRun(...)receives process dependencies explicitly, including realpath resolution, so package-bin symlink behavior is testable.
src/container.ts is the composition root. It creates shared infrastructure adapters, services, and command instances.
Dependencies are wired explicitly through constructors. Commands and services should not reach for global service
instances or construct their own collaborators. If a new service or command is added, it should be exposed through
Container once its local spec defines that behavior.
Command modules live in src/commands.
Commands are CLI adapters. They should parse command arguments and options, resolve CLI-facing values such as paths, and delegate to services. They should not download releases, verify signatures, inspect zip files, or apply distributions directly.
Current command flow:
specdd init [path] [--version]
-> DistributionInstaller.init(...)
specdd update [--version]
-> DistributionInstaller.update(...)
Services live under src/services, with each service in its own directory.
The current distribution pipeline is:
DistributionInstaller
-> DistributionClient downloads specdd.zip and specdd.zip.asc
-> SignatureVerifier verifies the zip against embedded trusted keys
-> DistributionApplier applies verified files to the target directory
Supporting services:
Configresolves configuration from readers and defaults.Loggerwrites CLI notices with levels and colors.
Services depend on narrow collaborator interfaces, usually Pick<...> types, so tests can provide small fakes and the
runtime container can provide concrete adapters.
Infrastructure adapters live in src/infrastructure.
These classes wrap Node or runtime APIs:
FetchClientwrapsfetch.FileSystemwraps filesystem operations.TempDirectorywraps temporary directory creation.
Adapters should stay thin. Application decisions belong in services, not infrastructure wrappers.
cli.sdd repository root spec
src/src.sdd source tree spec
src/main.ts CLI entrypoint
src/main.sdd entrypoint spec
src/container.ts composition root
src/container.sdd container spec
src/commands command handlers
src/infrastructure runtime adapters
src/services application services
src/services/*/service.sdd service specs
Run the full build and release-prep check:
make buildRun type checking:
yarn typecheckRun tests:
yarn testRun tests with coverage:
yarn test:coverageBuild:
yarn buildMaintain CHANGELOG.md in Common Changelog format. Add or update the release entry
before tagging a release; do not use an Unreleased section.
Release entries use:
## [VERSION] - YYYY-MM-DDUse the package version without a leading v, keep entries latest-first, and group changes with Common Changelog
categories such as Changed, Added, Removed, and Fixed.
Use the matching changelog entry as the GitHub release body.
The npm package includes a Unix manual page at man/specdd.1, exposed through the man field in package.json.
Keep it up to date when CLI commands, options, defaults, exit behavior, important paths, or user-visible safety behavior
change.
The Makefile owns manpage maintenance. The manpage version is synced from package.json:
make man-sync
make man-checkmake build runs the manpage check, and make release syncs the manpage version before release preflight.
Before considering a change complete, run:
make buildThe project expects full coverage for functions, services, and commands.
Dependency security is intentionally conservative:
- Direct runtime and development dependencies are pinned to exact versions in
package.json. yarn.lockis committed and checked withyarn install --frozen-lockfile.npm-shrinkwrap.jsonis committed and included in the published package so npm installs of the CLI use the locked dependency tree.- Yarn ignores
npm-shrinkwrap.json, so both lock files are maintained. make buildrefreshes the npm shrinkwrap metadata after version or dependency changes.- Shrinkwrap sync runs with
--ignore-scriptsto avoid executing dependency lifecycle scripts during lock metadata updates. - Both
yarn security:auditandnpm audit --audit-level=infoare run bymake build. npm pack --dry-runis part ofmake buildso package contents, includingnpm-shrinkwrap.json, are checked before release.
Runtime distribution verification is also self-contained:
- Distribution signatures are verified in Node.js through the OpenPGP library.
- Trusted SpecDD signing public keys are embedded in TypeScript source.
- Runtime verification must not shell out to external
gpgor other system commands.
After bumping the package version, run:
make buildThis updates shrinkwrap metadata, runs audits, typechecks, tests, builds, and verifies the package dry run.
To publish a release, update the Homebrew tap, and publish the Docker image after confirmation prompts, run:
make releaseTo update only the Homebrew tap formula after an npm package has already been published, run:
make bump-homebrewThe target reads the version from package.json by default. Use VERSION=1.2.3 to override it.
To build and push the official multi-architecture Docker images after the npm package has been published, run:
make docker-releaseThis publishes:
ghcr.io/specdd/cli:<version>
ghcr.io/specdd/cli:latest
specdd/cli:<version>
specdd/cli:latest
for:
linux/amd64
linux/arm64
The Docker image installs the published npm package for the requested version. It does not build from local source.
For a local single-architecture smoke check after publishing:
make docker-build
make docker-smokeAfter building, run:
node dist/main.js --helpThe package binary points to dist/main.js.
Start with the smallest relevant spec. For a new command, add a command spec and command module, then wire the command
through Container. For a new service, add a service directory with its service.sdd, implementation, and focused
tests.
Keep boundaries explicit:
- CLI parsing belongs in commands.
- Workflow orchestration belongs in services.
- Node/runtime calls belong behind infrastructure adapters.
- Shared runtime wiring belongs in
Container.
Config values currently come from environment variables using the SPECDD_ prefix.
Example:
SPECDD_LOG_LEVEL=debug yarn testConfig keys should have defaults declared in the config defaults class. Accessing an undeclared key intentionally emits a warning.