canaryscan

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

CANARY: REQ=CP-268; FEATURE="MermaidRefs"; ASPECT=Engine; STATUS=TESTED; TEST=TestCANARY_CBIN_202_ExtractDiagramRefs; UPDATED=2026-08-28

CANARY: REQ=CP-274; FEATURE="MigrateNotes"; ASPECT=Engine; STATUS=TESTED; TEST=TestCANARY_CBIN_301_ExtractMigrateNotes; UPDATED=2026-08-29

CANARY: REQ=CP-277; FEATURE="StalenessConfig"; ASPECT=Engine; STATUS=TESTED; TEST=TestCANARY_CBIN_304_StaleDaysFromConfig,TestCANARY_CBIN_304_UpdateStaleV2IDs,TestCANARY_CBIN_304_UpdateStaleAddsMissingUpdated,TestCANARY_CBIN_304_RunReportsActualRewriteCount; UPDATED=2026-08-29

Index

Constants

View Source
const DefaultStaleDays = 30

DefaultStaleDays is the fallback staleness window (in days) when neither Config.StaleDays nor .canary/project.yaml's verification.staleness_days is set.

Variables

This section is empty.

Functions

func AnnotateSources

func AnnotateSources(rep *Report, reg *sources.Registry)

AnnotateSources stamps each requirement with its source name and ticket URL.

func DefaultSkipRegex

func DefaultSkipRegex() *regexp.Regexp

DefaultSkipRegex returns the default skip path regex.

func LoadCanaryIgnore

func LoadCanaryIgnore(root string) (*ignore.GitIgnore, error)

LoadCanaryIgnore loads .canaryignore from root. Returns nil if file missing.

func MarshalSortedMap

func MarshalSortedMap(m map[string]int) ([]byte, error)

MarshalSortedMap ensures deterministic JSON object key order for map[string]int.

func RefTimeFromEnv

func RefTimeFromEnv() time.Time

RefTimeFromEnv returns time from CANARY_TEST_TIMESTAMP (RFC3339) for tests; zero if unset or invalid.

func Run

func Run(cfg Config, stdout, stderr io.Writer) (exitCode int)

Run runs the full scan pipeline: load config, scan, optional update-stale, write outputs, verify, strict. It writes to stdout/stderr via the given writers (use os.Stdout, os.Stderr from CLI). Returns exit code: 0 success, 2 verify/staleness failure, 3 parse/IO error.

func RunFromArgs

func RunFromArgs(root, out, csv, verifyPath, skipExpr string, strict, updateStale, projectOnly bool, staleDays int) int

RunFromArgs is a convenience that builds Config from flag-like args and runs with os.Stdout/os.Stderr. Used by tools/canary main. skipExpr is the regex string; if empty, default is used. staleDays overrides the staleness window in days; 0 means "use config/default".

func ScanSummaryLine

func ScanSummaryLine(rep Report) string

ScanSummaryLine returns a single parseable line for rep so agents get metrics without reading status.json.

func Stale

func Stale(rep Report, maxAge time.Duration, refTime time.Time) []string

Stale returns diagnostics for TESTED/BENCHED tokens older than maxAge. If refTime is zero, time.Now().UTC() is used.

func UpdateStaleTokens

func UpdateStaleTokens(root string, skip *regexp.Regexp, staleDiags []string, ignorePatterns *ignore.GitIgnore) (updatedFiles map[string]bool, tokenCount int, err error)

UpdateStaleTokens rewrites UPDATED in source files for tokens matching staleDiags REQ IDs. Only updates TESTED/BENCHED tokens. Existing UPDATED=YYYY-MM-DD values are rewritten to today; a token line missing UPDATED= entirely gains one. Returns the set of files that were modified and the number of individual token lines actually rewritten (which may differ from len(staleDiags): several diags can share one REQ, or a REQ can span multiple physical token lines/files).

ignorePatterns (from LoadCanaryIgnore) is honored the same way Scan honors it: relative path matched first, dirs skipped via SkipDir; nil means no .canaryignore patterns apply. This keeps the rewrite walk in sync with the read walk that produced staleDiags in the first place — without it, a file excluded from scanning by .canaryignore could still be silently mutated by --update-stale.

func VerifyClaims

func VerifyClaims(rep Report, gapPath string, reg *sources.Registry) []string

VerifyClaims reads the GAP file and returns diagnostics for claimed-but-not- TESTED/BENCHED requirements. Claims are lines like "✅ <ID>" where <ID> matches any configured source key; a nil registry means the default (CBIN). CANARY: REQ=CP-267; FEATURE="TicketSources"; ASPECT=Engine; STATUS=IMPL; TEST=TestCANARY_CBIN_201_VerifyClaimsTicketSource; UPDATED=2026-08-28

func WriteCSV

func WriteCSV(path string, rep Report) error

WriteCSV writes rep to path as CSV.

func WriteJSON

func WriteJSON(path string, rep Report) error

WriteJSON writes rep to path as JSON.

Types

type Config

type Config struct {
	Root        string
	Out         string
	CSV         string
	VerifyPath  string
	Strict      bool
	SkipRegex   *regexp.Regexp
	UpdateStale bool
	ProjectOnly bool
	// StaleDays overrides the staleness window (in days) used by --strict and
	// --update-stale. When <= 0, the effective threshold falls back to
	// .canary/project.yaml's verification.staleness_days, then to 30 days.
	StaleDays int
}

