Rewrite Existing Applications in Go
Purpose
Build a Go implementation of an existing application. Preserve the user-facing contract and core behavior, but do not blindly copy the old architecture. Prefer a clean Go design that is maintainable, testable, observable, and easy to extend.
Use this for full or substantial ports from an existing codebase. Do not activate it for ordinary Go bug fixes, small refactors, or new Go features with no legacy behavior to preserve.
This skill focuses on the project itself: source code, config, commands, behavior, tests, and runtime functionality. Ignore README/docs, packaging, publishing, release automation, and distribution unless the user explicitly asks for them or they are required to understand the app’s contract.
Default priorities
- Preserve the user-facing contract: commands, flags, config fields, environment variables, outputs, files, network/API behavior, exit codes, and workflows.
- Improve implementation quality where Go gives clear advantages: concurrency, typed config, interfaces, modular adapters, deterministic tests, error handling, retries, cancellation, and performance.
- Replace old-language dependencies with maintained Go packages where reasonable.
- For CLI/config-heavy apps, prefer Cobra for command structure and use Viper only when config discovery, env binding, or migration support materially helps; explain simpler choices.
- Keep secrets out of config files and source code. Prefer environment variables and explicit secret references.
Required first steps
When given an existing repo or local project:
- Inspect the repository structure.
- Identify the current language, runtime, app type, entrypoints, and execution modes.
- Check the worktree and avoid overwriting existing user changes.
- Read source code before relying on documentation. Treat docs as supporting evidence, not the source of truth.
- Use a scouting sub-agent when available for large or unfamiliar repositories; otherwise use targeted code search directly.
- Capture runnable baseline behavior when practical: commands, sample inputs, config examples, outputs, errors, and exit codes.
- Inventory the user-facing contract.
- Inventory dependencies and identify what each dependency actually does.
- Search for maintained Go replacements. Use a package-finder sub-agent when available for non-obvious choices; do not rely only on memory.
- Produce a concise implementation plan before building unless the user only asked for research.
If the user gives extra desired changes, incorporate them into the plan and clearly separate:
- preserved behavior
- intentionally changed behavior
- new improvements
- deferred or removed behavior
Contract inventory
Create a contract map before implementation. Include only behavior users or integrations can observe.
Capture:
- executable names and entrypoints
- commands and subcommands
- positional arguments
- flags, defaults, aliases, and precedence
- config files, schema, defaults, validation rules, and migration behavior
- environment variables and secret handling
- generated files and directory layout
- stdout/stderr behavior and log levels
- return codes and failure modes
- baseline command/output examples that the Go version must match or intentionally change
- network calls, API endpoints, rate limits, retry behavior, authentication, and user agents
- background jobs, schedules, polling, caches, state files, and databases
- externally consumed formats: JSON, Markdown, HTML, CSV, email bodies, webhook payloads, MCP tools, or API responses
- important non-functional behavior: concurrency, timeouts, ordering guarantees, deduplication, idempotency, localization, and determinism
Use this table format:
| Area |
Existing behavior |
Go rewrite behavior |
Compatibility level |
Notes |
| CLI |
... |
... |
exact / compatible / improved / removed |
... |
Compatibility levels:
- exact: behavior should match unless tests prove otherwise
- compatible: same user outcome, improved implementation or minor format differences
- improved: intentionally better behavior with migration notes
- removed: intentionally omitted; must be justified
Dependency replacement process
For every non-trivial existing dependency:
- Identify its role from usage in source code.
- Determine whether Go stdlib is sufficient.
- Search for actively maintained Go libraries that implement the same role.
- Compare at least two candidates when the choice is not obvious.
- Prefer small, stable libraries with good docs, tests, recent maintenance, compatible license, low transitive dependency load, and idiomatic Go APIs.
- Avoid packages that are abandoned, overbroad, license-incompatible, or unnecessary wrappers around simple HTTP calls.
- Record package evidence and the decision.
Use this decision table:
| Existing dependency |
Role in current app |
Go candidate |
Evidence of maintenance |
Decision |
Rationale |
| ... |
... |
... |
releases, commits, docs, users, issues |
use / avoid / stdlib |
... |
Maintenance evidence to check:
- latest release or commit recency
- Go module health on pkg.go.dev when available
- open issues/PRs and maintainer responsiveness
- security advisories or known CVEs
- project maturity and API stability
- license compatibility
- dependency footprint
- examples and tests
CLI and configuration rules
For CLI applications, use this division of responsibility by default:
- Cobra: command tree, arguments, flags, help, shell completion, command validation, command execution.
- pflag: flag definitions through Cobra.
- Viper: compatibility/convenience layer for discovery, defaults, env binding, and migration support when useful. Do not create two independent sources of truth.
Skip Cobra for libraries, daemons without a CLI contract, or small tools where the standard flag package preserves the contract with less complexity. Skip Viper when a typed config loader plus stdlib parsing is clearer.
Recommended precedence, highest to lowest:
- explicit CLI flags
- environment variables
- local config file specified by
--config
- default config search paths
- compiled defaults
Always produce a typed config struct. Validate config after merging and before doing work.
Support env-var substitution in string config values if the old app supports it or the user requests it. Leave unresolved variables visible and fail loudly at validation time when the value is required.
Go project architecture
For medium or large CLI, service, or pipeline rewrites, adapt this layout unless the app strongly suggests another shape. Omit packages that are not part of the target domain.
cmd/<app>/main.go # thin entrypoint
internal/cli/ # Cobra commands and flag binding
internal/config/ # config loading, env expansion, validation, migration
internal/app/ # orchestration/use cases
internal/domain/ # domain types and core rules
internal/adapters/ # external system adapters and implementations
internal/store/ # cache/state/subscriber storage if needed
internal/httpx/ # shared HTTP client, retry, rate limit, user agent
internal/logging/ # structured logging setup
Rules:
- Keep
main.go tiny.
- Put business logic outside Cobra command handlers.
- Use interfaces at module boundaries, not everywhere.
- Use
context.Context for all network and long-running operations.
- Use explicit timeouts and cancellation.
- Use typed errors where they improve retries or user messages.
- Use dependency injection for clients, clocks, filesystem, and external APIs where tests need control.
- Prefer deterministic ordering after concurrent work.
- Keep adapters isolated so unsupported or flaky integrations can be disabled without breaking the whole app.
Implementation workflow
Phase 1: Reconnaissance
Deliver:
- repository summary
- app entrypoints
- feature inventory
- user-facing contract map
- dependency map
- test/fixture inventory
- known risks and unknowns
Phase 2: Go package scouting
Deliver:
- package replacement table
- selected packages and why
- packages intentionally avoided
- stdlib-only decisions
- license/security concerns
Phase 3: Config and contract plan
Deliver:
- proposed Go config schema
- migration plan from old config
- env-var handling plan
- CLI command/flag plan
- compatibility matrix
- intentional improvements
Phase 4: Architecture plan
Deliver:
- package/module layout
- data model
- main interfaces
- pipeline flow
- concurrency/rate-limit strategy
- persistence/state strategy
- test strategy
Phase 5: Build
Implement incrementally:
- Preserve or create baseline fixtures from the original app before replacing behavior.
- Create Go module and project skeleton.
- Add typed config loader and validation.
- Add CLI command tree and bind flags when the original app has a CLI contract.
- Add domain models and core interfaces.
- Port one vertical slice end-to-end with tests.
- Add remaining adapters and workflow layers incrementally.
- Add integration tests using fixtures/mocks.
- Add contract tests for CLI behavior, config migration, output formats, and exit codes.
- Run
gofmt, go test ./..., and static checks if available.
Phase 6: Validation
Before considering the rewrite complete:
go test ./... passes.
- Contract tests compare representative old-app and Go-app behavior or document why direct comparison is not possible.
- CLI help and flags match the contract plan.
- Representative stdout/stderr, generated files, API payloads, and exit codes match the compatibility matrix.
- Config examples load and validate.
- Legacy config can be migrated or read where promised.
- Network adapters are covered by tests using fixtures or fake servers.
- Core pipeline can run in dry-run/mock mode without secrets.
- Errors are clear and actionable.
- Secrets are never printed in logs.
Improvement checklist
Look for opportunities to improve:
- typed config with validation and schema versioning
- migration from old config to new config
- source adapter plugin pattern
- rate limiting per source/API provider
- retry with exponential backoff and jitter
- circuit-breaker behavior for unreliable providers
- deterministic ordering after concurrency
- local cache/state to avoid repeated expensive calls
- partial-failure tolerance: one source failing should not fail the entire run unless configured
- structured logs with redaction
- dry-run mode
- fixture-based tests for scrapers and renderers
- snapshot/golden tests for generated output
- clear separation between fetch, dedupe, score, enrich, summarize, and deliver
- support for multiple LLM providers through one interface
- provider capability detection, such as whether temperature or streaming is supported
- strict output-size limits for chat/webhook/email channels
- idempotent delivery where possible
Optional target references
Load these only when the target app matches the scenario:
| Target type |
Read |
| AI/news aggregation pipeline similar to Horizon |
references/horizon-news-aggregation.md |
Output style during a run
When reporting progress to the user:
- Be concise.
- Show concrete findings as soon as they are known.
- Distinguish facts from recommendations.
- Do not claim package maintenance without checking current evidence.
- Do not ask for clarification when a reasonable best-effort plan can proceed.
- Be honest about unsupported features or uncertain package replacements.
Final completion report
End with:
- what was implemented
- compatibility status
- intentional differences from the original
- packages chosen and why
- tests run and results
- risks or features not implemented
- next concrete implementation step if the work is partial
1---2name: rewrite-in-go3description: Reimplement an existing application in Go while preserving observable behavior. Use when asked to port, rewrite, migrate, rebuild, or replace an existing app with a Go implementation, especially CLI tools, services, agents, scrapers, or source-driven automation systems. Avoid for greenfield Go features or small Go refactors.4license: MIT5---67# Rewrite Existing Applications in Go89## Purpose1011Build a Go implementation of an existing application. Preserve the user-facing contract and core behavior, but do not blindly copy the old architecture. Prefer a clean Go design that is maintainable, testable, observable, and easy to extend.1213Use this for full or substantial ports from an existing codebase. Do not activate it for ordinary Go bug fixes, small refactors, or new Go features with no legacy behavior to preserve.1415This skill focuses on the project itself: source code, config, commands, behavior, tests, and runtime functionality. Ignore README/docs, packaging, publishing, release automation, and distribution unless the user explicitly asks for them or they are required to understand the app’s contract.1617## Default priorities18191. Preserve the user-facing contract: commands, flags, config fields, environment variables, outputs, files, network/API behavior, exit codes, and workflows.202. Improve implementation quality where Go gives clear advantages: concurrency, typed config, interfaces, modular adapters, deterministic tests, error handling, retries, cancellation, and performance.213. Replace old-language dependencies with maintained Go packages where reasonable.224. For CLI/config-heavy apps, prefer Cobra for command structure and use Viper only when config discovery, env binding, or migration support materially helps; explain simpler choices.235. Keep secrets out of config files and source code. Prefer environment variables and explicit secret references.2425## Required first steps2627When given an existing repo or local project:28291. Inspect the repository structure.302. Identify the current language, runtime, app type, entrypoints, and execution modes.313. Check the worktree and avoid overwriting existing user changes.324. Read source code before relying on documentation. Treat docs as supporting evidence, not the source of truth.335. Use a scouting sub-agent when available for large or unfamiliar repositories; otherwise use targeted code search directly.346. Capture runnable baseline behavior when practical: commands, sample inputs, config examples, outputs, errors, and exit codes.357. Inventory the user-facing contract.368. Inventory dependencies and identify what each dependency actually does.379. Search for maintained Go replacements. Use a package-finder sub-agent when available for non-obvious choices; do not rely only on memory.3810. Produce a concise implementation plan before building unless the user only asked for research.3940If the user gives extra desired changes, incorporate them into the plan and clearly separate:4142- preserved behavior43- intentionally changed behavior44- new improvements45- deferred or removed behavior4647## Contract inventory4849Create a contract map before implementation. Include only behavior users or integrations can observe.5051Capture:5253- executable names and entrypoints54- commands and subcommands55- positional arguments56- flags, defaults, aliases, and precedence57- config files, schema, defaults, validation rules, and migration behavior58- environment variables and secret handling59- generated files and directory layout60- stdout/stderr behavior and log levels61- return codes and failure modes62- baseline command/output examples that the Go version must match or intentionally change63- network calls, API endpoints, rate limits, retry behavior, authentication, and user agents64- background jobs, schedules, polling, caches, state files, and databases65- externally consumed formats: JSON, Markdown, HTML, CSV, email bodies, webhook payloads, MCP tools, or API responses66- important non-functional behavior: concurrency, timeouts, ordering guarantees, deduplication, idempotency, localization, and determinism6768Use this table format:6970| Area | Existing behavior | Go rewrite behavior | Compatibility level | Notes |71|---|---|---|---|---|72| CLI | ... | ... | exact / compatible / improved / removed | ... |7374Compatibility levels:7576- exact: behavior should match unless tests prove otherwise77- compatible: same user outcome, improved implementation or minor format differences78- improved: intentionally better behavior with migration notes79- removed: intentionally omitted; must be justified8081## Dependency replacement process8283For every non-trivial existing dependency:84851. Identify its role from usage in source code.862. Determine whether Go stdlib is sufficient.873. Search for actively maintained Go libraries that implement the same role.884. Compare at least two candidates when the choice is not obvious.895. Prefer small, stable libraries with good docs, tests, recent maintenance, compatible license, low transitive dependency load, and idiomatic Go APIs.906. Avoid packages that are abandoned, overbroad, license-incompatible, or unnecessary wrappers around simple HTTP calls.917. Record package evidence and the decision.9293Use this decision table:9495| Existing dependency | Role in current app | Go candidate | Evidence of maintenance | Decision | Rationale |96|---|---|---|---|---|---|97| ... | ... | ... | releases, commits, docs, users, issues | use / avoid / stdlib | ... |9899Maintenance evidence to check:100101- latest release or commit recency102- Go module health on pkg.go.dev when available103- open issues/PRs and maintainer responsiveness104- security advisories or known CVEs105- project maturity and API stability106- license compatibility107- dependency footprint108- examples and tests109110## CLI and configuration rules111112For CLI applications, use this division of responsibility by default:113114- Cobra: command tree, arguments, flags, help, shell completion, command validation, command execution.115- pflag: flag definitions through Cobra.116- Viper: compatibility/convenience layer for discovery, defaults, env binding, and migration support when useful. Do not create two independent sources of truth.117118Skip Cobra for libraries, daemons without a CLI contract, or small tools where the standard `flag` package preserves the contract with less complexity. Skip Viper when a typed config loader plus stdlib parsing is clearer.119120Recommended precedence, highest to lowest:1211221. explicit CLI flags1232. environment variables1243. local config file specified by `--config`1254. default config search paths1265. compiled defaults127128Always produce a typed config struct. Validate config after merging and before doing work.129130Support env-var substitution in string config values if the old app supports it or the user requests it. Leave unresolved variables visible and fail loudly at validation time when the value is required.131132## Go project architecture133134For medium or large CLI, service, or pipeline rewrites, adapt this layout unless the app strongly suggests another shape. Omit packages that are not part of the target domain.135136```text137cmd/<app>/main.go # thin entrypoint138internal/cli/ # Cobra commands and flag binding139internal/config/ # config loading, env expansion, validation, migration140internal/app/ # orchestration/use cases141internal/domain/ # domain types and core rules142internal/adapters/ # external system adapters and implementations143internal/store/ # cache/state/subscriber storage if needed144internal/httpx/ # shared HTTP client, retry, rate limit, user agent145internal/logging/ # structured logging setup146```147148Rules:149150- Keep `main.go` tiny.151- Put business logic outside Cobra command handlers.152- Use interfaces at module boundaries, not everywhere.153- Use `context.Context` for all network and long-running operations.154- Use explicit timeouts and cancellation.155- Use typed errors where they improve retries or user messages.156- Use dependency injection for clients, clocks, filesystem, and external APIs where tests need control.157- Prefer deterministic ordering after concurrent work.158- Keep adapters isolated so unsupported or flaky integrations can be disabled without breaking the whole app.159160## Implementation workflow161162### Phase 1: Reconnaissance163164Deliver:165166- repository summary167- app entrypoints168- feature inventory169- user-facing contract map170- dependency map171- test/fixture inventory172- known risks and unknowns173174### Phase 2: Go package scouting175176Deliver:177178- package replacement table179- selected packages and why180- packages intentionally avoided181- stdlib-only decisions182- license/security concerns183184### Phase 3: Config and contract plan185186Deliver:187188- proposed Go config schema189- migration plan from old config190- env-var handling plan191- CLI command/flag plan192- compatibility matrix193- intentional improvements194195### Phase 4: Architecture plan196197Deliver:198199- package/module layout200- data model201- main interfaces202- pipeline flow203- concurrency/rate-limit strategy204- persistence/state strategy205- test strategy206207### Phase 5: Build208209Implement incrementally:2102111. Preserve or create baseline fixtures from the original app before replacing behavior.2122. Create Go module and project skeleton.2133. Add typed config loader and validation.2144. Add CLI command tree and bind flags when the original app has a CLI contract.2155. Add domain models and core interfaces.2166. Port one vertical slice end-to-end with tests.2177. Add remaining adapters and workflow layers incrementally.2188. Add integration tests using fixtures/mocks.2199. Add contract tests for CLI behavior, config migration, output formats, and exit codes.22010. Run `gofmt`, `go test ./...`, and static checks if available.221222### Phase 6: Validation223224Before considering the rewrite complete:225226- `go test ./...` passes.227- Contract tests compare representative old-app and Go-app behavior or document why direct comparison is not possible.228- CLI help and flags match the contract plan.229- Representative stdout/stderr, generated files, API payloads, and exit codes match the compatibility matrix.230- Config examples load and validate.231- Legacy config can be migrated or read where promised.232- Network adapters are covered by tests using fixtures or fake servers.233- Core pipeline can run in dry-run/mock mode without secrets.234- Errors are clear and actionable.235- Secrets are never printed in logs.236237## Improvement checklist238239Look for opportunities to improve:240241- typed config with validation and schema versioning242- migration from old config to new config243- source adapter plugin pattern244- rate limiting per source/API provider245- retry with exponential backoff and jitter246- circuit-breaker behavior for unreliable providers247- deterministic ordering after concurrency248- local cache/state to avoid repeated expensive calls249- partial-failure tolerance: one source failing should not fail the entire run unless configured250- structured logs with redaction251- dry-run mode252- fixture-based tests for scrapers and renderers253- snapshot/golden tests for generated output254- clear separation between fetch, dedupe, score, enrich, summarize, and deliver255- support for multiple LLM providers through one interface256- provider capability detection, such as whether temperature or streaming is supported257- strict output-size limits for chat/webhook/email channels258- idempotent delivery where possible259260## Optional target references261262Load these only when the target app matches the scenario:263264| Target type | Read |265|---|---|266| AI/news aggregation pipeline similar to Horizon | `references/horizon-news-aggregation.md` |267268## Output style during a run269270When reporting progress to the user:271272- Be concise.273- Show concrete findings as soon as they are known.274- Distinguish facts from recommendations.275- Do not claim package maintenance without checking current evidence.276- Do not ask for clarification when a reasonable best-effort plan can proceed.277- Be honest about unsupported features or uncertain package replacements.278279## Final completion report280281End with:282283- what was implemented284- compatibility status285- intentional differences from the original286- packages chosen and why287- tests run and results288- risks or features not implemented289- next concrete implementation step if the work is partial