markdown

package
v0.36.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package markdown provides parsing and mutation for SPEC.md files. It operates on line-level patterns, not a full AST — sufficient for the structured SPEC.md format.

Index

Constants

View Source
const (
	StepStatusPending    = "pending"
	StepStatusInProgress = "in-progress"
	StepStatusComplete   = "complete"
	StepStatusBlocked    = "blocked"
)

Step status constants.

View Source
const (
	ReviewStatusPending          = "pending"
	ReviewStatusApproved         = "approved"
	ReviewStatusChangesRequested = "changes_requested"
)

Review status constants.

Variables

This section is empty.

Functions

func AppendDecision

func AppendDecision(path, question, user string) (int, error)

AppendDecision adds a new question to the decision log in a file. Returns the assigned decision number.

func AppendTriageComment added in v0.18.0

func AppendTriageComment(path, actor, message string) error

AppendTriageComment appends a new comment to a triage file's history log and persists it in place. It is additive-only; existing entries are never touched.

func ArchiveTriageItem added in v0.18.0

func ArchiveTriageItem(path, reason, note, actor string) error

ArchiveTriageItem sets a triage item's status to "archived", records the resolution timestamp, and appends a closing history entry.

func Body

func Body(content string) string

Body returns the content after the frontmatter.

func EscalateTriageItem added in v0.18.0

func EscalateTriageItem(path string, currentlyUrgent bool, actor string) error

EscalateTriageItem toggles severity between "urgent" and "" (normal) and appends a history entry recording the escalation event.

func FormatDecisionTable

func FormatDecisionTable(entries []DecisionEntry) string

FormatDecisionTable renders decision entries as a markdown table.

func IsSectionNonEmpty

func IsSectionNonEmpty(sections []Section, slug string) bool

IsSectionNonEmpty checks if a section has meaningful content (not just whitespace).

func IsValidSectionSlug

func IsValidSectionSlug(slug string) bool

IsValidSectionSlug checks if the slug is a known section.

func MaxSpecNum added in v0.12.1

func MaxSpecNum(existingFiles []string) int

MaxSpecNum returns the highest SPEC-NNN number among the given filenames, or 0 if none. It is the bootstrap seed for the counter ref (SPEC-018 §7.1).

func MaxTriageNum added in v0.12.1

func MaxTriageNum(existingFiles []string) int

MaxTriageNum returns the highest TRIAGE-NNN number among the given filenames, or 0 if none.

func ReplaceSection

func ReplaceSection(path, slug, newContent string) error

ReplaceSection replaces the content of a section in a file.

func ReplaceSectionContent

func ReplaceSectionContent(content, slug, newContent string) (string, error)

ReplaceSectionContent replaces a section's content in the markdown string.

func ResolveAnchorTokens added in v0.35.0

func ResolveAnchorTokens(body []AnchorToken, quote, prefix string) (idx int, ok, ambiguous bool)

ResolveAnchorTokens finds one uniquely identified quote occurrence.

Matching runs over a concatenated character stream of the normalised tokens rather than token-by-token: renderers move word boundaries (inline code gains padding spaces, long words reflow), so `n`/`p` may tokenise as one word in markdown source and two in rendered output. Character-stream matching is immune to boundary drift while staying whitespace-, markup-, and case-insensitive.

func ResolveAnchorTokensLoose added in v0.35.0

func ResolveAnchorTokensLoose(body []AnchorToken, quote, prefix string) (idx int, ok, ambiguous bool)

ResolveAnchorTokensLoose matches like ResolveAnchorTokens but tolerates renderer-side truncation: Glamour clips long code lines and ellipsises wide table cells, so a block's full text may be absent from rendered output even though its head is on screen. It binary-searches the longest head of the quote that still occurs (occurrence is monotone in head length) and accepts it when the head is either the whole quote or long enough to be distinctive. Ambiguity still degrades — the head must identify exactly one position.

func ResolveDecision

func ResolveDecision(path string, number int, decision, rationale, user string) error

ResolveDecision updates an existing decision log entry.

func ScaffoldSpec

func ScaffoldSpec(id, title, author, cycle, source string) string

ScaffoldSpec generates a new SPEC.md from the template.

func ScaffoldTriage

func ScaffoldTriage(id, title, priority, source, sourceRef, reportedBy string) string

ScaffoldTriage generates a new TRIAGE.md from the template.

func UpdateFrontmatter

func UpdateFrontmatter(content string, meta *SpecMeta) (string, error)

UpdateFrontmatter returns content with updated frontmatter, preserving the body. It also updates the 'updated' field to today's date.

func UpdateTriageFields added in v0.18.0

func UpdateTriageFields(path, title, priority, source, body string) error

UpdateTriageFields updates the mutable fields of a triage file and replaces the body, while preserving existing comments and immutable metadata.

func ValidSectionSlugs

func ValidSectionSlugs() []string

ValidSectionSlugs returns all valid section slugs from the spec template.

func WriteMeta

func WriteMeta(path string, meta *SpecMeta) error

WriteMeta updates the frontmatter in a file, preserving the body content.

