design

package
v0.18.8 Latest Latest
Warning

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

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

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.

Index

Constants

View Source
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, TODO item 5.8).

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.

View Source
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]+)*$`
)
View Source
const (
	// TokenSubdir holds the W3C DTCG *.tokens.json files.
	TokenSubdir = "tokens"

	// FlowSubdir holds the mermaid *.mmd flow sources.
	FlowSubdir = "flows"
)

Canonical subdirectory names, named so callers can test membership and subtree relationships without repeating string literals.

View Source
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.

View Source
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".

View Source
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.

View Source
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.

View Source
const (
	ExportTargetCSS      = "css"
	ExportTargetTS       = "ts"
	ExportTargetTailwind = "tailwind"
	ExportTargetSwift    = "swift"
	ExportTargetKotlin   = "kotlin"
)

Export target identifiers. These are the exact strings the design_export_tokens `targets` argument accepts.

View Source
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.

View Source
const (
	KindManifest  = "manifest"
	KindToken     = "token"
	KindBrand     = "brand"
	KindIcon      = "icon"
	KindWireframe = "wireframe"
	KindScreen    = "screen"
	KindFlow      = "flow"
	KindFeedback  = "feedback"
	KindUnkn      = "unknown"
)

inventoryAssetKinds are the per-asset `kind` values the inventory rows carry, one per canonical design asset class (SP-140-2 §2c).

View Source
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.

View Source
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.

View Source
const ExportTargetAll = "all"

ExportTargetAll is the `targets` value that runs every exporter.

View Source
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.

View Source
const FeedbackSubdir = "feedback"

Feedback-subdirectory name under design/. Named so the validator, the design_assets inventory, and the parser all agree on one literal.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
const GitIgnoreFile = ".gitignore"

GitIgnoreFile is the repository-level file the ignore-policy findings (design/.cache/, design/generated/) attach to.

View Source
const GitIgnoreGeneratedPattern = "design/generated/"

GitIgnoreGeneratedPattern is the pattern the validator checks against the workspace .gitignore to emit its advisory design/generated/ finding.

View Source
const SyncModeApplyConst = "apply"

SyncModeApplyConst mirrors the handler's mode constant without importing the handler (the pure core must not depend on pkg/agent_tools).

View Source
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

View Source
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.

View Source
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.

View Source
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.

View Source
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.)

View Source
var ExportFilenames = map[string]string{
	ExportTargetCSS:      "tokens.css",
	ExportTargetTS:       "tokens.ts",
	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).

ExportTargets is the canonical target list, in the deterministic order exports are generated and reported (all → css, ts, tailwind, swift, kotlin). Callers must not rely on map order, so this slice is the one ordering authority.

View Source
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).

View Source
var Subdirs = []string{
	"tokens",
	"brand",
	"icons",
	"wireframes",
	"screens",
	"flows",
	"feedback",
}

Subdirs are the canonical design workspace subdirectories, in contract order.

Functions

func AppendGitContract

func AppendGitContract(dir string) error

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

func CompareDeltas(a, b SyncDelta) bool

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

func DriftRuleID(direction string) string

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

func FileExists(root string) bool

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 IsGitContractFile

func IsGitContractFile(p string) bool

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

func ManifestTemplate() ([]byte, error)

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 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 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

func Scaffold(dir string) error

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 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

func SlugMatches(name string) bool

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

func SubdirByName(name string) (string, bool)

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

func WireframeExists(exists bool) bool

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

type ContentReader interface {
	ReadFile(path string) ([]byte, error)
}

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).

const (
	DeltaKindToken     DeltaKind = "token"
	DeltaKindWireframe DeltaKind = "wireframe"
	DeltaKindFlow      DeltaKind = "flow"
	DeltaKindFeedback  DeltaKind = "feedback"
)

Delta kinds, fixed by SP-140-5 §5b.

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

type ExportTarget struct {
	Name string
	File string
}

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.

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) HasAnyDir

func (FSScanner) HasAnyDir(roots ...string) bool

HasAnyDir reports whether any root exists and is a directory.

func (FSScanner) HasAnyFile

func (FSScanner) HasAnyFile(paths ...string) bool

HasAnyFile reports whether any path exists and is a regular file.

func (FSScanner) WalkDir

func (FSScanner) WalkDir(root string, fn func(path string, d os.DirEntry, err error) error) error

WalkDir delegates to filepath.WalkDir; missing roots surface the usual *fs.PathError to the callback.

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

func ValidateBrandDir(root string) []Finding

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

func ValidateBrandFile(relPath string, content []byte) []Finding

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 ValidateConsistency

func ValidateConsistency(root string) []Finding

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

func ValidateDataURISizes(relPath string, content []byte) []Finding

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

func ValidateFeedbackDir(root string) ([]Finding, error)

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

func ValidateFile(root, relPath string) ([]Finding, error)

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, 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 ValidateFlows

func ValidateFlows(relPath string, content []byte, wireframeStems []string) []Finding

ValidateFlows validates one mermaid flow file per SP-140-1 §1c/§1g. wireframeStems is the set of wireframe file stems (without .svg) used for the node-id == stem rule on screen flows. Hard checks (SeverityError): exactly one flowchart declaration, unparseable statement lines, and screen-flow node ids that are not wireframe stems. The result is never nil.

func ValidateFlowsDir

func ValidateFlowsDir(root string) ([]Finding, error)

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 ValidateGitAttributesContent

func ValidateGitAttributesContent(relPath string, content []byte) []Finding

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

func ValidateGitContract(root string) []Finding

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

func ValidateGitIgnoreContent(relPath string, content []byte) []Finding

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

func ValidateIconsDir(root string) ([]Finding, error)

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

func ValidateInventory(root string) []Finding

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

func ValidateManifest(root string) []Finding

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

func ValidateScreensDir(root string) ([]Finding, error)

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 ValidateTokens

func ValidateTokens(relPath string, content []byte) []Finding

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

func ValidateTokensDir(root string) ([]Finding, error)

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

func ValidateTree(root string) ([]Finding, error)

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

func ValidateWireframesDir(root string) ([]Finding, error)

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
	HasArrow bool
}

FlowEdge is a single directed/undirected edge extracted from a flowchart.

type FlowNode

type FlowNode struct {
	ID    string
	Label string
}

FlowNode is a single node extracted from a mermaid flowchart.

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

func ParseFlowchart(content string) Flowchart

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

type Frame struct {
	Name   string
	Width  int
	Height int
}

Frame is a device frame declared in the manifest's frames block.

func ParseFrames

func ParseFrames(content string) ([]Frame, error)

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

func Scan(root string) (*Inventory, error)

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

type ProviderWordHit struct {
	File string
	Line int
	Text string
}

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 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"
)

func (Severity) String

func (s Severity) String() string

String returns the canonical lowercase name of the severity, matching the JSON representation in validator output.

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

type SyncFileInput struct {
	Path    string
	Content []byte
}

SyncFileInput is one touched code file: its path as the caller named it and its current content.

type SyncFileReader

type SyncFileReader func(rel string) ([]byte, bool)

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).

type TokenGroupCount

type TokenGroupCount struct {
	Group  string `json:"group"`
	Tokens int    `json:"tokens"`
}

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.

Jump to

Keyboard shortcuts

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