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
- func AppendIntent(ws workspace.Root, ctx, line string) error
- func IntentDebrisMarker() string
- func RemoveIntent(ws workspace.Root, ctx, recordID string) error
- func RetireRule(ws workspace.Root, c *Cascade, id string) (string, error)
- func SetContextMode(ws workspace.Root, ctx, mode string) error
- func SpecRel(ctx string) string
- type AuthorReq
- type AuthorRes
- type Cascade
- func (c *Cascade) All() []SpecFile
- func (c *Cascade) File(dir string) (SpecFile, bool)
- func (c *Cascade) Findings() []ears.Finding
- func (c *Cascade) ForNode(id, file string) []ResolvedRule
- func (c *Cascade) ForPath(rel string) []ResolvedRule
- func (c *Cascade) MaxNum(stem string) int
- func (c *Cascade) NextID(stem string) string
- func (c *Cascade) Rule(id string) (ears.Rule, bool)
- type ResolvedRule
- type SpecFile
Constants ¶
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 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
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 ¶
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
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.
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 ¶
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 ¶
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) 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 ¶
MaxNum returns the highest number used for a stem across the cascade — the floor for collision-free minting.
type ResolvedRule ¶
ResolvedRule pairs a rule with the directory scope it came from.