The interactive commands

Four command groups, eighteen subcommands. All of them are interactive: true, the deliberate scheduler bypass (#2642): the CLI spawns the process with the terminal's own stdout and parses nothing, so what the binary writes is the command's output.

These sections were part of the CLI's own command reference until #3214. They describe the same surface, now served by @putnami/sdd.

Group Subcommands Positionals Selection flags
features list validate snapshot inspect diff [query], none, none, <feature-id>, <base> <head> honored, honored, honored, refused, refused
specs list validate inspect init none, none, <feature-id>, <feature-id> honored, honored, refused, refused
architecture validate snapshot inspect init sync none, none, <domain-id>, <domain-id>, none global --baseline; init honors selection, sync refuses it
contracts generate check none (--project <selector>) --projects

architecture

The architecture namespace is experimental. go.putnami.dev/protocol/architecture is classified experimental in putnami.support.json, so the manifest format, finding IDs, and detector coverage may change without a migration path.

Adopting it is an explicit opt-in, and automatic admission is a second, separate decision under workspace options.sdd.verification.architecture. enforce blocks on coherent findings, report keeps the same typed findings advisory, and off skips the automatic DAG evaluation. Structural and policy errors always fail closed. This repository commits enforce, so architecture-validate remains a blocking step of validate-workspace, which the required quality job runs.

The namespace makes Architecture Rules as Code (ARC) and Domain Access & Replication Contracts (DARC) executable from any Putnami workspace:

putnami architecture validate
putnami architecture snapshot --output=json
putnami architecture inspect <domain-id>
putnami architecture validate --baseline origin/main

Domains author exact putnami.architecture.json files. The producer declares authoritative exported facts and compatibility; the consumer declares its minimized import, semantic access mode, lifecycle, transport availability, consistency and local-model guarantees. The global graph is derived from those distributed declarations, never authored as a second central matrix.

validate performs one contained discovery, validates both halves through go.putnami.dev/protocol/architecture, and compares exact cross-domain project dependencies in the resolved Putnami graph with declared bindings. A planned import cannot carry a binding, so a target design cannot hide current drift. The v1 snapshot explicitly reports database, HTTP and event detectors as not-detected; a declared API or event is not treated as observed evidence.

Existing adoption debt may be listed in the optional workspace-root architecture.baseline.json. Once committed, that file is shrink-only against the resolved Git baseline. Later temporary exceptions belong in architecture.waivers.json; expired and stale entries fail. The commands never create, grow, or rewrite either file. --baseline <git-ref> overrides the Git comparison point using the ordinary global baseline flag.

The cached architecture-validate job deliberately runs without a baseline and reads no git history at all; baseline ratcheting is an interactive and review concern. See 02-validation-jobs.md.

That automatic mode never changes these interactive commands: even at off, an explicit putnami architecture validate, snapshot, or inspect request runs normally.

snapshot emits the complete canonical declared/observed graph, coverage, stable findings and ratchet dispositions. inspect accepts only an exact manifest-minted domain ID and returns that domain plus its inbound, outbound, observed and violating relationships. Human and ResultV2 output are projections of the same evaluation. Relationship direction follows fact flow (producer → consumer): inbound means the selected domain consumes the facts; outbound means it produces facts consumed by another domain. This is not the dependency-graph arrow convention. See the protocol README and ADR 0008.

init and sync — the authoring half

putnami architecture init <domain-id> [--owner <owner>] [--at <dir>] [--dry-run]
putnami architecture sync [--apply]

Both write into the source tree, and both stop exactly where a human decision starts.

init creates one canonical putnami.architecture.json for a domain that does not exist yet. It states only what you stated: the domain id, its owner (defaulting to the domain id), and the projects your selection resolved to — this is the one subcommand of the four groups whose project selection is its content rather than its scope, so --projects and --scope are honored. Two modes are refused instead: --impacted resolves to what changed, which is not a membership, and --all would claim every project in the workspace for one domain. An unscoped run writes "projects": [], which is the protocol's own way of saying the domain maps nothing yet.

The manifest lands beside the first project the domain maps, in canonical order; --at <dir> puts it anywhere else in the workspace. A domain another manifest already declares is refused by identity, an existing file at the target path is refused by the exclusive create itself, and --dry-run prints the target path and the exact canonical bytes without writing. Exports, imports, and bindings stay for you to author, for the same reason specs init writes no requirement: each is an agreement between two domains that no generator can derive.

sync reconciles the mechanical half of every existing manifest against the resolved graph:

Change Why it needs no decision
Drop a project entry naming a project the workspace no longer contains Already a hard architecture.unknown_project error; there is nothing left to decide about
Drop a binding whose project edge the graph no longer observes Already a hard architecture.declared_binding_unobserved error; removing a permission is never the dangerous direction
Add a binding for an observed edge the consumer domain already declares an import for The relation was negotiated; the binding only names which projects implement it

Everything else is refused and named. Sync never writes an import — minimized facts, an access mode, consistency, a justification are a contract between two domains — so an observed dependency with no declared relation stays a failing architecture validate finding until somebody declares it. Two narrower cases refuse for the same reason: more than one candidate import (sync will not choose which contract authorizes an edge) and a planned-only candidate (a planned target cannot carry a binding, so the decision is whether to promote the import). Adding a project to a domain is refused by omission: membership is authority, not observation.

Without --apply nothing is written. With it, a changed manifest is rewritten in canonical form through the same builder an authoring program uses; an unchanged one is left exactly as its author committed it. The git diff is the authorization moment for every binding sync proposes — the write is not.

Sync succeeds even when it refuses: it is an authoring aid, and the verdict belongs to architecture validate. Like contracts generate, neither subcommand is a step of validate (ADR 0013, decision 5): a gate does not write into the source tree.

Both are post-#3214 additions, so neither has a recorded parity oracle — see 05-parity.md.

features

The feature-evidence namespace provides local, read-only commands for validating authored product intent against exact build/framework evidence:

putnami features list [query]
putnami features validate
putnami features snapshot
putnami features inspect <feature-id>
putnami features diff <base-revision> <head-revision>

list, validate, and snapshot take the normal project-selection vocabulary — --projects, --impacted, --all, --tag, --exclude-tag, --exclude, --baseline — with the same identities, aliases, groups, scope expressions, baseline resolution, and deterministic ordering a job command uses. inspect and diff already name an exact target, so they reject those flags instead of accepting one that would change nothing.

list is the compact catalog: every explicitly authored feature — native design declarations plus durable putnami.features.json entries — with its outcome, owner, implementing projects, and exact declaration sources. It is the same builder the sdd.list_features MCP tool answers from. An optional query filters ids, names, outcomes, and owners after project selection.

validate discovers putnami.features.json only at the workspace root and exact project roots, loads their separate evidence artifacts and Capability Manifests, coalesces identical dependency copies by semantic owner, and reports strict, sorted diagnostics. Missing, stale, contradicted, and unclassified evidence stays visible as assessment warnings; malformed documents, conflicting copies, and broken or ambiguous contribution references fail.

Selected projections

Selected projects are seeds, not a filesystem wall. A scoped run:

  • reads durable putnami.features.json and canonical specs/*.json at every root. Those are the only artifacts that mint an identity, so reading them workspace-wide is what keeps global duplicate detection exact under any selection. Each is one bounded document per root.
  • reads evidence fragments, capability manifests, and generated design graphs only for the selected projects plus the exact roots a selected feature reaches: the project that produced its evidence, and the project that owns a technical contribution that evidence names. Followed records keep their exact source project and path.
  • never opens an unselected project's design graph. That is the read whose cost grows with the repository.
  • fails on an external error that invalidates a selected feature — a duplicate declaration of a selected id is still an error, still named by its own path — and never on an error wholly outside the selection.

Human output opens with the resolved scope, and --output=json carries a selection object with the mode, resolved baseline and its resolution tier, the selected project ids, the selected feature/spec counts, and every external record followed. A --impacted run that finds no change is a successful, explicit no-op; a --projects selector matching nothing is a not-found error.

The selection is resolved by the CLI, not here: it arrives on the wire as a selection block naming the mode, the resolved baseline and its resolution tier, and the project ids. Nothing in this extension re-resolves it, because a second impact resolver is a second definition of what changed.

inspect shows one feature's authored intent and target, evidence-derived current maturity, each requirement state, exact semantic contribution identities/owners, transport containers, and content-bound declaration/artifact provenance. Feature metadata is inspection-only and never affects runtime activation or policy.

snapshot emits the deterministic current-workspace projection. diff resolves both arguments to exact commits, rebuilds each commit's own workspace and project membership, and evaluates its manifests, evidence, Capability artifacts, and source bindings directly from immutable Git objects. It reports added, removed, promoted, regressed, newly stale, newly contradicted, and newly unclassified facts without checking out either revision, moving HEAD, or consulting current-worktree artifacts. Unsafe, unresolved, or ambiguous revision arguments fail with typed diagnostics.

Snapshot and diff data are deliberately provisional until the Cloud parity/comprehension dogfood is complete. --output=json uses the stable CLI ResultV2 envelope while data carries compatibility: "provisional"; human output also labels these views provisional and is not a parsing surface. See the feature evidence and Capability Manifest v2 RFC for discovery, maturity, diagnostics, determinism, containment, redaction, and migration rules.

specs

The spec namespace reads the durable specifications defined by the spec contract: direct JSON children of specs/ at the workspace root or at an exact project root, each detailing exactly one already-authored feature.

putnami specs list
putnami specs inspect <feature-id>
putnami specs validate
putnami specs verify [--session <id>]
putnami specs baseline [--update]
putnami specs init <feature-id> [--dry-run]

list and validate take the same project-selection vocabulary features list does, and scope the same way (see Selected projections). inspect and init name an exact feature, and baseline derives a whole-workspace floor; all three reject those flags rather than silently ignoring them. There is no filename filter on any of them: identity is the feature field.

list is the compact catalog: for every document, the feature it details, its exact path and owning project, its first intended outcomes, and its non-goal, requirement, and decision counts. A filename never mints identity — the feature field does — so a path is provenance only. Discovery degrades rather than fails: a document that cannot be parsed is reported as a diagnostic and a document that does not resolve is marked valid: false, instead of hiding every healthy spec.

inspect answers for one exact feature id: its durable declaration from putnami.features.json, the spec's outcomes, non-goals and agreed requirement sentences, every linked doc/adr/*.md record with whether that record exists in this worktree, the source path and owning project, and the diagnostics scoped to that document. It loads no design graph, evidence document, or capability manifest.

validate reuses the protocol's own parser and repository validator — a second interpretation of these bytes would be a defect — and fails on an invalid document, an unresolvable or duplicated feature reference, an unsafe decision path, or two specs for one feature. It also reports, as warnings that never change the exit code, the authoring gaps around the contract: authored features with no spec (specs.missing_spec), publishable projects with no entry in putnami.support.json (specs.missing_support_entry), publishable projects with no owning feature link (specs.missing_feature_link), and linked decision records that are not in the tree (specs.unresolved_decision). Human output prints every error and a bounded number of warnings; --output=json always carries the complete list. Under a selection the completeness counts narrow to the selected publishable projects and authored features, while duplicate detection stays workspace-wide.

verify is the audit surface of the executable-spec gate (#3193, fixed decision 9). Core's post-session finalizer — attached to every real engine session containing test — joins the criteria projection specs-validate emits with the observation reports the test jobs declared, decides each textual requirement (verified, unmapped, unexecutable, missing, stale, or contradicted) through the protocol's one pure evaluator, and persists the verdict record beside the session. specs verify reproduces that decision: per spec-owning project, the effective enforce | report | off mode with its provenance (project override, workspace policy, or the built-in report), every requirement's state with per-check test provenance, the observation reports read with their SHA-256 digests, and the recorded blocking decision — reproduced, never recomputed. --session <id> names an exact recorded session; the default follows the latest one, and a spec no session covered is evaluated against zero observations through the same shared rule, so nothing unproven reads as green. Under an effective off policy the group reports automaticEvaluation: false and succeeds; structural spec errors keep failing exactly as specs validate fails. The committed policy lives in options.sdd.verification (workspace or spec-hosting project), and an unknown domain or value there fails every run before jobs execute.

baseline maintains the rollout ratchet of that gate (#3193, PR 3). It derives the enforced floor — every project whose effective specs verification mode is enforce, with the feature#requirement identities its criteria make executable — and compares it byte-for-byte with the committed specs.baseline.json at the workspace root. --update rewrites the file to the canonical derived floor and is the one place the baseline is raised or deliberately lowered; the specs-ratchet-validate step of validate-workspace only ever compares, and fails when an enforced project regressed to report/off or lost recorded requirement coverage without an edit to the committed baseline in the same change. Growth never requires an edit — the check passes and nudges you to raise the floor. The floor is whole-workspace by construction, so project selection flags are rejected.

init creates one minimal canonical skeleton next to the declaration of an already-authored feature. It refuses an unknown feature, refuses a feature that already has a spec, and never overwrites an existing file (the document is created exclusively, so the guarantee is a property of the write). The skeleton states only what the declaration states — the feature's own outcome, an empty requirement list, no decisions — because requirements and decisions are agreements no generator can derive. Its default path uses the final feature-id segment: billing/invoice-export creates specs/invoice-export.json, while the document and report keep the complete billing/invoice-export identity. Existing specs are never renamed. If another feature already occupies the same leaf path, init refuses that path instead of inventing a different filename. --dry-run prints the same target path and exact canonical bytes without writing. There is no batch or --all mode; see ADR 0007 for why this surface stays read-mostly and non-destructive.

contracts

Generate and check a project's committed contract artifacts.

putnami contracts generate --project /tooling/cli   # Regenerate committed artifacts
putnami contracts check --project /tooling/cli      # Fail on drift or a breaking change

generate validates the authored contract manifest and atomically rewrites the committed canonical IR, the Go type twin, and the JSON Schema under schema/. check regenerates in memory and exits 2 on artifact drift or on a renamed/removed enum value, scope, claim, or grant compared with the git-committed prior contract, emitting a machine-readable compatibility report. Both accept --output=jsonl.

Neither is part of the validate job, deliberately. check's exit 2 means "artifacts drifted", which is a different verdict from the job contract's generic failure, and flattening the two would lose the distinction exactly where it matters. generate writes into the source tree, which a gate should not do. Both take a per-project positional target rather than the selection vocabulary. Revisit after the validation jobs have adoption (ADR 0013, decision 5).

Where the dispatcher answers instead of the extension

Four differences from the pre-extraction built-ins are the CLI's, not this extension's, and cannot be reproduced from inside a subprocess the CLI never reaches:

  • putnami features nonexistent is answered by the CLI's extension dispatcher before the binary runs, so the message is the dispatcher's ("unknown subcommand", subcommands listed alphabetically) and the exit code is 1 rather than the built-in's 2.
  • putnami features with no subcommand prints the group's subcommand listing instead of the built-in's "unknown subcommand" error.
  • An undeclared flag is a deprecation warning for an extension group and a hard rejection for a built-in (#2631 refinement Q1).
  • A selection-rejecting subcommand given an invalid selector (putnami features inspect X --projects nope) reports the selector's own not-found, because selection resolves before dispatch. A valid selector still gets the verbatim refusal.

Everything else is byte-identical to the built-in it replaced, proven by the recorded parity fixtures — see 05-parity.md.