spec

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package spec implements the topological pillar: cascading, directory-scoped spec bundles. Rules live next to the code they govern, inside .spectackle folders:

.spectackle/spec.md           root scope (repo-wide architecture rules)
<dir>/.spectackle/spec.md     rules scoped to <dir> and everything below

Resolution for a path is root -> ancestor chain top-down -> nearest dir. Deeper files extend the inherited set (union); they win only explicitly:

  • `overrides: [RULE-ID, ...]` in front matter removes inherited rules;
  • `inherits: false` drops everything inherited.

This mirrors how .gitignore/CLAUDE.md cascade, and it is what makes context loading token-efficient: the server materializes only the rules on the root->dir spine of the files in an impact radius, never the whole corpus.

Index

Constants

View Source
const ContextModeLogical = "logical"

ContextModeLogical is the only explicit context mode. The empty value keeps the ordinary physical/historical classification inferred by the server.

Variables

This section is empty.

Functions

func AppendIntent

func AppendIntent(ws workspace.Root, ctx, line string) error

func IntentDebrisMarker added in v0.8.5

func IntentDebrisMarker() string

IntentDebrisMarker exposes intentDebrisMarker so internal/lifecycle can assert its truncation marker still matches the text this package heals.

func RemoveIntent added in v0.10.0

func RemoveIntent(ws workspace.Root, ctx, recordID string) error

RemoveIntent drops recordID's line from `## intent`, the inverse AppendIntent's first-wins contract needs to stay honest across a refused archive (B-01KYS6ZKRQEHW finding 2).

AppendIntent runs BEFORE the archive edge's git merge can succeed, and a stranded closure compensates the item archived->done. Nothing removed the line, and because AppendIntent keys on the record ID and keeps the FIRST copy, the successful RETRY's note was then silently discarded: the living spec permanently recorded the note of the attempt that was REFUSED, as that record's outcome. Measured end to end — the retry merged and the spec still read the failed attempt's note.

The fix is not to make AppendIntent last-wins. First-wins is the right contract for a retry of a transition that DID land, and it is pinned by TestAppendIntentIsIdempotentPerRecord; AppendIntent is untouched here. What was wrong is that a refused attempt left a "first" behind at all.

Scoped to the `## intent` span for the same reason AppendIntent's heal is: an ID-shaped bullet in a rule's Rationale or in a whitelisted prose section keys identically, and reaching outside the invariant deletes genuine records. Removing an ID that is not listed is a no-op, not an error — the compensation path calls this for every refused archive, including the many whose AppendIntent was a no-op because the line was already there.

func RetireRule

func RetireRule(ws workspace.Root, c *Cascade, id string) (string, error)

RetireRule deletes a rule block from its spec bundle; the full text survives in the journal (written by the caller). Returns the file path.

func SetContextMode added in v0.10.0

func SetContextMode(ws workspace.Root, ctx, mode string) error

SetContextMode updates only the context-mode key in a spec bundle's YAML frontmatter. The Markdown body is spliced back byte-for-byte and the final rename is atomic, so the supported metadata write path cannot rewrite rule text, prose, or a partially-written spec after a crash.

func SpecRel

func SpecRel(ctx string) string

SpecRel returns the repo-relative spec bundle path for a context dir ("" = root).

Types

type AuthorReq

type AuthorReq struct {
	Dir       string                                    // context dir ("" = root)
	Stem      string                                    // ID stem, e.g. "CUDA-KRN"; default: stem of last rule in target file
	ForceID   string                                    // exact ID to use (replay); skips minting, still lints
	Mint      func(stem string, floor int) (int, error) // collision-free minter (swarm coord); nil = local scan
	Sentence  string                                    // composed EARS sentence
	Rationale string
	Applies   []string
}

AuthorReq describes one rule to persist.

type AuthorRes

type AuthorRes struct {
	ID   string
	Path string // repo-relative spec file path

	// Root is the absolute dir Path is relative to: the workspace root
	// AddRule/EditRule were called with, not necessarily the main checkout.
	// GitHub issue 27: a caller inside a linked git worktree got back a
	// bare "ok ID .spectackle/spec.md" that read as if it landed in the
	// worktree, when the write had actually landed in the enclosing main
	// checkout — the relative Path alone can't tell the two apart. Root
	// pins down the other half of that join so a caller (or a future
	// mcpserver reporting change) can print filepath.Join(Root, Path) and
	// never wonder which directory it is relative to.
	Root string

	Findings []ears.Finding
	Written  bool
}

AuthorRes reports the outcome.

func AddRule

func AddRule(ws workspace.Root, c *Cascade, req AuthorReq) (AuthorRes, error)

AddRule lints the sentence and, if error-free, appends it to the context dir's spec bundle (creating the file with front matter when needed) under the next free ID. Lint errors block the write; warnings do not.

func EditRule

func EditRule(ws workspace.Root, c *Cascade, id, sentence, rationale string, applies []string) (AuthorRes, error)

EditRule replaces the block of an existing rule in place: new sentence (lint-gated), rationale and/or applies list. Empty sentence keeps the old one; empty rationale keeps the old one; applies always replaces.

type Cascade

type Cascade struct {
	// contains filtered or unexported fields
}

Cascade resolves the applicable rules for paths and nodes.

func Load

func Load(root string) (*Cascade, error)

Load discovers and parses all spec bundles under root. Parse and lint findings do not fail the load; they are available via Findings().

func (*Cascade) All

func (c *Cascade) All() []SpecFile

All returns every loaded spec file, shallow before deep.

func (*Cascade) File

func (c *Cascade) File(dir string) (SpecFile, bool)

File returns the spec file scoping a context dir, if loaded.

func (*Cascade) Findings

func (c *Cascade) Findings() []ears.Finding

Findings returns all lint findings gathered while loading the cascade.

func (*Cascade) ForNode

func (c *Cascade) ForNode(id, file string) []ResolvedRule

ForNode returns the rules binding a graph node (SPX-SPC-007): rules whose applies list names the node ID come first — explicit bindings are the strongest signal — followed by the cascade rules of the node's file, deduplicated by rule ID.

func (*Cascade) ForPath

func (c *Cascade) ForPath(rel string) []ResolvedRule

ForPath returns the rules applicable to a repo-relative file path, in resolution order (root -> nearest dir). Overrides and inherits:false of deeper files are applied.

func (*Cascade) MaxNum

func (c *Cascade) MaxNum(stem string) int

MaxNum returns the highest number used for a stem across the cascade — the floor for collision-free minting.

func (*Cascade) NextID

func (c *Cascade) NextID(stem string) string

NextID returns the next free ID for a stem, scanning the whole cascade so the result can never collide locally (E006). Cross-worktree uniqueness needs AuthorReq.Mint (coord counters).

func (*Cascade) Rule

func (c *Cascade) Rule(id string) (ears.Rule, bool)

Rule looks up a rule by ID.

type ResolvedRule

type ResolvedRule struct {
	ears.Rule
	ScopeDir string // "." for root, else the context dir
}

ResolvedRule pairs a rule with the directory scope it came from.

type SpecFile

type SpecFile struct {
	Path        string // repo-relative path of the spec.md
	Dir         string // context dir it scopes ("" = root)
	Prefix      string
	ContextMode string
	Scope       []string
	Inherits    bool
	Overrides   []string
	Rules       []ears.Rule
	Sections    []ears.Section
}

SpecFile is one parsed spec bundle.

Jump to

Keyboard shortcuts

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