buildwithjpegg 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +12 -0
- package/.codex/INSTALL.md +67 -0
- package/.opencode/INSTALL.md +118 -0
- package/.opencode/plugins/buildwithjpegg.js +95 -0
- package/LICENSE +24 -0
- package/README.md +134 -0
- package/RELEASE-NOTES.md +7 -0
- package/agents/code-reviewer.md +48 -0
- package/commands/evaluate.md +6 -0
- package/commands/run-build.md +6 -0
- package/commands/write-blueprint.md +6 -0
- package/hooks/hooks.json +16 -0
- package/hooks/run-hook.cmd +43 -0
- package/hooks/session-start.sh +46 -0
- package/lib/skills-core.js +208 -0
- package/package.json +39 -0
- package/rules/conventions.md +22 -0
- package/rules/git.md +18 -0
- package/rules/mcp-servers.md +28 -0
- package/rules/platform.md +10 -0
- package/rules/stack.md +29 -0
- package/rules/testing.md +18 -0
- package/rules/ui-ux.md +151 -0
- package/rules/workflow.md +48 -0
- package/skills/auto-release/SKILL.md +176 -0
- package/skills/blueprint/SKILL.md +116 -0
- package/skills/build/SKILL.md +84 -0
- package/skills/ci-loop/SKILL.md +98 -0
- package/skills/craft-skill/SKILL.md +655 -0
- package/skills/craft-skill/anthropic-best-practices.md +1150 -0
- package/skills/craft-skill/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/craft-skill/graphviz-conventions.dot +172 -0
- package/skills/craft-skill/persuasion-principles.md +187 -0
- package/skills/craft-skill/render-graphs.js +168 -0
- package/skills/craft-skill/testing-skills-with-subagents.md +384 -0
- package/skills/delegate/SKILL.md +242 -0
- package/skills/delegate/code-quality-reviewer-prompt.md +20 -0
- package/skills/delegate/implementer-prompt.md +78 -0
- package/skills/delegate/spec-reviewer-prompt.md +61 -0
- package/skills/draft-prs/SKILL.md +132 -0
- package/skills/evaluate/SKILL.md +96 -0
- package/skills/fan-out/SKILL.md +180 -0
- package/skills/handle-review/SKILL.md +213 -0
- package/skills/onboard/SKILL.md +95 -0
- package/skills/pr-stack/SKILL.md +112 -0
- package/skills/pre-ship/SKILL.md +139 -0
- package/skills/root-cause/CREATION-LOG.md +119 -0
- package/skills/root-cause/SKILL.md +296 -0
- package/skills/root-cause/condition-based-waiting-example.ts +158 -0
- package/skills/root-cause/condition-based-waiting.md +115 -0
- package/skills/root-cause/defense-in-depth.md +122 -0
- package/skills/root-cause/find-polluter.sh +63 -0
- package/skills/root-cause/root-cause-tracing.md +169 -0
- package/skills/root-cause/test-academic.md +14 -0
- package/skills/root-cause/test-pressure-1.md +58 -0
- package/skills/root-cause/test-pressure-2.md +68 -0
- package/skills/root-cause/test-pressure-3.md +69 -0
- package/skills/seek-review/SKILL.md +105 -0
- package/skills/seek-review/code-reviewer.md +146 -0
- package/skills/test-first/SKILL.md +371 -0
- package/skills/test-first/testing-anti-patterns.md +299 -0
- package/skills/worktree/SKILL.md +218 -0
- package/skills/wrap-up/SKILL.md +200 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract YAML frontmatter from a skill file.
|
|
7
|
+
* Current format:
|
|
8
|
+
* ---
|
|
9
|
+
* name: skill-name
|
|
10
|
+
* description: Use when [condition] - [what it does]
|
|
11
|
+
* ---
|
|
12
|
+
*
|
|
13
|
+
* @param {string} filePath - Path to SKILL.md file
|
|
14
|
+
* @returns {{name: string, description: string}}
|
|
15
|
+
*/
|
|
16
|
+
function extractFrontmatter(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
19
|
+
const lines = content.split('\n');
|
|
20
|
+
|
|
21
|
+
let inFrontmatter = false;
|
|
22
|
+
let name = '';
|
|
23
|
+
let description = '';
|
|
24
|
+
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
if (line.trim() === '---') {
|
|
27
|
+
if (inFrontmatter) break;
|
|
28
|
+
inFrontmatter = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (inFrontmatter) {
|
|
33
|
+
const match = line.match(/^(\w+):\s*(.*)$/);
|
|
34
|
+
if (match) {
|
|
35
|
+
const [, key, value] = match;
|
|
36
|
+
switch (key) {
|
|
37
|
+
case 'name':
|
|
38
|
+
name = value.trim();
|
|
39
|
+
break;
|
|
40
|
+
case 'description':
|
|
41
|
+
description = value.trim();
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { name, description };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return { name: '', description: '' };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find all SKILL.md files in a directory recursively.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} dir - Directory to search
|
|
58
|
+
* @param {string} sourceType - 'personal' or 'buildwithjpegg' for namespacing
|
|
59
|
+
* @param {number} maxDepth - Maximum recursion depth (default: 3)
|
|
60
|
+
* @returns {Array<{path: string, name: string, description: string, sourceType: string}>}
|
|
61
|
+
*/
|
|
62
|
+
function findSkillsInDir(dir, sourceType, maxDepth = 3) {
|
|
63
|
+
const skills = [];
|
|
64
|
+
|
|
65
|
+
if (!fs.existsSync(dir)) return skills;
|
|
66
|
+
|
|
67
|
+
function recurse(currentDir, depth) {
|
|
68
|
+
if (depth > maxDepth) return;
|
|
69
|
+
|
|
70
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
71
|
+
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
74
|
+
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
// Check for SKILL.md in this directory
|
|
77
|
+
const skillFile = path.join(fullPath, 'SKILL.md');
|
|
78
|
+
if (fs.existsSync(skillFile)) {
|
|
79
|
+
const { name, description } = extractFrontmatter(skillFile);
|
|
80
|
+
skills.push({
|
|
81
|
+
path: fullPath,
|
|
82
|
+
skillFile: skillFile,
|
|
83
|
+
name: name || entry.name,
|
|
84
|
+
description: description || '',
|
|
85
|
+
sourceType: sourceType
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Recurse into subdirectories
|
|
90
|
+
recurse(fullPath, depth + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
recurse(dir, 0);
|
|
96
|
+
return skills;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a skill name to its file path, handling shadowing
|
|
101
|
+
* (personal skills override buildwithjpegg skills).
|
|
102
|
+
*
|
|
103
|
+
* @param {string} skillName - Name like "jpegg:evaluate" or "my-skill"
|
|
104
|
+
* @param {string} pluginDir - Path to buildwithjpegg skills directory
|
|
105
|
+
* @param {string} personalDir - Path to personal skills directory
|
|
106
|
+
* @returns {{skillFile: string, sourceType: string, skillPath: string} | null}
|
|
107
|
+
*/
|
|
108
|
+
function resolveSkillPath(skillName, pluginDir, personalDir) {
|
|
109
|
+
// Strip jpegg: prefix if present
|
|
110
|
+
const forcePlugin = skillName.startsWith('jpegg:');
|
|
111
|
+
const actualSkillName = forcePlugin ? skillName.replace(/^jpegg:/, '') : skillName;
|
|
112
|
+
|
|
113
|
+
// Try personal skills first (unless explicitly jpegg:)
|
|
114
|
+
if (!forcePlugin && personalDir) {
|
|
115
|
+
const personalPath = path.join(personalDir, actualSkillName);
|
|
116
|
+
const personalSkillFile = path.join(personalPath, 'SKILL.md');
|
|
117
|
+
if (fs.existsSync(personalSkillFile)) {
|
|
118
|
+
return {
|
|
119
|
+
skillFile: personalSkillFile,
|
|
120
|
+
sourceType: 'personal',
|
|
121
|
+
skillPath: actualSkillName
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Try buildwithjpegg skills
|
|
127
|
+
if (pluginDir) {
|
|
128
|
+
const pluginPath = path.join(pluginDir, actualSkillName);
|
|
129
|
+
const pluginSkillFile = path.join(pluginPath, 'SKILL.md');
|
|
130
|
+
if (fs.existsSync(pluginSkillFile)) {
|
|
131
|
+
return {
|
|
132
|
+
skillFile: pluginSkillFile,
|
|
133
|
+
sourceType: 'buildwithjpegg',
|
|
134
|
+
skillPath: actualSkillName
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if a git repository has updates available.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} repoDir - Path to git repository
|
|
146
|
+
* @returns {boolean} - True if updates are available
|
|
147
|
+
*/
|
|
148
|
+
function checkForUpdates(repoDir) {
|
|
149
|
+
try {
|
|
150
|
+
// Quick check with 3 second timeout to avoid delays if network is down
|
|
151
|
+
const output = execSync('git fetch origin && git status --porcelain=v1 --branch', {
|
|
152
|
+
cwd: repoDir,
|
|
153
|
+
timeout: 3000,
|
|
154
|
+
encoding: 'utf8',
|
|
155
|
+
stdio: 'pipe'
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Parse git status output to see if we're behind
|
|
159
|
+
const statusLines = output.split('\n');
|
|
160
|
+
for (const line of statusLines) {
|
|
161
|
+
if (line.startsWith('## ') && line.includes('[behind ')) {
|
|
162
|
+
return true; // We're behind remote
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return false; // Up to date
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// Network down, git error, timeout, etc. - don't block bootstrap
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Strip YAML frontmatter from skill content, returning just the content.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} content - Full content including frontmatter
|
|
176
|
+
* @returns {string} - Content without frontmatter
|
|
177
|
+
*/
|
|
178
|
+
function stripFrontmatter(content) {
|
|
179
|
+
const lines = content.split('\n');
|
|
180
|
+
let inFrontmatter = false;
|
|
181
|
+
let frontmatterEnded = false;
|
|
182
|
+
const contentLines = [];
|
|
183
|
+
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
if (line.trim() === '---') {
|
|
186
|
+
if (inFrontmatter) {
|
|
187
|
+
frontmatterEnded = true;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
inFrontmatter = true;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (frontmatterEnded || !inFrontmatter) {
|
|
195
|
+
contentLines.push(line);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return contentLines.join('\n').trim();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export {
|
|
203
|
+
extractFrontmatter,
|
|
204
|
+
findSkillsInDir,
|
|
205
|
+
resolveSkillPath,
|
|
206
|
+
checkForUpdates,
|
|
207
|
+
stripFrontmatter
|
|
208
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "buildwithjpegg",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Development workflow skills for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
|
5
|
+
"author": "jpegg",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/jpeggdev/buildwithjpegg.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/jpeggdev/buildwithjpegg",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jpeggdev/buildwithjpegg/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude-code",
|
|
17
|
+
"skills",
|
|
18
|
+
"tdd",
|
|
19
|
+
"debugging",
|
|
20
|
+
"collaboration",
|
|
21
|
+
"best-practices",
|
|
22
|
+
"workflows",
|
|
23
|
+
"ai-agent"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"agents/",
|
|
27
|
+
"commands/",
|
|
28
|
+
"hooks/",
|
|
29
|
+
"lib/",
|
|
30
|
+
"rules/",
|
|
31
|
+
"skills/",
|
|
32
|
+
".claude-plugin/",
|
|
33
|
+
".codex/",
|
|
34
|
+
".opencode/",
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"README.md",
|
|
37
|
+
"RELEASE-NOTES.md"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Project Conventions
|
|
2
|
+
|
|
3
|
+
## Package Manager
|
|
4
|
+
|
|
5
|
+
Use `pnpm` for all package management commands (not npm or yarn).
|
|
6
|
+
|
|
7
|
+
Exception: Global install instructions for end users should use `npm install -g` since it's universal.
|
|
8
|
+
|
|
9
|
+
## Dependencies
|
|
10
|
+
|
|
11
|
+
Always check for the latest npm version when adding dependencies. Use `pnpm add <package>` (without version) to get the latest, or verify with `npm view <package> version` first.
|
|
12
|
+
|
|
13
|
+
## No Emojis
|
|
14
|
+
|
|
15
|
+
Do not use emojis anywhere (code, comments, output, docs).
|
|
16
|
+
|
|
17
|
+
## Docs Updates
|
|
18
|
+
|
|
19
|
+
When a change affects how humans or agents use a codebase (new/changed/removed commands, flags, behavior, or config), update all of these:
|
|
20
|
+
|
|
21
|
+
1. `README.md` -- user-facing documentation
|
|
22
|
+
2. `CLAUDE.md` -- agent-facing documentation
|
package/rules/git.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Git Conventions
|
|
2
|
+
|
|
3
|
+
## Commits
|
|
4
|
+
- Conventional commits: `type(scope): message`
|
|
5
|
+
- Types: feat, fix, refactor, test, docs, chore, ci
|
|
6
|
+
- Message in imperative mood, lowercase, no period
|
|
7
|
+
- Keep commits atomic -- one logical change per commit
|
|
8
|
+
|
|
9
|
+
## Branches
|
|
10
|
+
- `main` is production
|
|
11
|
+
- Feature branches: `feat/short-description`
|
|
12
|
+
- Fix branches: `fix/short-description`
|
|
13
|
+
- Never force-push to main
|
|
14
|
+
|
|
15
|
+
## PRs
|
|
16
|
+
- Title matches conventional commit format
|
|
17
|
+
- Body: summary bullets + test plan
|
|
18
|
+
- Squash merge to main
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# MCP Server Configuration
|
|
2
|
+
|
|
3
|
+
On Windows, MCP servers using npx must go through `cmd /c` since MINGW64 bash cannot run npx directly as a stdio command.
|
|
4
|
+
|
|
5
|
+
**Pattern:**
|
|
6
|
+
```json
|
|
7
|
+
"<server-name>": {
|
|
8
|
+
"type": "stdio",
|
|
9
|
+
"command": "cmd",
|
|
10
|
+
"args": ["/c", "npx", "-y", "<package>@latest"],
|
|
11
|
+
"env": {}
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Example (Context7):**
|
|
16
|
+
```json
|
|
17
|
+
"context7": {
|
|
18
|
+
"type": "stdio",
|
|
19
|
+
"command": "cmd",
|
|
20
|
+
"args": ["/c", "npx", "-y", "@upstash/context7-mcp@latest"],
|
|
21
|
+
"env": {}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Key points:
|
|
26
|
+
- Always use `"command": "cmd"` with `"/c"` as the first arg.
|
|
27
|
+
- Use `-y` with npx to auto-confirm install prompts.
|
|
28
|
+
- The `env` object can pass environment variables the server needs (API keys, etc.).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Platform specific
|
|
2
|
+
|
|
3
|
+
On Windows (MINGW64/Git Bash):
|
|
4
|
+
|
|
5
|
+
- Use forward slashes in paths for cross-platform compatibility.
|
|
6
|
+
- Shell commands run in bash (MINGW64), not cmd.exe or PowerShell.
|
|
7
|
+
- Avoid `NUL` -- use `/dev/null` instead.
|
|
8
|
+
- Quote paths containing spaces with double quotes.
|
|
9
|
+
- The `/c` flag for cmd.exe must not be converted to `C:/`.
|
|
10
|
+
- Always test MCP server registrations and hook commands for Windows compatibility.
|
package/rules/stack.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Default Tech Stack
|
|
2
|
+
|
|
3
|
+
When starting a new project or the user hasn't specified preferences:
|
|
4
|
+
|
|
5
|
+
## Desktop Applications (default for this workspace)
|
|
6
|
+
- **Language**: Python for everything — UI and backend in one language
|
|
7
|
+
- **UI framework**: Flet (Python + Flutter renderer) — `flet build` produces native binaries
|
|
8
|
+
- **Embedded server**: aiohttp or FastAPI for the local HTTP server
|
|
9
|
+
- **Database**: SQLite via `aiosqlite` for async access, raw `sqlite3` for sync
|
|
10
|
+
- **Validation**: Pydantic
|
|
11
|
+
- **Templates**: Jinja2
|
|
12
|
+
- **Testing**: pytest + pytest-asyncio
|
|
13
|
+
- **MCP integration**: Official Anthropic MCP Python SDK
|
|
14
|
+
- **Packaging**: `flet build windows/macos/linux` for native binaries
|
|
15
|
+
|
|
16
|
+
## Web Applications
|
|
17
|
+
- **Language**: TypeScript for everything
|
|
18
|
+
- **Frontend**: React + Next.js (App Router), Tailwind CSS, Zustand or TanStack Query
|
|
19
|
+
- **Backend**: Hono (lightweight) or Next.js API routes, PostgreSQL + Drizzle ORM, Zod
|
|
20
|
+
- **Testing**: Vitest + Testing Library + Playwright
|
|
21
|
+
|
|
22
|
+
## Infra (both)
|
|
23
|
+
- GitHub Actions for CI/CD
|
|
24
|
+
- Docker + Docker Compose for services that need it
|
|
25
|
+
|
|
26
|
+
## AI/ML
|
|
27
|
+
- Anthropic SDK (Claude) as primary LLM — Python or TypeScript depending on project type
|
|
28
|
+
|
|
29
|
+
Always ask before deviating from these defaults on new projects.
|
package/rules/testing.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Testing Conventions
|
|
2
|
+
|
|
3
|
+
## Philosophy
|
|
4
|
+
- Test behavior, not implementation
|
|
5
|
+
- Integration tests over unit tests for most code
|
|
6
|
+
- Unit tests for pure logic and edge cases
|
|
7
|
+
- E2E tests for critical user flows only
|
|
8
|
+
|
|
9
|
+
## Approach
|
|
10
|
+
- New features: write tests alongside implementation
|
|
11
|
+
- Bug fixes: write a failing test first, then fix
|
|
12
|
+
- Refactors: ensure tests pass before and after
|
|
13
|
+
|
|
14
|
+
## Tools (defaults)
|
|
15
|
+
- Vitest for unit/integration
|
|
16
|
+
- Playwright for E2E
|
|
17
|
+
- Testing Library for component tests
|
|
18
|
+
- pytest for Python projects
|
package/rules/ui-ux.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# UI/UX Guidelines
|
|
2
|
+
|
|
3
|
+
Concise rules for building accessible, fast, delightful UIs. Use MUST/SHOULD/NEVER to guide decisions.
|
|
4
|
+
|
|
5
|
+
## Interactions
|
|
6
|
+
|
|
7
|
+
### Keyboard
|
|
8
|
+
|
|
9
|
+
- MUST: Full keyboard support per [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/)
|
|
10
|
+
- MUST: Visible focus rings (`:focus-visible`; group with `:focus-within`)
|
|
11
|
+
- MUST: Manage focus (trap, move, return) per APG patterns
|
|
12
|
+
- NEVER: `outline: none` without visible focus replacement
|
|
13
|
+
|
|
14
|
+
### Targets & Input
|
|
15
|
+
|
|
16
|
+
- MUST: Hit target >=24px (mobile >=44px); if visual <24px, expand hit area
|
|
17
|
+
- MUST: Mobile `<input>` font-size >=16px to prevent iOS zoom
|
|
18
|
+
- NEVER: Disable browser zoom (`user-scalable=no`, `maximum-scale=1`)
|
|
19
|
+
- MUST: `touch-action: manipulation` to prevent double-tap zoom
|
|
20
|
+
- SHOULD: Set `-webkit-tap-highlight-color` to match design
|
|
21
|
+
|
|
22
|
+
### Forms
|
|
23
|
+
|
|
24
|
+
- MUST: Hydration-safe inputs (no lost focus/value)
|
|
25
|
+
- NEVER: Block paste in `<input>`/`<textarea>`
|
|
26
|
+
- MUST: Loading buttons show spinner and keep original label
|
|
27
|
+
- MUST: Enter submits focused input; in `<textarea>`, Cmd/Ctrl+Enter submits
|
|
28
|
+
- MUST: Keep submit enabled until request starts; then disable with spinner
|
|
29
|
+
- MUST: Accept free text, validate after--don't block typing
|
|
30
|
+
- MUST: Allow incomplete form submission to surface validation
|
|
31
|
+
- MUST: Errors inline next to fields; on submit, focus first error
|
|
32
|
+
- MUST: `autocomplete` + meaningful `name`; correct `type` and `inputmode`
|
|
33
|
+
- SHOULD: Disable spellcheck for emails/codes/usernames
|
|
34
|
+
- SHOULD: Placeholders end with `...` and show example pattern
|
|
35
|
+
- MUST: Warn on unsaved changes before navigation
|
|
36
|
+
- MUST: Compatible with password managers & 2FA; allow pasting codes
|
|
37
|
+
- MUST: Trim values to handle text expansion trailing spaces
|
|
38
|
+
- MUST: No dead zones on checkboxes/radios; label+control share one hit target
|
|
39
|
+
|
|
40
|
+
### State & Navigation
|
|
41
|
+
|
|
42
|
+
- MUST: URL reflects state (deep-link filters/tabs/pagination/expanded panels)
|
|
43
|
+
- MUST: Back/Forward restores scroll position
|
|
44
|
+
- MUST: Links use `<a>`/`<Link>` for navigation (support Cmd/Ctrl/middle-click)
|
|
45
|
+
- NEVER: Use `<div onClick>` for navigation
|
|
46
|
+
|
|
47
|
+
### Feedback
|
|
48
|
+
|
|
49
|
+
- SHOULD: Optimistic UI; reconcile on response; on failure rollback or offer Undo
|
|
50
|
+
- MUST: Confirm destructive actions or provide Undo window
|
|
51
|
+
- MUST: Use polite `aria-live` for toasts/inline validation
|
|
52
|
+
- SHOULD: Ellipsis (`...`) for options opening follow-ups ("Rename...") and loading states ("Loading...")
|
|
53
|
+
|
|
54
|
+
### Touch & Drag
|
|
55
|
+
|
|
56
|
+
- MUST: Generous targets, clear affordances; avoid finicky interactions
|
|
57
|
+
- MUST: Delay first tooltip; subsequent peers instant
|
|
58
|
+
- MUST: `overscroll-behavior: contain` in modals/drawers
|
|
59
|
+
- MUST: During drag, disable text selection and set `inert` on dragged elements
|
|
60
|
+
- MUST: If it looks clickable, it must be clickable
|
|
61
|
+
|
|
62
|
+
### Autofocus
|
|
63
|
+
|
|
64
|
+
- SHOULD: Autofocus on desktop with single primary input; rarely on mobile
|
|
65
|
+
|
|
66
|
+
## Animation
|
|
67
|
+
|
|
68
|
+
- MUST: Honor `prefers-reduced-motion` (provide reduced variant or disable)
|
|
69
|
+
- SHOULD: Prefer CSS > Web Animations API > JS libraries
|
|
70
|
+
- MUST: Animate compositor-friendly props (`transform`, `opacity`) only
|
|
71
|
+
- NEVER: Animate layout props (`top`, `left`, `width`, `height`)
|
|
72
|
+
- NEVER: `transition: all`--list properties explicitly
|
|
73
|
+
- SHOULD: Animate only to clarify cause/effect or add deliberate delight
|
|
74
|
+
- SHOULD: Choose easing to match the change (size/distance/trigger)
|
|
75
|
+
- MUST: Animations interruptible and input-driven (no autoplay)
|
|
76
|
+
- MUST: Correct `transform-origin` (motion starts where it "physically" should)
|
|
77
|
+
- MUST: SVG transforms on `<g>` wrapper with `transform-box: fill-box`
|
|
78
|
+
|
|
79
|
+
## Layout
|
|
80
|
+
|
|
81
|
+
- SHOULD: Optical alignment; adjust +/-1px when perception beats geometry
|
|
82
|
+
- MUST: Deliberate alignment to grid/baseline/edges--no accidental placement
|
|
83
|
+
- SHOULD: Balance icon/text lockups (weight/size/spacing/color)
|
|
84
|
+
- MUST: Verify mobile, laptop, ultra-wide (simulate ultra-wide at 50% zoom)
|
|
85
|
+
- MUST: Respect safe areas (`env(safe-area-inset-*)`)
|
|
86
|
+
- MUST: Avoid unwanted scrollbars; fix overflows
|
|
87
|
+
- SHOULD: Flex/grid over JS measurement for layout
|
|
88
|
+
|
|
89
|
+
## Content & Accessibility
|
|
90
|
+
|
|
91
|
+
- SHOULD: Inline help first; tooltips last resort
|
|
92
|
+
- MUST: Skeletons mirror final content to avoid layout shift
|
|
93
|
+
- MUST: `<title>` matches current context
|
|
94
|
+
- MUST: No dead ends; always offer next step/recovery
|
|
95
|
+
- MUST: Design empty/sparse/dense/error states
|
|
96
|
+
- SHOULD: Curly quotes; avoid widows/orphans (`text-wrap: balance`)
|
|
97
|
+
- MUST: `font-variant-numeric: tabular-nums` for number comparisons
|
|
98
|
+
- MUST: Redundant status cues (not color-only); icons have text labels
|
|
99
|
+
- MUST: Accessible names exist even when visuals omit labels
|
|
100
|
+
- MUST: Use `...` character (not `...` three dots)
|
|
101
|
+
- MUST: `scroll-margin-top` on headings; "Skip to content" link; hierarchical `<h1>`-`<h6>`
|
|
102
|
+
- MUST: Resilient to user-generated content (short/avg/very long)
|
|
103
|
+
- MUST: Locale-aware dates/times/numbers (`Intl.DateTimeFormat`, `Intl.NumberFormat`)
|
|
104
|
+
- MUST: Accurate `aria-label`; decorative elements `aria-hidden`
|
|
105
|
+
- MUST: Icon-only buttons have descriptive `aria-label`
|
|
106
|
+
- MUST: Prefer native semantics (`button`, `a`, `label`, `table`) before ARIA
|
|
107
|
+
- MUST: Non-breaking spaces: `10 MB`, `Cmd K`, brand names
|
|
108
|
+
|
|
109
|
+
## Content Handling
|
|
110
|
+
|
|
111
|
+
- MUST: Text containers handle long content (`truncate`, `line-clamp-*`, `break-words`)
|
|
112
|
+
- MUST: Flex children need `min-w-0` to allow truncation
|
|
113
|
+
- MUST: Handle empty states--no broken UI for empty strings/arrays
|
|
114
|
+
|
|
115
|
+
## Performance
|
|
116
|
+
|
|
117
|
+
- SHOULD: Test iOS Low Power Mode and macOS Safari
|
|
118
|
+
- MUST: Measure reliably (disable extensions that skew runtime)
|
|
119
|
+
- MUST: Track and minimize re-renders (React DevTools/React Scan)
|
|
120
|
+
- MUST: Profile with CPU/network throttling
|
|
121
|
+
- MUST: Batch layout reads/writes; avoid reflows/repaints
|
|
122
|
+
- MUST: Mutations (`POST`/`PATCH`/`DELETE`) target <500ms
|
|
123
|
+
- SHOULD: Prefer uncontrolled inputs; controlled inputs cheap per keystroke
|
|
124
|
+
- MUST: Virtualize large lists (>50 items)
|
|
125
|
+
- MUST: Preload above-fold images; lazy-load the rest
|
|
126
|
+
- MUST: Prevent CLS (explicit image dimensions)
|
|
127
|
+
- SHOULD: `<link rel="preconnect">` for CDN domains
|
|
128
|
+
- SHOULD: Critical fonts: `<link rel="preload" as="font">` with `font-display: swap`
|
|
129
|
+
|
|
130
|
+
## Dark Mode & Theming
|
|
131
|
+
|
|
132
|
+
- MUST: `color-scheme: dark` on `<html>` for dark themes
|
|
133
|
+
- SHOULD: `<meta name="theme-color">` matches page background
|
|
134
|
+
- MUST: Native `<select>`: explicit `background-color` and `color` (Windows fix)
|
|
135
|
+
|
|
136
|
+
## Hydration
|
|
137
|
+
|
|
138
|
+
- MUST: Inputs with `value` need `onChange` (or use `defaultValue`)
|
|
139
|
+
- SHOULD: Guard date/time rendering against hydration mismatch
|
|
140
|
+
|
|
141
|
+
## Design
|
|
142
|
+
|
|
143
|
+
- SHOULD: Layered shadows (ambient + direct)
|
|
144
|
+
- SHOULD: Crisp edges via semi-transparent borders + shadows
|
|
145
|
+
- SHOULD: Nested radii: child <= parent; concentric
|
|
146
|
+
- SHOULD: Hue consistency: tint borders/shadows/text toward bg hue
|
|
147
|
+
- MUST: Accessible charts (color-blind-friendly palettes)
|
|
148
|
+
- MUST: Meet contrast--prefer [APCA](https://apcacontrast.com/) over WCAG 2
|
|
149
|
+
- MUST: Increase contrast on `:hover`/`:active`/`:focus`
|
|
150
|
+
- SHOULD: Match browser UI to bg
|
|
151
|
+
- SHOULD: Avoid dark color gradient banding (use background images when needed)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Development Workflow
|
|
2
|
+
|
|
3
|
+
## Skill Invocation Sequence
|
|
4
|
+
|
|
5
|
+
For any new feature or fix, skills fire in this order — do not skip steps:
|
|
6
|
+
|
|
7
|
+
1. **`jpegg:evaluate`** — always first. Explores intent, asks clarifying questions, surfaces unstated requirements. Fires when a feature or fix is described.
|
|
8
|
+
2. **`jpegg:blueprint`** — after evaluation. Produces the plan document. User reviews and approves before any code is written.
|
|
9
|
+
3. **`jpegg:build`** — after plan approval. Works through the plan with PR-level checkpoints. One PR per logical chunk.
|
|
10
|
+
|
|
11
|
+
Within each implementation chunk:
|
|
12
|
+
|
|
13
|
+
4. **`jpegg:worktree`** — before writing code. Creates an isolated workspace for the branch.
|
|
14
|
+
5. **`jpegg:test-first`** — before writing implementation code. Tests first, always.
|
|
15
|
+
6. **`jpegg:pre-ship`** — before claiming done. Runs verification commands, reads actual output.
|
|
16
|
+
7. **`jpegg:seek-review`** — before opening the PR. Internal review pass first.
|
|
17
|
+
8. **`jpegg:draft-prs`** — when creating a PR in a stack. Downstream PRs are draft; promote on base merge.
|
|
18
|
+
9. **`jpegg:ci-loop`** — after PR creation. Monitors CI and fixes failures before reporting back.
|
|
19
|
+
10. **`jpegg:pr-stack`** — after every PR creation or merge. Updates `.claude/stack.json`.
|
|
20
|
+
|
|
21
|
+
When receiving feedback on an open PR:
|
|
22
|
+
|
|
23
|
+
11. **`jpegg:handle-review`** — fires before implementing any review comment. Evaluates feedback before acting.
|
|
24
|
+
|
|
25
|
+
## Stacked PR Workflow
|
|
26
|
+
|
|
27
|
+
- Each branch is created from the tip of the previous branch, not from `main`
|
|
28
|
+
- Each PR targets the previous branch as its base, not `main`
|
|
29
|
+
- Downstream PRs are always created as drafts until their base merges
|
|
30
|
+
- When a base merges: rebase the next branch, update its PR base to `main`, promote from draft to ready
|
|
31
|
+
- Session state is tracked in `.claude/stack.json` — read this at session start if it exists
|
|
32
|
+
|
|
33
|
+
## Resuming After a Break
|
|
34
|
+
|
|
35
|
+
1. Read `.claude/stack.json` to understand current stack state
|
|
36
|
+
2. Read the plan document referenced in the stack file
|
|
37
|
+
3. Check open PRs with `gh pr list`
|
|
38
|
+
4. Continue from the current branch
|
|
39
|
+
|
|
40
|
+
## After a PR is Merged
|
|
41
|
+
|
|
42
|
+
When the user reports a PR is merged:
|
|
43
|
+
1. `git fetch`
|
|
44
|
+
2. Rebase the next branch in the stack onto `main`
|
|
45
|
+
3. `git push --force-with-lease --force-if-includes`
|
|
46
|
+
4. Update the PR base on GitHub: `gh pr edit <number> --base main`
|
|
47
|
+
5. Promote from draft: `gh pr ready <number>`
|
|
48
|
+
6. Update `.claude/stack.json`
|