cli

package
v0.0.0-...-fc1e2a3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2026 License: MIT Imports: 63 Imported by: 0

Documentation

Overview

Package cli — `forge audit` command.

Audit produces a comprehensive snapshot of project state designed to orient an LLM (or human) without forcing them to grep ten different directories. It rolls up:

  • Forge version pin (forge.yaml forge_version vs binary buildinfo).
  • Project shape (kind, services + RPC counts, workers, operators, frontends, installed packs).
  • Convention compliance (rolled up forge lint counts per category).
  • Codegen state (last generate timestamp via .forge/checksums.json, orphan _gen files, uncommitted user edits to forge-space files).
  • Pack health (each installed pack's version against the embedded pack registry).
  • Pack graph health (every installed pack's `depends_on` is also installed; missing producers surface as errors).
  • Proto-vs-migration alignment (entity tables vs db/migrations/).
  • Migration safety summary (allowed_destructive count, latest migration timestamp, destructive_change severity).
  • Wire-coverage (unresolved Deps fields in pkg/app/wire_gen.go, rolled up from `forge lint --wire-coverage`).
  • FORGE_SCAFFOLD marker counts (P0 sharpening surface).
  • Deps health (go.sum freshness vs go.mod, gen/ presence).

JSON output groups checks by category with status: ok|warn|error so a sub-agent can branch on `.codegen.status == "warn"` directly.

Package cli — dev-mode forge/pkg replace handling.

Background: when forge is checked out alongside a project (sibling directories on disk), the project's go.mod commonly carries a

replace github.com/reliant-labs/forge/pkg => /absolute/host/path/forge/pkg

directive so the project can use forge/pkg subpackages that haven't been published yet. This works for `go build` on the host but breaks `docker build`, because the absolute host path isn't visible inside the build context.

The canonical fix in forge: detect such absolute-path replaces during `forge generate`, vendor the target into `<project>/.forge-pkg/`, and rewrite the replace to `./.forge-pkg`. The Dockerfile template emits a corresponding `COPY .forge-pkg/ ./.forge-pkg/` line whenever the vendored copy exists, so docker builds and host builds use the same replace target by construction.

This is a development affordance — production deploys want forge/pkg as a real go.mod requirement, not a vendored copy. The opt-in is implicit (presence of the host-absolute replace in go.mod) and can be disabled with `forge.yaml -> dev.vendor_local_forge_pkg: false`.

Package cli — `forge generate --explain` provenance log.

Explain mode runs the normal generate pipeline and then prints a per-file traceability log: for each forge-tracked output, which source files / proto descriptors / contract.go inputs drove its generation, plus a "reason" field saying whether the file was rewritten or skipped because the input was unchanged.

Implementation choice: rather than instrument every individual generator (mock_gen, middleware_gen, crud_gen, …) with a callback, we read the post-generate state from forge_descriptor.json, the project filesystem, and .forge/checksums.json. The derived provenance is "approximate but useful" — exactly what the LLM caller wants. We can tighten it later by threading a callback through the codegen package, but we don't need to pay that cost up front to ship the most-useful 80%.

Package cli — typed step plan for `forge generate`.

Pre-2026-05-06, runGeneratePipeline was a 584-line procedural function holding 25 numbered ordered steps gated by 93 Features.*Enabled() checks. New steps were 30 lines of boilerplate appended to a numbered-comment sequence (Step 0a, 0b, 0b.1, ..., 8d-iii, 8f.1) — the numbering itself pleaded for a data structure (FORGE_REVIEW_CODEBASE.md Tier 1.1).

This file replaces that procedural blob with a typed []GenStep plan. Each step is a small named function operating on a shared pipelineContext. The pipeline becomes a loop over the slice that:

  • calls step.Gate(ctx) — pure, side-effect-free predicate; false skips
  • calls step.Run(ctx) — the action, returning an error to abort

The shape unblocks several downstream wins documented in the codebase review:

  • --plan / --explain print the plan without executing it
  • `forge dev` watch loops re-run only steps whose Tag matches changes
  • per-step unit tests against a synthetic pipelineContext
  • one-time parse of services/entities into ctx (avoids re-parsing ParseEntityProtos 3× — see Tier 2.5)

As of 2026-05-07 the entire pre-refactor pipeline is now flat: every numbered legacy step has a dedicated stepXxx entry below, and runMidPipelineLegacy is gone. The single remaining shared-state hop (parse services + module path once for steps 4-6) is its own GenStep.

(2026-05-06 polish-phase, completed 2026-05-07) — closes FORGE_REVIEW_CODEBASE.md Tier 1.1.

Package cli — `forge map` command.

Map is a tree-shaped view of the project with ownership annotations on every leaf. The annotations come from cross-referencing:

  • The .forge/checksums.json file (forge-tracked = forge-space).
  • File-name conventions (*_gen.go = forge-regenerated).
  • Forge banner check (`Code generated by forge` first line).
  • FORGE_SCAFFOLD marker presence (scaffolded but not finalized).
  • Health flags: hand-edits to forge-space files (drift), proto/db entities not represented in migrations, etc.

Filtering and depth control mirror the spec: --depth N, --filter <subtree>, --json. The non-JSON output is plain-text indented tree (no box-drawing chars in the source — we render them at print time so the file stays grep-friendly).

Code generation functions for the protoc-gen-forge plugin.

Code generated by refactor extraction from main.go. Types and parsing for the protoc-gen-forge plugin.

Forge ORM is annotation-driven: a message becomes a database entity ONLY when it carries an explicit `option (forge.v1.entity) = { ... }` and has at least one field marked `(forge.v1.field) = { pk: true }`. There is no auto-detection by field name (`id`, `created_at`, `tenant_id`, `email`, `*_id`, etc.) — every semantic must be declared. Auto-detection was removed in forge v0.6 because it conflicted with the explicit annotation system: name-based heuristics silently picked up unintended messages (e.g. service Request/Response wrappers with an `id` field) and shadow-applied semantics that the user never asked for.

Index

Constants

View Source
const (
	OwnershipUser         = "user-owned"
	OwnershipForgeSpace   = "forge-space, regenerated"
	OwnershipForgeDrifted = "forge-space, hand-edited (drift from regen)"
	OwnershipScaffold     = "scaffold, FORGE_SCAFFOLD markers present"
	OwnershipScaffoldOnce = "user-owned, scaffolded once"
	OwnershipUnknown      = ""
)

Ownership classes used in the output. We pick a narrow set so a downstream agent can switch on them.

Variables

View Source
var ErrProjectConfigNotFound = errors.New("forge.yaml not found in current directory (run 'forge new' to create a project)")

ErrProjectConfigNotFound is returned when forge.yaml does not exist.

Functions

func CLIName

func CLIName() string

CLIName returns the command name users should type to invoke Forge. When the binary is "forge" (standalone install), it returns "forge". When embedded in another binary (e.g. "reliant"), it returns "reliant forge".

func Execute

func Execute() error

func GetGitCommit

func GetGitCommit() string

GetGitCommit returns the git commit SHA the forge binary was built from. Returns "unknown" if the binary was not built with ldflags.

func GetVersion

func GetVersion() string

GetVersion returns the forge binary's version string. Callers can use this to stamp the current forge version into generated artifacts (e.g. the .forge/checksums.json header), which enables pinned installs in CI.

func MergeDescriptorFragments

func MergeDescriptorFragments(descriptorOut string) error

MergeDescriptorFragments combines all per-invocation fragments under <descriptorOut>/<descriptorStageDir>/ into a single forge_descriptor.json at <descriptorOut>/forge_descriptor.json, then removes the staging dir. Called by runDescriptorGenerate in the parent process after buf returns.

Idempotent: running it twice is safe (the second run sees an empty stage dir and is a no-op apart from rewriting the final descriptor with what's already there). Returns nil silently when no fragments exist (clean projects with no services/entities/configs).

func NewRootCmd

func NewRootCmd() *cobra.Command

NewRootCmd builds and returns the fully assembled root command.

func SetVersion

func SetVersion(v, date, commit string)

func WriteSkills

func WriteSkills(outDir string, style SkillWriteStyle) (int, error)

WriteSkills exports every bundled skill into outDir using the requested style. It returns the number of skills written. The function is exported so callers (e.g. the reliant CLI embedding forge) can reuse it.

Types

type AuditCategory

type AuditCategory struct {
	Status  AuditStatus    `json:"status"`
	Summary string         `json:"summary"`
	Details map[string]any `json:"details,omitempty"`
}

AuditCategory is one section of the audit report. The shape mirrors the "category, status, summary, details" scheme called for in the spec — kept deliberately simple so a sub-agent can pluck `.summary` for a human-readable snippet or `.details` for structured fix-up data.

type AuditReport

type AuditReport struct {
	ProjectName   string                   `json:"project_name"`
	ProjectKind   string                   `json:"project_kind"`
	BinaryVersion string                   `json:"binary_version"`
	GeneratedAt   time.Time                `json:"generated_at"`
	Categories    map[string]AuditCategory `json:"categories"`
	OverallStatus AuditStatus              `json:"overall_status"`
}

AuditReport is the top-level JSON structure emitted by `forge audit --json`. Field order is stable so diffing two audits is human-readable.

type AuditStatus

type AuditStatus string

AuditStatus is the per-category roll-up. We keep the wire enum tiny so the JSON shape is easy to grep / jq against.

const (
	AuditStatusOK    AuditStatus = "ok"
	AuditStatusWarn  AuditStatus = "warn"
	AuditStatusError AuditStatus = "error"
)

type BacklogItem

type BacklogItem struct {
	ID       string `yaml:"id" json:"id,omitempty"`
	Severity string `yaml:"severity" json:"severity,omitempty"`
	Area     string `yaml:"area" json:"area,omitempty"`
	Status   string `yaml:"status" json:"status,omitempty"`
	FixedAt  string `yaml:"fixed_at" json:"fixed_at,omitempty"`

	Title string `json:"title"`
	Body  string `json:"body,omitempty"`
	// contains filtered or unexported fields
}

BacklogItem is one entry in FORGE_BACKLOG.md.

Items may have a yaml frontmatter block right under the section header; items without one are treated as legacy "untracked" entries (status: open, severity/area unknown). The Title is everything after the leading "## ".

type CodemodFn

type CodemodFn func(projectDir string) (CodemodReport, error)

CodemodFn is the contract every per-version codemod implements. projectDir is the absolute path to the project root (the dir containing forge.yaml).

type CodemodReport

type CodemodReport struct {
	// Auto is the list of mechanical rewrites the codemod applied,
	// each formatted as a single human-readable line ("removed
	// ApplyDeps in pkg/app/setup.go:42-48", etc.). Order matters for
	// the UPGRADE_NOTES.md output — keep insertion order.
	Auto []string

	// Manual is the list of items the codemod identified but didn't
	// rewrite, each with a file:line reference so the LLM can land
	// on the right spot. Reasons range from "pattern didn't match
	// the conservative shape we auto-rewrite" to "needs intent
	// inspection".
	Manual []ManualItem

	// VerifyCommands are the commands the user should run after the
	// codemod completes. Defaults to the triple-gate when the
	// codemod doesn't override.
	VerifyCommands []string
}

CodemodReport summarizes one upgrade codemod run. Auto entries are the deterministic rewrites the codemod applied; Manual entries are observations the codemod made about the project that need LLM/user review (e.g. ambiguous nil-checks it left in place).

type ExplainEntry

type ExplainEntry struct {
	OutputPath string   `json:"output_path"`
	Sources    []string `json:"sources,omitempty"`
	Kind       string   `json:"kind"` // "service-handler", "service-mock", "entity-orm", "config", "contract", "frontend"
	Notes      []string `json:"notes,omitempty"`
	Skipped    bool     `json:"skipped,omitempty"`
}

ExplainEntry is one row in the per-file provenance log.

type ForgeDescriptor

type ForgeDescriptor struct {
	Services []codegen.ServiceDef    `json:"services"`
	Entities []codegen.EntityDef     `json:"entities"`
	Configs  []codegen.ConfigMessage `json:"configs"`
}

ForgeDescriptor is the top-level JSON structure written by mode=descriptor. It aggregates all data the generate.go pipeline needs from proto descriptors.

type GenStep

type GenStep struct {
	// Name is the human-readable label printed by --plan / --explain
	// and used in error wrapping. Replaces the legacy "Step Nx" comment
	// numbering — make these stable; tests pin the order.
	Name string

	// Gate returns true when the step should execute for this context.
	// Gates MUST be pure: no I/O, no mutation. They're called by
	// stability tests and may be called repeatedly by future watch-mode
	// dispatchers.
	Gate func(*pipelineContext) bool

	// Run is the action. It may mutate ctx (parsed services, derived
	// flags, checksums) so subsequent steps can reuse the work. Errors
	// abort the pipeline; warnings should be logged in-step.
	Run func(*pipelineContext) error

	// Tag categorizes the step for future filtering. Conventional
	// values: "config", "proto", "codegen", "migrations", "frontend",
	// "deploy", "tools", "validate".
	Tag string
}

GenStep is one ordered unit of the generate pipeline. Steps are pure data plus two functions: Gate decides whether the step should run for the current pipelineContext (must be side-effect free), and Run executes the step. Tag categorizes the step for filtering by future callers (--plan output, `forge dev` watch-mode dispatch).

type ManualItem

type ManualItem struct {
	File   string // relative to projectDir
	Line   int    // 1-based; 0 means file-level (no specific line)
	Reason string // short, paste-into-an-LLM-prompt-friendly
}

ManualItem is one entry in CodemodReport.Manual — file:line pairs the LLM/user should look at after the codemod completes.

type MapNode

type MapNode struct {
	Path      string     `json:"path"`
	Name      string     `json:"name"`
	IsDir     bool       `json:"is_dir"`
	Ownership string     `json:"ownership,omitempty"`
	Flags     []string   `json:"flags,omitempty"`
	Children  []*MapNode `json:"children,omitempty"`
}

MapNode is one entry in the project tree. Children is empty for files; for directories it is sorted alphabetically (dirs first, then files, each group ordered by name).

type SkillScope

type SkillScope string

SkillScope identifies where a skill was discovered from.

const (
	// SkillScopeForge is a skill bundled with the forge binary (templates/project/skills).
	SkillScopeForge SkillScope = "forge"
	// SkillScopeProject is a skill discovered under <project_root>/.forge/skills/.
	SkillScopeProject SkillScope = "project"
	// SkillScopeUser is a skill discovered under ~/.forge/skills/.
	SkillScopeUser SkillScope = "user"
)

type SkillWriteStyle

type SkillWriteStyle string

SkillWriteStyle controls the on-disk layout produced by `forge skill write`.

const (
	// SkillWriteStyleForge mirrors forge's own layout: <out>/<skill>/SKILL.md.
	SkillWriteStyleForge SkillWriteStyle = "forge"
	// SkillWriteStyleClaude is Claude Code's `.claude/skills/` layout —
	// same on-disk shape as forge (<out>/<skill>/SKILL.md), but always
	// includes YAML frontmatter so Claude can discover/route the skill.
	SkillWriteStyleClaude SkillWriteStyle = "claude"
	// SkillWriteStyleMD is a flat layout: <out>/<skill>.md, no per-skill dir.
	SkillWriteStyleMD SkillWriteStyle = "md"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL