feat: Add RPC support for epic commands in daemon mode

- Added OpEpicStatus operation to protocol
- Implemented handleEpicStatus() in RPC server
- Added EpicStatus() method to RPC client
- Updated epic.go to use daemon RPC when available
- Server-side filtering for close-eligible reduces RPC payload
- Both 'bd epic status' and 'bd epic close-eligible' now work in daemon mode

Fixes #62
Closes bd-87

Amp-Thread-ID: https://ampcode.com/threads/T-44c6044e-de04-40a0-bac3-b26238c32a17
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-10-23 22:04:13 -07:00
parent 72d16229e0
commit 0b819e1f40
5 changed files with 150 additions and 45 deletions

View File

@@ -307,3 +307,8 @@ func (c *Client) Export(args *ExportArgs) (*Response, error) {
func (c *Client) Import(args *ImportArgs) (*Response, error) {
return c.Execute(OpImport, args)
}
// EpicStatus gets epic completion status via the daemon
func (c *Client) EpicStatus(args *EpicStatusArgs) (*Response, error) {
return c.Execute(OpEpicStatus, args)
}

View File

@@ -34,6 +34,7 @@ const (
OpCompactStats = "compact_stats"
OpExport = "export"
OpImport = "import"
OpEpicStatus = "epic_status"
)
// Request represents an RPC request from client to daemon
@@ -157,6 +158,11 @@ type CommentAddArgs struct {
Text string `json:"text"`
}
// EpicStatusArgs represents arguments for the epic status operation
type EpicStatusArgs struct {
EligibleOnly bool `json:"eligible_only,omitempty"`
}
// PingResponse is the response for a ping operation
type PingResponse struct {
Message string `json:"message"`

View File

@@ -648,6 +648,8 @@ func (s *Server) handleRequest(req *Request) Response {
resp = s.handleExport(req)
case OpImport:
resp = s.handleImport(req)
case OpEpicStatus:
resp = s.handleEpicStatus(req)
default:
s.metrics.RecordError(req.Operation)
return Response{
@@ -2212,3 +2214,53 @@ func (s *Server) handleImport(req *Request) Response {
Error: "import via daemon not yet implemented, use --no-daemon flag",
}
}
func (s *Server) handleEpicStatus(req *Request) Response {
var epicArgs EpicStatusArgs
if err := json.Unmarshal(req.Args, &epicArgs); err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("invalid epic status args: %v", err),
}
}
store, err := s.getStorageForRequest(req)
if err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("storage error: %v", err),
}
}
ctx := s.reqCtx(req)
epics, err := store.GetEpicsEligibleForClosure(ctx)
if err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to get epic status: %v", err),
}
}
if epicArgs.EligibleOnly {
filtered := []*types.EpicStatus{}
for _, epic := range epics {
if epic.EligibleForClose {
filtered = append(filtered, epic)
}
}
epics = filtered
}
data, err := json.Marshal(epics)
if err != nil {
return Response{
Success: false,
Error: fmt.Sprintf("failed to marshal epics: %v", err),
}
}
return Response{
Success: true,
Data: data,
}
}