Config holds scanner run options. Used by both the standalone tools/canary binary and the canary scan CLI subcommand so scan works from any CWD.

type DiagramRef

type DiagramRef struct {
	ReqID string
	File  string
	Line  int // 1-based
}

DiagramRef records one requirement-ID mention inside a mermaid diagram.

func ExtractDiagramRefs

func ExtractDiagramRefs(relPath, content string, reg *sources.Registry) []DiagramRef

ExtractDiagramRefs finds requirement IDs inside ```mermaid fenced blocks. relPath ending in .mmd is treated as a whole-file mermaid diagram (no fences required). IDs are normalized through reg (flatfile padding); reg nil means sources.Default() (the legacy CBIN series).

func ScanDiagramRefs

func ScanDiagramRefs(root string, skip *regexp.Regexp, reg *sources.Registry, ignorePatterns *ignore.GitIgnore) ([]DiagramRef, error)

ScanDiagramRefs walks root for .md/.markdown/.mmd files and extracts all requirement-ID mentions inside mermaid diagrams. Paths in the result are root-relative with forward slashes. ignorePatterns (from LoadCanaryIgnore) is honored the same way Scan honors it: relative path matched first, dirs skipped via SkipDir; nil means no .canaryignore patterns apply.

type Feature

type Feature struct {
	Feature string   `json:"feature"`
	Aspect  string   `json:"aspect"`
	Status  string   `json:"status"`
	Files   []string `json:"files"`
	Tests   []string `json:"tests"`
	Benches []string `json:"benches"`
	Owner   string   `json:"owner,omitempty"`
	Updated string   `json:"updated"`
}

Feature is one feature/aspect/status aggregation.

type MigrateNote added in v0.3.0

type MigrateNote struct {
	File   string   `json:"file"`
	Line   int      `json:"line"` // 1-based
	Text   string   `json:"text"`
	ReqIDs []string `json:"req_ids,omitempty"` // registry-pattern matches inside Text, normalized
}

MigrateNote is one CANARY:MIGRATE guidance comment.

func ExtractMigrateNotes added in v0.3.0

func ExtractMigrateNotes(relPath, content string, reg *sources.Registry) []MigrateNote

ExtractMigrateNotes finds CANARY:MIGRATE guidance comments in content. relPath is recorded verbatim on each note. reg nil means sources.Default() (the legacy CBIN series). A MIGRATE line is free text, not a KV token, and is never fed to parseKV — this is what keeps a MIGRATE line from ever aborting a scan.

func ScanMigrateNotes added in v0.3.0

func ScanMigrateNotes(root string, skip *regexp.Regexp, ignorePatterns *ignore.GitIgnore, reg *sources.Registry) ([]MigrateNote, error)

ScanMigrateNotes walks root and extracts CANARY:MIGRATE guidance notes from every non-binary file the scanner would otherwise read. Paths in the result are root-relative with forward slashes. ignorePatterns (from LoadCanaryIgnore) is honored the same way Scan honors it: relative path matched first, dirs skipped via SkipDir; nil means no .canaryignore patterns apply. Files larger than 1MB are skipped.

type ProjectConfig

type ProjectConfig struct {
	Project struct {
		Name        string `yaml:"name"`
		Description string `yaml:"description"`
	} `yaml:"project"`
	Requirements struct {
		IDPattern string `yaml:"id_pattern"`
	} `yaml:"requirements"`
	Scanner struct {
		ExcludePaths []string `yaml:"exclude_paths"`
	} `yaml:"scanner"`
	Verification struct {
		StalenessDays int `yaml:"staleness_days"`
	} `yaml:"verification"`
}

ProjectConfig is the .canary/project.yaml shape.

func LoadProjectConfig

func LoadProjectConfig(root string) (*ProjectConfig, error)

LoadProjectConfig loads .canary/project.yaml from root. Returns nil if file missing.

type Report

type Report struct {
	GeneratedAt    string        `json:"generated_at"`
	Requirements   []Requirement `json:"requirements"`
	Summary        Summary       `json:"summary"`
	MigrationNotes []MigrateNote `json:"migration_notes,omitempty"`
}

Report is the scan output (JSON/CSV shape).

func Scan

func Scan(root string, skip *regexp.Regexp, projectFilter *regexp.Regexp, ignorePatterns *ignore.GitIgnore) (Report, error)

Scan walks root and returns a Report. skip, projectFilter, and ignorePatterns may be nil.

type Requirement

type Requirement struct {
	ID        string    `json:"id"`
	Source    string    `json:"source,omitempty"`
	TicketURL string    `json:"ticket_url,omitempty"`
	Diagrams  []string  `json:"diagrams,omitempty"` // filled by Task 4
	Features  []Feature `json:"features"`
}

Requirement groups features by requirement ID.

type Summary

type Summary struct {
	ByStatus           map[string]int `json:"by_status"`
	ByAspect           map[string]int `json:"by_aspect"`
	TotalTokens        int            `json:"total_tokens"`
	UniqueRequirements int            `json:"unique_requirements"`
}

Summary holds aggregate counts.

Jump to

Keyboard shortcuts

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