Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- **`gofasta inspect <Resource>`** — AST-parses a resource's model, DTOs, service interface, controller, and routes; emits a structured report so agents planning a modification see the full picture from one command instead of opening six files.
- **`gofasta config schema`** — emits a Draft-7 JSON Schema describing `config.yaml`. Shells out to the project-local `cmd/schema/` helper so the schema always matches the `gofasta` version pinned in the project's `go.mod`. Feed to VS Code YAML, JetBrains editors, or CI validators.
- **`gofasta do <workflow>`** — named development workflows chaining multiple gofasta commands: `new-rest-endpoint`, `rebuild`, `fresh-start`, `clean-slate`, `health-check`. Includes `--dry-run` for previewing chains without execution.
- **`gofasta ai <agent>`** — opt-in installer for AI coding agent configuration. Supports Claude Code, Cursor, OpenAI Codex, Aider, and Windsurf. Idempotent; `--dry-run` / `--force` supported; install history tracked in `.gofasta/ai.json`. Sub-commands: `gofasta ai list`, `gofasta ai status`.
- **`gofasta ai <agent>`** — opt-in installer for AI coding agent configuration. Supports Claude Code, Cursor, OpenAI Codex, Aider, and Windsurf. Each install ships a slim root briefing (where the agent expects one) plus six topic chunks (`conventions`, `overview`, `workflow`, `commands`, `debugging`, `docs-index`) wrapped with the right activation metadata for that agent — Claude `.claude/rules/*.md` with `paths:` frontmatter, Cursor `.cursor/rules/*.mdc` with `alwaysApply`/`globs`/`description`, Codex `AGENTS.md` + `.codex/docs/`, Aider `CONVENTIONS.md` + `.aider/docs/` listed in `.aider.conf.yml`'s `read:`, Windsurf `.windsurf/rules/*.md` with `trigger:` frontmatter. Each chunk stays under the agent's per-file size cap (Claude 200-line target, Codex 32 KiB, Windsurf 12 KB). Idempotent; `--dry-run` / `--force` / `--switch` supported; install history tracked in `.gofasta/ai.json`. Sub-commands: `gofasta ai list`, `gofasta ai status`, `gofasta ai uninstall <agent>`.
- **Structured errors** — every CLI error now carries `{code, message, hint, docs}`. 38 stable error codes. Agents pattern-match on the code instead of regex-parsing English.
- **Global `--json` flag** — every structured-output command honors it, producing a single-line JSON document for agent consumption.
- **Post-generation auto-verify** — `gofasta g scaffold` automatically runs `go build ./...` after generation so template regressions surface immediately. Disable with `--no-verify`.
- **Generator `--dry-run`** — `gofasta g scaffold --dry-run` shows every file it would create and every patch it would apply without touching disk.
- **Per-resource controller test scaffolding** — `gofasta g scaffold` now emits a starter `<name>.controller_test.go` with smoke tests + a TODO placeholder, so generated resources are green on `go test` out of the box.
- **`AGENTS.md` in every scaffolded project** — comprehensive agent briefing (project overview, tech stack, every command, conventions, Wire gotcha walkthrough, "do not do" list, pre-commit self-check). Read automatically by Claude Code, OpenAI Codex, Cursor, Aider, and other MCP-aware agents.
- **No scaffold-time agent files** — `gofasta new` ships a project with zero agent-related files at the root. All agent setup is opt-in per agent via `gofasta ai <key>`, which writes a self-contained tree at that agent's native paths (no rename of pre-existing files, no shared `docs/agents/` directory; uninstall is a clean `rm` of every recorded path).
- **Scaffold ships `cmd/schema/main.go`** — the 10-line helper binary that `gofasta config schema` shells out to. Also callable directly as `go run ./cmd/schema` for CI or IDE extensions.

### Fixed
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,19 +378,21 @@ Pass `--dry-run` to preview the chain.

### `gofasta ai <agent>` — install agent-specific configuration

