Kyn CLI Specification¶
Working Name¶
Kyn
One-line Description¶
Kyn is a lightweight CLI for detecting related file changes and enforcing file relationship rules in CI.
Primary Goal¶
Build a stateless command-line tool that evaluates a set of changed files against configurable file relationship rules. It should work in normal repositories, package-based repositories, and monorepos such as Nx or Rush.js.
Kyn should answer questions like:
- If a component file changed, did its related story file also change?
- If a component has a Figma config sidecar, should CI mark it for Figma publishing?
- If a public model changed, did the related spec, docs, or schema file also change?
- If a specific family of files changed, should a specific CI rule fail?
The MVP should be a basic CLI only. No daemon, no server, no watcher, no long-running process.
Language Decision¶
Preferred Language: Go¶
Use Go for the first version.
Why Go Fits Kyn¶
- Easy to ship as a single binary.
- Fast startup time, which is ideal for CI.
- Simple cross-platform builds.
- Strong standard library for filesystem and process execution.
- Lower implementation complexity than Rust for this type of CLI.
- Easier for most teams to maintain.
Why Not Rust for V1¶
Rust would also be a good fit, especially if maximum safety, performance, and richer parser internals become important later. However, for this tool, the hard parts are mostly:
- path matching
- config parsing
- git diff collection
- rule evaluation
- reporting
These do not require Rust-level performance or memory control. Go is the better default for a portable CI utility.
Product Scope¶
In Scope for MVP¶
- CLI command:
kyn check - Load config from explicit path or default locations.
- Accept changed files directly via CLI.
- Accept changed files from a file.
- Detect changed files using
git diff. - Evaluate sibling/related-file rules.
- Support glob-based matching.
- Support file existence checks.
- Support basic pass/fail output.
- Support text and JSON output.
- Exit with deterministic exit codes.
Out of Scope for MVP¶
- Daemon/watch mode.
- PR comments.
- GitHub/GitLab API integration.
- Nx project graph parsing.
- Rush project graph parsing.
- Automatic Figma publishing.
- Automatic Storybook publishing.
- Autofixes or file creation.
- SARIF/checkstyle/reviewdog output.
- Plugin system.
These can be added after the core CLI works.
Core Mental Model¶
Kyn evaluates changed files by grouping related files into families.
A family represents one logical unit of code or configuration.
Example Angular component family:
libs/ui/button/button.component.ts
libs/ui/button/button.component.html
libs/ui/button/button.component.scss
libs/ui/button/button.spec.ts
libs/ui/button/button.stories.ts
libs/ui/button/figma.button.json
Kyn should understand that these files are related because they share a directory, base name, naming convention, or configured relationship pattern.
The evaluation flow is:
Collect changed files
-> Load config
-> Match changed files to family definitions
-> Resolve expected kin/sibling files
-> Evaluate rules
-> Print report
-> Exit 0 or nonzero
Terminology¶
Family¶
A group of related files that represent one logical unit.
Example:
button.component.ts
button.component.html
button.component.scss
button.stories.ts
button.spec.ts
Kin¶
A related file within a family.
Examples:
storyspectemplatestylefigmadocs
Rule¶
A configured policy that evaluates a file family and returns pass/fail/info/warn/error.
Change Set¶
The list of files that were added, modified, deleted, or renamed.
For MVP, Kyn can treat all changed files as a single flat list. Added/modified/deleted categories can be added as optional metadata later.
CLI Contract¶
Main Command¶
kyn check
Supported Flags¶
--config <path> Path to Kyn config file
--files <csv> Comma-separated changed files
--files-from <path> Path to file containing changed files, one per line; use '-' for stdin
--stdin Read changed files from stdin (alias for --files-from -)
--base <ref> Git base ref/SHA for diff detection
--head <ref> Git head ref/SHA for diff detection
--cwd <path> Working directory; defaults to current directory
--format <format> Output format: text | json; default text
--fail-on <level> Minimum severity that fails the command: error | warn; default error
--fail-on-empty Fail when no family instances match; default false
--show-passes Include passing rule results in text output; default false
--verbose Print diagnostic information
Usage Examples¶
Explicit files¶
kyn check --config kyn.config.yaml --files libs/ui/button/button.component.ts,libs/ui/button/button.component.html
Files from file¶
kyn check --config kyn.config.yaml --files-from changed-files.txt
Files from stdin¶
git diff --name-only origin/main...HEAD | kyn check --config kyn.config.yaml --files-from -
Equivalent stdin alias:
git diff --name-only origin/main...HEAD | kyn check --config kyn.config.yaml --stdin
Git diff mode¶
kyn check --config kyn.config.yaml --base origin/main --head HEAD
JSON output¶
kyn check --config kyn.config.yaml --base origin/main --head HEAD --format json
Fail on warnings¶
kyn check --config kyn.config.yaml --base origin/main --head HEAD --fail-on warn
Input Mode Rules¶
Exactly one change input mode must be selected:
--files--files-from--stdin--baseand--headusing git diff
Invalid combinations are CLI usage errors (exit code 2).
If git mode is used, both --base and --head are required.
For MVP, do not silently infer default git refs unless explicitly configured later.
Exit Codes¶
0 = all required rules passed
1 = one or more rules failed
2 = invalid CLI usage or invalid config
3 = runtime/provider error, such as git failure
Config Format¶
MVP Config Format¶
Use YAML for MVP.
Reasoning:
- Easier to write than JSON.
- Easier to validate than executable JS/TS.
- Language-neutral for a Go CLI.
- Better fit for CI tooling.
Default config lookup order:
kyn.config.yaml
kyn.config.yml
.kyn.yaml
.kyn.yml
If --config is provided, use only that path.
Example Config¶
version: 1
families:
- id: angular-component
include:
- "libs/**/*.component.ts"
- "libs/**/*.component.html"
- "libs/**/*.component.scss"
baseName:
stripSuffixes:
- ".component"
kin:
story: "{dir}/{base}.stories.ts"
spec: "{dir}/{base}.spec.ts"
figma: "{dir}/figma.{base}.json"
rules:
- id: storybook-sync
description: "Component changes should be reviewed against Storybook stories."
family: angular-component
severity: error
when:
changedAny:
- source
kinExists:
- story
require:
kinChanged:
- story
message: "Component changed but its Storybook file did not change."
- id: figma-publish-required
description: "Components with Figma configs require a Figma publish check."
family: angular-component
severity: warn
when:
changedAny:
- source
kinExists:
- figma
require:
emitFlag: figmaPublishRequired
message: "Component changed and has a Figma config. Figma publish may be required."
Config Schema Concepts¶
Top Level¶
version: 1
families: []
rules: []
Family Definition¶
families:
- id: string
include: string[]
exclude?: string[]
baseName?:
stripSuffixes?: string[]
kin:
[kinName: string]: string
Family Fields¶
id¶
Unique family identifier.
Example:
id: angular-component
include¶
Glob patterns used to detect files that belong to this family.
Example:
include:
- "libs/**/*.component.ts"
- "libs/**/*.component.html"
exclude¶
Optional glob patterns to ignore.
Example:
exclude:
- "**/*.spec.ts"
baseName.stripSuffixes¶
Suffixes to remove from the filename stem before resolving kin patterns.
Example:
Input:
button.component.ts
Filename stem:
button.component
After stripping .component:
button
kin¶
A map of related file names to path templates.
Supported template variables:
{dir} Directory of changed file
{file} Full changed file path
{name} Filename without extension
{base} Normalized base name after suffix stripping
{ext} File extension
Example:
kin:
story: "{dir}/{base}.stories.ts"
spec: "{dir}/{base}.spec.ts"
Rule Definition¶
rules:
- id: string
description?: string
family: string
severity: info | warn | error
when?:
changedAny?: string[]
kinExists?: string[]
kinMissing?: string[]
require?:
kinChanged?: string[]
kinUnchanged?: string[]
kinExists?: string[]
kinMissing?: string[]
emitFlag?: string
message: string
Rule Behavior¶
A rule applies to each resolved family instance matching the configured family id.
A rule has two phases:
when: determines if the rule should run.require: determines if the rule passes or fails.
If when is omitted, the rule runs for every family instance.
If require is omitted, the rule should emit an informational result if the when block matches.
Predicate semantics for MVP:
whenkeys are evaluated withANDsemantics.requirekeys are evaluated withANDsemantics.- list predicates require all listed kin names unless documented otherwise.
changedAnyisANYacross listed groups.
Built-in Rule Semantics for MVP¶
changedAny¶
Checks whether any files in a named group changed.
For MVP, the default group is source.
A file matched by the family include patterns is considered part of source.
Example:
when:
changedAny:
- source
kinExists¶
Checks that specific kin files exist in the repository.
Example:
when:
kinExists:
- story
kinMissing¶
Checks that specific kin files do not exist.
Example:
when:
kinMissing:
- spec
kinChanged¶
Checks that specific kin files are present in the changed file list.
Example:
require:
kinChanged:
- story
kinUnchanged¶
Checks that specific kin files are not present in the changed file list.
Example:
require:
kinUnchanged:
- generated
emitFlag¶
Emits a named output flag instead of validating a file condition.
Example:
require:
emitFlag: figmaPublishRequired
This is informational and does not fail by itself.
Result Model¶
Internally, use a result model similar to:
type Severity string
const (
SeverityInfo Severity = "info"
SeverityWarn Severity = "warn"
SeverityError Severity = "error"
)
type ResultStatus string
const (
StatusPass ResultStatus = "pass"
StatusFail ResultStatus = "fail"
StatusInfo ResultStatus = "info"
)
type RuleResult struct {
RuleID string `json:"ruleId"`
FamilyID string `json:"familyId"`
FamilyName string `json:"familyName"`
Severity Severity `json:"severity"`
Status ResultStatus `json:"status"`
Message string `json:"message"`
ChangedFiles []string `json:"changedFiles,omitempty"`
ExpectedFiles []string `json:"expectedFiles,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
Summary:
type Summary struct {
OK bool `json:"ok"`
Passed int `json:"passed"`
Failed int `json:"failed"`
Infos int `json:"infos"`
Warnings int `json:"warnings"`
Errors int `json:"errors"`
Results []RuleResult `json:"results"`
Flags []string `json:"flags,omitempty"`
}
Text Output Example¶
kyn check
FAIL
Rules failed: 1
Warnings: 1
Infos: 0
[ERROR] storybook-sync
Family: angular-component
Instance: libs/ui/button/button
Message: Component changed but its Storybook file did not change.
Changed files:
- libs/ui/button/button.component.ts
- libs/ui/button/button.component.html
Expected files:
- libs/ui/button/button.stories.ts
[WARN] figma-publish-required
Family: angular-component
Instance: libs/ui/button/button
Message: Component changed and has a Figma config. Figma publish may be required.
Expected files:
- libs/ui/button/figma.button.json
JSON Output Example¶
{
"ok": false,
"passed": 1,
"failed": 1,
"infos": 0,
"warnings": 1,
"errors": 1,
"flags": ["figmaPublishRequired"],
"results": [
{
"ruleId": "storybook-sync",
"familyId": "angular-component",
"familyName": "libs/ui/button/button",
"severity": "error",
"status": "fail",
"message": "Component changed but its Storybook file did not change.",
"changedFiles": [
"libs/ui/button/button.component.ts",
"libs/ui/button/button.component.html"
],
"expectedFiles": [
"libs/ui/button/button.stories.ts"
]
}
]
}
Git Diff Provider¶
For MVP, implement a git provider using:
git diff --name-status -M <base>...<head>
If --base and --head are provided, run:
git -C <cwd> diff --name-status -M <base>...<head>
Normalize output by:
- trimming whitespace
- dropping empty lines
- converting Windows path separators to
/ - removing leading
./
MVP flattening behavior:
- include
AandMpaths in changed-set evaluation - include rename destination path for
R*statuses - exclude
Dpaths from changed-set evaluation
If git fails, exit with code 3 and show the git error.
File Matching¶
Use a Go glob library that supports common doublestar patterns such as:
**/*.component.ts
libs/**/*.stories.ts
Recommended package:
github.com/bmatcuk/doublestar/v4
Use slash-normalized relative paths internally.
Filesystem Rules¶
All path checks should be relative to --cwd.
kinExists should use the real filesystem, not just changed files.
A kin file is considered changed if its normalized path appears in the changed file set.
Family instances should be deduplicated by default.
Suggested Go Project Structure¶
kyn/
go.mod
cmd/
kyn/
main.go
internal/
cli/
check.go
flags.go
config/
config.go
load.go
validate.go
changes/
changes.go
git.go
manual.go
matcher/
glob.go
normalize.go
family/
family.go
resolver.go
template.go
rules/
engine.go
evaluator.go
result.go
report/
text.go
json.go
testdata/
angular/
kyn.config.yaml
libs/
ui/
button/
button.component.ts
button.component.html
button.stories.ts
figma.button.json
Suggested Dependencies¶
github.com/spf13/cobra
For CLI command parsing.
gopkg.in/yaml.v3
For YAML config parsing.
github.com/bmatcuk/doublestar/v4
For glob matching with ** support.
Optional:
github.com/stretchr/testify
For test assertions.
MVP Acceptance Criteria¶
CLI Behavior¶
kyn check --helpprints usage.kyn check --config ./kyn.config.yaml --files a.ts,b.tsruns rules.kyn check --config ./kyn.config.yaml --files-from changed.txtruns rules.kyn check --config ./kyn.config.yaml --base origin/main --head HEADruns git diff and rules.- Missing config returns exit code
2. - Invalid config returns exit code
2. - Git failure returns exit code
3. - Rule failure returns exit code
1. - Successful run returns exit code
0.
Rule Behavior¶
- Can detect changed files matching family includes.
- Can normalize base names with configured suffix stripping.
- Can resolve kin paths from templates.
- Can check whether kin files exist.
- Can check whether kin files changed.
- Can fail when required kin files did not change.
- Can emit flags for informational/CI follow-up rules.
Output Behavior¶
- Text output is human-readable.
- Text output lists failures before informational/passing results.
- JSON output is valid JSON.
- JSON output includes
ok, counts, results, and flags. --fail-on errorfails only on error results.--fail-on warnfails on warn or error results.--fail-on-emptyfails when no family instances matched.--show-passesincludes pass results in text output.- Output ordering for results/files/flags is deterministic.
Example MVP Test Case¶
Files¶
libs/ui/button/button.component.ts
libs/ui/button/button.component.html
libs/ui/button/button.component.scss
libs/ui/button/button.stories.ts
libs/ui/button/figma.button.json
Changed Files¶
libs/ui/button/button.component.ts
libs/ui/button/button.component.html
Expected Result¶
storybook-syncfails becausebutton.stories.tsexists but did not change.figma-publish-requiredemits a warning/flag becausefigma.button.jsonexists.- Exit code is
1whenstorybook-syncseverity iserror.
Future Enhancements¶
Monorepo Adapters¶
Later versions can add providers/adapters for:
- Nx affected files/projects
- Rush.js changed projects
- Turborepo package scopes
- pnpm/yarn/npm workspaces
These should be optional integrations and should not be required for the core CLI.
Additional Output Formats¶
Possible future reporters:
- SARIF
- Checkstyle XML
- reviewdog rdjson
- GitLab code quality report
- Markdown summary
Plugins / Rule Packs¶
Possible future packages:
@kyn/angular
@kyn/react
@kyn/storybook
@kyn/figma
In Go, this may be better implemented as built-in rule presets rather than runtime plugins.
Autofix¶
Future commands could include:
kyn init
kyn explain
kyn check --suggest
kyn create-missing
Do not implement these for MVP.
Implementation Notes for Codex¶
Please implement this as a Go CLI.
Start with the MVP only. Prioritize clean architecture and tests over extra features.
Use cobra for the CLI, yaml.v3 for config parsing, and doublestar for glob matching.
Keep all paths normalized to slash-separated relative paths internally.
Do not implement Nx, Rush, GitHub, GitLab, PR comments, daemon mode, watch mode, or plugins in the first version.
The main deliverable is a working kyn check command with config-driven family/rule evaluation.