~bigbes/sr-ht-spec

ref: 105e0b003b482928921d6894dfcf6d73ed776d73 sr-ht-spec/mcpsrv/list.go -rw-r--r-- 3.5 KiB
105e0b00 — bigbes chore(beads): track the spec.sr.ht issue backlog 26 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package mcpsrv

import (
	"context"
	"errors"
	"strings"
)

type listInput struct {
	Space string `json:"space,omitempty" jsonschema:"the space to list documents from, written \"~owner/name\". Omit it to list the spaces on this instance instead."`
	Rev   string `json:"rev,omitempty" jsonschema:"pin the listing to one immutable revision, given as a git object name (lowercase hex). Omit to list the space's approved head. Requires space."`
}

type spaceEntry struct {
	// Space is the name every other tool takes: "~owner/name".
	Space string `json:"space"`
	Owner string `json:"owner"`
	Name  string `json:"name"`
}

type documentEntry struct {
	// ID is how spec_read addresses this document.
	ID    string `json:"id"`
	DocID string `json:"doc_id,omitempty"`
	Path  string `json:"path"`
	Blob  string `json:"blob,omitempty"`
	Title string `json:"title,omitempty"`
	// Section is the top-level directory the document lives under, and is what
	// spec_search's sections filter takes.
	Section string `json:"section,omitempty"`
	// Status is the authored lifecycle marker, not approval state.
	Status  string   `json:"status,omitempty"`
	Summary string   `json:"summary,omitempty"`
	Tags    []string `json:"tags,omitempty"`
}

// listOutput carries whichever of the two listings was asked for. One tool
// rather than two because the answer to "what can I read" is one question with
// a drill-down, and an agent that has just been handed a space name should not
// have to find a second tool to use it.
type listOutput struct {
	// Spaces is set when no space was named.
	Spaces []spaceEntry `json:"spaces,omitempty"`
	// Space, Rev and Documents are set when one was.
	Space string `json:"space,omitempty"`
	// Rev is the revision listed, resolved to an immutable commit. Pass it to
	// spec_read to read any of these documents at exactly this revision.
	Rev       string          `json:"rev,omitempty"`
	Documents []documentEntry `json:"documents,omitempty"`
}

func listHandler(ctx context.Context, b Backend, in listInput) (listOutput, error) {
	if strings.TrimSpace(in.Space) == "" {
		// A rev with no space is a caller that meant to name one. Ignoring it
		// would answer a different question than was asked and look like it
		// had worked.
		if strings.TrimSpace(in.Rev) != "" {
			return listOutput{}, errors.New("rev names a revision of a space; pass space as well, or omit rev to list spaces")
		}
		return listSpaces(ctx, b)
	}

	ref, err := parseSpace(in.Space)
	if err != nil {
		return listOutput{}, err
	}
	rev, err := parseRev(in.Rev)
	if err != nil {
		return listOutput{}, err
	}
	sp, err := b.Docs.OpenSpace(ctx, ref)
	if err != nil {
		return listOutput{}, err
	}
	arc, _, resolved, err := archiveAt(ctx, b, sp, rev)
	if err != nil {
		return listOutput{}, err
	}

	out := listOutput{Space: ref.String(), Rev: resolved, Documents: make([]documentEntry, 0, len(arc.All()))}
	for _, p := range arc.All() {
		out.Documents = append(out.Documents, documentEntry{
			ID:      p.ID,
			DocID:   p.DocID,
			Path:    p.Path,
			Blob:    p.Blob,
			Title:   p.Title,
			Section: p.Section,
			Status:  string(p.Status),
			Summary: p.Summary,
			Tags:    p.Tags,
		})
	}
	return out, nil
}

func listSpaces(ctx context.Context, b Backend) (listOutput, error) {
	spaces, err := b.Docs.ListSpaces(ctx)
	if err != nil {
		return listOutput{}, err
	}
	out := listOutput{Spaces: make([]spaceEntry, 0, len(spaces))}
	for _, sp := range spaces {
		out.Spaces = append(out.Spaces, spaceEntry{
			Space: sp.Ref.String(),
			Owner: sp.Ref.Owner,
			Name:  sp.Ref.Name,
		})
	}
	return out, nil
}