Skip to content

Commit 19943bf

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(design): scope working-tree checks and validate Studio
1 parent 61bedfd commit 19943bf

7 files changed

Lines changed: 54 additions & 18 deletions

File tree

‎.github/workflows/test-build.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ jobs:
311311

312312
# Only findings (exit 1) are non-blocking; operational errors still fail CI.
313313
- name: Design conformance (warnings)
314+
if: always() && steps.audit_base.outcome == 'success'
314315
env:
315316
DESIGN_BASE: ${{ steps.audit_base.outputs.ref }}
316317
DESIGN_HEAD: ${{ steps.audit_base.outputs.head }}

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.c
1010
- **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Inside `withRouteHandler` the logger already carries the request ID — no manual `withMetadata({ requestId })`.
1111
- **Comments**: TSDoc for documentation. An inline `//` only for a terse, non-obvious why, or for a script-enforced `// <tag>: <reason>` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, …). No `====` separators.
1212
- **ID generation**: `generateId()` (UUID v4, the default) or `generateShortId(size?)` (URL-safe, 21 chars by default) from `@sim/utils/id` — never `crypto.randomUUID()`, `nanoid`, or `uuid`. Both use `crypto.getRandomValues()`, so they also work in non-secure (HTTP) browsers.
13-
- **Common utilities**: use the shared helpers from `@sim/utils` instead of inline implementations:
1413
- **Design conformance**: Run `bun run check:design --base origin/staging --working-tree` while editing product UI. Reuse suitable EMCN components and global tokens; explain intentional Extras in the PR. Design findings are advisory, while an incomplete check is an error. See `scripts/design-conformance/README.md`.
1514
- **Styling**: Derive product styling from central tokens, component props and recipes where they fit. Keep product composition local. Explain intentional changes to shared standards. Landing and docs are outside the product design check.
15+
- **Common utilities**: use the shared helpers from `@sim/utils` instead of inline implementations:
1616
- `sleep(ms)` from `@sim/utils/helpers` — never `new Promise(resolve => setTimeout(resolve, ms))`
1717
- `toError(e)` from `@sim/utils/errors` — normalize caught values to `Error`; never `e instanceof Error ? e : new Error(String(e))`
1818
- `getErrorMessage(e, fallback?)` from `@sim/utils/errors` — never `e instanceof Error ? e.message : 'fallback'`

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
"agent-stream-docs:check": "bun run scripts/sync-agent-stream-docs.ts --check",
121121
"prepare": "bun husky",
122122
"test:workflow-sync": "bun --no-env-file scripts/test-integration.ts lib/workspaces/__integration__",
123-
"type-check": "bun run type-check:design && turbo run type-check",
123+
"type-check": "bun run type-check:design && bun run type-check:studio && turbo run type-check",
124124
"release": "bun run scripts/create-single-release.ts",
125125
"test:scripts": "vitest run --config scripts/vitest.config.ts",
126126
"check:design": "bun --no-env-file scripts/check-design-conformance.ts",

‎packages/emcn/src/AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ Applies to `packages/emcn/**`. Read `.claude/rules/emcn-components.md` before ch
66
- Components own their exact geometry tokens (e.g. `Button` uses `rounded-[5px]`) but never their own font-weight: every primitive inherits the document 400 and steps up only deliberately.
77
- TSDoc for public components and APIs.
88

9-
Central component, recipe and icon changes are design-system decisions. Run `bun run check:design --base origin/staging --working-tree` and explain intended shared changes in the PR. Review warnings remain visible. See `scripts/design-conformance/README.md`.
9+
Central component, recipe and icon changes are design-system decisions. Run `bun run check:design --base origin/staging --working-tree` (using the actual PR target as the base) and explain intended shared changes in the PR. Review warnings remain visible. See `scripts/design-conformance/README.md`.

‎scripts/check-design-conformance-command.test.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ function fixture() {
3232
git(repo, ['init', '-q'])
3333
writeFileSync(path.join(repo, TOKEN_FILE), ':root{--text-body:#434343}')
3434
writeFileSync(path.join(repo, ui), 'const A=()=> <p className="text-[var(--text-body)]"/>')
35+
writeFileSync(path.join(repo, 'README.md'), '# Fixture\n')
3536
const base = commit(repo)
3637
writeFileSync(path.join(repo, ui), 'const A=()=> <p className="text-[#434343]"/>')
3738
const head = commit(repo)
@@ -90,6 +91,30 @@ test('working-tree mode checks staged, unstaged and new product files without co
9091
expect(run(['--repo', repo, '--base', head, '--head', 'HEAD']).status).toBe(0)
9192
})
9293