Every scaffolded project ships `AGENTS.md` at the root by default (the universal file every modern agent reads). For agent-specific configuration — permission allowlists, pre-commit hooks, slash commands, conventions files — opt in with one command:
A brand-new `gofasta new` project ships **zero** agent-related files. Agent setup is fully opt-in, per agent. Running `gofasta ai <key>` installs a self-contained tree at the locations that agent reads natively — no rename of pre-existing files, every file lands at its final path on first install and is removed wholesale on uninstall.

```bash
gofasta ai claude # .claude/ settings + hooks + slash commands
gofasta ai cursor # .cursor/rules/gofasta.mdc
gofasta ai codex # .codex/config.toml
gofasta ai aider # .aider.conf.yml + .aider/CONVENTIONS.md
gofasta ai windsurf # .windsurfrules
gofasta ai claude # CLAUDE.md + .claude/{settings,hooks,commands,rules}/
gofasta ai cursor # .cursor/rules/*.mdc (6 topic rules, no root briefing)
gofasta ai codex # AGENTS.md + .codex/{config.toml, docs/*.md}
gofasta ai aider # CONVENTIONS.md + .aider.conf.yml + .aider/docs/*.md
gofasta ai windsurf # .windsurf/rules/*.md (6 topic rules, no root briefing)
gofasta ai list # supported agents
gofasta ai status # what's currently installed in this project
```