func WriteTriageMeta added in v0.18.0

func WriteTriageMeta(path string, meta *TriageMeta) error

WriteTriageMeta updates triage frontmatter in a file, preserving the body.

Types

type AnchorMatch added in v0.35.0

type AnchorMatch struct {
	Found     bool
	Ambiguous bool
	Line      int
	Col       int
}

AnchorMatch locates a quote within section content. Ambiguous means the quote exists more than once and its prefix did not identify one occurrence.

func ResolveAnchor added in v0.35.0

func ResolveAnchor(sectionBody, quote, prefix string) AnchorMatch

ResolveAnchor finds quote within sectionBody. An ambiguous match is a graceful miss: callers degrade to the section rather than silently choosing the first occurrence.

type AnchorToken added in v0.35.0

type AnchorToken struct {
	Text string
	Line int
	Col  int
}

AnchorToken is one normalised word tagged with its original position.

func TokenizeAnchor added in v0.35.0

func TokenizeAnchor(text string) []AnchorToken

TokenizeAnchor tokenises text into normalised words with source positions. Normalisation folds case and drops every non-alphanumeric rune — interior ones included — so markdown inline markup (`n`/`p`, **bold**, smart quotes) tokenises identically on the source and rendered sides of a Glamour render.

type BlockRange added in v0.35.0

type BlockRange struct {
	StartLine int
	EndLine   int
}

BlockRange is one source block, expressed as [StartLine, EndLine). Blocks are the units the reader can quote: paragraphs, individual list items, individual table rows, fenced blocks, and blockquote paragraphs.

func BlockRanges added in v0.35.0

func BlockRanges(source string) []BlockRange

BlockRanges splits markdown into stable reader anchor units. This is a source-oriented scanner rather than a renderer concern; it deliberately keeps list items and table rows separate even without blank lines.

type BuildStep

type BuildStep struct {
	// Repo is the repository this step targets.
	Repo string `yaml:"repo"`

	// Description is a brief summary of what this step accomplishes.
	Description string `yaml:"description"`

	// Branch is the git branch name for this step (auto-generated if empty).
	Branch string `yaml:"branch,omitempty"`

	// PR is the pull request number once created.
	PR int `yaml:"pr,omitempty"`

	// StoryKey is the linked PM story key for this step (e.g. "PLAT-201"),
	// set when story sync is enabled so board analytics track build progress.
	StoryKey string `yaml:"story_key,omitempty"`

	// Status tracks the step's progress: pending, in-progress, complete, blocked.
	Status string `yaml:"status"`

	// BlockedReason explains why this step is blocked (when Status == "blocked").
	BlockedReason string `yaml:"blocked_reason,omitempty"`
}

BuildStep represents a single step in the build plan. Steps are a checklist of work items, not git-level PR stacking.

type DecisionEntry

type DecisionEntry struct {
	Number    int
	Question  string
	Options   string
	Decision  string
	Rationale string
	DecidedBy string
	Date      string
}

DecisionEntry represents a row in the decision log table.

func ParseDecisionLog

func ParseDecisionLog(content string) ([]DecisionEntry, error)

ParseDecisionLog extracts decision entries from markdown content.

func ParseDecisionLogFromFile

func ParseDecisionLogFromFile(path string) ([]DecisionEntry, error)

ParseDecisionLogFromFile reads a spec file and extracts the decision log.

type ReviewApproval

type ReviewApproval struct {
	Reviewer   string `yaml:"reviewer"`
	ApprovedAt string `yaml:"approved_at"`
}

ReviewApproval records a single reviewer's approval.

type ReviewState

type ReviewState struct {
	// RequestedAt is when the review was requested.
	RequestedAt string `yaml:"requested_at,omitempty"`

	// Reviewers is the list of users/roles who should review.
	Reviewers []string `yaml:"reviewers,omitempty"`

	// Approvals records who has approved and when.
	Approvals []ReviewApproval `yaml:"approvals,omitempty"`

	// Status is the overall review state: pending, approved, changes_requested.
	Status string `yaml:"status"`

	// Feedback contains reviewer feedback when changes are requested.
	Feedback string `yaml:"feedback,omitempty"`
}

ReviewState tracks plan review status for async approval workflow.

type Section

type Section struct {
	Slug      string // e.g., "problem_statement"
	Heading   string // e.g., "## 1. Problem Statement"
	Level     int    // heading level (2 = ##, 3 = ###)
	Owner     string // from <!-- owner: role --> marker, or "auto"
	Content   string // raw markdown content (excluding heading line)
	StartLine int    // line number in the source file (1-indexed)
	EndLine   int    // line number (exclusive)
}

Section represents a parsed markdown section with ownership.

func ExtractSections

func ExtractSections(content string) []Section

ExtractSections parses markdown content into sections.

func ExtractSectionsFromFile

func ExtractSectionsFromFile(path string) ([]Section, error)

ExtractSectionsFromFile reads a file and extracts sections.

func FindSection

func FindSection(sections []Section, slug string) *Section

FindSection returns the section with the given slug, or nil.

type SpecMeta

