rule

package
v0.46.0 Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package rule parses, scaffolds, and indexes SpecScore Rule artifacts.

A Rule is the *normative* artifact kind: one sentence an agent or human MUST or MUST NEVER do, with the scope it binds, the reason it exists, the sources that produced it, and the control that enforces it. It is deliberately the smallest kind in the spec tree — a Lesson explains a process gap, a Decision explains a choice, an Idea explains a direction; a Rule is the single transferable sentence that survives all three and can be handed to a fresh agent with no other context.

Rules exist because durable operating knowledge accumulated in per-agent memory files, which do not transfer: a new session, a new machine, or a new runtime starts blind. A Rule is reviewable, greppable, scoped, and — at the Enforced and Automated tiers — attached to a named control.

Two forms, one entity

An INLINE rule is exactly one row in spec/rules/README.md and nothing else. Most rules are one sentence; giving each of them a directory would be ceremony that discourages recording them at all.

A DETAILED rule keeps the identical index row and adds spec/rules/<slug>/README.md carrying the reason, worked compliant and violating examples, agent instructions, exceptions, and supersession. Its index row's identity cell is a link, so the index alone says which rules have more to read.

The index row is the source of truth for every field it carries. A detail document repeats those fields in its header for readability, and lint (R-011) requires them to agree — with --fix rewriting the document from the row, never the reverse. That is what keeps two representations of one rule from drifting into two different rules.

Index

Constants

View Source
const (
	IndexHeaderRow    = "| Rule | Status | Scope | Enforcement | Control | Sources | Statement |"
	IndexSeparatorRow = "|---|---|---|---|---|---|---|"
)

IndexHeaderRow and IndexSeparatorRow are the exact canonical table header lines, compared byte-for-byte so a hand-edited index that dropped a column is reported rather than silently reinterpreted.

View Source
const (
	ScopeFleet   = "fleet"   // every repository, every product.
	ScopeProduct = "product" // one product by name, e.g. product:sneat.
	ScopeRepo    = "repo"    // one repository, e.g. repo:specscore/specscore-cli.
	ScopePath    = "path"    // a glob over paths, e.g. path:**/*.go.
)

Scope kinds. A Rule binds everywhere its Scope list says it binds, and nowhere else — the property that lets `rule list --applies-to <path>` answer "which rules apply to what I am about to touch" without a human reading them all.

View Source
const (
	SourceLesson   = "lesson"
	SourceDecision = "decision"
	SourceIdea     = "idea"
	SourceURL      = "url" // a free http(s) reference; never resolved locally
)

Source kinds. A Rule's **Sources:** list names the artifacts that produced it, so a reader can always get back to *why* — the Lesson whose process gap it closes, the Decision that chose it, the Idea that proposed it — without the Rule itself having to restate any of them.

View Source
const DefaultSkillsPath = "ai/skills"

DefaultSkillsPath is the repository-relative directory scanned for skills when specscore.yaml declares no override.

View Source
const FormatURL = "https://specscore.md/rule-specification"

FormatURL is the canonical spec URL for the Rule detail document. It is carried verbatim in both the frontmatter `format:` field and the adherence-footer line, per the artifact-frontmatter-convention.

View Source
const IndexEmptyPlaceholder = "_No rules recorded yet._"

IndexEmptyPlaceholder is written in place of the table body when a project has no Rules yet.

View Source
const IndexFormatURL = "https://specscore.md/rules-index-specification"

IndexFormatURL is the canonical spec URL for the rules index (spec/rules/README.md) — which, unusually for an index, is also the primary artifact: an inline rule lives nowhere else.

View Source
const IndexHeading = "## Rules"

IndexHeading is the H2 the rules table lives under.

View Source
const LessonPromotesToField = "Promotes To"

LessonPromotesToField is the bold field name written on the Lesson.

View Source
const Sentinel = "—"

