Documentation
¶
Overview ¶
Package quest is the quest file format: the load-bearing types plus parsing and schema validation of the quest document. A quest is JSON — the single source of truth — embedded in a self-contained HTML file inside a <script type="application/json" id="quest"> block. The terminal reads the JSON and renders a detail pane (render.go); a build step turns the same JSON into the browser HTML (build.go). Two renderers, one parsed model.
Stage 1 parses, validates, renders, and displays gates; it does not execute them (the gate runner is Stage 2). Status is human-owned and never set by an executing agent.
Index ¶
- Constants
- func Approve(q *Quest) error
- func AuthoringClause() string
- func Build(q *Quest) ([]byte, error)
- func DeleteComment(q *Quest, id string) error
- func ExtractJSON(raw []byte) ([]byte, error)
- func Home() string
- func MarkDone(q *Quest) error
- func Marshal(q *Quest) ([]byte, error)
- func NewID(timestamp int64) string
- func NewIDWithSuffix(timestamp int64, suffix int) string
- func NextCommentID(q *Quest, timestamp int64) string
- func OpenCommentCount(q *Quest) int
- func QuestsDir() string
- func RenderBoardListRows(q *Quest, runtime Runtime, width int) []string
- func RenderDetail(q *Quest, runtime Runtime, width int) string
- func RenderDetailFocused(q *Quest, runtime Runtime, width int, focus DetailFocus) string
- func RenderDetailLines(q *Quest, runtime Runtime, width int, focus DetailFocus) ([]string, int)
- func RenderListRow(q *Quest, runtime Runtime, width int, mode ListTagMode) string
- func RenderTrackerLine(q *Quest, width int) string
- func SetStatus(q *Quest, to Status) error
- func ToggleGate(q *Quest, gateName string) (bool, error)
- func UpdateCommentBody(q *Quest, id, body string) error
- func Validate(q *Quest) error
- func ValidateCommentAnchor(q *Quest, anchor CommentAnchor) error
- func Withdraw(q *Quest) error
- func WorkingClause(q *Quest) string
- type Adventurer
- type AttachmentRef
- type Block
- type CommentAnchor
- type CommentAnchorKind
- type CommentStatus
- type DetailFocus
- type DetailLineSelection
- type DetailTarget
- type DetailTargetKind
- type FileStore
- func (s *FileStore) Delete(id string) error
- func (s *FileStore) Dir() string
- func (s *FileStore) Exists(id string) bool
- func (s *FileStore) Fingerprint() (string, error)
- func (s *FileStore) List() ([]Quest, error)
- func (s *FileStore) Load(id string) (*Quest, error)
- func (s *FileStore) Path(id string) string
- func (s *FileStore) Save(q *Quest) error
- func (s *FileStore) Update(id string, mutate func(*Quest) error) (*Quest, error)
- type Gate
- type GateType
- type ListTagMode
- type LoopRuntime
- type ProjectGroup
- type Quest
- type QuestComment
- type RelatedLink
- type Runtime
- type Status
Constants ¶
const ( BlockHeading = "heading" BlockText = "text" BlockList = "list" BlockCode = "code" BlockRich = "rich" )
Block type discriminators for the body[] array.
const BeforePR = "pr"
BeforePR is the only non-empty gate position in Stage 1: a barrier the loop will not cross until the gate passes, holding PR creation.
const HomeEnv = "QUESTMASTER_HOME"
HomeEnv overrides the questmaster home directory (the parent of quests/). Defaults to ~/.questmaster. Authored quest docs live here, deliberately separate from the ephemeral session-state root (~/.questmaster-state).
const IDPrefix = "quest-"
const UnsortedProject = "Unsorted"
UnsortedProject is the trailing section label for quests with no project (e.g. quests authored before project stamping, or outside any git repo).
Variables ¶
This section is empty.
Functions ¶
func AuthoringClause ¶
func AuthoringClause() string
AuthoringClause is the briefing for a master or standalone session: how to create and write a conformant quest through qm, where quests live, that gates must be real checkable criteria, to run the validator, and that the author cannot post (approve) or close (mark done) a quest. Injected as persistent identity so any master/standalone is quest-authoring-aware.
func Build ¶
Build renders a quest to a self-contained, browser-openable HTML document. It is the sibling of the terminal renderer: the same body-block dispatch, HTML output. The canonical JSON is written verbatim into the <script type="application/json" id="quest"> block (the source of truth, machine-read back by Parse), the docs-style <meta> frontmatter is emitted so a future quest index can read the built files, and a collapsible Source panel pretty-prints the same JSON for in-browser audit. Runs on save and on `quest open`.
func DeleteComment ¶ added in v0.3.30
DeleteComment removes one comment from active quest data.
func ExtractJSON ¶
ExtractJSON pulls the canonical quest JSON out of a quest HTML file. It does not unmarshal — see Parse. Returns an error if the quest script block is absent.
Build always emits the canonical block last, after the body — where a raw rich-html block could carry its own id="quest" script and shadow the real source of truth. So we take the LAST match, which is the guaranteed-canonical block, rather than the first.
func Home ¶
func Home() string
Home resolves the questmaster home directory: $QUESTMASTER_HOME, else ~/.questmaster. Returns "" only when neither the override nor $HOME is set.
func Marshal ¶
Marshal renders a Quest as canonical, human-readable JSON (two-space indent, HTML left unescaped so authored rich content stays legible). This is the editor buffer and the Source-panel text. For embedding in the <script> block, build.go neutralizes the closing-tag sequence.
func NewIDWithSuffix ¶ added in v0.3.16
NewIDWithSuffix formats a collision-retry ID for an auto-generated quest.
func NextCommentID ¶ added in v0.3.28
NextCommentID returns a generated comment id that does not collide within q.
func OpenCommentCount ¶ added in v0.3.28
OpenCommentCount returns the number of unresolved comments on a quest.
func QuestsDir ¶
func QuestsDir() string
QuestsDir is <home>/quests, the store root. Quests are never written into a repo and never committed.
func RenderBoardListRows ¶ added in v0.3.25
RenderBoardListRows returns the board's two-line row: title first, quest id below. The board supplies outer gutters and selection backgrounds.
func RenderDetail ¶
RenderDetail returns the read-only quest detail pane (no interactive focus). Pure and deterministic — golden-testable.
func RenderDetailFocused ¶
func RenderDetailFocused(q *Quest, runtime Runtime, width int, focus DetailFocus) string
RenderDetailFocused renders the detail pane with an optional interactive focus on one row (a toggle gate or a related entry). The board passes an active focus when the pane has focus; qm quest view passes none. Layout: title + status, meta line, attached/adventurers line (from runtime), objective, definition of done, related, then the body.
func RenderDetailLines ¶
RenderDetailLines is RenderDetailFocused split into lines plus the index of the focused interactive row's line (-1 when the pane is not focused). The board uses the index to paint a full-width selection background on that line, matching the list; the renderer itself draws no focus marker.
func RenderListRow ¶
func RenderListRow(q *Quest, runtime Runtime, width int, mode ListTagMode) string
RenderListRow returns one quest-log list line: id, goal, and the right-column marker selected by mode. Fits width on a single line.
func RenderTrackerLine ¶
RenderTrackerLine returns the tracker's per-session quest line: "⚑ id · goal", no status. Fits width.
func ToggleGate ¶ added in v0.3.35
ToggleGate flips one human-owned toggle gate and returns its new checked state.
func UpdateCommentBody ¶ added in v0.3.30
UpdateCommentBody replaces one comment's body text. The lifecycle, anchor, author, and timestamps are left unchanged.
func Validate ¶
Validate enforces the quest schema (quest-format.md). It is the refuse-malformed gate: a quest that fails Validate must not be saved, and the error is fed back to the author (the refuse-and-re-engage loop). Errors are single-line and specific so an authoring session can self-correct. Malformed JSON is caught earlier, by Parse.
func ValidateCommentAnchor ¶ added in v0.3.28
func ValidateCommentAnchor(q *Quest, anchor CommentAnchor) error
ValidateCommentAnchor checks whether an anchor currently points at a stable quest element. It is exported so mutation commands can fail before opening an editor when the requested target cannot exist.
func WorkingClause ¶
WorkingClause is the per-session briefing for a session attached to a quest: the goal, the gates as the definition of done, the plan, and the working rules. qm seeds it as the opening prompt at spawn and injects it on attach. It hands the agent the parsed quest and the one hard rule that keeps status human-owned: you work to the gates, you cannot mark the quest done, and you can re-read the current quest at any time.
Types ¶
type Adventurer ¶ added in v0.3.21
type Adventurer struct {
ID string `json:"id"`
Agent string `json:"agent,omitempty"`
// State is the hook-observed primary-pane state: working | done | blocked
// | starting | unknown.
State string `json:"state,omitempty"`
// Since is when the current state began (WorkingSince for working,
// otherwise the last state transition).
Since time.Time `json:"since,omitempty"`
// Loop is the session's armed loop marker, when present.
Loop *LoopRuntime `json:"loop,omitempty"`
}
Adventurer is one attached session's live activity, derived from the session scan at render time (never stored on the quest).
type AttachmentRef ¶ added in v0.3.35
type AttachmentRef struct {
ItemID string `json:"item_id"`
Type string `json:"type"`
Title string `json:"title"`
}
AttachmentRef is a non-owning legacy attachment reference. The target is not resolved by current commands; the field remains so existing quest JSON keeps loading and saving without format churn.
type Block ¶
type Block struct {
Type string `json:"type"`
ID string `json:"id,omitempty"`
// heading
Level int `json:"level,omitempty"`
// heading, text, code
Text string `json:"text,omitempty"`
// list
Ordered bool `json:"ordered,omitempty"`
Items []string `json:"items,omitempty"`
// code
Lang string `json:"lang,omitempty"`
// rich
Format string `json:"format,omitempty"`
Fallback string `json:"fallback,omitempty"`
Content string `json:"content,omitempty"`
}
Block is one ordered body block. Its meaning is discriminated by Type; the fields a Type uses are documented per kind. Array order is document order, so structure is preserved for free. A single flat shape keeps the model simple and lets both renderers dispatch on Type. Unknown types are not an error — they degrade to Fallback at render time (forward compatibility).
- heading: Level, Text
- text: Text
- list: Ordered, Items
- code: Lang, Text
- rich: Format, Fallback, Content
ID is an optional stable handle on any block, for future referencing.
type CommentAnchor ¶ added in v0.3.28
type CommentAnchor struct {
Kind CommentAnchorKind `json:"kind"`
ID string `json:"id,omitempty"`
Item *int `json:"item,omitempty"`
}
CommentAnchor points a comment at a stable quest element. Quest anchors carry no id; all other anchors use a stable id local to their element kind. Block anchors may optionally carry a zero-based list item index.
func ParseCommentAnchor ¶ added in v0.3.35
func ParseCommentAnchor(raw string) (CommentAnchor, error)
ParseCommentAnchor parses the CLI/wire shorthand for a stable quest element.
func (CommentAnchor) String ¶ added in v0.3.28
func (a CommentAnchor) String() string
func (CommentAnchor) WithItem ¶ added in v0.3.28
func (a CommentAnchor) WithItem(index int) CommentAnchor
WithItem returns a block anchor narrowed to a zero-based list item index.
type CommentAnchorKind ¶ added in v0.3.28
type CommentAnchorKind string
CommentAnchorKind names the stable quest element a comment is attached to.
const ( CommentAnchorQuest CommentAnchorKind = "quest" CommentAnchorGate CommentAnchorKind = "gate" CommentAnchorRelated CommentAnchorKind = "related" CommentAnchorBody CommentAnchorKind = "block" )
type CommentStatus ¶ added in v0.3.28
type CommentStatus string
CommentStatus is the authored lifecycle of an inline quest comment.
const ( CommentOpen CommentStatus = "open" CommentResolved CommentStatus = "resolved" )
type DetailFocus ¶
type DetailFocus struct {
Active bool
Kind DetailTargetKind
Index int
ItemIndex int
Anchor CommentAnchor
CommentID string
}
DetailFocus describes which interactive row the detail pane has focused. Active is false for read-only renders (qm quest view); the board sets it when the pane has focus. Shared by the board (navigation) and the renderer (highlight) so they agree on what is selected.
type DetailLineSelection ¶ added in v0.3.28
DetailLineSelection is the set of detail-renderer line indexes that should receive selection treatment. Primary is the line used for scroll tracking; Lines is the painted set. Most targets select one row, while focused body blocks select every rendered content line in the block.
func RenderDetailLineSelection ¶ added in v0.3.28
func RenderDetailLineSelection(q *Quest, runtime Runtime, width int, focus DetailFocus) ([]string, DetailLineSelection)
RenderDetailLineSelection is RenderDetailLines plus the full selected line set, used by the board to paint multi-line body block focus.
func (DetailLineSelection) Contains ¶ added in v0.3.28
func (s DetailLineSelection) Contains(line int) bool
Contains reports whether line should be drawn selected.
type DetailTarget ¶
type DetailTarget struct {
Kind DetailTargetKind
Index int
ItemIndex int
Anchor CommentAnchor
CommentID string
}
DetailTarget is one interactive row in the detail pane, addressed by its index into the matching quest slice. Anchor is the stable comment target for rows that can receive a comment. CommentID is set only for open comment rows, which can be resolved.
func DetailTargets ¶
func DetailTargets(q *Quest) []DetailTarget
DetailTargets enumerates the interactive rows in display order: quest anchor, open quest comments, gates, open gate comments, related entries, open related comments, body blocks, and open body comments. Auto gates are focusable but remain read-only; only toggle gates flip.
type DetailTargetKind ¶
type DetailTargetKind int
DetailTargetKind identifies an interactive row in the detail pane.
const ( // TargetQuest is the quest-level anchor. TargetQuest DetailTargetKind = iota // TargetGate is a definition-of-done gate. Only toggle gates are flippable. TargetGate // TargetRelated is a related entry (openable in T12). TargetRelated // TargetBody is a body block with a stable id. TargetBody // TargetListItem is one item inside a list body block. TargetListItem // TargetComment is an open inline comment row. TargetComment )
type FileStore ¶
type FileStore struct {
// contains filtered or unexported fields
}
FileStore is the on-disk quest store rooted at a quests directory (under the questmaster home — never a repo path). Files are self-contained, browser- openable HTML carrying the canonical JSON in a <script id="quest"> block.
func DefaultStore ¶
func DefaultStore() *FileStore
DefaultStore returns a FileStore rooted at the resolved QuestsDir.
func NewStore ¶
NewStore returns a FileStore rooted at dir. The directory is created lazily on Save.
func (*FileStore) Delete ¶ added in v0.3.20
Delete removes a quest file by id. The id must be a safe single path component (same guard as Load/Save). A missing quest is reported as not-found rather than surfacing the raw os error.
func (*FileStore) Fingerprint ¶ added in v0.3.30
Fingerprint returns a cheap signature of the store's current contents — each quest file's name, size, and modification time — WITHOUT reading or parsing any file. The board polls on a timer; comparing fingerprints lets it skip the parse-heavy List() (regexp + JSON per file) on ticks where nothing on disk changed. A changed fingerprint means "re-read"; an unchanged one is safe to treat as "no authored change since last poll".
func (*FileStore) List ¶
List returns every parseable quest in the store, sorted by id. Files that fail to parse are skipped so one malformed quest never blanks the board.
func (*FileStore) Load ¶
Load reads and parses a quest file by id. It does not re-validate the schema (the write path is the gate); callers wanting integrity Validate the result.
func (*FileStore) Path ¶
Path returns the absolute file path for a quest id, always under the store directory. An id that is not a safe single path component yields a path that Load/Save reject.
func (*FileStore) Save ¶
Save validates the quest and, only if it conforms, rebuilds the HTML body from the canonical JSON (Build) and writes it to <dir>/<id>.html atomically (tmp + rename). A malformed quest is refused and the validation error returned, to be fed back to the author (refuse-and-re-engage). Quests are never written into a repo and never committed.
type Gate ¶
type Gate struct {
Name string `json:"name"`
Type GateType `json:"type"`
Check string `json:"check,omitempty"`
Before string `json:"before,omitempty"`
// Checked is the human-authored met-state of a toggle gate ([x] vs [ ]).
// It is authored, so it lives in the JSON. Auto gates never carry it —
// their results are observed, not authored, and live in the runtime sidecar.
Checked bool `json:"checked,omitempty"`
}
Gate is a single done-criterion. Check is required iff Type == auto and must be empty for toggle. Before names the transition the gate guards ("" guards done; "pr" is a barrier before PR creation). The check grammar is authored and displayed on the quest, then executed by qm.
type ListTagMode ¶ added in v0.3.20
type ListTagMode int
ListTagMode selects the right-column marker for a list row.
const ( // TagStatus shows the colour-coded status glyph (◆/○/●, ⚔ when attached). // Used by `quest ls`, which has no tabs to convey status. TagStatus ListTagMode = iota // TagAttached shows only the ⚔ on-it marker (blank otherwise). Used by the // board, where the selected status tab already conveys status. TagAttached )
type LoopRuntime ¶ added in v0.3.13
type LoopRuntime struct {
SessionID string `json:"session_id"`
Iterations int `json:"iterations"`
LastVerdict string `json:"last_verdict,omitempty"`
// Phase is what the armed loop is doing right now: waiting | checking |
// paused. Empty on markers written by older binaries.
Phase string `json:"phase,omitempty"`
}
LoopRuntime is the render-time view of an armed quest loop marker.
func (LoopRuntime) Label ¶ added in v0.3.13
func (l LoopRuntime) Label() string
Label returns the compact loop-mode indicator used by tracker and board.
type ProjectGroup ¶ added in v0.3.20
ProjectGroup is one project section of the quest log: a project name (or UnsortedProject) and its quests, ordered by status then id.
func GroupByProject ¶ added in v0.3.20
func GroupByProject(quests []Quest) []ProjectGroup
GroupByProject sorts quests into project sections — alphabetical, with the project-less "Unsorted" section last — and within each section by status (active → wip → done) then id. The board and `quest ls` share it so both surfaces present the log identically.
type Quest ¶
type Quest struct {
ID string `json:"id"`
Title string `json:"title"`
Status Status `json:"status"`
Summary string `json:"summary"`
Date string `json:"date,omitempty"`
// Agent is accepted when parsing legacy quests, but canonical save/build
// output drops it. Display uses the attached session runtime instead.
Agent string `json:"agent,omitempty"`
Project string `json:"project,omitempty"`
Related []RelatedLink `json:"related,omitempty"`
// Attachments are read-only legacy refs retained for existing quest JSON.
Attachments []AttachmentRef `json:"attachments,omitempty"`
Gates []Gate `json:"gates,omitempty"`
Body []Block `json:"body,omitempty"`
Comments []QuestComment `json:"comments,omitempty"`
}
Quest is the parsed canonical JSON of a quest: docs-style frontmatter, the gates that are the definition of done, and the ordered body blocks. It is the single source of truth — the HTML body is generated from it and never parsed back.
func Parse ¶
Parse reads the canonical JSON from a quest HTML file and unmarshals it into a Quest. The generated HTML body is never parsed back; only the JSON block is read. Parse does not validate the schema (Validate is the separate gate).
func ParseJSON ¶
ParseJSON unmarshals bare canonical quest JSON into a Quest. Used by the edit flow, which re-reads the JSON a human edited in $EDITOR.
func Scaffold ¶
Scaffold returns a minimal, schema-valid wip quest for `qm quest new`. It is born wip — only the Questmaster approves it to active — and carries placeholder content plus a safe toggle reminding the author to define the real gates via `qm quest edit`. title and summary fall back to safe placeholders so the scaffold always validates.
type QuestComment ¶ added in v0.3.28
type QuestComment struct {
ID string `json:"id"`
Anchor CommentAnchor `json:"anchor"`
Status CommentStatus `json:"status"`
Author string `json:"author,omitempty"`
Body string `json:"body"`
CreatedAt string `json:"created_at"`
ResolvedAt string `json:"resolved_at,omitempty"`
}
QuestComment is a single human-authored note attached to a quest anchor.
func AddComment ¶ added in v0.3.35
func AddComment(q *Quest, anchor CommentAnchor, author, body string, now time.Time) (QuestComment, error)
AddComment appends a new open comment after validating its anchor and body.
func CommentByID ¶ added in v0.3.30
func CommentByID(q *Quest, id string) (QuestComment, bool)
CommentByID returns a comment by stable id.
func OpenComments ¶ added in v0.3.28
func OpenComments(q *Quest) []QuestComment
OpenComments returns the actionable comments in authored order.
type RelatedLink ¶
type RelatedLink struct {
ID string `json:"id,omitempty"`
Type string `json:"type,omitempty"`
Title string `json:"title"`
URL string `json:"url,omitempty"`
}
RelatedLink is a related ticket / PR / doc: a typed, titled, optionally linked reference. The HTML build renders it as an anchor; the terminal shows the title (and, later, the app can open the URL). For backward compatibility a bare JSON string decodes into a RelatedLink with just its Title set (see UnmarshalJSON in parse.go).
func (*RelatedLink) UnmarshalJSON ¶
func (r *RelatedLink) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts either the structured object form ({"type","title","url"}) or a bare string (which becomes the Title), so older quests authored with plain string related entries still parse.
type Runtime ¶
type Runtime struct {
// Sessions are the session IDs currently attached to (on) the quest.
Sessions []string `json:"sessions"`
// Adventurers is the per-session live activity for the attached sessions, in
// Sessions order. Populated by the shared runtime scan; renderers fall
// back to the bare Sessions line when it is empty.
Adventurers []Adventurer `json:"adventurers"`
// Agent is derived from the attached session's primary agent at render
// time. Authored quest JSON does not decide the displayed agent.
Agent string `json:"agent"`
// Gates overlays observed auto-gate results (gate name → "pass"/"fail"/
// "error") from the runtime sidecar. Empty until a check has run. Toggle
// gates ignore this — their state is authored in the JSON.
Gates map[string]string `json:"gates,omitempty"`
// GatesAt overlays each observed auto-gate result's run time, so the
// renderer can show how fresh a verdict is.
GatesAt map[string]time.Time `json:"gates_at,omitempty"`
// ObservedAt is when this runtime was derived. Durations and verdict ages
// are computed against it, keeping the renderer pure (no global clock).
ObservedAt time.Time `json:"observed_at"`
// Loop is present when a visible foreground `qm quest loop` is armed for
// one of the attached sessions.
Loop *LoopRuntime `json:"loop,omitempty"`
}
Runtime is the derived, render-time state of a quest — which sessions are on it — gathered from the session scan and injected at render time. It is never stored on the quest itself (one fact, one home: the file holds authored content + status; attachment lives on session state).