type SpecMeta struct {
	ID          string   `yaml:"id"`
	Title       string   `yaml:"title"`
	Status      string   `yaml:"status"`
	Version     string   `yaml:"version"`
	Author      string   `yaml:"author"`
	Cycle       string   `yaml:"cycle"`
	EpicKey     string   `yaml:"epic_key,omitempty"`
	Repos       []string `yaml:"repos,omitempty"`
	RevertCount int      `yaml:"revert_count"`
	Source      string   `yaml:"source,omitempty"`
	Created     string   `yaml:"created"`
	Updated     string   `yaml:"updated"`

	// StageEnteredAt records when the spec entered its current stage (RFC3339).
	// It drives the time-urgency gradient's dwell calculation and is stamped on
	// every stage transition (advance/revert/eject/resume). Unlike Updated it is
	// NOT reset by ordinary edits, so editing a spec does not reset its
	// staleness. Legacy specs without it fall back to Updated.
	StageEnteredAt string `yaml:"stage_entered_at,omitempty"`

	// Assignees are the people responsible for moving the spec at its current
	// stage. Drives personal dashboard scope (assignee-scoped stages). Empty
	// means the spec is unclaimed and surfaces to the whole owning role.
	Assignees []string `yaml:"assignees,omitempty"`

	// BlockedFrom records the stage a spec was in when it was ejected to
	// `blocked`, so the dashboard can role-scope the BLOCKED section and
	// `spec resume` can restore the stage without parsing the escape-hatch log.
	BlockedFrom string `yaml:"blocked_from,omitempty"`

	// Steps is the structured build plan.
	// Replaces unstructured §7.3 prose with authoritative step tracking.
	Steps []BuildStep `yaml:"steps,omitempty"`

	// Review tracks plan review state for the engineering stage.
	Review *ReviewState `yaml:"review,omitempty"`

	// FastTrack marks this as a fast-track bug fix (skips ceremony stages).
	FastTrack bool `yaml:"fast_track,omitempty"`
}

SpecMeta represents the YAML frontmatter of a SPEC.md file.

func ParseMeta

func ParseMeta(content string) (*SpecMeta, error)

ParseMeta parses YAML frontmatter from markdown content.

func ReadMeta

func ReadMeta(path string) (*SpecMeta, error)

ReadMeta reads and parses the YAML frontmatter from a markdown file.

func (*SpecMeta) AllStepsComplete

func (m *SpecMeta) AllStepsComplete() bool

AllStepsComplete returns true if all steps have status "complete".

func (*SpecMeta) CurrentStep

func (m *SpecMeta) CurrentStep() int

CurrentStep returns the index (0-based) of the first non-complete step, or -1 if all steps are complete or there are no steps.

func (*SpecMeta) HasAssignee added in v0.22.1

func (m *SpecMeta) HasAssignee(identity string) bool

HasAssignee reports whether identity (a user name or handle) is one of the spec's assignees. Matching is case-insensitive and tolerates a leading '@' on either side, mirroring how reviewers are matched elsewhere.

func (*SpecMeta) IsReviewApproved

func (m *SpecMeta) IsReviewApproved() bool

IsReviewApproved returns true if the review status is "approved".

func (*SpecMeta) IsReviewChangesRequested

func (m *SpecMeta) IsReviewChangesRequested() bool

IsReviewChangesRequested returns true if reviewer requested changes.

func (*SpecMeta) IsReviewPending

func (m *SpecMeta) IsReviewPending() bool

IsReviewPending returns true if review was requested but not yet completed.

func (*SpecMeta) StepsExist

func (m *SpecMeta) StepsExist() bool

StepsExist returns true if at least one step is defined.

type TriageComment added in v0.18.0

type TriageComment struct {
	Actor   string `yaml:"actor"`
	Message string `yaml:"message"`
	At      string `yaml:"at"` // RFC3339 timestamp
}

TriageComment is a single immutable entry in a triage item's history log.

type TriageMeta

type TriageMeta struct {
	ID         string          `yaml:"id"`
	Title      string          `yaml:"title"`
	Status     string          `yaml:"status"`
	Priority   string          `yaml:"priority"`
	Severity   string          `yaml:"severity,omitempty"`    // "urgent" or "normal" (default normal)
	LinkedSpec string          `yaml:"linked_spec,omitempty"` // SPEC-NNN if manually linked
	Source     string          `yaml:"source,omitempty"`
	SourceRef  string          `yaml:"source_ref,omitempty"`
	ReportedBy string          `yaml:"reported_by,omitempty"`
	Created    string          `yaml:"created"`
	ResolvedAt string          `yaml:"resolved_at,omitempty"` // set when closed/archived
	Comments   []TriageComment `yaml:"comments,omitempty"`    // append-only history log
}

TriageMeta represents the YAML frontmatter of a TRIAGE.md file.

func ParseTriageMeta

func ParseTriageMeta(content string) (*TriageMeta, error)

ParseTriageMeta parses triage YAML frontmatter.

func ReadTriageMeta

func ReadTriageMeta(path string) (*TriageMeta, error)

ReadTriageMeta reads and parses triage frontmatter.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL