Replaces placeholder with full csharp@0.1.0 plugin. Adds helper/ package (locate, process, client, protocol) for JSON-RPC over stdio to ctx-roslyn-helper. project.go finds .sln (fallback: single .csproj), loads it, retrieves projectSummary, formats dense markdown with project details, reference graph, and multi-targeting section. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
package csharp
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"github.com/ricarneiro/ctx/internal/core"
|
|
"github.com/ricarneiro/ctx/internal/plugins/csharp/helper"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// errExit is a sentinel used to trigger os.Exit(1) via the SilenceErrors flow.
|
|
// The real error message has already been printed to ctx.Stderr.
|
|
var errExit = fmt.Errorf("exit 1")
|
|
|
|
func projectCmd(ctx *core.Context) *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "project",
|
|
Short: "Summarize the .NET solution in compact markdown",
|
|
Long: `Analyze the .NET solution in the current directory and emit a compact
|
|
markdown summary suitable for Claude Code consumption.
|
|
|
|
Requires the Roslyn helper (ctx-roslyn-helper) to be built and accessible.
|
|
Set CTX_ROSLYN_HELPER to the exact path, or build it alongside ctx.exe.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return runProject(ctx)
|
|
},
|
|
}
|
|
}
|
|
|
|
func runProject(ctx *core.Context) error {
|
|
slnPath, err := findSolution(ctx.WorkDir, ctx)
|
|
if err != nil {
|
|
fmt.Fprintln(ctx.Stderr, err.Error())
|
|
return errExit
|
|
}
|
|
|
|
client, err := helper.NewClient()
|
|
if err != nil {
|
|
fmt.Fprintln(ctx.Stderr, err.Error())
|
|
return errExit
|
|
}
|
|
defer client.Close()
|
|
|
|
if _, err := client.LoadSolution(slnPath); err != nil {
|
|
fmt.Fprintln(ctx.Stderr, err.Error())
|
|
return errExit
|
|
}
|
|
|
|
summary, err := client.ProjectSummary()
|
|
if err != nil {
|
|
fmt.Fprintln(ctx.Stderr, err.Error())
|
|
return errExit
|
|
}
|
|
|
|
return WriteSummary(ctx.Stdout, summary)
|
|
}
|
|
|
|
// findSolution locates a .sln file in dir.
|
|
// Falls back to a single .csproj if no .sln exists.
|
|
func findSolution(dir string, ctx *core.Context) (string, error) {
|
|
slns, err := filepath.Glob(filepath.Join(dir, "*.sln"))
|
|
if err != nil {
|
|
return "", fmt.Errorf("glob .sln: %w", err)
|
|
}
|
|
|
|
// Filter to files that actually exist
|
|
slns = existingFiles(slns)
|
|
|
|
if len(slns) > 0 {
|
|
sort.Strings(slns)
|
|
if len(slns) > 1 {
|
|
fmt.Fprintf(ctx.Stderr, "warning: multiple .sln files found, using %s\n", filepath.Base(slns[0]))
|
|
}
|
|
return slns[0], nil
|
|
}
|
|
|
|
// Fallback: single .csproj
|
|
csprojPaths, err := filepath.Glob(filepath.Join(dir, "*.csproj"))
|
|
if err != nil {
|
|
return "", fmt.Errorf("glob .csproj: %w", err)
|
|
}
|
|
csprojPaths = existingFiles(csprojPaths)
|
|
|
|
switch len(csprojPaths) {
|
|
case 0:
|
|
return "", fmt.Errorf("no .sln or .csproj found in %s", dir)
|
|
case 1:
|
|
return csprojPaths[0], nil
|
|
default:
|
|
return "", fmt.Errorf(
|
|
"no .sln found and multiple .csproj files exist in %s\n"+
|
|
"Run ctx inside a specific project folder, or create a .sln:\n"+
|
|
" dotnet new sln --format sln\n"+
|
|
" dotnet sln add **/*.csproj",
|
|
dir,
|
|
)
|
|
}
|
|
}
|
|
|
|
func existingFiles(paths []string) []string {
|
|
out := paths[:0]
|
|
for _, p := range paths {
|
|
if _, err := os.Stat(p); err == nil {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|