Sentinel is the em-dash placeholder every optional field carries when it has no value. An empty value is a violation; the sentinel is the explicit "nothing here" a reader can trust (mirroring the canonical Lesson's relation fields).

View Source
const SkillRulesHeading = "## Rules"

SkillRulesHeading is the H2 a skill uses to declare the rules that bind it.

Variables

View Source
var DetailFields = []string{
	"Status",
	"Date",
	"Owner",
	"Statement",
	"Scope",
	"Enforcement",
	"Control",
	"Sources",
	"Why",
	"Exceptions",
	"Supersedes",
	"Superseded By",
}

DetailFields is the closed, ordered set of bold metadata fields every detail document MUST declare. The mirrored block comes first, then the fields the document alone owns.

View Source
var DetailSections = []string{"Instructions", "Examples", "Open Questions"}

DetailSections is the closed, ordered set of H2 headings a detail document MUST carry. Instructions is what an agent acts on; Examples is what stops "never mock a backend" from being read three different ways.

View Source
var EnforcementTiers = []string{"Stated", "Enforced", "Automated"}

EnforcementTiers is the closed, ordered **Enforcement:** vocabulary.

  • Stated — an agent or human is told; nothing refuses.
  • Enforced — a named control refuses (a wb verb, a hook profile, a CI check, a review probe).
  • Automated — a named control both refuses and repairs, with no human in the loop.

Enforced and Automated MUST name a control; Stated MUST NOT be required to, because the whole point of that tier is that no control exists yet.

View Source
var ExampleSubsections = []string{"Compliant", "Violation"}

ExampleSubsections are the H3 headings required inside `## Examples`. Both are required: a rule with only a compliant example teaches the happy path and leaves the reader guessing at the boundary the rule actually draws.

View Source
var MirroredFields = []string{"Status", "Statement", "Scope", "Enforcement", "Control", "Sources"}

MirroredFields are the header fields a detail document repeats from its index row, in canonical order. The row is authoritative for every one of them.

View Source
var Statuses = []string{"Draft", "Active", "Superseded"}

Statuses is the closed, ordered Rule **Status:** vocabulary.

  • Draft — written down, not yet binding.
  • Active — binding within its declared Scope.
  • Superseded — replaced by another Rule, named in **Superseded By:**.

Functions

func ApplyFieldEdits

func ApplyFieldEdits(content []byte, edits []FieldEdit) ([]byte, error)

ApplyFieldEdits rewrites the named bold fields of a rule detail document in place, preserving every other byte — comments, worked examples, hand-written Open Questions, section order, trailing whitespace. Nothing is regenerated from a template, because an update that reformatted the whole document would silently discard an author's examples and make `rule update` unsafe to run on a reviewed rule.

When **Status:** is edited, the frontmatter `status:` mirror is rewritten with it, so the status-mirror lint rule can never be broken by an update.

A named field absent from the document is inserted directly after the last present field that precedes it in DetailFields order; that keeps the canonical ordering intact for a document written before the field existed.

func ClearLessonPromotesTo

func ClearLessonPromotesTo(lessonPath string) error

ClearLessonPromotesTo removes the Lesson's `**Promotes To:**` field entirely. Writing the em-dash sentinel instead would leave a dangling "promoted to nothing" line that reads as a half-finished promotion.

func DetailPath

func DetailPath(rulesDir, slug string) string

DetailPath returns the detail-document path for a slug.

func DetailsBySlug

func DetailsBySlug(rulesDir string) (map[string]*Detail, error)

DetailsBySlug is DiscoverDetails keyed by slug.

func EnforcementList

func EnforcementList() string

EnforcementList renders the canonical tier vocabulary for an error message.

func EnsureIndex

func EnsureIndex(rulesDir string) error

EnsureIndex writes the lint-clean index stub when spec/rules/README.md does not exist. An existing file is left byte-for-byte untouched.

func IndexContent

func IndexContent() string

IndexContent is the lint-clean stub written when spec/rules/README.md does not exist yet.

func IndexPath

func IndexPath(rulesDir string) string

IndexPath returns the rules index path.

func IsEnforcement

func IsEnforcement(value string) bool

IsEnforcement reports whether value is one of the canonical tiers.

func IsStatus

func IsStatus(value string) bool

IsStatus reports whether value is one of the canonical Rule statuses.

func LessonSources

func LessonSources(sources []SourceRef) []string

LessonSources returns just the lesson slugs a source list names, in order.

func MergeSources

func MergeSources(current, add, remove []string) ([]string, error)

MergeSources applies --add-source / --remove-source to an existing list, preserving order, rejecting duplicates and unknown removals. Both inputs are validated as source references first, so a typo can never silently no-op.

func MirroredValuesOf

func MirroredValuesOf(row Row) map[string]string

MirroredValuesOf projects a row into the same shape, for comparison.

func ParseEnforcement

func ParseEnforcement(value string) (string, bool)

ParseEnforcement resolves a case-insensitive tier name to its canonical spelling.

func ParsePromotesTo

func ParsePromotesTo(value string) (slug string, ok bool, err error)

ParsePromotesTo extracts the rule slug from a Lesson's `**Promotes To:**` value. It returns ok=false for the empty value or the em-dash sentinel, and an error for a value that is present but not a `rule:<slug>` reference.

func ParseStatus

func ParseStatus(value string) (string, bool)

ParseStatus resolves a case-insensitive status name to its canonical spelling.

func PromotesToRef

func PromotesToRef(slug string) string

PromotesToRef is the value a Lesson carries in its optional `**Promotes To:**` field when it has been promoted into a Rule.

func RemoveRow

func RemoveRow(rulesDir, slug string) error

RemoveRow deletes slug's row, restoring the empty placeholder when the table becomes empty. A missing row or a missing index file is a no-op, not an error: `rule delete` must still finish on a tree whose index had drifted.

func RepairHint added in v0.39.1

func RepairHint(m MalformedRow) string

RepairHint names the repair that actually applies to a malformed row.

`--fix` can only escape a surplus `|` in a Statement, so telling a reader to run it on a row it cannot touch sends them in a circle — which is exactly what an empty identity cell used to do: every verb refused, and the refusal recommended a fixer that would not help.

func RequiresControl

func RequiresControl(tier string) bool

RequiresControl reports whether an enforcement tier obliges the Rule to name a control. Stated is exactly the tier that does not.

func RulesDir

func RulesDir(projectRoot string) string

RulesDir returns the rules directory for a project root (the directory that contains spec/, not spec/ itself).

func ScaffoldDetail

func ScaffoldDetail(opts Options) ([]byte, error)

ScaffoldDetail returns a lint-clean rule detail document: the artifact-frontmatter-convention frontmatter, the `# Rule:` title, the twelve ordered bold fields (the first six mirroring the index row), Instructions, paired Examples, Open Questions, and the adherence footer.

func ScopesMatch

func ScopesMatch(scopes []Scope, p string) bool

ScopesMatch reports whether any scope in the list covers p.

func SetLessonPromotesTo

func SetLessonPromotesTo(lessonPath, ruleSlug string) error

SetLessonPromotesTo writes (or rewrites) the Lesson's `**Promotes To:**` field to point at ruleSlug. It preserves every other byte of the Lesson.

func SetSkillRules

func SetSkillRules(skillPath string, slugs []string) error

SetSkillRules rewrites a skill's `## Rules` section to list exactly slugs, appending the section when absent. It preserves every other byte of the skill, because a skill file is hand-written instruction text that a rule verb has no business reformatting.

func SkillsByName

func SkillsByName(skillsDir string) (map[string]*Skill, error)

SkillsByName is DiscoverSkills keyed by skill name.

func SkillsDir

func SkillsDir(projectRoot, configuredPath string) string

SkillsDir resolves the skills directory for a project root, honouring the optional `rules.skills_path` override.

func StatusList

func StatusList() string

StatusList renders the canonical status vocabulary for an error message.

func TitleCaseFromSlug

func TitleCaseFromSlug(slug string) string

TitleCaseFromSlug turns "never-mock-backends" into "Never Mock Backends".

func UpsertRow

func UpsertRow(rulesDir string, row Row) error

UpsertRow inserts or replaces exactly the row owned by row.Slug, leaving every other row byte-identical. It is deliberately narrower than a full rewrite so a create or update verb has a bounded declared write set.

func ValidateSlug

func ValidateSlug(slug string) error

ValidateSlug returns nil when slug is a lowercase, hyphen-separated, URL-safe identifier with no `/`.

func WriteFileAtomic

func WriteFileAtomic(path string, data []byte) error

WriteFileAtomic publishes data to path through a same-directory temp file and a rename, then fsyncs the directory, so a crash mid-write can never leave a half-written index behind.

func WriteIndexRows

func WriteIndexRows(path string, rows []Row, preserved []MalformedRow) error

WriteIndexRows replaces the `## Rules` table with rows, preserving the prologue before the heading and everything from the next H2 onward (typically `## Open Questions`). Row data is never derived from anything but the rows handed in: the index is the source of truth, so a regeneration that re-read the detail documents could overwrite an author's edit with a stale mirror.

preserved holds row-like lines the contract could not parse. They are re-emitted verbatim, after the sorted rows, so that no write path in this package can reduce the rule set. A caller that drops them is deleting a rule it never managed to read — which is exactly the failure this signature exists to make impossible to write by accident.

Types

type Detail

type Detail struct {
	Path string
	Slug string

	HasRuleTitle bool
	TitleLine    int
	Title        string

	Status     string
	StatusLine int

	Date     string
	DateLine int

	Owner     string
	OwnerLine int

	Statement     string
	StatementLine int

	// ScopesRaw holds every value written on a **Scope:** line, comma-split, in
	// source order. Both repeated lines and one comma-separated line parse the
	// same; the CLI always writes one line.
	ScopesRaw []string
	ScopeText string
	ScopeLine int

	Enforcement     string
	EnforcementLine int

	Control     string
	ControlLine int

	SourcesRaw  []string
	SourcesText string
	SourcesLine int

	Why     string
	WhyLine int

	Exceptions     string
	ExceptionsLine int

	Supersedes     string
	SupersedesLine int

	SupersededBy     string
	SupersededByLine int

	FrontmatterStatus     string
	FrontmatterStatusLine int

	// FieldCounts records how many times each bold field name appeared, so a
	// duplicated field is reportable rather than a silent last-one-wins.
	FieldCounts map[string]int
	// FieldOrder lists the canonical fields in the order they were found.
	FieldOrder []string

	SectionLines    map[string]int
	SubsectionLines map[string]int

	// SkillRefs are the `skill:<name>` references the document's body carries,
	// deduplicated and sorted. They are the rule half of the rule<->skill pair.
	SkillRefs []string
}

Detail is a parsed rule detail document at spec/rules/<slug>/README.md.

Every `<Field>Line` is the 1-based source line of that bold field, or 0 when the field is absent — the same shape pkg/lesson uses, so a lint violation can point at a line rather than a whole file.

func DiscoverDetails

func DiscoverDetails(rulesDir string) ([]*Detail, error)

DiscoverDetails parses every rule detail document under rulesDir, sorted by slug. A missing directory is not an error: it yields an empty set, so every read verb works in a repository that has recorded no rule yet.

func ParseDetail

func ParseDetail(path string) (*Detail, error)

ParseDetail reads a candidate rule detail document. It returns a populated Detail even when the file is not actually one (HasRuleTitle == false), so callers can tell "not a rule document" from "malformed rule document".

func (*Detail) HasControl

func (d *Detail) HasControl() bool

HasControl reports whether the document names a real control.

func (*Detail) HasSection

func (d *Detail) HasSection(title string) bool

HasSection reports whether title is present as an H2 heading.

func (*Detail) MirroredValues

func (d *Detail) MirroredValues() map[string]string

MirroredValues projects the document's copy of the row-owned fields, so the mirror check compares like with like.

func (*Detail) MissingExampleSubsections

func (d *Detail) MissingExampleSubsections() []string

MissingExampleSubsections returns the required H3 headings absent from `## Examples`.

func (*Detail) MissingSections

func (d *Detail) MissingSections() []string

MissingSections returns the required H2 headings absent from the body.

func (*Detail) Scopes

func (d *Detail) Scopes() ([]Scope, error)

Scopes parses the raw scope list.

func (*Detail) Sources

func (d *Detail) Sources() ([]SourceRef, error)

Sources parses the raw source list.

type FieldEdit

type FieldEdit struct {
	Name  string
	Value string
}

FieldEdit is one in-place bold-field rewrite of a detail document. Value is written verbatim after `**<Name>:** `, so callers normalize it first.

type IndexReport

type IndexReport struct {
	Rows []Row
	// HeaderSeen is true when the canonical header + separator pair was found.
	HeaderSeen bool
	// Malformed carries every row-like line the contract cannot represent,
	// verbatim, so no writer has to guess at what it would be discarding.
	Malformed []MalformedRow
	// Duplicates lists slugs that appear in more than one row.
	Duplicates []string
}

IndexReport is everything a reader or a linter needs about the index file in one pass.

func ReadIndex

func ReadIndex(path string) (IndexReport, error)

ReadIndex scans the canonical `## Rules` table.

func (IndexReport) BySlug

func (rep IndexReport) BySlug() map[string]Row

BySlug indexes the report's rows.

func (IndexReport) HasMalformed

func (rep IndexReport) HasMalformed() bool

HasMalformed reports whether the index carries unparseable row-like content.

func (IndexReport) MalformedExcept

func (rep IndexReport) MalformedExcept(slugs ...string) []MalformedRow

MalformedExcept returns the malformed rows that are NOT one of the named slugs. A verb editing `x` may proceed over a broken row whose identity cell reads `x` — it is about to replace that row — but must not touch the index while some other rule's row is broken.

func (IndexReport) MalformedLines

func (rep IndexReport) MalformedLines() []int

MalformedLines returns the 1-based source lines of the unparseable content.

func (IndexReport) Slugs

func (rep IndexReport) Slugs() []string

Slugs returns every row slug, sorted and deduplicated.

type MalformedRow

type MalformedRow struct {
	// Line is the 1-based source line in the index.
	Line int
	// Text is the line exactly as written, including its leading pipe.
	Text string
	// SlugHint is the identity cell's slug when that much parsed, so a caller
	// can tell "the row I am editing is the broken one" from "I am about to
	// step on someone else's broken row".
	SlugHint string
	// Reason explains, in one clause, why the line did not parse.
	Reason string
}

MalformedRow is a row-like line the seven-column contract cannot represent.

Text is kept verbatim, and every writer in this package re-emits it unchanged. That is the whole point: the most ordinary authoring slip there is — an unescaped `|` pasted into a Statement — must never cause the rule to disappear. A kind that exists so operating knowledge stops evaporating cannot have a path where a benign command evaporates one.

func (MalformedRow) Repair

func (m MalformedRow) Repair() (Row, bool)

Repair attempts the one repair that is unambiguous: a row with SURPLUS cells because the Statement — the last column, and the only free-text one after Sources — carried an unescaped `|`. The surplus cells are rejoined with their pipe restored and then escaped.

It is only safe because the four columns between the identity cell and the Statement have constrained grammars. All four must validate before the tail is rejoined, which is what rules out the other reading — a pipe inside Control, where the same line shape would otherwise be silently reinterpreted. Anything else is reported rather than guessed at.

type Options

type Options struct {
	Slug        string
	Title       string   // detail only; defaults to a title-cased slug
	Owner       string   // detail only; defaults to "unknown"
	Date        string   // detail only; ISO-8601, defaults to today's UTC date
	Status      string   // defaults to Draft
	Statement   string   // defaults to a TODO prompt
	Scopes      []string // defaults to ["fleet"]
	Enforcement string   // defaults to Stated
	Control     string   // defaults to the em-dash sentinel
	Sources     []string // defaults to the em-dash sentinel

	// Detail-only fields.
	Why          string   // defaults to a TODO prompt
	Exceptions   string   // defaults to "none"
	Supersedes   string   // defaults to the em-dash sentinel
	Instructions string   // defaults to a TODO prompt
	Compliant    string   // a worked compliant example
	Violation    string   // a worked violating example
	Skills       []string // `skill:<name>` references to record in Instructions
}

Options carries everything both forms of a rule need. Only Slug is mandatory: a rule recorded under time pressure with nothing but a slug is still a lint-clean inline row that a later `rule update` can sharpen, which is the whole reason the kind exists.

func (*Options) Normalize

func (o *Options) Normalize() error

Normalize fills every unset field with its default and validates the closed vocabularies. It is shared by the row builder and the detail scaffolder, so the two forms of one rule cannot drift apart at creation time.

func (Options) Row

func (o Options) Row(linked bool) Row

Row projects normalized options into the canonical index row.

type Row

type Row struct {
	Slug string
	// Linked is true when the identity cell is a Markdown link, which is the
	// index's own statement that spec/rules/<slug>/README.md exists.
	Linked      bool
	Status      string
	Scope       string // raw, comma-separated
	Enforcement string
	Control     string
	Sources     string // raw, comma-separated
	Statement   string
	// Line is the 1-based source line of the row, for violation reporting.
	Line int
}

Row is one rule as the index records it — the whole of an inline rule, and the authoritative half of a detailed one.

func NewRow

func NewRow(slug string, linked bool, status, statement string, scopes []string, enforcement, control string, sources []string) Row

NewRow builds a canonical row from already-normalized values.

func ResolveRow

func ResolveRow(rulesDir, slug string) (Row, error)

ResolveRow returns the index row for slug, or an exit-3 NotFound error naming it. The index is the source of truth, so this — not a directory probe — is how every verb resolves a rule.

func RowFromDetail

func RowFromDetail(d *Detail) Row

RowFromDetail projects a detail document back into its canonical row. It is used only to repair an index that lost a row for an existing document; the row remains authoritative everywhere else.

func (r Row) DetailLink() string

DetailLink is the identity cell's link target for a detailed rule.

func (Row) Detailed

func (r Row) Detailed() bool

Detailed reports whether this rule is expected to have a detail document.

func (Row) Equals

func (r Row) Equals(o Row) bool

Equals compares two rows on every field but their source line.

func (Row) HasControl

func (r Row) HasControl() bool

HasControl reports whether the row names a real control.

func (Row) Render

func (r Row) Render() string

Render renders the row as one Markdown table line.

func (Row) ScopeList

func (r Row) ScopeList() []string

ScopeList parses the row's scope cell.

func (Row) SourceList

func (r Row) SourceList() []string

SourceList parses the row's sources cell.

type Scope

type Scope struct {
	Kind  string // one of the Scope* constants
	Value string // "" for fleet; the product name, owner/repo, or glob otherwise
	Raw   string // the value exactly as written in the artifact
}

Scope is one parsed entry of a Rule's **Scope:** list.

func ParseScope

func ParseScope(raw string) (Scope, error)

ParseScope parses one raw scope token. It never guesses: an unprefixed token other than the bare `fleet` keyword is an error rather than a silently accepted product name, because a mis-scoped Rule is worse than a rejected one — it binds work it was never meant to bind.

func ParseScopes

func ParseScopes(raws []string) ([]Scope, error)

ParseScopes parses every entry of a scope list, reporting the first error.

func (Scope) Matches

func (s Scope) Matches(p string) bool

Matches reports whether this scope covers the given path.

fleet            matches everything.
path:<glob>      doublestar match against the slash-normalized path and
                 against every trailing suffix of it, so a repo-relative
                 pattern still matches an absolute path a caller holds.
                 Deliberately generous: `path:cli/**` also matches
                 `vendor/x/cli/y.go`.
product:<name>   the name appears as a whole path segment.
repo:<owner>/<n> `<owner>` and `<n>` appear as CONSECUTIVE whole path
                 segments. A bare `<n>` never matches.

The repo rule is the strict one on purpose. Matching a bare repository name would make every rule scoped to a repo called `docs`, `api`, `web` or `cli` bind every path in the fleet that happens to contain that directory — and a mis-scoped rule that binds work it was never meant to bind is worse than one that fails to match, because nobody goes looking for it.

Callers that know the repository or product for certain should filter on `--scope` rather than inferring it from a path.

func (Scope) String

func (s Scope) String() string

String renders a Scope back to its canonical wire form.

type Skill

type Skill struct {
	Name string // the containing directory name, which is also the skill name
	Path string // absolute path to SKILL.md
	// RuleRefs are the `rule:<slug>` references listed under `## Rules`,
	// deduplicated and sorted.
	RuleRefs []string
	// RulesHeadingLine is the 1-based line of `## Rules`, or 0 when absent.
	RulesHeadingLine int
}

Skill is a discovered agent skill and the rule slugs it declares.

func DiscoverSkills

func DiscoverSkills(skillsDir string) ([]*Skill, error)

DiscoverSkills reads every <skillsDir>/<name>/SKILL.md. A missing directory is not an error: a repository with no skills simply has no pairs to check.

type SourceRef

type SourceRef struct {
	Kind  string // one of the Source* constants
	Value string // the slug, decision id, or full URL
	Raw   string // the entry exactly as written
}

SourceRef is one parsed entry of a Rule's **Sources:** list.

func ParseSource

func ParseSource(raw string) (SourceRef, error)

ParseSource parses one raw source token. An `http://` or `https://` prefix is a free URL; everything else MUST carry an explicit `<kind>:` prefix, so a bare word is rejected rather than silently filed under a guessed kind.

func ParseSources

func ParseSources(raws []string) ([]SourceRef, error)

ParseSources parses every entry of a source list, reporting the first error.

func (SourceRef) String

func (s SourceRef) String() string

String renders a SourceRef back to its canonical wire form.

Jump to

Keyboard shortcuts

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