Documentation
¶
Overview ¶
Package design defines the design workspace directory contract: the design/ tree layout, the manifest scaffold, and parsers for the machine-readable manifest blocks. See roadmap/SP-140-1-formats.md.
drift_view.go — the design_validate / design_assets reporting surface for the §5c drift report, split from drift.go (which owns the analysis core: AnalyzeDrift + the per-direction analyzers). These functions render a DriftReport as validator findings, a summary line, and per-direction lookups, so both reporting surfaces share one vocabulary with the analyzer.
Index ¶
- Constants
- Variables
- func AppendGitContract(dir string) error
- func CompareDeltas(a, b SyncDelta) bool
- func DriftDirections() []string
- func DriftRuleID(direction string) string
- func DriftSummaryLine(report *DriftReport) string
- func FileExists(root string) bool
- func FlowMMDFilename(name string) string
- func FlowMMDInputHash(srcRaw []byte, srcRelPath string, allScreens []TokenExportSource, ...) string
- func FlowSourcePaths(root string) ([]string, error)
- func FlowSourceRelPath(name string) string
- func FlowTouchedScreens(src *FlowSource, screens *ScreenIndexDoc) []string
- func IsGitContractFile(p string) bool
- func ManifestTemplate() ([]byte, error)
- func PendingFeedbackCount(states []FeedbackFileState) int
- func RenderExport(target string, tokens *TokenExport) ([]byte, error)
- func RenderFlowMMD(doc *FlowExportDoc, hash string) []byte
- func RenderScreenBrief(b *ScreenBrief) string
- func RenderScreensIndex(doc *ScreenIndexDoc) ([]byte, error)
- func RenderSyncSummary(r *SyncReport) string
- func Scaffold(dir string) error
- func ScaffoldRuntimeAssets(designRoot string) error
- func ScreenRelPath(stem string) string
- func SerializeScreenBrief(b *ScreenBrief) ([]byte, error)
- func SlugMatches(name string) bool
- func SubdirByName(name string) (string, bool)
- func SubdirsJoined() []string
- func TokenExportInputHash(docs []TokenExportSource) string
- func WireframeExists(exists bool) bool
- func WriteExportedArtifacts(root string, artifacts []ExportedArtifact) error
- func WriteExportedArtifactsAt(root string, artifacts []ExportedArtifact) error
- type AliasViolation
- type AssetRow
- type BriefFeedback
- type BriefFlowEdge
- type BriefTokenRef
- type Confidence
- type ContentReader
- type DeltaBasis
- type DeltaKind
- type DesignStatus
- type DirtyAliasError
- type DriftDirectionRow
- type DriftReport
- type ExportTarget
- type ExportedArtifact
- type ExportedToken
- type FSScanner
- type FeedbackFileState
- type Finding
- func DriftDirectionFindings(report *DriftReport) []Finding
- func ValidateBrandDir(root string) []Finding
- func ValidateBrandFile(relPath string, content []byte) []Finding
- func ValidateComponent(relPath string, content []byte) []Finding
- func ValidateComponentInventory(root string) []Finding
- func ValidateComponentsDir(root string) ([]Finding, error)
- func ValidateConsistency(root string) []Finding
- func ValidateDataURISizes(relPath string, content []byte) []Finding
- func ValidateFeedbackDir(root string) ([]Finding, error)
- func ValidateFile(root, relPath string) ([]Finding, error)
- func ValidateFlowSource(relPath string, content []byte, stems []string) []Finding
- func ValidateFlows(root, relPath string, content []byte, wireframeStems []string, ...) []Finding
- func ValidateFlowsDir(root string) ([]Finding, error)
- func ValidateFlowsTree(root string) []Finding
- func ValidateGitAttributesContent(relPath string, content []byte) []Finding
- func ValidateGitContract(root string) []Finding
- func ValidateGitIgnoreContent(relPath string, content []byte) []Finding
- func ValidateIconsDir(root string) ([]Finding, error)
- func ValidateInventory(root string) []Finding
- func ValidateManifest(root string) []Finding
- func ValidateScreensDir(root string) ([]Finding, error)
- func ValidateScreensIndex(root string) ([]Finding, error)
- func ValidateTokens(relPath string, content []byte) []Finding
- func ValidateTokensDir(root string) ([]Finding, error)
- func ValidateTree(root string) ([]Finding, error)
- func ValidateWireframe(relPath string, content []byte, knownStems []string, frames []Frame) []Finding
- func ValidateWireframesDir(root string) ([]Finding, error)
- type FlowCounts
- type FlowEdge
- type FlowExportDoc
- type FlowExportEdge
- type FlowExportNode
- type FlowExportedArtifact
- type FlowNode
- type FlowSource
- type FlowSourceFile
- type FlowStep
- type Flowchart
- type Frame
- type Inventory
- type ManifestSummary
- type ProviderWordHit
- type Scanner
- type ScreenBrief
- type ScreenBriefInput
- type ScreenIndexDoc
- type ScreenIndexEntry
- type ScreenIndexNav
- type Severity
- type StatusDriftRow
- type StatusFeedback
- type StatusFeedbackEntry
- type StatusFinding
- type StatusValidation
- type SyncApplyPlan
- type SyncApplyProposal
- type SyncApplyWrite
- type SyncDelta
- type SyncFileInput
- type SyncFileReader
- type SyncInput
- type SyncPlanningOp
- type SyncReport
- type TokenExport
- type TokenExportSource
- type TokenGroupCount
Constants ¶
const ( // BriefDepthSummary is the default depth (§5g): a condensed brief — counts // and headings rather than the full annotation/detail text. BriefDepthSummary = "summary" // BriefDepthFull is the detailed brief: annotation notes, per-edge detail, // and the flow file list. BriefDepthFull = "full" )
Design↔code sync, screen-brief half (SP-140-5 §5g).
This file is the pure, read-only core of the `design_brief` tool: given a screen name (a wireframe stem) and the workspace root, it reads the design/ tree *for that screen* and returns a structured brief the implementing agent reads before building it:
- purpose — the screen's purpose, from the README Screens listing;
- wireframe path — design/wireframes/<stem>.svg;
- flows in/out — every flow edge touching the screen, with its trigger (the mermaid edge label), direction, and flow file;
- token paths — the DTCG token paths the wireframe refers to (its `{group.token}` comments), plus the token groups available to consume;
- open feedback — pending §4d feedback annotations for the screen;
- status — the README status marker (draft/review/ready).
The brief is a *contract, not a generator* (§5g): it writes nothing and it produces no component code. It is purely advisory context returned in the ToolResult. Determinism is a property of the core — the same tree must yield the same brief byte for byte — so every slice is sorted by a canonical key and nothing is ordered by map iteration.
const ( // DirName is the design workspace root directory, relative to the // project root. DirName = "design" // ManifestName is the inventory manifest at the design root. ManifestName = "README.md" // SlugPattern is the name rule for screens, flows, and icons. SlugPattern = `^[a-z0-9]+(-[a-z0-9]+)*$` )
const ( // TokenSubdir holds the W3C DTCG *.tokens.json files. TokenSubdir = "tokens" // FlowSubdir holds the mermaid *.mmd flow sources. FlowSubdir = "flows" // RuntimeSubdir holds the SP-143 screen-kit fixed assets: the screen // runtime (sprout-screens.js), device chrome (chrome.css), and the base // documents screens start from (base/*.html). // It is NOT in Subdirs — it is scaffolded tool-owned content, not a user // authoring tier, so it never appears in the manifest's directory // contract. The validator (SP-143 §143.5) registers it separately. RuntimeSubdir = "runtime" )
Canonical subdirectory names, named so callers can test membership and subtree relationships without repeating string literals.
const ( // DriftDirectionDesignAhead is design/ ahead of generated/code. DriftDirectionDesignAhead = "design-ahead" // DriftDirectionCodeAhead is implementation ahead of the semantic layer. DriftDirectionCodeAhead = "code-ahead" // DriftDirectionSynced is the balanced row: neither side is ahead. Both // direction rows still report, with Synced=true and an empty remedy, so a // consumer reads a stable two-row shape and "no drift" is explicit. DriftDirectionSynced = "synced" )
Drift direction ids, fixed by §5c. These are the stable row identifiers both design_assets and design_validate emit.
const ( // DriftSeverityInfo marks a row with no remedy (in sync, or ahead with // nothing countable to point at). DriftSeverityInfo = "info" // DriftSeverityWarn marks a row that carries an actionable remedy. DriftSeverityWarn = "warn" )
Drift severities. Drift is signal, never an error (§5c): both rows are advisory. info = "nothing to do", warn = "there is a remedy to run".
const ( // DriftRowDesignAhead is the design-ahead row. DriftRowDesignAhead = "design-ahead" // DriftRowCodeAhead is the code-ahead row. DriftRowCodeAhead = "code-ahead" )
Drift rows (stable row order). Every DriftReport carries exactly these two, in this order.
const ( // DriftDesignAheadNextStep is the design-ahead remedy pointer: regenerate // the generated theme from the token sources (§5a). The count of screens // ahead of the build is reported in the row's Count/Summary. DriftDesignAheadNextStep = "" /* 144-byte string literal not displayed */ // DriftCodeAheadNextStep is the code-ahead remedy pointer: import the // semantic deltas the implementation introduced (§5b). DriftCodeAheadNextStep = "" /* 127-byte string literal not displayed */ )
Drift remedy next-steps: the run-me pointers §5c fixes. They are stable strings the handler rows and the tests assert, so the two surfaces cannot drift apart in wording either.
const ( ExportTargetCSS = "css" ExportTargetTS = "ts" ExportTargetJSON = "json" ExportTargetTailwind = "tailwind" ExportTargetSwift = "swift" ExportTargetKotlin = "kotlin" )
Export target identifiers. These are the exact strings the design_export_tokens `targets` argument accepts.
const ( // GitAttributesRule fires when the workspace .gitattributes is missing // or lacks the design SVG diff rule; its finding is a fix carrying the // exact line to append (GitAttributesDiffHTMLLine). GitAttributesRule = "gitattributes_diff_html" // GitIgnoreCacheRule fires when the workspace .gitignore does not // ignore design/.cache/; its finding is a fix carrying the exact line // to append (GitIgnoreCacheLine). GitIgnoreCacheRule = "gitignore_design_cache" // GitIgnoreGeneratedRule is advisory: the workspace .gitignore also // ignores design/generated/, which SP-140-1 §1h deliberately leaves // un-ignored (committing generated artifacts is the project's choice, // and the provenance hashes keep either choice consistent). GitIgnoreGeneratedRule = "gitignore_design_generated" // DataURISizeRule is advisory (warn): an embedded data: URI inside a // design SVG exceeds DataURISizeWarnThreshold bytes. DataURISizeRule = "svg_data_uri_size" // FixGitAttributesRule is the machine-applicable rule id for the // .gitattributes fix. FixGitAttributesRule = "gitattributes_diff_html_fix" // FixGitIgnoreCacheRule is the machine-applicable rule id for the // .gitignore fix. FixGitIgnoreCacheRule = "gitignore_design_cache_fix" )
Rule ids for the design-tree git contract, SP-140-1 §1h. GitAttributesRule and GitIgnoreCacheRule name the contract clauses (they are the finding rule ids); the machine-applicable fix findings carry the FixRule ids so callers can tell a report from a patch instruction.
const ( KindManifest = "manifest" KindToken = "token" KindBrand = "brand" KindIcon = "icon" KindWireframe = "wireframe" KindComponent = "component" KindScreen = "screen" KindFlow = "flow" KindFeedback = "feedback" // KindRuntime lists the SP-143 screen-kit fixed assets under // design/runtime/ (the runtime, device chrome, base documents). They are // tool-owned, not an authoring tier, but design_assets lists the tier so // a model sees what is actually in the tree (SP-143 §143.5). KindRuntime = "runtime" KindUnkn = "unknown" )
inventoryAssetKinds are the per-asset `kind` values the inventory rows carry, one per canonical design asset class (SP-140-2 §2c).
const ( // DeltaBasisLiteral: a token variable was renamed/revalued in code and // maps 1:1 to a DTCG entry. Safe to apply mechanically. DeltaBasisLiteral deltaBasis = "literal" // DeltaBasisStructural: a new route/screen/component/nav target — // structure, not value. Maps to wireframe/flow changes. DeltaBasisStructural deltaBasis = "structural" // DeltaBasisInferred: styling with no token counterpart. A proposal. DeltaBasisInferred deltaBasis = "inferred" )
Basis values, fixed by SP-140-5 §5b.
const ( UtilityGroupColor = "color" UtilityGroupSpace = "space" UtilityGroupFont = "font" UtilityGroupRadius = "radius" UtilityGroupShadow = "shadow" )
The SP-143 §143.1 utility vocabulary: a fixed set of known token groups that map to utility classes in the css export target, so a screen styles with `.bg-*`/`.p-*` instead of re-deriving CSS from scratch. Unknown groups pass through as variables only — the vocabulary is closed on purpose (SP-143 Premise §1), so a project tier named `sizing` never generates surprise classes.
const DataURISizeWarnThreshold = 1 << 20
DataURISizeWarnThreshold is the byte size above which an embedded data: URI is a validator warn (SP-140-1 §1h, "binary hygiene"). 1 MiB keeps diffs reviewable: past it the escape hatch — move the raster to brand/ and reference it — is the better trade.
const ExportTargetAll = "all"
ExportTargetAll is the `targets` value that runs every exporter.
const ExportTargetFlows = "flows"
ExportTargetFlows is the SP-140-9 §9b derived-flow export target: it regenerates design/flows/<name>.mmd from each flow source .json plus the touched screens. Like the screens index it is explicit-only — never part of `all` — because it derives from the flow sources, not the token sources, so a token re-theme must not rewrite the flow exports.
const ExportTargetScreens = "screens"
ExportTargetScreens is the design_export_tokens target that writes the derived screens.json. It is deliberately not part of ExportTargets (the `all` set): the token targets render from the token sources, while the screens index derives from design/screens/*.html, so a plain re-theme run (`all`) never rewrites the screen graph — it is an explicit regeneration.
const FeedbackStatusChangesRequested = "changes-requested"
FeedbackStatusChangesRequested is the §4d status a human-written annotation carries while the agent still has to address it. The feedback affordance writes it (webui/src/design/feedbackWrite.ts PENDING_FEEDBACK_STATUS) and the design-system skill's loop starts any target carrying it with a read of its feedback file.
const FeedbackSubdir = "feedback"
Feedback-subdirectory name under design/. Named so the validator, the design_assets inventory, and the parser all agree on one literal.
const FlowDraftStatus = "draft"
FlowDraftStatus is the status a §5b-created wireframe/flow edge carries: draft, so the next design turn reviews it rather than trusting it.
const FlowSourceHashLabel = "flow-source-hash"
FlowSourceHashLabel is the header key on the derived .mmd carrying the §9b input hash. The value renders with the token-export digest label (fnv1a64:<16 hex>), so every derived artifact in the tree verifies the same way.
const GeneratedSubdir = "generated"
GeneratedSubdir is the design subdirectory export writes to, under DirName (SP-140-5 §5a: "targets design/generated/"). It is deliberately not part of Subdirs — it is a generated output location, not a scaffolded source tier, and SP-140-1 §1h leaves it un-ignored.
const GitAttributesDiffHTMLLine = "design/**/*.svg diff=html"
GitAttributesDiffHTMLLine is the exact line the design tree's .gitattributes contract requires (SP-140-1 §1h): readable markup diffs for wireframe, icon, and logo SVGs. Repos already carrying text/binary rules keep them — the line is appended, never clobbered.
const GitContractFile = ".gitattributes"
GitContractFile is the pseudo design-asset path the §1h git-contract findings attach to. It is repository-level, not a file inside design/, so single-file validation runs never emit these findings — only whole-tree runs (design_validate with no path) check them.
const GitIgnoreCacheLine = "design/.cache/"
GitIgnoreCacheLine is the exact line the design tree's ignore policy requires (SP-140-1 §1h): render PNGs and critique scratch live in design/.cache/ and are never sources. design/generated/ is deliberately *not* covered — committing generated artifacts stays the project's choice.
const GitIgnoreFile = ".gitignore"
GitIgnoreFile is the repository-level file the ignore-policy findings (design/.cache/, design/generated/) attach to.
const GitIgnoreGeneratedPattern = "design/generated/"
GitIgnoreGeneratedPattern is the pattern the validator checks against the workspace .gitignore to emit its advisory design/generated/ finding.
const RuntimeFilename = "sprout-screens.js"
RuntimeFilename is the screen runtime's fixed asset name under design/runtime/ (SP-143 §2).
const ScreensIndexFilename = "screens.json"
ScreensIndexFilename is the screens.json artifact name under design/generated/ (SP-143 §4).
const StatusFindingsCap = 200
StatusFindingsCap is the maximum number of findings the status payload carries. The tallies stay authoritative (they count everything); the list is capped so one broken tree cannot flood a browser. The cap keeps the validator's canonical order (first N in sorted order).
const SyncModeApplyConst = "apply"
SyncModeApplyConst mirrors the handler's mode constant without importing the handler (the pure core must not depend on pkg/agent_tools).
const TokenExportSourceHashLabel = "fnv1a64"
TokenExportSourceHash is the label prefix on every content hash this package emits. It names the algorithm in-band so a consumer reading a header knows how to recompute the digest without reading the implementation.
Variables ¶
var ErrDirtyAliasGraph = errors.New("dirty alias graph: token aliases do not resolve")
ErrDirtyAliasGraph is returned when the token tree contains at least one dangling or cyclic whole-value alias (SP-140-5 §5a: export "refuses dirty alias graphs"). It is a distinct sentinel so the handler can report the refusal as a structured, actionable failure and name the offending token — see DirtyAliasError for the per-token detail. Export never writes a partial or broken theme on a dirty graph.
var ErrManifestExists = errors.New("design manifest already exists")
ErrManifestExists is returned by Scaffold when the design manifest already exists, so callers can detect that a scaffold would be a no-op rather than clobbering user work.
var ErrManifestIsDirectory = errors.New("design manifest path is a directory")
ErrManifestIsDirectory is returned by Scaffold when the manifest path exists but is a directory, which Scaffold cannot replace with a file.
var ErrNoFlowSources = errors.New("no flow sources found under design/flows")
ErrNoFlowSources is returned when the `flows` export target is requested but design/flows/ holds no flow source documents — there is nothing to derive, and an empty export would be misleading. A distinct sentinel so the handler reports a usage error, mirroring ErrNoScreensForIndex.
var ErrNoScreensForIndex = errors.New("no design screens found to index")
ErrNoScreensForIndex is returned when the screens index is requested but design/screens/ holds no screens — there is nothing to derive, and an empty index would be misleading. A distinct sentinel so the handler reports a usage error, mirroring ErrNoTokensForExport.
var ErrNoTokensForExport = errors.New("no design tokens found to export")
ErrNoTokensForExport is returned when no token leaves exist under the workspace's design/tokens/ directory — there is nothing to export, and emitting target boilerplate with no tokens would be misleading. It is a distinct sentinel so the handler can report a usage error rather than a crash. (Exporting requires tokens/, which is also how the handler refuses a workspace with no design/ tree at all.)
var ExportFilenames = map[string]string{ ExportTargetCSS: "tokens.css", ExportTargetTS: "tokens.ts", ExportTargetJSON: "tokens.json", ExportTargetTailwind: "tailwind.theme.css", ExportTargetSwift: "tokens.swift", ExportTargetKotlin: "tokens.kt", }
ExportFilenames maps each target to the file it writes under design/generated/. The names are fixed by SP-140-5 §5a so generated output is predictable to consumers (a webui build can hardcode tokens.css).
var ExportTargets = []string{ ExportTargetCSS, ExportTargetTS, ExportTargetJSON, ExportTargetTailwind, ExportTargetSwift, ExportTargetKotlin, }
ExportTargets is the canonical target list, in the deterministic order exports are generated and reported (all → css, ts, json, tailwind, swift, kotlin). Callers must not rely on map order, so this slice is the one ordering authority.
var ProprietaryNames = []string{
"figma",
"penpot",
"sketch",
"illustrator",
"adobe",
"photoshop",
}
ProprietaryNames is the hardcoded word list SP-140-1's Acceptance Criteria fixes for the design tier: the six proprietary design-tool product names.
The design tree's formats are deliberately open (W3C DTCG tokens, SVG, mermaid, self-contained HTML — see SP-140-1 §1), so the design-tier code must never couple itself to a specific vendor. The list is versioned here, not in the guard test, so any future design-tier file (Go or webui) can be scanned with the same vocabulary without duplicating it.
One entry is load-bearing: the "sketch" product name collides with SP-140-2's `design_import_sketch` tool, which takes its name from the *activity* of sketching, not the product. This list is therefore scoped to the SP-140-1 design-tier files only (pkg/design and its design_validate handler); SP-140-2's tool files are covered by their own spelling convention (`import_sketch`) rather than this rule.
The literals below are the vocabulary itself — the one sanctioned place the names may appear (see providerScanExemptBasenames).
var RuntimeAssets = []string{
"sprout-screens.js",
"chrome.css",
"base/phone.html",
"base/desktop.html",
}
RuntimeAssets are the SP-143 fixed screen-kit assets the scaffold copies into design/runtime/, in copy order: the runtime itself, device chrome, then the base documents new screens start from. They are versioned assets, not user content — Scaffold never overwrites an existing file, so a tree may pin its own copy without the scaffold fighting it.
var Subdirs = []string{
"tokens",
"brand",
"icons",
"wireframes",
"components",
"screens",
"flows",
"feedback",
}
Subdirs are the canonical design workspace subdirectories, in contract order.
Functions ¶
func AppendGitContract ¶
AppendGitContract appends the SP-140-1 §1h lines to the workspace .gitattributes and .gitignore when absent, creating either file when missing. It is append-only: existing lines are preserved verbatim, and a contract already satisfied (directly or via a broader ignore rule) is left alone. For example, appending .gitattributes to:
- text=auto eol=lf *.go text eol=lf
yields the same rules plus "design/**/*.svg diff=html" at the end — the repo's text/binary rules are never clobbered. design/generated/ is deliberately never added (SP-140-1 §1h leaves that choice to the project).
func CompareDeltas ¶
CompareDeltas is the canonical delta ordering: by basis, then kind, then code file, then the delta text. It is the determinism guarantee for the report — two runs over the same inputs emit the same Deltas slice.
func DriftDirections ¶
func DriftDirections() []string
driftDirectionOrder exposes the fixed direction order to consumers that build their own view of the report (the design_assets output rows).
func DriftRuleID ¶
DriftRuleID is the design_validate rule id for one drift direction row. The ids are the direction names (design-ahead / code-ahead), prefixed with the shared `drift_` namespace so a consumer can tell a drift row from a validator rule at a glance.
func DriftSummaryLine ¶
func DriftSummaryLine(report *DriftReport) string
DriftSummaryLine renders the report as one human/agent-readable sentence for the design_assets summary line: both directions named with their counts, so the two-row shape is visible in the text as well as the JSON.
func FileExists ¶
FileExists reports whether the workspace at root has a design/ directory. The design_validate handler uses it to distinguish "clean tree" from "no design/ at all" when a run yields zero findings.
func FlowMMDFilename ¶ added in v0.18.17
FlowMMDFilename returns the derived export path for a flow name.
func FlowMMDInputHash ¶ added in v0.18.17
func FlowMMDInputHash(srcRaw []byte, srcRelPath string, allScreens []TokenExportSource, touched []string) string
FlowMMDInputHash computes the §9b provenance hash over one flow's inputs in the documented order: the flow source bytes first, then the touched screens' bytes in stem order. allScreens are the raw screen documents (as ScreensIndexSources read them); screens the flow touches but the tree does not carry are skipped (unknown stems are the screen validator's finding).
func FlowSourcePaths ¶ added in v0.18.17
FlowSourcePaths globs the flow source documents under root (design/flows/*.json, the §7d .layout.json sidecars excluded), in sorted order. A missing or empty flows directory yields an empty slice.
func FlowSourceRelPath ¶ added in v0.18.17
FlowSourceRelPath returns the canonical flow source path for a flow name: design/flows/<name>.json (SP-140-9 §9b).
func FlowTouchedScreens ¶ added in v0.18.17
func FlowTouchedScreens(src *FlowSource, screens *ScreenIndexDoc) []string
FlowTouchedScreens returns the flow's transitive data-nav closure: the step screens plus every screen reachable from them. Sorted; the off-path cross check and the hash-input assembly share this one closure definition.
func IsGitContractFile ¶
IsGitContractFile reports whether path is a repository-level git-contract file (.gitattributes or .gitignore). They are valid design_validate path arguments even though they live outside design/.
func ManifestTemplate ¶
ManifestTemplate returns the embedded starter manifest content, for callers that need the template itself (for example to repair a manifest or to preview what a scaffold would write).
func PendingFeedbackCount ¶
func PendingFeedbackCount(states []FeedbackFileState) int
PendingFeedbackCount returns the number of pending feedback files: those whose status is "changes-requested" or that carry at least one unresolved annotation (§4d). It is the single predicate the design_assets summary uses and the skill loop keys on.
func RenderExport ¶
func RenderExport(target string, tokens *TokenExport) ([]byte, error)
RenderExport renders the token set for one target, returning the exact bytes to write. The output opens with the target's provenance header (item 5.2) and ends with a single trailing newline; it is byte-identical across runs for identical inputs.
func RenderFlowMMD ¶ added in v0.18.17
func RenderFlowMMD(doc *FlowExportDoc, hash string) []byte
RenderFlowMMD renders the derived .mmd bytes: the provenance banner, the declaration, node definitions (walk then off-path), then edges (walk then off-path), triggers as |label| edge labels. Deterministic by construction — the doc's slices are ordered by DeriveFlowMMD, the hash is an input.
func RenderScreenBrief ¶
func RenderScreenBrief(b *ScreenBrief) string
RenderScreenBrief builds the human/agent-readable one-block summary of the brief, so a model reads the shape from the text without parsing JSON. The summary contracts to counts at summary depth and includes the annotation notes at full depth.
func RenderScreensIndex ¶ added in v0.18.16
func RenderScreensIndex(doc *ScreenIndexDoc) ([]byte, error)
RenderScreensIndex renders the derived screens.json bytes. Deterministic: the doc's slices are sorted by construction, encoding/json emits struct fields in declaration order, and there is no map anywhere in the shape, so identical screen bytes render identical bytes on every run and platform.
func RenderSyncSummary ¶
func RenderSyncSummary(r *SyncReport) string
RenderSyncSummary is the human/agent-readable one-liner for a report: the touched-file count and the delta split by basis, plus a pointer to the safe subset apply can write. Aimed at the model reading the ToolResult text.
func Scaffold ¶
Scaffold creates the design workspace tree under dir: the design/ root, each canonical subdirectory with a .gitkeep placeholder (git does not track empty directories), the manifest from the embedded template, and the workspace git contract (SP-140-1 §1h).
The git contract appends GitAttributesDiffHTMLLine to the workspace .gitattributes and GitIgnoreCacheLine to the workspace .gitignore when the line is absent, creating either file when missing. Existing rules are never clobbered — the lines are appended, and an already-satisfied contract (including one covered by a broader rule) is a no-op.
Scaffold never overwrites user work: if design/README.md exists with content it returns ErrManifestExists (wrapped with the path) and leaves the file untouched. A 0-byte manifest is treated as an empty placeholder — the greenfield flow creates the tree before filling the manifest — and is replaced with the template. If the manifest path is a directory it returns ErrManifestIsDirectory. Subdirectory and .gitkeep creation is idempotent — existing entries are skipped, missing ones are created.
func ScaffoldRuntimeAssets ¶ added in v0.18.16
ScaffoldRuntimeAssets copies the SP-143 screen-kit assets (device chrome + base templates) from the embedded templates into root/design/runtime/, creating the directory. Idempotent and overwrite-free: an existing asset is left byte-for-byte alone, so a scaffold over a brownfield tree never clobbers user work — the same posture as the manifest.
func ScreenRelPath ¶ added in v0.18.17
ScreenRelPath returns the canonical primary-screen path for a stem: design/screens/<stem>.html (SP-140-9 §9a). Shared by the deprecation findings and the flow tooling, which name screens by this path.
func SerializeScreenBrief ¶
func SerializeScreenBrief(b *ScreenBrief) ([]byte, error)
SerializeScreenBrief renders the brief to deterministic, 2-space-indented JSON. The bytes are stable for identical trees, so a caller can hash or diff the output (the tests rely on this for the determinism assertion).
func SlugMatches ¶
SlugMatches reports whether a name satisfies the shared slug rule (SlugPattern, SP-140-1). It is exported so the sync handler/tests can assert a proposed stem is a legal wireframe name without duplicating the regexp.
func SubdirByName ¶
SubdirByName returns the canonical subdirectory path under DirName for the given name (for example "wireframes" yields "design/wireframes"). The second return value is false for a name not in Subdirs.
func SubdirsJoined ¶
func SubdirsJoined() []string
SubdirsJoined returns each canonical subdirectory joined under DirName (for example "design/tokens"), in contract order. The result is always non-nil, even when there are no canonical subdirectories.
func TokenExportInputHash ¶
func TokenExportInputHash(docs []TokenExportSource) string
TokenExportInputHash hashes the *token inputs* — the DTCG source documents the export read, not the rendered artifact. It is the authoritative provenance hash recorded in every generated artifact's header (SP-140 invariant 2, SP-140-5 §5f "provenance at any ref").
Why inputs and not artifact bytes: the header must be identical across the five targets (only the rendering differs, the semantic content does not), and it has to be recomputable without re-running the export. A consumer at an arbitrary checkout recomputes it with:
exportTokenSourceHash(dir(os.ReadFile(design/tokens/*.tokens.json)))
— i.e. concat the raw bytes of design/tokens/*.tokens.json sorted by basename, hash that. Nothing else (no timestamps, no paths, no env) feeds the digest, so it is deterministic across machines and checkouts.
Each document is folded in as "len(bytes)\n bytes" so two different file partitions cannot concatenate to the same byte stream and collide.
func WireframeExists ¶
WireframeExists is a tiny convenience so the token-reference call reads clearly; it mirrors the boolean field.
func WriteExportedArtifacts ¶
func WriteExportedArtifacts(root string, artifacts []ExportedArtifact) error
WriteExportedArtifacts writes each artifact (already rendered and carrying its final design-relative slash path in RelPath) under root, creating parent directories as needed. It returns an error on the first failure.
Every RelPath must be confined to design/ — the handler asserts this before calling (relocateArtifacts + resolveExportOutDir), and this function re-checks it so a future caller cannot use it to escape the tree. Writes are plain file writes; the handler has prechecked every target path already.
func WriteExportedArtifactsAt ¶
func WriteExportedArtifactsAt(root string, artifacts []ExportedArtifact) error
WriteExportedArtifactsAt is WriteExportedArtifacts under its explicit name: it writes artifacts at their own RelPath, which the handler has already relocated to the requested output directory. Keeping both names lets the handler read naturally while the default-path helper stays for tests.
Types ¶
type AliasViolation ¶
type AliasViolation struct {
// Token is the dotted path of the token whose alias is broken
// (e.g. "color.brand.text").
Token string
// Path is the workspace-relative slash path of the source document the
// token lives in (design/tokens/color.tokens.json).
Path string
// Line is the 1-based source line of the token leaf's value.
Line int
// Rule is the validator rule id (token_alias_dangling or
// token_alias_cycle), so the refusal uses the same vocabulary as
// design_validate.
Rule string
// Kind is a short human label: "dangling" or "cycle".
Kind string
// Message is the validator's finding message (already names the token
// and, for cycles, the member loop).
Message string
}
AliasViolation is one dangling or cyclic alias in the token tree.
type AssetRow ¶
type AssetRow struct {
Path string `json:"path"`
Kind string `json:"kind"`
Name string `json:"name"`
Status string `json:"status,omitempty"`
Summary string `json:"summary,omitempty"`
}
AssetRow is one inventory entry `{path, kind, name, status?, summary?}` (SP-140-2 §2c). Path is the workspace-relative slash path; Name is the asset's stem (the screen/flow/icon name, `README` for the manifest, or the file stem otherwise). Status is populated from the manifest's status markers when one is declared for the asset; Summary is a short, best-effort description parsed from the manifest listing when present.
type BriefFeedback ¶
type BriefFeedback struct {
// Path is the feedback file (workspace-relative) when one exists.
Path string `json:"path,omitempty"`
// Status is the §4d top-level status ("changes-requested", "resolved", …).
Status string `json:"status,omitempty"`
// Open is the number of annotations with `resolved: false`.
Open int `json:"open"`
// Total is the total annotation count in the file.
Total int `json:"total"`
// Pending reports whether the file still needs the agent's attention
// (status changes-requested or at least one unresolved annotation, §4d).
Pending bool `json:"pending"`
// Resolution is the agent's closing note, when present.
Resolution string `json:"resolution,omitempty"`
// Notes are the open annotations' notes ("[area] note"), in file order.
// Populated at full depth only.
Notes []string `json:"notes,omitempty"`
}
BriefFeedback is the §4d feedback view for the screen: the feedback file (when one exists) plus the open (unresolved) annotation notes. Notes carries the annotation text at full depth and is empty at summary depth.
type BriefFlowEdge ¶
type BriefFlowEdge struct {
// Flow is the flow file (workspace-relative, e.g. design/flows/sign-up.mmd).
Flow string `json:"flow"`
// FlowName is the flow's stem (e.g. "sign-up").
FlowName string `json:"flowName"`
// Source / Target are the edge endpoints (node ids == wireframe stems).
Source string `json:"source"`
Target string `json:"target"`
// Direction is "in" (the screen is the target), "out" (the screen is the
// source), or "both" (a self-edge). It is the §5g "in/out" split.
Direction string `json:"direction"`
// Trigger is the edge label, i.e. what causes the transition (§5g
// "triggers"). "" when the edge carries no label.
Trigger string `json:"trigger,omitempty"`
// OtherStem is the far endpoint of the edge (the node the screen moves to,
// or comes from). "" for a self-edge.
OtherStem string `json:"otherStem,omitempty"`
// OtherLabel is the far endpoint's node label, when the flow declared one.
OtherLabel string `json:"otherLabel,omitempty"`
}
BriefFlowEdge is one flow edge touching the briefed screen (§5g "flows in/out with triggers"). Trigger is the mermaid edge label — the `-- "tap Submit" -->` form carries the trigger semantics per the design manifest contract; a labelled edge whose label is empty reports "".
type BriefTokenRef ¶
type BriefTokenRef struct {
// Path is the dotted token path (e.g. "color.brand.primary").
Path string `json:"path"`
// Known is true when the reference resolves to a DTCG leaf in
// design/tokens/.
Known bool `json:"known"`
}
BriefTokenRef is one DTCG token a wireframe refers to, resolved from the wireframe's `{group.token}` comments (the SP-140-4 §4b convention). Known reports whether the reference resolves to a real DTCG leaf.
type Confidence ¶
type Confidence = string
Confidence is a coarse automation gate on a delta. Literal and structural deltas are safe to apply; inferred deltas are proposals and carry a lower confidence.
const ( ConfidenceHigh Confidence = "high" ConfidenceMedium Confidence = "medium" ConfidenceLow Confidence = "low" )
Confidence values. High means "safe to apply mechanically", medium means "mapped to a known design artifact with reasonable certainty", low means "a proposal a human or the agent should confirm".
type ContentReader ¶
ContentReader is the optional companion to Scanner: when implemented, the scan reads file bodies through it instead of os.ReadFile. FSScanner does not implement it (os.ReadFile is correct for real paths); the test fake does.
type DeltaBasis ¶
type DeltaBasis = deltaBasis
DeltaBasis is the basis of one semantic delta (SP-140-5 §5b). It governs automation: literal and structural deltas are the safe-to-apply subset; inferred deltas are proposals.
type DeltaKind ¶
type DeltaKind = string
DeltaKind is the design-tier class a delta belongs to (SP-140-5 §5b: token|wireframe|flow|feedback).
type DesignStatus ¶ added in v0.18.9
type DesignStatus struct {
Exists bool `json:"exists"`
Validation StatusValidation `json:"validation"`
// Drift carries the two §5c rows in fixed order plus the overall synced
// flag (true when neither direction is ahead).
Drift struct {
DesignAhead StatusDriftRow `json:"designAhead"`
CodeAhead StatusDriftRow `json:"codeAhead"`
Synced bool `json:"synced"`
} `json:"drift"`
Feedback StatusFeedback `json:"feedback"`
// TokenRefs maps a dotted token path ({color.brand.primary}) to the number
// of lexical alias references to it across design/tokens/*.tokens.json —
// the SP-140-7 §7c alias-warning input ("N tokens reference this").
// Unreferenced tokens are absent. Deterministic via JSON map marshalling.
TokenRefs map[string]int `json:"tokenRefs,omitempty"`
// Summary is the one-line human/agent-readable rollup for logs and titles.
Summary string `json:"summary"`
}
DesignStatus is the full GET /api/design/status payload. Exists=false (no design/ directory) is reported with a 200 and zeroed sections — the health strip renders "no design tree", not an error (§6b).
func BuildDesignStatus ¶ added in v0.18.9
func BuildDesignStatus(root string, touched []SyncFileInput) *DesignStatus
BuildDesignStatus computes the §6b status for the workspace at root. touched is the code-ahead evidence set: an HTTP read has none (pass nil), and the code-ahead row then reports synced/not-assessable with its evidence — the honest tree-only answer (§5c). The call never fails: scanner errors become zeroed sections or synthetic findings, never an error return, so the endpoint has one shape.
type DirtyAliasError ¶
type DirtyAliasError struct {
// Violations are the offending aliases, one per (rule, token) pair.
Violations []AliasViolation
}
DirtyAliasError is the structured refusal returned by ResolveExportTokens (wrapping ErrDirtyAliasGraph) when the alias graph is dirty. Violations are sorted by token path so the message and the structured detail are deterministic across runs and file read orders.
func (*DirtyAliasError) Error ¶
func (e *DirtyAliasError) Error() string
Error renders the refusal so a model reads which token is dirty without parsing structured data. It lists every offending token in path order.
func (*DirtyAliasError) Unwrap ¶
func (e *DirtyAliasError) Unwrap() error
Unwrap lets errors.Is(err, ErrDirtyAliasGraph) recognize the structured refusal, so the handler can branch on the sentinel without a type assertion.
type DriftDirectionRow ¶
type DriftDirectionRow struct {
// Direction is design-ahead | code-ahead (the stable row id).
Direction string `json:"direction"`
// Ahead is true when this direction actually holds (there is drift here).
Ahead bool `json:"ahead"`
// Synced is true when this direction has nothing ahead — the complement of
// Ahead, explicit so a model reads "no drift" without a negation.
Synced bool `json:"synced"`
// Count is the concrete magnitude of the drift: screens ahead of the build
// (design-ahead) or semantic deltas to import (code-ahead). 0 when Synced
// or when nothing countable was found.
Count int `json:"count"`
// Summary is the one-line description of what is ahead ("2 screens ahead
// of the exported theme"). Empty when Synced.
Summary string `json:"summary,omitempty"`
// Remedy is the run-me pointer for this direction (§5c): regenerate the
// theme (design-ahead) or run design_sync (code-ahead). Empty when Synced.
Remedy string `json:"remedy,omitempty"`
// NextStep is the tool-level pointer the remedy names (the design_export_tokens
// or design_sync call), stable so a consumer can route on it. Empty when Synced.
NextStep string `json:"nextStep,omitempty"`
// Advisory is the severity class: info (nothing to do) or warn (a remedy to
// run). Drift is signal, never an error (§5c), so this is never "error".
Advisory string `json:"advisory"`
}
DriftDirectionRow is one direction row of the drift report (§5c). Both directions always produce a row; a row with nothing to report reads Synced=true with an empty Count and Remedy, so the two-row shape is stable.
func DriftDirectionRowFor ¶
func DriftDirectionRowFor(report *DriftReport, direction string) DriftDirectionRow
DriftDirectionRowFor returns the report's row for one direction, or a zero row when the direction is unknown. Both handlers use it so a consumer reads a direction by name rather than by slice index.
type DriftReport ¶
type DriftReport struct {
// Rows are the two direction rows, design-ahead then code-ahead.
Rows []DriftDirectionRow `json:"rows"`
// DesignAheadCount / CodeAheadCount mirror the rows for a reader that does
// not want to index Rows.
DesignAheadCount int `json:"designAheadCount"`
CodeAheadCount int `json:"codeAheadCount"`
// Synced is true when neither direction is ahead.
Synced bool `json:"synced"`
// Evidence carries how each count was computed (the generated provenance
// hash basis, the touched-file basis, or the offline fallback), so a model
// can judge the confidence of a count. Deterministic order.
Evidence []string `json:"evidence,omitempty"`
}
DriftReport is the §5c drift-direction view: the two direction rows (always both, always in DriftRowDesignAhead, DriftRowCodeAhead order) plus the mapping of each row onto a design_validate finding, so both reporting surfaces share one vocabulary. It is deterministic: the rows are fixed in order and every count is derived from a sorted walk.
func AnalyzeDrift ¶
func AnalyzeDrift(root string, touched []SyncFileInput) *DriftReport
AnalyzeDrift computes the §5c drift report for one workspace.
It is deliberately cheap and deterministic — it is called by both design_assets and design_validate, tools a turn runs freely — and it never fails a caller: a workspace with no design/ tree yields a synced report rather than an error (the design_assets handler's {exists:false} path is the scaffold signal; drift is only meaningful once a tree exists).
The two signals:
design-ahead — the design/ tree versus the exported provenance. When design/generated/ carries the §5a source-hash provenance header, the current token-input hash is recomputed and compared: a difference means the tokens moved after the last export, so the generated theme is behind. Screens/wireframes give the count ("N screens ahead of the build"). Without a generated/ tree the whole tree counts as ahead — the design side exists and nothing has been exported from it yet.
code-ahead — the implementation versus the semantic layer. When the caller knows the touched code files (design_validate can ask the ChangeTracker; a standalone design_assets run passes none), the §5b analysis is reused verbatim: the delta count is exactly what design_sync would import, restricted to the literal/structural subset design_sync can actually adopt. Without a touched set, whether the implementation is ahead is simply not knowable from the tree alone, so the row reports synced and Evidence says so — the remedy direction is unchanged, the count is just not claimable. This is core-owned deliberately: the pkg/agent_tools layer supplies the touched files, the semantics live here.
type ExportTarget ¶
ExportTarget is one resolved export target: its identifier and its design-relative file name.
func ResolveExportTargets ¶
func ResolveExportTargets(raw string) ([]ExportTarget, error)
ResolveExportTargets maps a `targets` argument value to the ordered target list to run. An empty value or "all" selects every target in ExportTargets order. A value may be a single target ("css") or a comma-separated list ("css, ts"); each entry is trimmed and lowercased. Unknown targets are an error naming the accepted set, so a typo never silently exports nothing.
type ExportedArtifact ¶
type ExportedArtifact struct {
// Target is the exporter that produced it (css, ts, ...).
Target string
// RelPath is the workspace-relative slash path (design/generated/<file>).
RelPath string
// Content is the exact byte content written.
Content []byte
// Hash is the deterministic content hash (see tokenExportArtifactHash).
Hash string
}
ExportedArtifact is one written (or to-be-written) generated file.
func RenderArtifacts ¶
func RenderArtifacts(tokens *TokenExport, targets []ExportTarget) ([]ExportedArtifact, error)
RenderArtifacts renders every target into memory without touching the filesystem, in canonical target order. The handler uses this to precheck and write each file; tests use it to assert byte-identical output.
func RenderScreensIndexArtifact ¶ added in v0.18.16
func RenderScreensIndexArtifact(root string) (ExportedArtifact, error)
RenderScreensIndexArtifact derives, renders, and hashes the screens.json artifact for the export tool's `screens` target: the deterministic bytes plus the content hash the handler reports per file. The RelPath is the canonical design/generated/ location; the handler's relocation logic owns any out_dir override.
type ExportedToken ¶
type ExportedToken struct {
// Name is the dotted token path (e.g. "color.brand.primary").
Name string
// Type is the leaf's $type (one of the nine DTCG types).
Type string
// Value is the raw (unresolved) $value.
Value any
// Resolved is the transitive alias resolution of Value. For a whole-value
// alias it is the final non-alias $value; for a plain value it is Value
// itself; for a value with no final scalar (an alias to a group, an alias
// chain that cannot end at a string, or a non-string structured value) it
// is nil and ResolvedOK is false.
Resolved any
// ResolvedOK reports whether Resolved is usable.
ResolvedOK bool
// HasAlias is true when Value (or any link in its alias chain) is a
// whole-value alias reference. Targets use it to decide between a literal
// and a var()/token() reference.
HasAlias bool
// AliasPath is the dotted path of the first whole-value alias in the
// chain (from Value itself, or the deepest resolvable link), used for
// cross-token references. "" when HasAlias is false.
AliasPath string
}
ExportedToken is one DTCG token leaf prepared for export.
type FSScanner ¶
type FSScanner struct{}
FSScanner is the production Scanner backed by os.Stat and filepath.WalkDir.
func (FSScanner) HasAnyFile ¶
HasAnyFile reports whether any path exists and is a regular file.
type FeedbackFileState ¶
type FeedbackFileState struct {
// Path is the workspace-relative slash path of the feedback file
// (e.g. "design/feedback/login.json").
Path string `json:"path"`
// Target is the §4d target (e.g. "design/wireframes/login.svg"); empty
// when the file is invalid or omits it.
Target string `json:"target,omitempty"`
// Status is the §4d top-level status ("changes-requested", "resolved",
// …); empty when absent.
Status string `json:"status,omitempty"`
// Resolution is the agent's closing note; empty while the loop is open.
Resolution string `json:"resolution,omitempty"`
// Annotations is the total annotation count.
Annotations int `json:"annotations"`
// Resolved is the count of annotations with `resolved: true`.
Resolved int `json:"resolved"`
// Pending is the count of annotations with `resolved: false` (i.e.
// Annotations - Resolved); it is the count the agent must address.
Pending int `json:"pending"`
// Valid reports whether the file parsed as the §4d document shape.
Valid bool `json:"valid"`
}
FeedbackFileState is the parsed, reporting-oriented view of one design/feedback/*.json file: the §4d fields plus the pending/resolved annotation counts the design_assets pending-feedback section needs.
Parse is lenient by design — a malformed document yields Valid=false and the zero counts rather than an error, so a reporting run never fails on one bad file (the validator, not the reader, is where a bad file becomes a finding).
func ScanFeedbackDir ¶
func ScanFeedbackDir(root string) ([]FeedbackFileState, error)
ScanFeedbackDir reads every design/feedback/*.json file under root and returns the parsed §4d state for each, sorted by path. A missing feedback directory yields an empty, non-nil slice; a real I/O failure reading the directory is returned as an error. Individual unreadable files are skipped rather than failing the walk: reporting must not break on one bad file.
func (FeedbackFileState) IsPending ¶
func (f FeedbackFileState) IsPending() bool
IsPending reports whether this feedback file still needs the agent's attention: its status is "changes-requested" OR it carries at least one annotation with `resolved: false` (§4d — the two conditions the design-system skill's loop starts on). A file that is not valid is never pending.
type Finding ¶
type Finding struct {
// File is the design workspace path the finding applies to.
File string
// Line is the 1-based source line; 0 means the line was not
// derivable.
Line int
Severity Severity
Message string
Rule string
}
Finding is one structured validator result for a single design file ({file, line?, severity, message, rule}, SP-140-1 §1g). Every design validator rule emits it: the token rules per §1a, and the later SVG/mermaid/README rules per §1g.
func DriftDirectionFindings ¶
func DriftDirectionFindings(report *DriftReport) []Finding
DriftDirectionFindings maps a drift report onto design_validate findings: one advisory finding per direction row that is AHEAD (§5c: both directions are reported, and the tools share the same rows). A synced direction emits no finding — the always-present two-row shape lives in the structured drift section, and a "nothing to do" finding on every clean run would be noise rather than signal. When a direction is ahead its finding names the count and the remedy, so the validator surface carries the run-me pointer too.
Severity is advisory by construction: ahead rows are warns (an actionable remedy), never errors. Drift is signal, not guilt (§5c).
func ValidateBrandDir ¶
ValidateBrandDir validates design/brand/brand.md under root, SP-140-1 §1d. A missing brand directory or brand.md yields no findings (not an error) — a whole-tree run must not fail on workspaces without a brand file.
func ValidateBrandFile ¶
ValidateBrandFile validates one brand.md file. A raw hex color is a warn finding (reference a token instead); a brand file with no token references is advisory (info). The result is never nil.
func ValidateComponent ¶ added in v0.18.12
ValidateComponent validates one design/components/*.svg component spec, SP-140-1 §1b applied to the component tier: a self-contained, low-fidelity SVG that shows one reusable UI component in its key variants and states. relPath is the design-relative path (e.g. "design/components/button.svg").
Components are the composable layer beneath screens: a screen wireframe is a composition of components, so a component spec carries no navigation semantics (no data-nav check) and its viewBox is the component's bounding box, not a device frame (no frame-match check). Everything else the wireframe contract requires applies unchanged: well-formed XML, a root <svg> with an integer viewBox, self-containment, the slug name rule, <text> labels (advisory), data-URI size (advisory), and token-usage comments (advisory).
Hard checks (SeverityError): well-formedness, root/viewBox, self- containment, slug name. Advisory (info/warn): text usage, data-URI size, token usage. The result is never nil.
func ValidateComponentInventory ¶ added in v0.18.12
ValidateComponentInventory runs the component-inventory pack over the whole design tree under root, SP-140-4 §4b "Component inventory": every component stem must appear in the README's Components listing. A component that appears there is orphaned — it exists on disk but the manifest does not track it — and is surfaced as `info`. Components are referenced by screen compositions (README Composition table, data-component attributes) rather than by flows, so the README listing is their only reference channel; unlike screens, a flow reference does not rescue a component.
A workspace with no components yields no findings; the result is sorted and never nil.
func ValidateComponentsDir ¶ added in v0.18.12
ValidateComponentsDir validates every design/components/*.svg under root. A missing or empty components directory yields no findings, not an error — a whole-tree validator run must not fail on workspaces without a component tier. Findings are sorted by file, line, rule, message; errors are I/O failures only.
func ValidateConsistency ¶
ValidateConsistency runs the cross-artifact consistency packs over the whole design tree under root, SP-140-4 §4b. It covers the cross-artifact checks the per-artifact validators cannot see:
- ruleConsistencyFlowEdgeWireframe: every non-terminal node referenced by a flow edge has a wireframe counterpart (a terminal state is exempt);
- ruleManifestLinkDangling: every artifact named by the README manifest's Screens/Flows listings maps to a real file (a Screens entry to a wireframe or screen, a Flows entry to a flow or wireframe).
The third bullet of §4b ("every data-nav target exists") is the existing ruleSVGDataNavDangling hard rule (svg.go); it is emitted through ValidateWireframesDir/ValidateWireframe and is covered by this pack's tests rather than re-implemented here.
It also runs the screen-inventory and naming packs of §4b (orphan screens, screens/ ↔ wireframes/ name mismatches, duplicate screen names), which live in inventory_rules.go alongside this pack because they share the same cross-artifact dispatch point and finding schema. A workspace with no design/ tree yields no findings. Findings are sorted by file, line, rule, message; the result is never nil.
func ValidateDataURISizes ¶
ValidateDataURISizes scans one SVG body for data: URIs above DataURISizeWarnThreshold and returns one warn finding per oversized URI (SP-140-1 §1h binary hygiene). lineHint, when positive, is the 1-based line of the first data: URI occurrence in the document, used to point the finding at the embedded raster. The result is never nil.
func ValidateFeedbackDir ¶
ValidateFeedbackDir validates all design/feedback/*.json files under root, SP-140-4 §4d: the file parses as a feedback JSON object (hard `feedback_json` when it does not), target is non-empty (hard `feedback_target_ref`), each annotation id is non-empty and unique (`feedback_annotation_id`, advisory), each annotation note is non-empty (`feedback_annotation_note`, advisory), and the target resolves to a known wireframe/screen/flow stem (`feedback_target_dangling`, advisory). A missing feedback directory yields no findings. Findings are sorted by file, line, rule, message.
func ValidateFile ¶
ValidateFile validates one design asset under root, SP-140-1 §1g. relPath is workspace-relative (e.g. "design/wireframes/login.svg"); a path that does not start with design/ is treated as relative to design/ (so "wireframes/login.svg" also resolves). The path must stay under design/ and the file must exist.
Dispatch is by location + extension: tokens/*.tokens.json, wireframes/*.svg, components/*.svg, icons/*.svg, flows/*.mmd, screens/*.html, README.md, brand/brand.md, and the repository-level git-contract files .gitattributes and .gitignore (§1h). Anything else is an error, not a silent pass. The result is never nil.
func ValidateFlowSource ¶ added in v0.18.17
ValidateFlowSource validates one flow source document (SP-140-9 §9b). relPath is the workspace-relative slash path; stems is the transitional screen-stem union from 9.1 (wireframe OR screen stems) that step screens resolve against. Hard checks (SeverityError): the document parses strictly, the name is a slug matching the file stem, step ids are unique, every non-empty next targets an existing step, a step is terminal exactly when its next is empty, and every screen resolves to a known stem. The result is never nil and sorted.
func ValidateFlows ¶
func ValidateFlows(root, relPath string, content []byte, wireframeStems []string, screenStems ...string) []Finding
ValidateFlows validates one mermaid flow file per SP-140-1 §1c/§1g, adjusted by SP-140-9 §9a: screen-flow node ids may equal either a wireframe stem or a delivered-screen stem during the format migration (9.4 collapses the tier; post-migration only screens remain). Hard checks (SeverityError): exactly one flowchart declaration, unparseable statement lines, and screen-flow node ids matching neither tier. The result is never nil.
func ValidateFlowsDir ¶
ValidateFlowsDir validates every design/flows/*.mmd under root, SP-140-1 §1c/§1g. Wireframe stems are gathered from design/wireframes/*.svg so the node-id == stem rule resolves. A missing or empty flows directory yields no findings, not an error. Findings are sorted by file, line, rule, message.
func ValidateFlowsTree ¶ added in v0.18.17
ValidateFlowsTree runs the §9b whole-tree flow checks on top of the per-file flow rules: for every flow source document, the .json schema rules; for every flows/*.mmd, the derived-drift check (a source + a header-less or stale .mmd) or the legacy notice; and the off-path cross-check for .json-bearing trees. Called from ValidateTree after the per-artifact passes. The result is never nil and sorted.
func ValidateGitAttributesContent ¶
ValidateGitAttributesContent checks one .gitattributes body against the SP-140-1 §1h contract, for callers (and single-file runs) that already hold the content. The result is never nil.
func ValidateGitContract ¶
ValidateGitContract runs the SP-140-1 §1h git-contract checks for the workspace at root:
- .gitattributes must carry the exact GitAttributesDiffHTMLLine; a missing file or a missing line yields a fix finding with the line to append (appending never clobbers the repo's existing text/binary rules). A line already present, however it is qualified (e.g. "design/**/*.svg text diff=html"), satisfies the contract.
- .gitignore must ignore design/.cache/ only; a missing file or line yields a fix finding with the line to append, and ignoring design/generated/ yields an advisory info finding.
A workspace with no design/ tree yields no findings: the contract belongs to the design tree, so a workspace without one is not a violation (matches the "missing design/ is not an error" rule of ValidateTree). Findings are sorted and the result is never nil.
func ValidateGitIgnoreContent ¶
ValidateGitIgnoreContent checks one .gitignore body against the SP-140-1 §1h ignore policy: design/.cache/ is required (fix), design/generated/ is advisory (info). The result is never nil.
func ValidateIconsDir ¶
ValidateIconsDir validates every design/icons/*.svg under root, SP-140-1 §1f: self-containment (no <script>, no external/local refs), the slug name rule, and — for the optional sprite.svg — <symbol> entries whose ids follow the slug rule. A missing or empty icons directory yields no findings, not an error. Findings are sorted by file, line, rule, message.
func ValidateInventory ¶
ValidateInventory runs the screen-inventory pack over the whole design tree under root, SP-140-4 §4b "Screen inventory": every wireframe stem must appear in either a flow (as a node) or the README (as a Screens listing). A stem that appears in neither is an orphan screen, surfaced as `info` — the screen exists on disk but nothing navigates to it or tracks it.
The tree's per-artifact rules stay with their own validators; this pack only reads the stems and the cross-artifact references. A workspace with no design/ tree yields no findings; the result is sorted and never nil.
func ValidateManifest ¶
ValidateManifest validates the design README (design/README.md) under root, SP-140-1 §1e/§1g: the frames: block parses as name → WxH (a missing, empty, or malformed block is a hard finding), every relative link resolves to a real design asset (hard finding), and the draft/review/ready status markers are present (advisory). A missing README yields no findings (not an error). Findings are sorted and the result is never nil.
func ValidateScreensDir ¶
ValidateScreensDir validates every design/screens/*.html under root, SP-140-1 §1i/§1g. Device frames are read from the design README (when present) to enable the advisory width check. A missing or empty screens directory yields no findings, not an error. Findings are sorted by file, line, rule, message.
func ValidateScreensIndex ¶ added in v0.18.16
ValidateScreensIndex runs the SP-143 §143.5 index/runtime checks: the screens.json drift (rule d) and the runtime source-hash (rule c). It is a whole-tree check (the index is one artifact spanning every screen), called from ValidateTree; a tree with no screens tier contributes nothing.
func ValidateTokens ¶
ValidateTokens validates one DTCG token file per SP-140-1 §1a: JSON parse, leaf/group structure, $type membership, and within-file alias resolution. Alias checks are per-file by design: tiers are self-contained, and cross-file aliasing is not specified.
The returned findings carry File=relPath and SeverityError — every token rule is a hard violation — sorted by file, line, rule, message. The result is never nil.
func ValidateTokensDir ¶
ValidateTokensDir validates every design/tokens/*.tokens.json under root, SP-140-1 §1a. A missing or empty tokens directory yields no findings, not an error — a whole-tree validator run must not fail on workspaces without tokens (or without design/ at all).
Files are validated in glob order (filepath.Glob is lexical); the combined findings are sorted by file, line, rule, message. Errors are I/O failures only — per-file rule violations are findings, not errors.
func ValidateTree ¶
ValidateTree validates the whole design/ tree under root, SP-140-1 §1g: every §1.x whole-tree validator runs (tokens, wireframes, flows, screens, icons, brand, README manifest), plus the §1h git contract (.gitattributes diff rule, .gitignore cache policy) and the §4d human feedback channel (design/feedback/*.json), and the combined findings are sorted by file, line, rule, message. root is the workspace root (the parent of design/).
A missing or partial design/ tree yields empty findings, not an error — a whole-tree run must not fail on workspaces without design assets. Errors are real I/O failures only (an unreadable file that matches a glob, for example); rule violations are findings, never errors. The result is never nil.
func ValidateWireframe ¶
func ValidateWireframe(relPath string, content []byte, knownStems []string, frames []Frame) []Finding
ValidateWireframe validates one SVG wireframe per SP-140-1 §1b. relPath is the design-relative path (e.g. "design/wireframes/login.svg"); knownStems is the set of all wireframe file stems (without .svg) used to resolve data-nav targets; frames (optional) enables the advisory frame-match check and is nil for runs without declared device frames.
Hard checks (SeverityError): XML well-formedness, a root <svg> with an integer viewBox, self-containment (no <script>, no external/local resource refs), the slug name rule, and data-nav targets resolving to a wireframe stem. Advisory checks (SeverityInfo): frame match, <text> usage, and stable ids on interactive elements. The result is never nil.
func ValidateWireframesDir ¶
ValidateWireframesDir validates every design/wireframes/*.svg under root, SP-140-1 §1b/§1g. All wireframe stems are gathered first so data-nav targets resolve across files; device frames are read from the design README (when present) to enable the advisory frame-match check. A missing or empty wireframes directory yields no findings, not an error — a whole-tree validator run must not fail on workspaces without wireframes.
Files are validated in glob order; the combined findings are sorted by file, line, rule, message. Errors are I/O failures only — per-file rule violations are findings, not errors.
type FlowCounts ¶
type FlowCounts struct {
Path string `json:"path"`
Name string `json:"name"`
Nodes int `json:"nodes"`
Edges int `json:"edges"`
}
FlowCounts is the node/edge count of one design/flows/*.mmd file (SP-140-2 §2c "flow node/edge counts").
type FlowEdge ¶
type FlowEdge struct {
Source string
Target string
// Label is the |trigger| text on a labeled edge (A -->|trigger| B);
// "" on a label-free edge. SP-140-9 §9b renders the derived .mmd's
// triggers as edge labels, so the parser surfaces them instead of
// dropping them.
Label string
HasArrow bool
}
FlowEdge is a single directed/undirected edge extracted from a flowchart.
type FlowExportDoc ¶ added in v0.18.17
type FlowExportDoc struct {
// Name is the flow name (= the source document stem).
Name string
// Direction is the mermaid layout direction (always defaultFlowDirection
// today).
Direction string
// Nodes are the graph nodes: the walk steps in source order first, then
// the off-path screens sorted by stem.
Nodes []FlowExportNode
// Edges are the graph edges: the step-to-step walk edges in step order
// first, then the off-path nav edges sorted by (source, trigger, target).
Edges []FlowExportEdge
}
FlowExportDoc is the derived graph of one flow: the walk (the steps) plus every data-nav-reachable screen marked off-path. It is the render model of the .mmd — everything RenderFlowMMD emits, nothing else.
func DeriveFlowMMD ¶ added in v0.18.17
func DeriveFlowMMD(src *FlowSource, screens *ScreenIndexDoc) *FlowExportDoc
DeriveFlowMMD derives the export doc for one flow source against the screens' data-nav graph (the parsed screens index). A step screen unknown to the index contributes no nav edges — the screen validator reports the unknown stem, and the export still renders the walk. Self-loop navs (a screen navigating to itself) are suppressed: they are state navigation inside one screen, not graph edges.
type FlowExportEdge ¶ added in v0.18.17
type FlowExportEdge struct {
Source string
// Target is the target node id (a step id or an off-path screen stem).
Target string
// Trigger is the edge label: the step's trigger for walk edges, the
// data-nav trigger for off-path edges; "" renders an unlabeled edge.
Trigger string
// OffPath marks an edge the flow steps do not account for (a data-nav
// edge reached by closure, not by a step).
OffPath bool
}
FlowExportEdge is one edge of the derived graph.
type FlowExportNode ¶ added in v0.18.17
type FlowExportNode struct {
// ID is the §9c stable node id: the step id for walk nodes, the screen
// stem for off-path nodes.
ID string
// Label is the rendered label: the step's label text, or the screen stem.
Label string
// Screen is the stem of the screen the node stands for; "" for a
// non-screen flow's step (agent-turn walks no surface) and for pure
// walk nodes whose screen is only implied by the step.
Screen string
// OffPath marks a data-nav-reachable screen that is not a step.
OffPath bool
}
FlowExportNode is one node of the derived graph.
type FlowExportedArtifact ¶ added in v0.18.17
type FlowExportedArtifact struct {
// Name is the flow name (the .json stem).
Name string
// RelPath is the workspace-relative slash path (design/flows/<name>.mmd).
RelPath string
// Content is the exact byte content to write.
Content []byte
// Hash is the §9b input hash carried in the provenance header.
Hash string
}
FlowExportedArtifact is one rendered derived .mmd ready to write.
func RenderAllFlowMDMArtifacts ¶ added in v0.18.17
func RenderAllFlowMDMArtifacts(root string) ([]FlowExportedArtifact, error)
RenderAllFlowMDMArtifacts derives the export artifact for every flow source under root, in flow-name order.
func RenderFlowMDMArtifact ¶ added in v0.18.17
func RenderFlowMDMArtifact(root string, name string) (*FlowExportedArtifact, error)
RenderFlowMDMArtifact derives one flow's export artifact under root: read the flow source, read the screens, derive, render, hash.
type FlowSource ¶ added in v0.18.17
type FlowSource struct {
// Name is the flow's slug; it must equal the document's file stem
// (design/flows/sign-up.json names itself "sign-up"), the same
// identity-by-location rule the rest of the tree uses.
Name string `json:"name"`
// Steps are the process steps in walk order. The first step is the
// entry; a step with an empty Next is terminal.
Steps []FlowStep `json:"steps"`
}
FlowSource is the parsed v1 flow source document.
func ParseFlowSource ¶ added in v0.18.17
func ParseFlowSource(relPath string, content []byte) (*FlowSource, error)
ParseFlowSource parses one flow source document strictly: it must be a JSON object with exactly the v1 fields, a slug name, and at least one step. Structural problems (bad JSON, unknown fields, empty required fields) come back as a single error whose message names the fault — the validator renders it as a finding; the export refuses on it. Name-vs-filename is the caller's check (it owns the path).
type FlowSourceFile ¶ added in v0.18.17
type FlowSourceFile struct {
RelPath string
Name string
Raw []byte
Source *FlowSource
}
FlowSourceFile is one flow source document as read from disk: its workspace-relative slash path, its stem (the flow name), its exact bytes, and the parsed source. Produced by ReadFlowSources.
func ReadFlowSources ¶ added in v0.18.17
func ReadFlowSources(root string) ([]FlowSourceFile, error)
ReadFlowSources reads and parses every flow source under root, sorted by flow name. A malformed document is an error naming the file — the export refuses on it; the validator reports it as a finding instead (via ValidateFlowSource) and never needs this strictness.
type FlowStep ¶ added in v0.18.17
type FlowStep struct {
// ID is the step's stable node id (§9c): the derived .mmd and the layout
// sidecar key on it, and a regeneration must not change it.
ID string `json:"id"`
// Label is the human step name rendered as the node label.
Label string `json:"label"`
// Screen is the stem of the screen the step happens on. It resolves to
// a screen stem or — during the §9a migration — a wireframe stem.
// Optional: a non-screen flow's steps carry no screen (SP-140-9 §9b's
// agent-turn example walks plan → tools → review with no surface).
Screen string `json:"screen,omitempty"`
// Trigger is the label on the edge to the next step; "" on a terminal
// step (and rendered as a plain unlabeled edge when Next is set without
// a trigger).
Trigger string `json:"trigger,omitempty"`
// Next is the id of the following step; "" marks the step terminal.
Next string `json:"next,omitempty"`
}
FlowStep is one step of a FlowSource.
type Flowchart ¶
type Flowchart struct {
Direction string
Declarations int // number of flowchart/graph declaration lines
Nodes map[string]FlowNode
Edges []FlowEdge
NodeOrder []string // first-seen order of node ids
BadLines []int // 1-based line numbers of statements that could not be parsed
}
Flowchart is the result of parsing a mermaid flowchart file (the flowchart subset — node/edge extraction, not full syntax fidelity, SP-140-1 §1c).
func ParseFlowchart ¶
ParseFlowchart parses a mermaid flowchart document and extracts its nodes and edges. It is a subset parser: it handles flowchart/graph declarations, node definitions (id plus any shape/label), and edge statements (including chained A --> B --> C forms), while skipping comments, subgraph/end, and styling keywords (classDef/class/style/linkStyle/click). Full syntax fidelity is intentionally not required (SP-140-1 §1c).
type Frame ¶
Frame is a device frame declared in the manifest's frames block.
func ParseFrames ¶
ParseFrames extracts device frames from manifest content. The frames block starts at a non-indented `frames:` line and continues through indented `name: WxH` entries until the first non-indented, non-blank line (or end of input). Blank lines inside the block are ignored, and any indentation depth counts as an entry line. Frames are returned in declaration order.
A manifest without a frames block yields an empty slice and no error; frame presence is validated separately. A frames block with no entries, duplicate names (checked across all blocks in the content), a frame name not matching the slug rule, a non-integer width or height, a non-lowercase-x separator, or non-positive dimensions is an error carrying the 1-based line number.
type Inventory ¶
type Inventory struct {
Assets []AssetRow `json:"assets"`
TokenGroups []TokenGroupCount `json:"tokenGroups"`
Flows []FlowCounts `json:"flows"`
Manifest ManifestSummary `json:"manifest"`
Findings []Finding `json:"findings"`
BySeverity map[string]int `json:"bySeverity"`
}
Inventory is the structured `design_assets` result for a workspace that has a design/ tree (SP-140-2 §2c). Assets holds one row per discovered asset, sorted by path. TokenGroups holds per-group leaf-token counts sorted by group name. Flows holds per-flow node/edge counts sorted by path. Findings holds the (uncached, always-fresh) validator results for the tree, and BySeverity tallies them.
func Scan ¶
Scan walks the design/ tree under root and builds its inventory (SP-140-2 §2c). root is the workspace root (the parent of design/); it must already contain a design/ directory — callers check FileExists first.
The walk is bounded to the canonical subdirectories; unknown files inside design/ are reported with KindUnkn rather than skipped, so the model sees what is actually there. Findings come from ValidateTree and are advisory: an error-severity finding never makes Scan fail. Only a real I/O failure (an unreadable matching file) returns an error.
type ManifestSummary ¶
type ManifestSummary struct {
Exists bool `json:"exists"`
Frames []Frame `json:"frames"`
Status []string `json:"status"`
}
ManifestSummary is the high-level manifest view: whether design/README.md exists, the device frames it declares, and the status markers it documents (SP-140-2 §2c "manifest summary").
type ProviderWordHit ¶
ProviderWordHit is one occurrence of a proprietary product name in a scanned file. Line is 1-based; Text is the trimmed offending line so a failure message can point at the exact coupling.
func ScanProviderNames ¶
func ScanProviderNames(root, lang string, fs Scanner) ([]ProviderWordHit, bool, error)
ScanProviderNames walks the design-tier surface for lang (relative to root) — the directory trees plus the explicit files in designTier — and returns every occurrence of a name in ProprietaryNames, sorted by file then line. Absent roots are skipped silently (the webui design directories do not exist until SP-140-3 lands). The second return value reports whether any root for lang existed, so a caller can fail loudly on a vacuous scan instead of passing green over nothing.
type Scanner ¶
type Scanner interface {
// HasAnyDir reports whether any of the given filesystem paths exists and
// is a directory.
HasAnyDir(roots ...string) bool
// HasAnyFile reports whether any of the given filesystem paths exists and
// is a regular file.
HasAnyFile(paths ...string) bool
// WalkDir visits every regular, non-hidden file under root, honoring
// SkipDir results. The callback receives the full path and the directory
// entry so the caller can inspect the mode and control descent.
WalkDir(root string, fn func(path string, d os.DirEntry, err error) error) error
}
Scanner is the read-only file surface ScanProviderNames needs. Tests inject a fake so the scan contract can be exercised without touching disk; production callers use FSScanner.
type ScreenBrief ¶
type ScreenBrief struct {
// ScreenName is the requested wireframe stem.
ScreenName string `json:"screenName"`
// Depth is the depth the brief was rendered at (summary|full).
Depth string `json:"depth"`
// Found reports whether the screen exists in the design tree (a wireframe
// with that stem, or a feedback file targeting it). An unknown screen
// yields Found=false with Guidance, never an error.
Found bool `json:"found"`
// Purpose is the screen's purpose from the README Screens listing (§5g
// "README purpose"). "" when the README lists no entry for the screen.
Purpose string `json:"purpose,omitempty"`
// Status is the README status marker (draft|review|ready). "" when the
// README declares none.
Status string `json:"status,omitempty"`
// ListedInReadme reports whether the README Screens listing names the
// screen (the §4b inventory signal: an unlisted wireframe is an orphan).
ListedInReadme bool `json:"listedInReadme"`
// Wireframe is the wireframe path (design/wireframes/<stem>.svg). It is
// always set: the brief names where the wireframe *would* live, so a
// not-found brief still points the agent at the right file.
Wireframe string `json:"wireframe"`
// WireframeExists reports whether that file exists.
WireframeExists bool `json:"wireframeExists"`
// FlowsIn / FlowsOut are the flow edges touching the screen, split by
// direction (§5g "flows in/out with triggers"). A self-edge appears in
// both. Both are always non-nil.
FlowsIn []BriefFlowEdge `json:"flowsIn"`
FlowsOut []BriefFlowEdge `json:"flowsOut"`
// TokenPaths are the DTCG tokens the wireframe refers to, sorted by path
// (§5g "token paths to consume").
TokenPaths []BriefTokenRef `json:"tokenPaths"`
// TokenGroups are the top-level token groups available to consume, sorted
// by group — the tree's token vocabulary, so the agent knows what exists
// even when the wireframe refers to nothing yet.
TokenGroups []TokenGroupCount `json:"tokenGroups"`
// Feedback is the §4d pending-feedback view for the screen (§5g "open
// feedback annotations"). The matching feedback file is the one whose §4d
// target resolves to the screen's stem, or — when no target matches — the
// one whose own file name (stem) equals the screen; the first match by
// path order wins (ScanFeedbackDir sorts by path).
Feedback BriefFeedback `json:"feedback"`
// ScreenFile is the delivered design/screens/<stem>.html path, when one
// exists (full depth only when it differs from the wireframe).
ScreenFile string `json:"screenFile,omitempty"`
// Guidance is a not-found helper: what to do when the screen is unknown
// ("" when the screen exists).
Guidance string `json:"guidance,omitempty"`
// Notes carry run-level caveats (e.g. a tree with no README).
Notes []string `json:"notes,omitempty"`
// WritesNothing states the §5g contract explicitly: the brief is read-only
// advisory context, never a generator. Always true.
WritesNothing bool `json:"writesNothing"`
}
ScreenBrief is the structured §5g brief for one screen.
func BuildScreenBrief ¶
func BuildScreenBrief(in ScreenBriefInput) (*ScreenBrief, error)
BuildScreenBrief reads the design/ tree under root and returns the §5g brief for the screen. It never writes and never errors on a missing screen or a missing design/ tree: an unknown screen yields Found=false with guidance, and a workspace with no design/ yields the same plus a note. Only a real I/O failure reading a file it must read is returned as an error.
type ScreenBriefInput ¶
type ScreenBriefInput struct {
// Root is the workspace root (the parent of design/).
Root string
// ScreenName is the wireframe stem to brief.
ScreenName string
// Depth is summary|full; "" defaults to summary.
Depth string
}
ScreenBriefInput is the read-only input for BuildScreenBrief.
type ScreenIndexDoc ¶ added in v0.18.16
type ScreenIndexDoc struct {
Provenance string `json:"provenance"`
SourceHash string `json:"source-hash"`
Source string `json:"source"`
FormatVersion int `json:"format-version"`
Screens []ScreenIndexEntry `json:"screens"`
}
ScreenIndexDoc is the whole design/screens/ graph derived from the screens' data-attributes. Screens are sorted by stem; Nav is sorted by (trigger, to) and deduplicated. Everything is derived, so the JSON is byte-identical for identical screen bytes — the determinism the drift check depends on.
func DeriveScreensIndex ¶ added in v0.18.16
func DeriveScreensIndex(root string) (*ScreenIndexDoc, error)
DeriveScreensIndex reads design/screens/*.html under root and derives the screens.json document (screen graph + provenance over the input bytes). An empty screens tier is not an error: it yields an empty-document index; the export tool refuses to write an empty one and ValidateScreensIndex treats an absent index as clean for an empty tier, so trees without screens (or without the runtime kit at all) validate clean.
func ReadScreensIndex ¶ added in v0.18.16
func ReadScreensIndex(data []byte) (*ScreenIndexDoc, error)
ReadScreensIndex parses the design/generated/screens.json bytes into a ScreenIndexDoc. A file that does not parse yields an error with the JSON fault included, so the drift finding can name the real problem (a hand-edit that broke the JSON, not just a stale index).
type ScreenIndexEntry ¶ added in v0.18.16
type ScreenIndexEntry struct {
// Stem is the screen's file stem (the slug nav targets and the README
// Screens listing use).
Stem string `json:"stem"`
// Device is the screen's data-device attribute, or "" when absent (the
// desktop default; chrome.css draws no chrome for it).
Device string `json:"device"`
// Frame is the px width a container on this screen is sized to when it
// matches a README-declared frame (advisory per the existing frame rule),
// else 0. Derived from the same width scan the frame validator runs.
Frame int `json:"frame"`
// States are the screen's declared state names (html[data-states]),
// in declaration order, deduplicated.
States []string `json:"states"`
Nav []ScreenIndexNav `json:"nav"`
}
ScreenIndexEntry is one screen's derived metadata.
func ParseScreenHTML ¶ added in v0.18.16
func ParseScreenHTML(content []byte, frames []Frame) ScreenIndexEntry
ParseScreenHTML derives the screens-index view of one screen document: the html element's data-device / data-states, the body's data-nav edges, and the declared frame a container width matches. It reads data-attributes only — the point of the index is that other tooling never parses HTML at read time, so the one place that does is here.
frames is the README-declared device frame list (may be nil). A container width matching a declared frame width is reported as Frame (the first match in frame order); no match or no frames yields 0.
type ScreenIndexNav ¶ added in v0.18.16
type ScreenIndexNav struct {
// only to:.
Trigger string `json:"trigger,omitempty"`
}
ScreenIndexNav is one data-nav edge: the target stem and the authored trigger label (`to:<stem>;trigger:<label>`).
type Severity ¶
type Severity string
Severity classifies a validator finding. error marks a hard violation; warn and info are advisory; fix marks a machine-applicable fix (warn and fix are added by the advisory pass, SP-140-1 §1h).
const ( // SeverityError is a hard violation that fails validation. SeverityError Severity = "error" // SeverityWarn is an advisory finding added by SP-140-1 §1h. SeverityWarn Severity = "warn" // SeverityInfo is an advisory finding. SeverityInfo Severity = "info" // SeverityFix is a machine-applicable fix added by SP-140-1 §1h. SeverityFix Severity = "fix" )
type StatusDriftRow ¶ added in v0.18.9
type StatusDriftRow struct {
Ahead bool `json:"ahead"`
Synced bool `json:"synced"`
Count int `json:"count"`
Summary string `json:"summary,omitempty"`
Remedy string `json:"remedy,omitempty"`
NextStep string `json:"nextStep,omitempty"`
}
StatusDriftRow is one §5c drift direction for the status payload: the fields the health strip renders (count + remedy, §6c) plus NextStep so the strip can route a click (design-ahead → Tokens tab; code-ahead → agent-panel prefill, SP-140-6 §6c). Shape-mirrors DriftDirectionRow without exposing the full evidence machinery.
type StatusFeedback ¶ added in v0.18.9
type StatusFeedback struct {
PendingCount int `json:"pendingCount"`
Pending []StatusFeedbackEntry `json:"pending"`
}
StatusFeedback is the feedback section: the pending count plus the sorted pending targets.
type StatusFeedbackEntry ¶ added in v0.18.9
type StatusFeedbackEntry struct {
Target string `json:"target"`
Unresolved int `json:"unresolved"`
Status string `json:"status,omitempty"`
}
StatusFeedbackEntry is one pending feedback target in the status payload: the §4d target and how many annotations are still open on it.
type StatusFinding ¶ added in v0.18.9
type StatusFinding struct {
File string `json:"file"`
Line int `json:"line"`
Severity string `json:"severity"`
Message string `json:"message"`
Rule string `json:"rule"`
}
StatusFinding is one validator finding in the status payload — the §1g finding shape ({file, line?, severity, message, rule}) with JSON tags for the HTTP surface.
type StatusValidation ¶ added in v0.18.9
type StatusValidation struct {
Errors int `json:"errors"`
Warnings int `json:"warnings"`
Infos int `json:"infos"`
Findings []StatusFinding `json:"findings"`
}
StatusValidation is the validation section: authoritative tallies plus the capped finding list. Warnings count SeverityWarn AND SeverityFix — both are advisory-and-actionable classes, and the strip renders one "warnings" chip.
type SyncApplyPlan ¶
type SyncApplyPlan struct {
// Mode is always "apply" (this is the apply half's plan).
Mode string `json:"mode"`
// Report is the analyze report the plan was derived from, so a consumer can
// see the full delta set behind a partial apply.
Report *SyncReport `json:"report"`
// Writes is the ordered safe-subset write set (deterministic, design/-only).
Writes []SyncApplyWrite `json:"writes"`
// Proposals are the deltas apply deliberately left unwritten (inferred
// proposals, plus any unsafe/unplannable delta).
Proposals []SyncApplyProposal `json:"proposals"`
// Refused are design-file writes that were suppressed because a write would
// have escaped design/ (§5e). Always empty in a plan produced by
// PlanSyncApply, which drops such writes; kept so a consumer can distinguish
// "nothing to apply" from "something was refused".
Refused []string `json:"refused,omitempty"`
// AppliedCount / ProposalCount are the write and proposal tallies.
AppliedCount int `json:"appliedCount"`
ProposalCount int `json:"proposalCount"`
// WritePaths is the sorted set of distinct design/ paths the plan writes,
// surfaced so a caller can assert design/-confinement without walking Writes.
WritePaths []string `json:"writePaths"`
// Notes carries run-level caveats (e.g. a malformed token file a write
// could not be planned from).
Notes []string `json:"notes,omitempty"`
}
SyncApplyPlan is the pure result of planning a report's safe subset. The handler performs the Writes; nothing here touches the filesystem.
func PlanSyncApply ¶
func PlanSyncApply(report *SyncReport, read SyncFileReader) *SyncApplyPlan
PlanSyncApply is the pure apply core: given a report and a reader for the current design-file bytes, it returns the plan of safe-subset writes plus the proposals apply leaves alone. It performs no writes and never plans a write outside design/.
A nil report (or one with no deltas) yields an empty, valid plan — apply on an already-synced tree is a no-op.
func (*SyncApplyPlan) IsConfinedToDesign ¶
func (p *SyncApplyPlan) IsConfinedToDesign() bool
IsConfinedToDesign reports whether every write in the plan targets a file inside design/ (§5e hard assertion). An empty plan is trivially confined.
type SyncApplyProposal ¶
type SyncApplyProposal struct {
// Delta is the semantic delta description.
Delta string `json:"delta"`
// Kind is the delta kind.
Kind DeltaKind `json:"kind"`
// Basis is literal|structural|inferred.
Basis deltaBasis `json:"basis"`
// Reason is why apply left it alone: "inferred" (§5b proposal),
// "outside-design" (§5e refusal), or a specific refusal message.
Reason string `json:"reason"`
// ProposalKind / Proposed carry the §5b proposal fields when the delta is
// an inferred proposal.
ProposalKind string `json:"proposalKind,omitempty"`
Proposed string `json:"proposed,omitempty"`
// CodeFiles are the touched code files the delta came from.
CodeFiles []string `json:"codeFiles"`
}
SyncApplyProposal records a delta the apply half deliberately did NOT write: an inferred delta (a proposal §5b leaves to the agent/user via normal file edits), or a malformed/unsafe delta that could not be applied mechanically.
type SyncApplyWrite ¶
type SyncApplyWrite struct {
// Path is the design/ file to write, workspace-relative.
Path string `json:"path"`
// Op is the planning operation that produced this write.
Op SyncPlanningOp `json:"op"`
// Kind is the delta kind (token|wireframe|flow) this write serves, so a
// consumer can group the run by the §5b vocabulary.
Kind DeltaKind `json:"kind"`
// Delta is the semantic delta description this write applies.
Delta string `json:"delta"`
// Token is the DTCG token path the write concerns ("" otherwise).
Token string `json:"token,omitempty"`
// Stem is the wireframe stem the write concerns ("" otherwise).
Stem string `json:"stem,omitempty"`
// Edge is the flow edge the write adds ("" otherwise).
Edge string `json:"edge,omitempty"`
// Status is the status marker the artifact carries (draft for §5b-created
// artifacts; "" for a token revalue).
Status string `json:"status,omitempty"`
// Created is true when the write creates a file that did not exist.
Created bool `json:"created"`
// Content is the full new file bytes. It is deliberately JSON-omitted:
// the plan's JSON shape names *what* will be written, and the handler is
// the one that writes it; a consumer needing the bytes reads the file
// after apply.
Content []byte `json:"-"`
}
SyncApplyWrite is one design-file write the apply half will perform. Path is workspace-relative, slash-separated, and always confined to design/ (§5e). Content is the full new file bytes (the write is a whole-file replace, which is what makes the ChangeTracker see an ordinary workspace edit).
type SyncDelta ¶
type SyncDelta struct {
// Delta is the one-line description of the detected change.
Delta string `json:"delta"`
// Kind is token|wireframe|flow|feedback.
Kind DeltaKind `json:"kind"`
// Basis is literal|structural|inferred.
Basis deltaBasis `json:"basis"`
// Confidence is high|medium|low.
Confidence Confidence `json:"confidence"`
// DesignFiles are the design/ paths this delta would touch, sorted and
// slash-separated, workspace-relative. Always non-nil (possibly empty) so
// the JSON shape is stable; empty means "the semantic layer has no
// counterpart yet" (apply's job to create one, subject to basis).
DesignFiles []string `json:"designFiles"`
// SafeToApply is the automation gate: true for literal and structural
// deltas, false for inferred (and for any delta whose basis could not be
// established). Redundant with Basis but explicit for a model reading the
// report.
SafeToApply bool `json:"safeToApply"`
// Proposal marks an inferred delta: a proposal (new token, or switch to an
// existing one), never auto-applied.
Proposal bool `json:"proposal,omitempty"`
// ProposalKind is new-token|switch-to-existing for an inferred delta.
ProposalKind string `json:"proposalKind,omitempty"`
// Proposed is the proposed new token path (inferred, new token) or the
// design/ path proposal ("" otherwise).
Proposed string `json:"proposed,omitempty"`
// Candidate is the existing DTCG token path an inferred delta could switch
// to, when one matches the literal value ("" otherwise).
Candidate string `json:"candidate,omitempty"`
// Token is the DTCG token path a token delta concerns ("" otherwise).
Token string `json:"token,omitempty"`
// TokenFile is the design/tokens/*.tokens.json the token lives in
// ("" otherwise), from the token export machinery.
TokenFile string `json:"tokenFile,omitempty"`
// TokenEntry is the DTCG entry key as it appears in TokenFile: the
// dotted token path for new/revalued entries, or the pre-change alias
// target for a literal rename ("" otherwise).
TokenEntry string `json:"tokenEntry,omitempty"`
// WireframeStem is the proposed wireframe stem for a structural delta
// ("" otherwise).
WireframeStem string `json:"wireframeStem,omitempty"`
// FlowEdge is the proposed flow edge ("login --> home") for a structural
// delta ("" otherwise).
FlowEdge string `json:"flowEdge,omitempty"`
// FlowFile is the design/flows/*.mmd the edge would be added to
// ("" otherwise).
FlowFile string `json:"flowFile,omitempty"`
// Status is the status a created artifact would carry (draft for
// §5b-created wireframes/edges).
Status string `json:"status,omitempty"`
// CodeFiles are the touched code files this delta was detected in, sorted
// and slash-separated.
CodeFiles []string `json:"codeFiles"`
// Evidence is the offending code snippet/line, trimmed (best-effort).
Evidence string `json:"evidence,omitempty"`
}
SyncDelta is one detected semantic delta (SP-140-5 §5b). Delta is the human/agent-readable description; Kind/Basis/Confidence govern what apply may do with it; DesignFiles names the design/ paths it would touch.
type SyncFileInput ¶
SyncFileInput is one touched code file: its path as the caller named it and its current content.
type SyncFileReader ¶
SyncFileReader returns the current bytes of an existing design file (path is workspace-relative, slash-separated). It returns (nil, false) when the file does not exist (a create) or cannot be read (a note, not a failure). The handler supplies a workspace-confined reader; the pure core never does I/O.
type SyncInput ¶
type SyncInput struct {
// Root is the workspace root (the parent of design/).
Root string
// Touched are the code files to analyse. Paths may be absolute or
// workspace-relative; Contents maps each path (as given) to its bytes.
// A path with no content entry is skipped (advisory, named in Skipped).
Touched []SyncFileInput
}
SyncInput is the analyze-mode input: the touched code files and their current contents, plus the workspace root so the design/ tree can be read.
type SyncPlanningOp ¶
type SyncPlanningOp = string
SyncPlanningOp is the kind of write a plan step performs.
const ( // SyncPlanningUpdateToken rewrites an existing DTCG entry's value in place // (a literal token revalue). Delta is the token path. SyncPlanningUpdateToken SyncPlanningOp = "update-token" // SyncPlanningAddToken adds a DTCG entry that does not yet exist (a literal // token rename/addition the code introduced). Delta is the token path. SyncPlanningAddToken SyncPlanningOp = "add-token" // SyncPlanningAddWireframe creates a skeleton wireframe SVG with draft // status. Delta is the wireframe stem. SyncPlanningAddWireframe SyncPlanningOp = "add-wireframe" // SyncPlanningAddFlowEdge appends the proposed edge to a flow file. Delta is // the edge ("login --> home"). SyncPlanningAddFlowEdge SyncPlanningOp = "add-flow-edge" )
Planning ops, fixed by the apply half.
type SyncReport ¶
type SyncReport struct {
// Mode is the mode the report was produced in ("" for a bare analyze
// call; apply mode is item 5.4). It is advisory metadata for a stored
// report.
Mode string `json:"mode,omitempty"`
// TokensPath / WireframeDir / FlowsDir are the design-tier directories the
// run cross-referenced, slash-separated and workspace-relative. They are
// always present so the report reads as a design-tree lookup rather than a
// bag of deltas.
TokensPath string `json:"tokensPath"`
WireframeDir string `json:"wireframesPath"`
FlowsDir string `json:"flowsPath"`
// TouchedCount is the number of touched code files analysed.
TouchedCount int `json:"touchedCount"`
// DeltaCount is the number of deltas; also broken out by Basis and Kind
// so a model reads the automation split without counting.
DeltaCount int `json:"deltaCount"`
ByBasis map[string]int `json:"byBasis"`
ByKind map[string]int `json:"byKind"`
// Deltas is the ordered semantic-delta work list (deterministic).
Deltas []SyncDelta `json:"deltas"`
// WritesNothing states the §5e invariant explicitly: analyze mode writes
// no files, and apply (5.4) is confined to design/. Always true here.
WritesNothing bool `json:"writesNothing"`
// NextStep is a one-line pointer to the apply half / the skill loop, so a
// model knows analyze is the first half.
NextStep string `json:"nextStep"`
// Skipped are touched paths the analysis could not read (missing on disk,
// a directory, or not a recognised UI file). Advisory.
Skipped []string `json:"skipped,omitempty"`
// Notes carries human-readable caveats (e.g. "no design/ tree, so
// literal deltas have no DTCG counterpart"). Deterministic order.
Notes []string `json:"notes,omitempty"`
}
SyncReport is the structured result of one `design_sync` analyze run (SP-140-5 §5b). It is deliberately the shape apply mode consumes: Deltas is the ordered work list, each delta naming the design files it would touch.
func AnalyzeTouchedFiles ¶
func AnalyzeTouchedFiles(in SyncInput) (*SyncReport, error)
AnalyzeTouchedFiles is the pure analyze core (no I/O beyond the design/ tree read): given the touched code files and the workspace root, it returns the §5b sync report. It never writes anything.
The analysis is convention-based, in three passes over the touched files:
literal — CSS custom properties and token references, cross-referenced
against the DTCG export projection.
structural — router routes/screens, component file names, and nav targets.
inferred — raw hex colours and magic spacing values with no token
counterpart, emitted as proposals.
The result is deterministic: touched paths and every emitted slice are sorted, and the delta order is fixed by CompareDeltas.
type TokenExport ¶
type TokenExport struct {
Leaves []ExportedToken
// InputHash is the §5f provenance hash of the token source documents this
// projection was built from (see TokenExportInputHash). Every generated
// artifact's header records it, so the artifact can be verified against
// design/tokens/ at any checkout, offline.
InputHash string
}
TokenExport is the resolved, target-neutral projection of every DTCG token leaf in a tier set. Leaves is sorted by Name; the validators have already established that each leaf has a well-formed $type, so the projection can assume one.
func ResolveExportTokens ¶
func ResolveExportTokens(root string) (*TokenExport, error)
ResolveExportTokens reads every design/tokens/*.tokens.json under root and projects each DTCG leaf into an ExportedToken. Files are read in glob order (filepath.Glob is lexical) and the leaves are sorted by dotted name, so the result is identical for identical inputs.
A malformed tokens file is an error: the validator is the reporting surface (design_validate), but export must not silently emit a partial theme from a file it could not parse. A missing or empty tokens/ directory yields ErrNoTokensForExport.
A *dirty alias graph* — any dangling or cyclic whole-value alias in any source file — is also an error (ErrDirtyAliasGraph, item 5.2): export refuses rather than emit a theme whose var()/*token* references cannot be resolved. The check reuses the validator's own alias machinery (checkAliases), so export and design_validate agree by construction on what "dirty" means.
type TokenExportSource ¶
type TokenExportSource struct {
// Path is the workspace-relative slash path of the source document.
Path string
// Name is the source document's base name (color.tokens.json); it is the
// stable sort key, so the hash does not depend on the workspace root.
Name string
// Content is the document's exact bytes.
Content []byte
}
TokenExportSource is one DTCG token source document as read from disk: its workspace-relative slash path (design/tokens/<name>.tokens.json) and its exact bytes. Collected by ResolveExportTokens and retained on the TokenExport so the provenance header can be built from the same read that produced the leaves (no second filesystem pass, no chance of drift).
func ScreensIndexSources ¶ added in v0.18.16
func ScreensIndexSources(root string) ([]TokenExportSource, error)
ScreensIndexSources reads every design/screens/*.html under root into the §5f hash-input shape (workspace-relative slash path, stem, exact bytes), sorted by stem. A missing screens directory yields an empty slice and no error.
type TokenGroupCount ¶
TokenGroupCount is the number of leaf tokens in one top-level token group across every design/tokens/*.tokens.json file (SP-140-2 §2c "token group counts"). Counting is per group name, summed across files, because tokens are split one file per tier and consumers glob.
Source Files
¶
- brand.go
- brief.go
- consistency.go
- deprecate.go
- design.go
- drift.go
- drift_view.go
- export.go
- export_alias.go
- export_artifacts.go
- export_render.go
- feedback.go
- finding.go
- flowchart.go
- flowsource.go
- flowsource_io.go
- flowsource_mmd.go
- frames.go
- gitcontract.go
- icons.go
- inventory.go
- inventory_listing.go
- inventory_rules.go
- manifest.go
- providernames.go
- scaffold.go
- screens.go
- screensindex.go
- status.go
- svg.go
- svg_helpers.go
- sync.go
- sync_apply.go
- sync_apply_tokens.go
- sync_delta_inferred.go
- sync_delta_structural.go
- sync_delta_tokens.go
- sync_report.go
- tokens.go
- tokens_alias.go
- tokens_parse.go
- tree.go
- utilities.go