diff --git a/internal/p9/beads.go b/internal/p9/beads.go index e5ea367..2f61df7 100644 --- a/internal/p9/beads.go +++ b/internal/p9/beads.go @@ -446,8 +446,8 @@ func (b *BeadsFS) executeCtl(cmd string) error { for _, a := range args { if a == "--no-lint" { noLint = true - } else if strings.HasPrefix(a, "capability=") { - capLevel = strings.TrimPrefix(a, "capability=") + } else if level, ok := strings.CutPrefix(a, "capability="); ok { + capLevel = level } else { filtered = append(filtered, a) } @@ -506,7 +506,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { if len(args) > 1 { assignee = args[1] } - updates := map[string]interface{}{ + updates := map[string]any{ "assignee": assignee, "status": bd.StatusInProgress, } @@ -519,7 +519,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { if err := b.store.CloseIssue(b.ctx, args[0], "completed", actor, ""); err != nil { return err } - return b.store.UpdateIssue(b.ctx, args[0], map[string]interface{}{"assignee": ""}, actor) + return b.store.UpdateIssue(b.ctx, args[0], map[string]any{"assignee": ""}, actor) case "fail": if len(args) < 2 { @@ -528,7 +528,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { if err := b.store.CloseIssue(b.ctx, args[0], args[1], actor, ""); err != nil { return err } - return b.store.UpdateIssue(b.ctx, args[0], map[string]interface{}{"assignee": ""}, actor) + return b.store.UpdateIssue(b.ctx, args[0], map[string]any{"assignee": ""}, actor) case "dep", "add-dep": if len(args) < 2 { @@ -551,7 +551,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { if len(args) < 3 { return fmt.Errorf("usage: update 'value'") } - updates := map[string]interface{}{ + updates := map[string]any{ args[1]: args[2], } return b.store.UpdateIssue(b.ctx, args[0], updates, actor) @@ -599,7 +599,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { return err } for _, lbl := range existing { - if _, ok := strings.CutPrefix(lbl, capabilityPrefix); ok { + if level, ok := strings.CutPrefix(lbl, capabilityPrefix); ok && level != "" { _ = b.store.RemoveLabel(b.ctx, beadID, lbl, actor) } } @@ -635,7 +635,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { if len(args) > 1 { assignee = args[1] } - updates := map[string]interface{}{ + updates := map[string]any{ "status": StatusPendingApproval, "assignee": assignee, } @@ -656,7 +656,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { if len(args) > 1 { assignee = args[1] } - updates := map[string]interface{}{ + updates := map[string]any{ "status": StatusPendingReview, "assignee": assignee, } @@ -674,7 +674,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { if len(args) > 1 { assignee = args[1] } - updates := map[string]interface{}{ + updates := map[string]any{ "status": bd.StatusInProgress, "assignee": assignee, } @@ -686,7 +686,7 @@ func (b *BeadsFS) executeCtl(cmd string) error { if len(args) < 1 { return fmt.Errorf("usage: unclaim ") } - updates := map[string]interface{}{ + updates := map[string]any{ "assignee": "", "status": bd.StatusOpen, } @@ -957,8 +957,7 @@ func (b *BeadsFS) createSubtask(parentID, title, description, actor string) (str nextChild := 1 prefix := parentID + "." for _, issue := range children { - if strings.HasPrefix(issue.ID, prefix) { - suffix := strings.TrimPrefix(issue.ID, prefix) + if suffix, ok := strings.CutPrefix(issue.ID, prefix); ok { if !strings.Contains(suffix, ".") { if n, err := strconv.Atoi(suffix); err == nil && n >= nextChild { nextChild = n + 1 diff --git a/internal/p9/server.go b/internal/p9/server.go index 2d343e2..00ff1fa 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -15,6 +15,7 @@ import ( "os" "path/filepath" "regexp" + "slices" "sort" "strconv" "strings" @@ -464,18 +465,11 @@ func (s *Server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall { qid = plan9.Qid{Type: QTFile, Path: qidToolsBase} if name == "help" { newPath = "/tools/help" + } else if slices.Contains(caps, name) { + qid = plan9.Qid{Type: QTDir, Path: qidToolsBase + hashID(name)} + newPath = "/tools/" + name } else { - // Check if it's a capability - for _, cap := range caps { - if cap == name { - qid = plan9.Qid{Type: QTDir, Path: qidToolsBase + hashID(name)} - newPath = "/tools/" + name - break - } - } - if newPath == "" { - return errFcall(fc, "not found") - } + return errFcall(fc, "not found") } } else { return errFcall(fc, "not found") @@ -491,17 +485,11 @@ func (s *Server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall { qid = plan9.Qid{Type: QTFile, Path: qidSkillsBase} if name == "help" { newPath = "/skills/help" + } else if slices.Contains(intents, name) { + qid = plan9.Qid{Type: QTDir, Path: qidSkillsBase + hashID(name)} + newPath = "/skills/" + name } else { - for _, intent := range intents { - if intent == name { - qid = plan9.Qid{Type: QTDir, Path: qidSkillsBase + hashID(name)} - newPath = "/skills/" + name - break - } - } - if newPath == "" { - return errFcall(fc, "not found") - } + return errFcall(fc, "not found") } } else { return errFcall(fc, "not found") @@ -531,16 +519,17 @@ func (s *Server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall { } // Check if it's a mailbox directory - if name == "inbox" { + switch name { + case "inbox": qid = plan9.Qid{Type: QTDir, Path: qidInboxBase + hashID(sessID)} newPath = path + "/inbox" - } else if name == "outbox" { + case "outbox": qid = plan9.Qid{Type: QTDir, Path: qidOutboxBase + hashID(sessID)} newPath = path + "/outbox" - } else if name == "completed" { + case "completed": qid = plan9.Qid{Type: QTDir, Path: qidCompletedBase + hashID(sessID)} newPath = path + "/completed" - } else { + default: // Regular session file idx := fileIndex(name) if idx < 0 { @@ -656,15 +645,13 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall { input := strings.TrimSpace(string(fc.Data)) parts := strings.Split(strings.TrimPrefix(path, "/"), "/") - fmt.Fprintf(os.Stderr, "[DEBUG] write: path=%q parts=%v len=%d\n", path, parts, len(parts)) - // Handle beads writes - if strings.HasPrefix(path, "/beads/") { + if beadsPath, ok := strings.CutPrefix(path, "/beads/"); ok { if s.beads != nil { cs.mu.Lock() sessionID := cs.sessionID cs.mu.Unlock() - if err := s.beads.Write(strings.TrimPrefix(path, "/beads/"), fc.Data, sessionID); err != nil { + if err := s.beads.Write(beadsPath, fc.Data, sessionID); err != nil { return errFcall(fc, err.Error()) } return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))} @@ -675,8 +662,6 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall { // /ctl - create new session if path == "/ctl" { args := strings.Fields(input) - fmt.Fprintf(os.Stderr, "[DEBUG] ctl write: input=%q args=%v len=%d fc.Count=%d len(fc.Data)=%d\n", - input, args, len(args), fc.Count, len(fc.Data)) if len(args) < 2 || args[0] != "new" { return errFcall(fc, "usage: new [role=] [tasks=]") } @@ -694,15 +679,14 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall { cwdSet := false for i := 2; i < len(args); i++ { arg := args[i] - if strings.HasPrefix(arg, "role=") { - role = strings.TrimPrefix(arg, "role=") - } else if strings.HasPrefix(arg, "tasks=") { - taskStr := strings.TrimPrefix(arg, "tasks=") - if taskStr != "" { - tasks = strings.Split(taskStr, ",") + if r, ok := strings.CutPrefix(arg, "role="); ok { + role = r + } else if t, ok := strings.CutPrefix(arg, "tasks="); ok { + if t != "" { + tasks = strings.Split(t, ",") } - } else if strings.HasPrefix(arg, "model=") { - model = strings.TrimPrefix(arg, "model=") + } else if m, ok := strings.CutPrefix(arg, "model="); ok { + model = m } else if !cwdSet { // First positional argument is cwd cwd = strings.Trim(arg, `"`) @@ -741,7 +725,6 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall { if err != nil { return errFcall(fc, err.Error()) } - fmt.Fprintf(os.Stderr, "[DEBUG] ctl write: session created, returning Count=%d\n", uint32(len(fc.Data))) return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))} } @@ -1146,11 +1129,12 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { mailMgr := s.mgr.GetMailManager() if mailMgr != nil { var messages []*mailbox.Message - if mailboxType == "inbox" { + switch mailboxType { + case "inbox": messages = mailMgr.GetInbox("user") - } else if mailboxType == "outbox" { + case "outbox": messages = mailMgr.GetOutbox("user") - } else if mailboxType == "completed" { + case "completed": messages = mailMgr.GetCompleted("user") } @@ -1180,9 +1164,10 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { // Add regular files for i, name := range fileNames { mode := uint32(0444) - if name == "ctl" || name == "in" || name == "out" { + switch name { + case "ctl", "in", "out": mode = 0222 - } else if name == "alias" || name == "context" || name == "state" { + case "alias", "context", "state": mode = 0644 } content := s.getSessionFile(sess, i) @@ -1198,13 +1183,14 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { for _, dirName := range dirNames { var qidBase uint64 var mode uint32 - if dirName == "inbox" { + switch dirName { + case "inbox": qidBase = qidInboxBase mode = 0555 // read-only (can list and read files) - } else if dirName == "outbox" { + case "outbox": qidBase = qidOutboxBase mode = 0555 // read-only (can list and read files) - } else { + default: qidBase = qidCompletedBase mode = 0555 // read-only (can list and read files) } @@ -1226,11 +1212,12 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte { } var messages []*mailbox.Message - if mailboxType == "inbox" { + switch mailboxType { + case "inbox": messages = mailMgr.GetInbox(sessID) - } else if mailboxType == "outbox" { + case "outbox": messages = mailMgr.GetOutbox(sessID) - } else if mailboxType == "completed" { + case "completed": messages = mailMgr.GetCompleted(sessID) } @@ -1289,9 +1276,9 @@ func (s *Server) readFile(path string) string { } // Handle beads paths - if strings.HasPrefix(path, "/beads/") { + if beadsPath, ok := strings.CutPrefix(path, "/beads/"); ok { if s.beads != nil { - data, err := s.beads.Read(strings.TrimPrefix(path, "/beads/")) + data, err := s.beads.Read(beadsPath) if err != nil { return "" } diff --git a/internal/p9/skills.go b/internal/p9/skills.go index 88e9b18..4f8967b 100644 --- a/internal/p9/skills.go +++ b/internal/p9/skills.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "9fans.net/go/plan9" @@ -72,16 +73,15 @@ func parseSkillFrontMatter(skillDir string) (*SkillMeta, error) { continue } - if strings.HasPrefix(line, "intent:") { - intents := strings.TrimPrefix(line, "intent:") - for _, i := range strings.Split(intents, ",") { + if intents, ok := strings.CutPrefix(line, "intent:"); ok { + for i := range strings.SplitSeq(intents, ",") { i = strings.TrimSpace(i) if i != "" { meta.Intents = append(meta.Intents, i) } } - } else if strings.HasPrefix(line, "description:") { - meta.Description = strings.TrimSpace(strings.TrimPrefix(line, "description:")) + } else if desc, ok := strings.CutPrefix(line, "description:"); ok { + meta.Description = strings.TrimSpace(desc) } } @@ -155,11 +155,8 @@ func (s *SkillsFS) listSkillsInIntent(intent string) ([]*SkillMeta, error) { result = append(result, skill) continue } - for _, i := range skill.Intents { - if i == intent { - result = append(result, skill) - break - } + if slices.Contains(skill.Intents, intent) { + result = append(result, skill) } } return result, nil diff --git a/internal/p9/tools.go b/internal/p9/tools.go index 588ed9f..3d5ca0e 100644 --- a/internal/p9/tools.go +++ b/internal/p9/tools.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "9fans.net/go/plan9" @@ -70,22 +71,21 @@ func parseFrontMatter(path string) (*ToolMeta, error) { scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() - if !strings.HasPrefix(line, "#") { + if comment, ok := strings.CutPrefix(line, "#"); ok { + line = strings.TrimSpace(comment) + } else { break } - line = strings.TrimPrefix(line, "#") - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "capabilities:") { - caps := strings.TrimPrefix(line, "capabilities:") - for _, c := range strings.Split(caps, ",") { + if caps, ok := strings.CutPrefix(line, "capabilities:"); ok { + for c := range strings.SplitSeq(caps, ",") { c = strings.TrimSpace(c) if c != "" { meta.Capabilities = append(meta.Capabilities, c) } } - } else if strings.HasPrefix(line, "description:") { - meta.Description = strings.TrimSpace(strings.TrimPrefix(line, "description:")) + } else if desc, ok := strings.CutPrefix(line, "description:"); ok { + meta.Description = strings.TrimSpace(desc) } } return meta, nil @@ -142,11 +142,8 @@ func (t *ToolsFS) listToolsInCapability(capability string) ([]*ToolMeta, error) var result []*ToolMeta for _, tool := range tools { - for _, cap := range tool.Capabilities { - if cap == capability { - result = append(result, tool) - break - } + if slices.Contains(tool.Capabilities, capability) { + result = append(result, tool) } } return result, nil