~bigbes/sr-ht-spec

ref: e5927f582c8f4bff0659eac52e3bf4de85de0d87 sr-ht-spec/mcpsrv/read.go -rw-r--r-- 3.8 KiB
e5927f58 — bigbes chore: promote go-arg to a direct dependency 27 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
package mcpsrv

import (
	"context"
	"fmt"

	"sourcecraft.dev/bigbes/sr-ht-spec/service"
)

type readInput struct {
	Space    string `json:"space" jsonschema:"the space to read from, written \"~owner/name\" as spec_list and spec_search report it"`
	Document string `json:"document" jsonschema:"which document: its frontmatter id (\"SPEC-0007\"), or its path with or without the \".md\" extension (\"specs/0007-storage\")"`
	Rev      string `json:"rev,omitempty" jsonschema:"pin the read to one immutable revision, given as a git object name (lowercase hex, as returned in the rev field of any result). Omit this to read the space's approved head, which is what an agent almost always wants: the reviewed text. Branch names are not accepted."`
}

type readOutput struct {
	Space string `json:"space"`
	// ID is how this document is addressed: its frontmatter id when that is
	// well-formed and unique in the space, otherwise its path without the
	// extension.
	ID string `json:"id"`
	// DocID is the frontmatter id when the document has a well-formed one, and
	// empty otherwise. It differs from ID exactly when the document has no
	// usable id, which is the case an agent proposing an edit needs to see.
	DocID string `json:"doc_id,omitempty"`
	Path  string `json:"path"`
	// Rev is the commit this content was read at. It is immutable: pass it back
	// as the rev argument to re-read these exact bytes. It is also the value
	// the Phase 3 write plane takes as If-Match when it is an approved head.
	Rev string `json:"rev"`
	// Blob is the sha of this document's content, and changes only when the
	// content does.
	Blob string `json:"blob"`
	// Pinned reports how Rev was chosen. False means the caller named no
	// revision and this is the space's approved head as of this call — the
	// reviewed, canonical text. True means the caller pinned a revision, and
	// whether that revision is on the approved branch is the caller's business:
	// this service does not claim it either way.
	Pinned  bool   `json:"pinned"`
	Title   string `json:"title,omitempty"`
	Section string `json:"section,omitempty"`
	// Status is the document's authored lifecycle marker (draft, review,
	// superseded). It is not approval state: a document is approved by being
	// reachable from the approved ref, never by its frontmatter.
	Status  string   `json:"status,omitempty"`
	Summary string   `json:"summary,omitempty"`
	Tags    []string `json:"tags,omitempty"`
	// Markdown is the whole document, frontmatter included, exactly as stored.
	// It is the text to edit and send back when proposing a change.
	Markdown string `json:"markdown"`
}

func readHandler(ctx context.Context, b Backend, in readInput) (readOutput, error) {
	ref, err := parseSpace(in.Space)
	if err != nil {
		return readOutput{}, err
	}
	rev, err := parseRev(in.Rev)
	if err != nil {
		return readOutput{}, err
	}
	sp, err := b.Docs.OpenSpace(ctx, ref)
	if err != nil {
		return readOutput{}, err
	}
	arc, bodies, resolved, err := archiveAt(ctx, b, sp, rev)
	if err != nil {
		return readOutput{}, err
	}
	page, err := resolvePage(arc, in.Document)
	if err != nil {
		return readOutput{}, err
	}
	body, ok := bodies[page.Path]
	if !ok {
		// The archive is built from these very bodies, so a page without one
		// is a broken invariant rather than a missing document. Returning an
		// empty markdown field would be indistinguishable from an empty
		// document.
		return readOutput{}, fmt.Errorf("document %s at %s in %s has no body", page.Path, resolved, ref)
	}
	return readOutput{
		Space:    ref.String(),
		ID:       page.ID,
		DocID:    page.DocID,
		Path:     page.Path,
		Rev:      resolved,
		Blob:     page.Blob,
		Pinned:   rev != service.ApprovedRev,
		Title:    page.Title,
		Section:  page.Section,
		Status:   string(page.Status),
		Summary:  page.Summary,
		Tags:     page.Tags,
		Markdown: string(body),
	}, nil
}