Installs are idempotent, support `--dry-run`, and are tracked in `.gofasta/ai.json`.
Every install ships a slim root briefing (where the agent expects one) plus six topic chunks — `conventions`, `overview`, `workflow`, `commands`, `debugging`, `docs-index` — wrapped with the right activation metadata for that agent (Claude's `paths:`, Cursor's `alwaysApply` / `globs` / `description`, Windsurf's `trigger:`, etc.). Each chunk stays under the agent's per-file size cap (Claude 200-line target, Codex 32 KiB, Windsurf 12 KB).

Installs are idempotent, support `--dry-run`, and are tracked in `.gofasta/ai.json`. Only one agent can be active at a time — pass `--switch` to atomically uninstall the previous one and install the new one.

### `--json` on every command

Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.25.0

require (
github.com/creack/pty v1.1.24
github.com/dave/dst v0.27.3
github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.1
Expand All @@ -12,6 +13,7 @@ require (
github.com/stretchr/testify v1.11.1
golang.org/x/sys v0.44.0
golang.org/x/term v0.43.0
golang.org/x/tools v0.45.0
)

require (
Expand All @@ -25,5 +27,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY=
github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg=
github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBsZGUok=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
Expand All @@ -30,6 +36,8 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
Expand All @@ -38,10 +46,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
166 changes: 166 additions & 0 deletions internal/clierr/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,64 @@ const (
// to call interactive commands programmatically rather than getting
// a hung process or garbled output.
CodeInteractiveOnly Code = "INTERACTIVE_ONLY"

// --- Cross-resource impact analysis (gofasta xrefs / impact) ---
//
// Type-aware symbol lookup uses golang.org/x/tools/go/packages, so
// failures split between "module won't load" (PACKAGE_LOAD_FAILED) and
// "symbol not present after load" (SYMBOL_NOT_FOUND).
CodeSymbolNotFound Code = "SYMBOL_NOT_FOUND"
CodeTypeAnalysisFailed Code = "TYPE_ANALYSIS_FAILED"
CodePackageLoadFailed Code = "PACKAGE_LOAD_FAILED"
CodeAmbiguousSymbol Code = "AMBIGUOUS_SYMBOL"

// --- Change-scoped verify (--since / --changed) ---
//
// Three failure modes: not a git repo at all, the diff command failed
// (permissions, malformed ref syntax), or the ref does not resolve.
CodeGitNotAvailable Code = "GIT_NOT_AVAILABLE"
CodeGitDiffFailed Code = "GIT_DIFF_FAILED"
CodeGitRefNotFound Code = "GIT_REF_NOT_FOUND"

// --- Modify-aware generators (g method / g field / g endpoint / ...) ---
//
// AST-based generators that edit existing resources. Idempotency checks
// surface RESOURCE_NOT_FOUND / *_ALREADY_EXISTS so agents can branch on
// "is this a fresh add or a no-op?" without re-parsing the source tree.
CodeResourceNotFound Code = "RESOURCE_NOT_FOUND"
CodeMethodAlreadyExists Code = "METHOD_ALREADY_EXISTS"
CodeFieldAlreadyExists Code = "FIELD_ALREADY_EXISTS"
CodeRouteAlreadyExists Code = "ROUTE_ALREADY_EXISTS"
CodeASTParseFailed Code = "AST_PARSE_FAILED"
CodeASTPatchFailed Code = "AST_PATCH_FAILED"

// --- Migration safety preview (gofasta migrate up --explain) ---
CodeMigrationLintFailed Code = "MIGRATION_LINT_FAILED"
CodeMigrationParseFailed Code = "MIGRATION_PARSE_FAILED"

// --- Mock regeneration (gofasta g mock) ---
//
// MOCK_DRIFT is the --check exit code: the on-disk mock does not match
// the generated output. Use in CI to gate "mocks are stale" PRs.
CodeInterfaceNotFound Code = "INTERFACE_NOT_FOUND"
CodeMockGenFailed Code = "MOCK_GEN_FAILED"
CodeMockDrift Code = "MOCK_DRIFT"

// --- Job / task introspection (gofasta inspect-jobs / inspect-tasks) ---
CodeJobsDirMissing Code = "JOBS_DIR_MISSING"
CodeTasksDirMissing Code = "TASKS_DIR_MISSING"

// --- Debug replay (gofasta debug replay) ---
//
// SSRF-guarded re-fire of a captured request. UNSAFE fires when the
// override would target a host other than the configured app URL.
CodeDebugReplayNotFound Code = "DEBUG_REPLAY_NOT_FOUND"
CodeDebugReplayFailed Code = "DEBUG_REPLAY_FAILED"
CodeDebugReplayUnsafe Code = "DEBUG_REPLAY_UNSAFE"

// --- Debug stack resolver (gofasta debug stack) ---
CodeDebugStackParseFailed Code = "DEBUG_STACK_PARSE_FAILED"
CodeDebugSourceUnavailable Code = "DEBUG_SOURCE_UNAVAILABLE"
)

// meta carries the remediation hint and docs URL for a code. Looked up
Expand Down Expand Up @@ -354,6 +412,114 @@ var registry = map[Code]meta{
Hint: "EXPLAIN is SELECT-only and requires the app to have registered its *gorm.DB via devtools.RegisterDB — verify the app was built with the devtools tag",
Docs: "https://gofasta.dev/docs/guides/debugging",
},

CodeSymbolNotFound: {
Hint: "the symbol was not found in the current module — check the spelling and package qualifier (e.g. `pkg.Func` or `pkg.Type.Method`)",
Docs: "https://gofasta.dev/docs/cli-reference/xrefs",
},
CodeTypeAnalysisFailed: {
Hint: "go/packages could not type-check the module; run `go build ./...` to surface the underlying compile error",
Docs: "https://gofasta.dev/docs/cli-reference/xrefs",
},
CodePackageLoadFailed: {
Hint: "one or more packages failed to load — fix the build error above before running impact analysis",
Docs: "https://gofasta.dev/docs/cli-reference/impact",
},
CodeAmbiguousSymbol: {
Hint: "the unqualified symbol matches definitions in multiple packages; pass the fully qualified name (e.g. `irodata/app/services.OrderService.Archive`)",
Docs: "https://gofasta.dev/docs/cli-reference/xrefs",
},

CodeGitNotAvailable: {
Hint: "this directory is not a git repository — run `git init` or drop the --since/--changed flag",
Docs: "https://gofasta.dev/docs/cli-reference/verify",
},
CodeGitDiffFailed: {
Hint: "`git diff` returned an error; check that the ref exists locally (`git fetch` may be needed) and that you have read access to the repo",
Docs: "https://gofasta.dev/docs/cli-reference/verify",
},
CodeGitRefNotFound: {
Hint: "the supplied git ref does not resolve — try `git fetch origin` or pass a known commit / branch / tag",
Docs: "https://gofasta.dev/docs/cli-reference/verify",
},

CodeResourceNotFound: {
Hint: "no files match the given resource name — check spelling (PascalCase, singular) or run `gofasta g scaffold <Name>` to create it first",
Docs: "https://gofasta.dev/docs/cli-reference/generate/method",
},
CodeMethodAlreadyExists: {
Hint: "a method with that name already exists on the target interface — pick a different name or skip this generator",
Docs: "https://gofasta.dev/docs/cli-reference/generate/method",
},
CodeFieldAlreadyExists: {
Hint: "the model already has a field with that name — pick a different name or remove the existing field first",
Docs: "https://gofasta.dev/docs/cli-reference/generate/field",
},
CodeRouteAlreadyExists: {
Hint: "that METHOD + path combination is already registered — pick a different path or use `gofasta g middleware` to attach behavior",
Docs: "https://gofasta.dev/docs/cli-reference/generate/endpoint",
},
CodeASTParseFailed: {
Hint: "the target Go file has a syntax error and cannot be parsed — fix the error above and re-run",
Docs: "https://gofasta.dev/docs/cli-reference/generate/method",
},
CodeASTPatchFailed: {
Hint: "could not locate the AST insertion target (e.g. interface or struct named for the resource) — the file may have been heavily restructured; inspect manually",
Docs: "https://gofasta.dev/docs/cli-reference/generate/method",
},

CodeMigrationLintFailed: {
Hint: "static SQL analysis errored on a pending migration — inspect the file for malformed SQL or unsupported syntax",
Docs: "https://gofasta.dev/docs/cli-reference/migrate",
},
CodeMigrationParseFailed: {
Hint: "could not split the migration into statements — check for unmatched `$$` dollar-quote blocks or stray string literals",
Docs: "https://gofasta.dev/docs/cli-reference/migrate",
},

CodeInterfaceNotFound: {
Hint: "no interface with that name was found under app/services/interfaces/ or app/repositories/interfaces/ — check spelling or pass --all to refresh every mock",
Docs: "https://gofasta.dev/docs/cli-reference/generate/mock",
},
CodeMockGenFailed: {
Hint: "the mock template failed to render — inspect the error above; often caused by an interface that uses unsupported features (generics, embedded external interfaces)",
Docs: "https://gofasta.dev/docs/cli-reference/generate/mock",
},
CodeMockDrift: {
Hint: "the on-disk mock no longer matches the interface — run `gofasta g mock --all` to regenerate, then commit the result",
Docs: "https://gofasta.dev/docs/cli-reference/generate/mock",
},

CodeJobsDirMissing: {
Hint: "app/jobs/ was not found — generate a job with `gofasta g job <name> \"<cron>\"` first, or run this command from the project root",
Docs: "https://gofasta.dev/docs/cli-reference/inspect-jobs",
},
CodeTasksDirMissing: {
Hint: "app/tasks/ was not found — generate a task with `gofasta g task <name>` first, or run this command from the project root",
Docs: "https://gofasta.dev/docs/cli-reference/inspect-tasks",
},

CodeDebugReplayNotFound: {
Hint: "the request id is not in the capture ring — it may have been evicted (rings hold at most 200 requests); re-issue the request you want to replay",
Docs: "https://gofasta.dev/docs/cli-reference/debug",
},
CodeDebugReplayFailed: {
Hint: "the replayed request failed at the target app — inspect the response payload above or check the app logs",
Docs: "https://gofasta.dev/docs/cli-reference/debug",
},
CodeDebugReplayUnsafe: {
Hint: "the replay override was rejected by the SSRF guard — overrides may change path / headers / body but cannot change scheme, host, or port",
Docs: "https://gofasta.dev/docs/cli-reference/debug",
},

CodeDebugStackParseFailed: {
Hint: "the stack frame does not match the expected `file:line function` format — verify the source is a gofasta-captured stack (TraceSpan.Stack or ExceptionEntry.Stack)",
Docs: "https://gofasta.dev/docs/cli-reference/debug",
},
CodeDebugSourceUnavailable: {
Hint: "the source file referenced in the stack frame is not present on disk (deleted, vendored, or outside the current module) — the frame is still resolvable but without source context",
Docs: "https://gofasta.dev/docs/cli-reference/debug",
},
}

// lookup returns the metadata for code, or an empty meta{} if code is not
Expand Down
Loading
Loading