commit 69cadb4ea6c6347ee1e93ea279138d0bb46833ef Author: Ricardo Carneiro Date: Mon Apr 27 13:43:10 2026 -0300 chore: initial scaffold with plugin system and placeholders Co-Authored-By: Claude Sonnet 4.6 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8460e74 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab +indent_size = 4 + +[*.{yaml,yml,json,toml}] +indent_style = space +indent_size = 2 + +[*.md] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7350e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Compiled binaries +/ctx +/ctx.exe +cmd/ctx/ctx +cmd/ctx/ctx.exe +/bin/ +/dist/ + +# Test and coverage artifacts +*.test +*.out +coverage.out +coverage.html + +# Profiling +*.prof + +# Logs +*.log + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Claude Code local settings +.claude/ + +# OS artifacts +.DS_Store +Thumbs.db +desktop.ini diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6cc45f7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ricardo Carneiro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d84c34 --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# ctx — anti-tokens CLI for Claude Code + +> Analyze your codebase locally. Feed Claude dense summaries, not raw files. + +**Status:** alpha — under active development. Interfaces will change. + +## Why + +Every token Claude reads costs money and burns context window. When working on +a large C# or React codebase, Claude often spends thousands of tokens just +reading files to build a mental model before doing any actual work. + +`ctx` runs locally, analyzes your project with language-aware tools (Roslyn for +C#, tree-sitter for TypeScript), and emits compact markdown summaries that give +Claude everything it needs in a fraction of the tokens. + +## Installation + +```sh +go install github.com/ricarneiro/ctx/cmd/ctx@latest +``` + +Requires Go 1.22+. Binaries for common platforms will be published after the +MVP is validated. + +## Usage + +```sh +# Git context: recent commits, status, branch info +ctx git + +# Auto-detect stack and emit project overview +ctx auto project + +# C# project structure (requires .NET SDK) +ctx csharp project + +# C# file outline: types, methods, signatures +ctx csharp outline src/MyService.cs + +# List compilation errors +ctx csharp errors +``` + +All output is UTF-8 markdown on stdout. Pipe it where you need it: + +```sh +ctx csharp project | pbcopy # macOS +ctx csharp project | clip # Windows +``` + +Or reference it in a `CLAUDE.md`: + +```markdown +Run `ctx csharp project` to get the project overview before making changes. +``` + +## Architecture + +`ctx` uses a plugin system. Each stack (`git`, `csharp`, `react`, `auto`) is a +plugin that implements the `core.Plugin` interface and registers itself via +`init()`. + +``` +core.Plugin interface + Name() string + Version() string + ShortDescription() string + Command(ctx *core.Context) *cobra.Command +``` + +In the MVP, plugins are compiled into the binary. Future plan: migrate to +subprocess dispatch (binaries named `ctx-csharp`, `ctx-react` in PATH), same +pattern as `kubectl` plugins. + +C# analysis uses a separate helper process (`tools/roslyn-helper/`) written in +C# with Roslyn. The helper communicates with `ctx` via JSON-RPC over +stdin/stdout. This lets us use the best tool for the job without pulling a .NET +runtime into the Go binary. + +See `docs/DECISIONS.md` for the full rationale behind each architectural choice. + +## Contributing + +Contributions welcome. See `CONTRIBUTING.md` (coming soon) for guidelines. +Open an issue first if you plan a large change. + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/cmd/ctx/main.go b/cmd/ctx/main.go new file mode 100644 index 0000000..07bd354 --- /dev/null +++ b/cmd/ctx/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/ricarneiro/ctx/internal/cli" + _ "github.com/ricarneiro/ctx/internal/plugins" +) + +func main() { + cli.Execute() +} diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md new file mode 100644 index 0000000..236eafb --- /dev/null +++ b/docs/DECISIONS.md @@ -0,0 +1,158 @@ +# Architecture Decision Records — ctx + +Decisions are recorded here as they are made. Each entry has context (why we +needed to decide), decision (what we chose), and consequences (what it implies). + +--- + +## 1. Language: Go (not C# or TypeScript) + +**Context:** ctx is a CLI tool that runs locally on developer machines. It +needs to start fast (< 100ms cold start), produce a single distributable +binary, and work on macOS, Linux, and Windows without asking the user to +install a runtime. + +**Decision:** Go. + +**Consequences:** +- Single static binary, trivial cross-compilation (`GOOS=linux go build`). +- No runtime install required on target machines. +- Go's stdlib covers file I/O, JSON, HTTP, process spawning — no heavy deps. +- Cannot use Roslyn directly for C# analysis (Go has no decent C# parser). + Handled by a separate helper process (see decision 4). + +--- + +## 2. CLI framework: Cobra + +**Context:** Need subcommands (`ctx git`, `ctx csharp outline`), flags, help +text, and shell completion. Rolling our own is wasteful. + +**Decision:** `github.com/spf13/cobra`. Optionally `viper` for config in the +future, but not added yet. + +**Consequences:** +- Cobra is the de facto standard (`kubectl`, `gh`, `hugo`, `helm` all use it). +- Well-understood by contributors. Good docs. Stable API. +- Shell completion (bash/zsh/fish/PowerShell) comes for free. + +--- + +## 3. Plugin system: compile-time in MVP, subprocess later + +**Context:** ctx needs to support multiple stacks. Plugins must be modular. +Two valid approaches: (a) compile all plugins into one binary, (b) dispatch +to external binaries (`ctx-csharp`, `ctx-react`) like kubectl. + +**Decision:** MVP uses compile-time plugins registered via `init()`. The +`core.Plugin` interface is designed so subprocess plugins can implement it +later without changing callers. + +**Consequences:** +- Simpler to build and ship in early stages. +- All plugins must be written in Go (or wrap external tools). +- Subprocess migration is planned but deferred until we need third-party + plugins. At that point: scan PATH for `ctx-*` binaries, wrap them with a + shim that implements `core.Plugin`. + +--- + +## 4. C# analysis: separate Roslyn helper process + +**Context:** Semantic C# analysis (types, method signatures, usages, errors) +requires a compiler-level parser. Roslyn is the only production-quality option. +Go has no usable C# parser. + +**Decision:** A separate C# program (`tools/roslyn-helper/`) that loads +projects with Roslyn and answers queries sent as JSON-RPC messages over +stdin/stdout. ctx spawns it as a subprocess and communicates via pipes. + +**Consequences:** +- Requires .NET SDK on the machine for C# commands (acceptable — users of + `ctx csharp` are already .NET developers). +- Clean separation: Go handles CLI, orchestration, output formatting; C# + handles semantic analysis. +- JSON-RPC over stdin/stdout is trivial to test in isolation. +- Performance: subprocess is spawned once per ctx invocation, amortized if we + batch queries (future optimization). + +--- + +## 5. Output format: UTF-8 markdown without BOM on stdout + +**Context:** ctx output is consumed by Claude Code (via pipe or CLAUDE.md +instructions). Claude understands markdown well. BOM causes issues in some +contexts. + +**Decision:** All plugin output is plain UTF-8 markdown, no BOM, on stdout. +Errors go to stderr. No ANSI colors (markdown already has structure). + +**Consequences:** +- Output is directly pasteable into Claude conversations. +- Easy to redirect to file: `ctx csharp project > context.md`. +- Plugins must use `ctx.Stdout` / `ctx.Stderr`, never `fmt.Println` directly, + so output destination can be overridden in tests. + +--- + +## 6. Subcommand structure: ctx + +**Context:** Multiple stacks, each with multiple commands. Need a consistent, +scalable interface. + +**Decision:** `ctx [args] [flags]`. Examples: +- `ctx csharp project` +- `ctx csharp outline src/Foo.cs` +- `ctx git` +- `ctx auto project` + +**Consequences:** +- Adding a new stack = adding a new top-level subcommand. +- Commands within a stack are subcommands of the stack command. +- Consistent with kubectl, gh, and other modern CLIs. + +--- + +## 7. Module path: github.com/ricarneiro/ctx + +**Context:** Source is hosted on a private Gitea instance +(`git.carneiro.ddnsfree.com`). Go module paths do not need to match the actual +git host. If we want `go install` from the public GitHub mirror later, the +module path must match. + +**Decision:** `github.com/ricarneiro/ctx` from day one. + +**Consequences:** +- Developers cloning from Gitea still use this module path — no friction. +- `go install github.com/ricarneiro/ctx/cmd/ctx@latest` works once we push + to GitHub. No rename needed. +- If we never publish to GitHub, the module path is just an identifier and + causes no problems. + +--- + +## 8. License: MIT + +**Context:** Want maximum adoption. No patent clause complexity. + +**Decision:** MIT, copyright Ricardo Carneiro. + +**Consequences:** +- Anyone can use, fork, embed, sell without restrictions. +- No copyleft. If this matters later, we can dual-license, but that's a + future problem. + +--- + +## 9. Commit convention: Conventional Commits + +**Context:** Want automated CHANGELOG generation in the future. Need a +consistent commit format from day one. + +**Decision:** Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`, +`refactor:`, `test:`). Enforced by convention, not by hook (yet). + +**Consequences:** +- `git log --oneline` is readable. +- Can run `git-cliff` or `conventional-changelog` later to generate CHANGELOG. +- PRs should squash to a single conventional commit on merge. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2b9f46a --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/ricarneiro/ctx + +go 1.26.1 + +require github.com/spf13/cobra v1.10.2 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a6ee3e0 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/cli/plugins.go b/internal/cli/plugins.go new file mode 100644 index 0000000..8981a9f --- /dev/null +++ b/internal/cli/plugins.go @@ -0,0 +1,15 @@ +package cli + +import ( + "github.com/ricarneiro/ctx/internal/core" + "github.com/spf13/cobra" +) + +// registerPluginCommands iterates the core registry and adds each plugin's +// root command as a subcommand of rootCmd. Called once from Execute(). +func registerPluginCommands(rootCmd *cobra.Command) { + ctx := newContext() + for _, p := range core.All() { + rootCmd.AddCommand(p.Command(ctx)) + } +} diff --git a/internal/cli/root.go b/internal/cli/root.go new file mode 100644 index 0000000..70f477f --- /dev/null +++ b/internal/cli/root.go @@ -0,0 +1,48 @@ +package cli + +import ( + "context" + "os" + + "github.com/ricarneiro/ctx/internal/core" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "ctx", + Short: "Anti-tokens CLI for Claude Code", + Long: `ctx reduces token consumption in Claude Code sessions by analyzing your +codebase locally and emitting dense markdown summaries that Claude consumes +instead of reading dozens of raw source files. + +Each subcommand targets a specific stack: + ctx git — git log, status and diff summary + ctx auto — auto-detect project stack and emit context + ctx csharp — C# / .NET analysis via Roslyn + ctx react — React / TypeScript analysis + +Output is always UTF-8 markdown on stdout, suitable for piping into Claude.`, + SilenceUsage: true, +} + +// Execute runs the root command. Called by cmd/ctx/main.go. +func Execute() { + registerPluginCommands(rootCmd) + // Set version after plugins are registered so versionString() sees them all. + rootCmd.Version = versionString() + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} + +// newContext builds the core.Context injected into plugins at runtime. +func newContext() *core.Context { + wd, _ := os.Getwd() + return &core.Context{ + Stdout: os.Stdout, + Stderr: os.Stderr, + WorkDir: wd, + Verbose: false, + Ctx: context.Background(), + } +} diff --git a/internal/cli/version.go b/internal/cli/version.go new file mode 100644 index 0000000..4f3cb9e --- /dev/null +++ b/internal/cli/version.go @@ -0,0 +1,51 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/ricarneiro/ctx/internal/core" + "github.com/spf13/cobra" +) + +// Version, Commit, and BuildDate are injected at build time via ldflags: +// +// go build -ldflags "-X github.com/ricarneiro/ctx/internal/cli.Version=1.0.0 \ +// -X github.com/ricarneiro/ctx/internal/cli.Commit=abc1234 \ +// -X github.com/ricarneiro/ctx/internal/cli.BuildDate=2025-01-01" +var ( + Version = "dev" + Commit = "unknown" + BuildDate = "unknown" +) + +func init() { + rootCmd.AddCommand(versionCmd) + rootCmd.SetVersionTemplate("{{ .Version }}\n") + // rootCmd.Version is set lazily in Execute() after all plugins are + // registered, so that versionString() can read the full plugin list. +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print ctx version and compiled plugins", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(versionString()) + }, +} + +// versionString builds the full version line including plugin list. +func versionString() string { + line1 := fmt.Sprintf("ctx %s (commit %s, built %s)", Version, Commit, BuildDate) + + plugins := core.All() + if len(plugins) == 0 { + return line1 + "\nplugins: none" + } + + parts := make([]string, len(plugins)) + for i, p := range plugins { + parts[i] = fmt.Sprintf("%s@%s", p.Name(), p.Version()) + } + return line1 + "\nplugins: " + strings.Join(parts, ", ") +} diff --git a/internal/core/context.go b/internal/core/context.go new file mode 100644 index 0000000..5c63e6f --- /dev/null +++ b/internal/core/context.go @@ -0,0 +1,26 @@ +package core + +import ( + "context" + "io" +) + +// Context is passed to plugins when their command is executed. +// Allows ctx to inject dependencies and configuration without coupling +// plugins to its internals. Mirrors the pattern used by kubectl plugins. +type Context struct { + // Stdout is where plugins write their primary markdown output. + Stdout io.Writer + + // Stderr is where plugins write errors and diagnostic messages. + Stderr io.Writer + + // WorkDir is the directory where ctx was invoked. + WorkDir string + + // Verbose enables extra diagnostic output when true. + Verbose bool + + // Ctx carries deadlines and cancellation signals. + Ctx context.Context +} diff --git a/internal/core/plugin.go b/internal/core/plugin.go new file mode 100644 index 0000000..b6732b5 --- /dev/null +++ b/internal/core/plugin.go @@ -0,0 +1,24 @@ +package core + +import "github.com/spf13/cobra" + +// Plugin represents a ctx module (a stack or utility). +// The interface is designed to support future migration from compile-time +// registration (via init()) to subprocess dispatch (ctx- binaries in PATH, +// kubectl-plugin style). Adding subprocess support later should not require +// changing this interface. +type Plugin interface { + // Name returns the plugin identifier (e.g. "csharp", "react", "git"). + // Used as the subcommand name: ctx ... + Name() string + + // Version returns the semantic version of the plugin. + Version() string + + // ShortDescription returns a one-line description shown in ctx --help. + ShortDescription() string + + // Command returns the root cobra.Command for this plugin, with all + // subcommands already configured. ctx adds this as a subcommand of root. + Command(ctx *Context) *cobra.Command +} diff --git a/internal/core/registry.go b/internal/core/registry.go new file mode 100644 index 0000000..1ab7c39 --- /dev/null +++ b/internal/core/registry.go @@ -0,0 +1,44 @@ +package core + +import ( + "fmt" + "sort" + "sync" +) + +var ( + registry = make(map[string]Plugin) + registryMu sync.RWMutex +) + +// Register adds a plugin to the registry. Should be called from init() +// in plugin packages. Panics if a plugin with the same name is already registered. +func Register(p Plugin) { + registryMu.Lock() + defer registryMu.Unlock() + if _, exists := registry[p.Name()]; exists { + panic(fmt.Sprintf("ctx: plugin %q already registered", p.Name())) + } + registry[p.Name()] = p +} + +// All returns all registered plugins, sorted by name. +func All() []Plugin { + registryMu.RLock() + defer registryMu.RUnlock() + plugins := make([]Plugin, 0, len(registry)) + for _, p := range registry { + plugins = append(plugins, p) + } + sort.Slice(plugins, func(i, j int) bool { + return plugins[i].Name() < plugins[j].Name() + }) + return plugins +} + +// Get returns a plugin by name, or nil if not found. +func Get(name string) Plugin { + registryMu.RLock() + defer registryMu.RUnlock() + return registry[name] +} diff --git a/internal/output/encoding.go b/internal/output/encoding.go new file mode 100644 index 0000000..d8da5cd --- /dev/null +++ b/internal/output/encoding.go @@ -0,0 +1,15 @@ +// Package output provides helpers for writing consistent markdown to stdout. +package output + +// UTF-8 and BOM notes: +// +// Go's string type is UTF-8 by default, and os.Stdout writes raw bytes. +// On Windows, some programs write a UTF-8 BOM (0xEF 0xBB 0xBF) to signal +// encoding, but Claude and most Unix tools do not expect or want a BOM. +// +// ctx never writes a BOM. All output is plain UTF-8. If a future caller +// needs a file with BOM (e.g. for Excel compatibility), that's a caller +// responsibility — not this package's job. +// +// Git Bash on Windows already uses UTF-8 for stdout when piped, so no +// runtime encoding conversion is needed. diff --git a/internal/output/markdown.go b/internal/output/markdown.go new file mode 100644 index 0000000..6ff7aa6 --- /dev/null +++ b/internal/output/markdown.go @@ -0,0 +1,50 @@ +package output + +import ( + "fmt" + "io" + "strings" +) + +// H1 writes a level-1 markdown heading followed by a blank line. +func H1(w io.Writer, text string) { + fmt.Fprintf(w, "# %s\n\n", text) +} + +// H2 writes a level-2 markdown heading followed by a blank line. +func H2(w io.Writer, text string) { + fmt.Fprintf(w, "## %s\n\n", text) +} + +// H3 writes a level-3 markdown heading followed by a blank line. +func H3(w io.Writer, text string) { + fmt.Fprintf(w, "### %s\n\n", text) +} + +// CodeBlock writes a fenced code block with an optional language identifier. +// If language is empty, the fence has no language tag. +func CodeBlock(w io.Writer, language, content string) { + fmt.Fprintf(w, "```%s\n%s\n```\n\n", language, strings.TrimRight(content, "\n")) +} + +// KeyValue writes a single "**key:** value" line followed by a newline. +func KeyValue(w io.Writer, key, value string) { + fmt.Fprintf(w, "**%s:** %s\n", key, value) +} + +// BulletList writes a markdown bullet list. Each item gets its own line. +// A blank line is written after the list. +func BulletList(w io.Writer, items []string) { + for _, item := range items { + fmt.Fprintf(w, "- %s\n", item) + } + fmt.Fprintln(w) +} + +// Section writes an H2 heading then calls body(w), then writes a blank line. +// Use this to group related output under a named section. +func Section(w io.Writer, title string, body func(w io.Writer)) { + H2(w, title) + body(w) + fmt.Fprintln(w) +} diff --git a/internal/plugins/auto/auto.go b/internal/plugins/auto/auto.go new file mode 100644 index 0000000..303aacb --- /dev/null +++ b/internal/plugins/auto/auto.go @@ -0,0 +1,31 @@ +// Package auto implements the ctx auto plugin. +// Full implementation: prompt 2. +package auto + +import ( + "fmt" + + "github.com/ricarneiro/ctx/internal/core" + "github.com/spf13/cobra" +) + +func init() { + core.Register(&autoPlugin{}) +} + +type autoPlugin struct{} + +func (a *autoPlugin) Name() string { return "auto" } +func (a *autoPlugin) Version() string { return "0.0.1" } +func (a *autoPlugin) ShortDescription() string { return "Auto-detect project stack and emit context" } + +func (a *autoPlugin) Command(ctx *core.Context) *cobra.Command { + return &cobra.Command{ + Use: "auto", + Short: a.ShortDescription(), + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Fprintln(ctx.Stderr, "Not implemented yet — coming in prompt 2") + return nil + }, + } +} diff --git a/internal/plugins/csharp/csharp.go b/internal/plugins/csharp/csharp.go new file mode 100644 index 0000000..7878d81 --- /dev/null +++ b/internal/plugins/csharp/csharp.go @@ -0,0 +1,31 @@ +// Package csharp implements the ctx csharp plugin. +// Full implementation: prompts 4–6 (requires Roslyn helper from prompt 3). +package csharp + +import ( + "fmt" + + "github.com/ricarneiro/ctx/internal/core" + "github.com/spf13/cobra" +) + +func init() { + core.Register(&csharpPlugin{}) +} + +type csharpPlugin struct{} + +func (c *csharpPlugin) Name() string { return "csharp" } +func (c *csharpPlugin) Version() string { return "0.0.1" } +func (c *csharpPlugin) ShortDescription() string { return "C# / .NET project analysis via Roslyn" } + +func (c *csharpPlugin) Command(ctx *core.Context) *cobra.Command { + return &cobra.Command{ + Use: "csharp", + Short: c.ShortDescription(), + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Fprintln(ctx.Stderr, "Not implemented yet — coming in prompt 4") + return nil + }, + } +} diff --git a/internal/plugins/git/git.go b/internal/plugins/git/git.go new file mode 100644 index 0000000..de37ed2 --- /dev/null +++ b/internal/plugins/git/git.go @@ -0,0 +1,31 @@ +// Package git implements the ctx git plugin. +// Full implementation: prompt 1. +package git + +import ( + "fmt" + + "github.com/ricarneiro/ctx/internal/core" + "github.com/spf13/cobra" +) + +func init() { + core.Register(&gitPlugin{}) +} + +type gitPlugin struct{} + +func (g *gitPlugin) Name() string { return "git" } +func (g *gitPlugin) Version() string { return "0.0.1" } +func (g *gitPlugin) ShortDescription() string { return "Git repository summary for Claude" } + +func (g *gitPlugin) Command(ctx *core.Context) *cobra.Command { + return &cobra.Command{ + Use: "git", + Short: g.ShortDescription(), + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Fprintln(ctx.Stderr, "Not implemented yet — coming in prompt 1") + return nil + }, + } +} diff --git a/internal/plugins/plugins.go b/internal/plugins/plugins.go new file mode 100644 index 0000000..9562fb6 --- /dev/null +++ b/internal/plugins/plugins.go @@ -0,0 +1,11 @@ +// Package plugins imports all built-in plugins to trigger their init() +// registration with the core registry. Import this package with a blank +// identifier in cmd/ctx/main.go. +package plugins + +import ( + _ "github.com/ricarneiro/ctx/internal/plugins/auto" + _ "github.com/ricarneiro/ctx/internal/plugins/csharp" + _ "github.com/ricarneiro/ctx/internal/plugins/git" + _ "github.com/ricarneiro/ctx/internal/plugins/react" +) diff --git a/internal/plugins/react/react.go b/internal/plugins/react/react.go new file mode 100644 index 0000000..c20e461 --- /dev/null +++ b/internal/plugins/react/react.go @@ -0,0 +1,31 @@ +// Package react implements the ctx react plugin. +// Full implementation: phase 4 (future prompts). +package react + +import ( + "fmt" + + "github.com/ricarneiro/ctx/internal/core" + "github.com/spf13/cobra" +) + +func init() { + core.Register(&reactPlugin{}) +} + +type reactPlugin struct{} + +func (r *reactPlugin) Name() string { return "react" } +func (r *reactPlugin) Version() string { return "0.0.1" } +func (r *reactPlugin) ShortDescription() string { return "React / TypeScript project analysis" } + +func (r *reactPlugin) Command(ctx *core.Context) *cobra.Command { + return &cobra.Command{ + Use: "react", + Short: r.ShortDescription(), + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Fprintln(ctx.Stderr, "Not implemented yet — coming in a future prompt") + return nil + }, + } +} diff --git a/pkg/.gitkeep b/pkg/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tools/.gitkeep b/tools/.gitkeep new file mode 100644 index 0000000..e69de29