94+
test('working-tree mode ignores unrelated tracked and untracked files', () => {
95+
const { repo, head } = fixture()
96+
mkdirSync(path.join(repo, 'tools'), { recursive: true })
97+
writeFileSync(path.join(repo, 'tools/notes.txt'), 'Not product styling')
98+
const result = run(['--repo', repo, '--base', head, '--working-tree', '--format', 'json'])
99+
expect(result.status).toBe(0)
100+
const report = JSON.parse(result.stdout) as Report
101+
expect(report.status).toBe('completed')
102+
expect(report.findings).toHaveLength(0)
103+
})
104+
105+
test('working-tree mode includes a changed central contract registry', () => {
106+
const { repo } = fixture()
107+
const file = 'scripts/design-conformance/contracts.json'
108+
mkdirSync(path.join(repo, path.dirname(file)), { recursive: true })
109+
writeFileSync(path.join(repo, file), '{"version":1}\n')
110+
const base = commit(repo)
111+
writeFileSync(path.join(repo, file), '{"version":2}\n')
112+
const result = run(['--repo', repo, '--base', base, '--working-tree', '--format', 'json'])
113+
expect(result.status).toBe(1)
114+
const report = JSON.parse(result.stdout) as Report
115+
expect(report.findings.some((finding) => finding.file === file)).toBe(true)
116+
})
117+
93118
test('text, JSON and output files preserve finding identity and normal command exit codes', () => {
94119
const { repo, base, head } = fixture()
95120
const args = ['--repo', repo, '--base', base]

‎scripts/design-conformance/command.ts‎

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
} from '#design-conformance/model'
2323
import { githubAnnotations, githubSummary, textReport } from '#design-conformance/reporting'
2424
import { gitSnapshot } from '#design-conformance/system-snapshot'
25-
import { GitSource } from '#design-conformance/worktree-source'
25+
import { GitSource, inspectedSource } from '#design-conformance/worktree-source'
2626

2727
export const repositoryRoot = fileURLToPath(new URL('../../', import.meta.url))
2828
const usage =
@@ -97,18 +97,21 @@ export async function checkComparison(args: CheckArguments): Promise<Report> {
9797
function workingChanges(before: GitSource, after: GitSource): Change[] {
9898
const old = new Map(before.entries.map((entry) => [entry.path, entry]))
9999
const current = new Map(after.entries.map((entry) => [entry.path, entry]))
100-
return [...new Set([...old.keys(), ...current.keys()])].sort().flatMap((file) => {
101-
const previous = old.get(file)
102-
const next = current.get(file)
103-
if (previous?.blob === next?.blob && previous?.mode === next?.mode) return []
104-
return [
105-
{
106-
before: previous ?? null,
107-
after: next ?? null,
108-
status: previous ? (next ? 'M' : 'D') : 'A',
109-
},
110-
]
111-
})
100+
return [...new Set([...old.keys(), ...current.keys()])]
101+
.sort()
102+
.filter(inspectedSource)
103+
.flatMap((file) => {
104+
const previous = old.get(file)
105+
const next = current.get(file)
106+
if (previous?.blob === next?.blob && previous?.mode === next?.mode) return []
107+
return [
108+
{
109+
before: previous ?? null,
110+
after: next ?? null,
111+
status: previous ? (next ? 'M' : 'D') : 'A',
112+
},
113+
]
114+
})
112115
}
113116

114117
/** The regular command always preserves 0/1/2; only the CI wrapper tolerates findings. */

‎scripts/design-conformance/worktree-source.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { lstatSync, readFileSync, readlinkSync, realpathSync } from 'node:fs'
44
import path from 'node:path'
55
import { productScope } from '#control-analysis/scope'
66
import { typographySource } from '#control-analysis/typography'
7-
import { centralInventory, registry } from '#design-conformance/contracts'
7+
import { centralInventory, isRegistry, registry } from '#design-conformance/contracts'
88
import { verifiedText } from '#design-conformance/io'
99
import type { Entry } from '#design-conformance/model'
1010
import { TOKEN_FILE } from '#design-conformance/model'
@@ -15,6 +15,13 @@ export interface SourceEntry extends Entry {
1515
kind: string
1616
}
1717

18+
/** Files whose authored bytes participate in local design analysis. */
19+
export const inspectedSource = (file: string) =>
20+
centralInventory(file) ||
21+
isRegistry(file) ||
22+
productScope(file) === 'check' ||
23+
typographySource(file)
24+
1825
/** Git tree/blob reads only: no checkout, textconv, filters, hooks or application imports. */
1926
export class GitSource {
2027
readonly mode: 'snapshot' | 'working-tree'
@@ -73,7 +80,7 @@ export class GitSource {
7380
if (stat.isDirectory())
7481
return [{ path: file, mode: '160000', kind: 'commit', blob: '', bytes: 0 }]
7582
const mode = stat.isSymbolicLink() ? '120000' : stat.mode & 0o111 ? '100755' : '100644'
76-
if (!centralInventory(file) && productScope(file) !== 'check' && !typographySource(file))
83+
if (!inspectedSource(file))
7784
return [{ path: file, mode, kind: 'blob', blob: '', bytes: stat.size }]
7885
if (realpathSync(path.dirname(absolute)) !== path.dirname(absolute))
7986
throw new Error(`Source has a symlinked parent directory: ${file}`)

0 commit comments

Comments
 (0)