mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-19 17:46:36 +03:00
Reorganize contributor docs
This commit is contained in:
+217
@@ -0,0 +1,217 @@
|
||||
# Developer Guide
|
||||
|
||||
Documentation for contributors to Impeccable.
|
||||
|
||||
## Architecture
|
||||
|
||||
The skill at `skill/` is transformed into provider-specific formats by a config-driven factory. Each provider is defined as a config object in `scripts/lib/transformers/providers.js` -- adding a new provider requires only a new config entry.
|
||||
|
||||
For detailed harness capabilities (which frontmatter fields each supports, placeholder systems, directory structures), see [HARNESSES.md](HARNESSES.md).
|
||||
|
||||
## Source Format
|
||||
|
||||
### Skill (`skill/SKILL.src.md`)
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: skill-name
|
||||
description: What this skill provides
|
||||
argument-hint: "[target]"
|
||||
user-invocable: true
|
||||
license: License info (optional)
|
||||
compatibility: Environment requirements (optional)
|
||||
---
|
||||
|
||||
Your skill instructions here...
|
||||
```
|
||||
|
||||
**Frontmatter fields** (based on [Agent Skills spec](https://agentskills.io/specification)):
|
||||
- `name` (required): Skill identifier (1-64 chars, lowercase/numbers/hyphens)
|
||||
- `description` (required): What the skill provides (1-1024 chars)
|
||||
- `user-invocable` (optional): Boolean -- if `true`, the skill can be invoked as a slash command
|
||||
- `argument-hint` (optional): Hint shown during autocomplete (e.g., `[target]`, `[area (feature, page...)]`)
|
||||
- `license` (optional): License/attribution info
|
||||
- `compatibility` (optional): Environment requirements (1-500 chars)
|
||||
- `metadata` (optional): Arbitrary key-value pairs
|
||||
- `allowed-tools` (optional, experimental): Pre-approved tools list
|
||||
|
||||
**Body placeholders** (replaced per-provider during build):
|
||||
- `{{model}}` -- Provider-specific model name (e.g., "Claude", "Gemini", "GPT")
|
||||
- `{{config_file}}` -- Provider-specific config file (e.g., "CLAUDE.md", ".cursorrules")
|
||||
- `{{ask_instruction}}` -- How to ask the user for clarification
|
||||
- `{{command_prefix}}` -- Slash command prefix (`/` for most, `$` for Codex)
|
||||
- `{{available_commands}}` -- Comma-separated list of user-invocable commands
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
- Bun (fast JavaScript runtime and package manager)
|
||||
- No external dependencies required
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
# Build all provider formats
|
||||
bun run build
|
||||
|
||||
# Clean dist folder
|
||||
bun run clean
|
||||
|
||||
# Rebuild from scratch
|
||||
bun run rebuild
|
||||
```
|
||||
|
||||
### What Gets Generated
|
||||
|
||||
```
|
||||
source/ -> dist/
|
||||
skills/{name}/SKILL.md {provider}/{configDir}/skills/{name}/SKILL.md
|
||||
```
|
||||
|
||||
Each provider gets its own output directory.
|
||||
|
||||
## Build System Details
|
||||
|
||||
The build system uses a factory pattern under `scripts/`:
|
||||
|
||||
```
|
||||
scripts/
|
||||
build.js # Main orchestrator
|
||||
lib/
|
||||
utils.js # Frontmatter parsing, placeholder replacement, YAML generation
|
||||
zip.js # ZIP bundle generation
|
||||
transformers/
|
||||
factory.js # createTransformer() -- generates transformer functions from config
|
||||
providers.js # PROVIDERS config map -- one entry per provider
|
||||
index.js # Re-exports factory-generated transformer functions
|
||||
```
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
1. Add a placeholder config to `PROVIDER_PLACEHOLDERS` in `scripts/lib/utils.js`:
|
||||
```javascript
|
||||
'my-provider': {
|
||||
model: 'MyModel',
|
||||
config_file: 'CONFIG.md',
|
||||
ask_instruction: 'ask the user directly to clarify.',
|
||||
command_prefix: '/'
|
||||
}
|
||||
```
|
||||
|
||||
2. Add a provider config to `PROVIDERS` in `scripts/lib/transformers/providers.js`:
|
||||
```javascript
|
||||
'my-provider': {
|
||||
provider: 'my-provider',
|
||||
configDir: '.my-provider',
|
||||
displayName: 'My Provider',
|
||||
frontmatterFields: ['user-invocable', 'argument-hint', 'license'],
|
||||
}
|
||||
```
|
||||
|
||||
3. Run `bun run build` -- the provider is automatically picked up by the build loop.
|
||||
|
||||
4. Update `HARNESSES.md` with the provider's capabilities.
|
||||
|
||||
### Provider Config Options
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `provider` | Key for output directory and placeholder lookup |
|
||||
| `configDir` | Dot-directory name (e.g., `.claude`) |
|
||||
| `displayName` | Human-readable name for build logs |
|
||||
| `frontmatterFields` | Which optional fields to emit (see `factory.js` FIELD_SPECS) |
|
||||
| `bodyTransform` | Optional `(body, skill) => body` function for post-processing |
|
||||
| `placeholderProvider` | Override which PROVIDER_PLACEHOLDERS key to use (for variants sharing config) |
|
||||
|
||||
### Key Functions
|
||||
|
||||
- `createTransformer(config)`: Factory that returns a transformer function from a provider config
|
||||
- `parseFrontmatter()`: Extracts YAML frontmatter and body from SKILL.md files
|
||||
- `readSourceFiles()`: Reads `skill/SKILL.src.md` plus its `reference/` and `scripts/` siblings
|
||||
- `replacePlaceholders()`: Substitutes `{{model}}`, `{{config_file}}`, etc. per provider
|
||||
- `generateYamlFrontmatter()`: Serializes objects to YAML frontmatter (auto-quotes values starting with `[` or `{`)
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
bun run test # Default suite — unit + static fixtures (no API keys needed)
|
||||
bun run test:live-e2e # Opt-in — full-cycle live-mode E2E across framework fixtures (~2 min, needs `npx playwright install chromium` once)
|
||||
bun run test:skill-behavior # Opt-in — LLM-backed checks that the SKILL.md Setup flow actually drives the agent (~5 min, costs cents, needs `.env`)
|
||||
```
|
||||
|
||||
The skill-behavior suite runs three providers (claude-haiku-4-5, gpt-5.4-mini, gemini-3.1-flash-lite — the cheapest tier of each, every run) with the source `skill/SKILL.src.md` inlined as the system prompt and a workspace-scoped `bash`/`read`/`write`/`list` tool set. It then asserts on the tool-call trace, not on free-form output. Use it whenever you edit `skill/SKILL.src.md`'s Setup section, `skill/scripts/context.mjs`, or any Setup-touching reference (`teach.md`, `document.md`, `brand.md`, `product.md`, sub-command refs). Per-scenario assertions and the current baseline (21-22/24) live in `tests/skill-behavior/README.md`. Provider keys live in repo-root `.env` (gitignored); missing keys skip cleanly.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Skill Writing
|
||||
|
||||
1. **Focused scope**: One clear domain per skill
|
||||
2. **Clear descriptions**: Make purpose obvious
|
||||
3. **Clear instructions**: LLM should understand exactly what to do
|
||||
4. **Include examples**: Where they clarify intent
|
||||
5. **State constraints**: What NOT to do as clearly as what to do
|
||||
6. **Test across providers**: Verify it works in multiple contexts. For Setup-related edits to `skill/`, `bun run test:skill-behavior` automates this across three providers.
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
- [Agent Skills Specification](https://agentskills.io/specification) - Open standard
|
||||
- [HARNESSES.md](HARNESSES.md) - Provider capabilities matrix
|
||||
- [Cursor Skills](https://cursor.com/docs/context/skills)
|
||||
- [Claude Code Skills](https://code.claude.com/docs/en/skills)
|
||||
- [Gemini CLI Skills](https://geminicli.com/docs/cli/skills/)
|
||||
- [Codex CLI Skills](https://developers.openai.com/codex/skills/)
|
||||
- [VS Code Copilot Skills](https://code.visualstudio.com/docs/copilot/customization/agent-skills)
|
||||
- [Kiro Skills](https://kiro.dev/docs/skills/)
|
||||
- [OpenCode Skills](https://opencode.ai/docs/skills/)
|
||||
- [Pi Skills](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md)
|
||||
- [Qoder Skills](https://docs.qoder.com/extensions/skills)
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
impeccable/
|
||||
source/ # Edit these! Source of truth
|
||||
skills/ # Skill definitions
|
||||
frontend-design/
|
||||
SKILL.md
|
||||
reference/*.md # Domain-specific references
|
||||
audit/SKILL.md
|
||||
polish/SKILL.md
|
||||
...
|
||||
dist/ # Generated output (gitignored)
|
||||
scripts/
|
||||
build.js # Main orchestrator
|
||||
lib/
|
||||
utils.js # Shared utilities
|
||||
zip.js # ZIP generation
|
||||
transformers/
|
||||
factory.js # Config-driven transformer factory
|
||||
providers.js # Provider config map
|
||||
index.js # Re-exports
|
||||
tests/ # Bun test suite
|
||||
docs/
|
||||
HARNESSES.md # Provider capabilities reference
|
||||
DEVELOP.md # This file
|
||||
README.md # User documentation
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build fails with YAML parsing errors
|
||||
- Check frontmatter indentation (YAML is indent-sensitive)
|
||||
- Ensure `---` delimiters are on their own lines
|
||||
- Values starting with `[` or `{` are auto-quoted; other special YAML chars may need manual quoting
|
||||
|
||||
### Output doesn't match expectations
|
||||
- Check the provider config in `scripts/lib/transformers/providers.js`
|
||||
- Verify source file has correct frontmatter structure
|
||||
- Run `bun run rebuild` to ensure clean build
|
||||
|
||||
### Provider doesn't recognize the files
|
||||
- Check installation path for your provider
|
||||
- Verify file naming matches provider requirements
|
||||
- Consult [HARNESSES.md](HARNESSES.md) for provider-specific details
|
||||
|
||||
## Questions?
|
||||
|
||||
Open an issue or submit a PR!
|
||||
@@ -0,0 +1,97 @@
|
||||
# Harness Skills Capabilities Reference
|
||||
|
||||
Source of truth for what each AI coding harness supports in terms of agent skills.
|
||||
Used to inform provider configs in `scripts/lib/transformers/providers.js`.
|
||||
|
||||
Last verified: 2026-04-28
|
||||
|
||||
## Official Documentation
|
||||
|
||||
| Harness | Docs URL |
|
||||
|---------|----------|
|
||||
| Claude Code | https://code.claude.com/docs/en/skills |
|
||||
| Cursor | https://cursor.com/docs/context/skills |
|
||||
| Gemini CLI | https://geminicli.com/docs/cli/skills/ |
|
||||
| Codex CLI | https://developers.openai.com/codex/skills |
|
||||
| GitHub Copilot (Agents) | https://code.visualstudio.com/docs/copilot/customization/agent-skills |
|
||||
| Kiro | https://kiro.dev/docs/skills/ |
|
||||
| OpenCode | https://opencode.ai/docs/skills/ |
|
||||
| Pi | https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md |
|
||||
| Qoder | https://docs.qoder.com/extensions/skills |
|
||||
| Trae | TBD (no official skills docs found yet) |
|
||||
| Rovo Dev | https://support.atlassian.com/rovo/docs/extend-rovo-dev-cli-with-agent-skills |
|
||||
|
||||
## Spec Compliance
|
||||
|
||||
All harnesses follow the [Agent Skills specification](https://agentskills.io/specification) to varying degrees. The spec defines these frontmatter fields: `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools`.
|
||||
|
||||
Provider-specific extensions beyond the spec: `user-invocable`, `argument-hint`, `disable-model-invocation`, `allowed-tools` (extended syntax), `model`, `effort`, `context`, `agent`, `hooks`, `subtask`, `mcp`.
|
||||
|
||||
## Frontmatter Support
|
||||
|
||||
Fields marked with * are spec-standard. Others are provider extensions.
|
||||
|
||||
| Field | Claude Code | Cursor | Gemini | Codex | Copilot | Kiro | OpenCode | Pi | Qoder | Rovo Dev |
|
||||
|-------|:-----------:|:------:|:------:|:-----:|:-------:|:----:|:--------:|:--:|:-----:|:--------:|
|
||||
| `name`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `description`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `license`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `compatibility`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `metadata`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes | Yes | Yes |
|
||||
| `allowed-tools`* | Yes | No | Ignored | No | No | No | Yes | Yes | Yes | Yes |
|
||||
| `user-invocable` | Yes | No | No | No | Yes | No | Yes | No | Yes | Yes |
|
||||
| `argument-hint` | Yes | No | No | No | Yes | No | Yes | No | Yes | Yes |
|
||||
| `disable-model-invocation` | Yes | Yes | No | No | Yes | No | Yes | Yes | TBD | TBD |
|
||||
| `model` | Yes | No | No | No | No | No | Yes | No | No | No |
|
||||
| `effort` | Yes | No | No | No | No | No | No | No | No | No |
|
||||
| `context` | Yes | No | No | No | No | No | No | No | No | No |
|
||||
| `agent` | Yes | No | No | No | No | No | Yes | No | No | No |
|
||||
| `hooks` | Yes | No | No | No | No | No | No | No | No | No |
|
||||
|
||||
Notes:
|
||||
- Gemini CLI validates only `name` and `description`; other spec fields are parsed but ignored.
|
||||
- Codex CLI uses a separate `agents/openai.yaml` sidecar for skill metadata (icons, branding, MCP tools, invocation control). Codex also auto-discovers subagents bundled inside an installed skill's `agents/` folder (TOML), which is how Impeccable ships its asset-producer. Standalone custom agents can still live under `.codex/agents/` or `~/.codex/agents/`, but Impeccable no longer installs anything there.
|
||||
- Kiro recognizes `user-invocable` and `disable-model-invocation` per community reports but does not formally document them.
|
||||
- Unknown fields are silently ignored by all harnesses.
|
||||
|
||||
## Skill Directory Structure
|
||||
|
||||
| Harness | Native directory | Also reads |
|
||||
|---------|-----------------|------------|
|
||||
| Claude Code | `.claude/skills/` | - |
|
||||
| Cursor | `.cursor/skills/` | `.agents/skills/`, `.claude/skills/` |
|
||||
| Gemini CLI | `.gemini/skills/` | `.agents/skills/` |
|
||||
| Codex CLI | `.agents/skills/` (primary) | - |
|
||||
| GitHub Copilot | `.github/skills/` | `.agents/skills/`, `.claude/skills/` |
|
||||
| Kiro | `.kiro/skills/` | - |
|
||||
| OpenCode | `.opencode/skills/` | `.agents/skills/`, `.claude/skills/` |
|
||||
| Pi | `.pi/skills/` | `.agents/skills/` |
|
||||
| Qoder | `.qoder/skills/` | `~/.qoder/skills/` (user-level) |
|
||||
| Trae China | `.trae-cn/skills/` | TBD |
|
||||
| Trae International | `.trae/skills/` | TBD |
|
||||
| Rovo Dev | `.rovodev/skills/` | `~/.rovodev/skills/` (user-level) |
|
||||
|
||||
All harnesses support the `{skill-name}/SKILL.md` directory structure with optional `reference/`, `scripts/`, and `assets/` subdirectories.
|
||||
|
||||
## Native Subagent Directory Structure
|
||||
|
||||
| Harness | Native directory | File format |
|
||||
|---------|------------------|-------------|
|
||||
| Claude Code | `.claude/agents/` (installed plugin) | Markdown with YAML frontmatter |
|
||||
| Codex CLI | `<skill>/agents/` (nested, auto-discovered) | TOML |
|
||||
|
||||
Impeccable keeps canonical agent prompts under `skill/agents/` and emits provider-native files only for harnesses with documented subagent formats. Claude reads its agents from the installed plugin; Codex auto-discovers the TOML bundled inside the installed skill's own `agents/` folder, so the normal skills install carries it with no separate sidecar.
|
||||
|
||||
## Placeholder / Variable Substitution
|
||||
|
||||
Claude Code supports runtime variable substitution directly in SKILL.md bodies: `$ARGUMENTS`, `$0`-`$N`, `${CLAUDE_SKILL_DIR}`, `${CLAUDE_SESSION_ID}`. No other harness supports substitution in skills.
|
||||
|
||||
Some harnesses have separate "custom commands" systems (distinct from skills) with their own substitution:
|
||||
|
||||
| Harness | Command system | Substitution syntax |
|
||||
|---------|---------------|-------------------|
|
||||
| Gemini CLI | `.gemini/commands/` (TOML) | `{{args}}`, `!{shell}`, `@{file}` |
|
||||
| Codex CLI | `.codex/prompts/` | `$ARGNAME` |
|
||||
| OpenCode | `.opencode/commands/` | `$ARGUMENTS`, `$1`-`$N`, `` !`shell` `` |
|
||||
|
||||
Our build system handles cross-provider placeholders at compile time via `replacePlaceholders()` for `{{model}}`, `{{config_file}}`, `{{ask_instruction}}`, and `{{available_commands}}`.
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# STYLE.md
|
||||
|
||||
Editorial brief for impeccable.design. Read this before writing or editing user-facing copy: the homepage, sub-pages, command editorials, tutorials, and READMEs.
|
||||
|
||||
The bar: **for every paragraph, point to the sentence that makes it specifically yours.** If you can't, the paragraph is AI by default, even if a human typed it.
|
||||
|
||||
## Principles
|
||||
|
||||
1. **Open with the reader's wrong belief, your strongest claim, or the example.** No "in this guide", no "let's dive in".
|
||||
2. **Take a position someone could disagree with.** If the paragraph could be inverted without changing meaning, it has no position. Sign your stance.
|
||||
3. **Name names. Use numbers.** Real competitors, real customer names, real version numbers, real file paths, real benchmarks. Cut "lightweight"; write "54 KB".
|
||||
4. **Verbs lead. Nouns follow.** Imperative is fine. Active voice. Cut nominalizations ("the implementation of" → "implementing").
|
||||
5. **Vary sentence length on purpose.** Long, long, short. Smooth uniform rhythm is the deepest AI tell.
|
||||
6. **Prose carries the load; structure supports it.** Bullets are for parallel options. Paragraphs are for argument. Don't bullet what would be tighter as a sentence.
|
||||
7. **Plain words. Technical terms only when something specifically rests on them.** Mixing levels lets the technical terms hit harder.
|
||||
8. **Allow ungrammatical fragments for rhythm.** Five words. Confidence signal.
|
||||
9. **Respect the reader's competence.** No "developers should consider"; just "you might not need an effect".
|
||||
10. **Read it aloud. Fix anything you stumble over.**
|
||||
11. **Concrete over comprehensive.** Coverage is an AI obsession. Trade coverage for momentum. Leave things out.
|
||||
12. **Close by handing off the next move.** Don't summarize. End on the strongest sentence, give a directive ("Now do this"), or just stop.
|
||||
|
||||
## Denylist
|
||||
|
||||
The build's `validateProse` step (in `scripts/build.js`) fails the build on these. The list is the editorial brief, enforced. Add a rule here when you ban a new pattern; remove a rule when the term has earned a real meaning here. **Do not silently allowlist** by working around the regex.
|
||||
|
||||
### Stolen-engineer diction
|
||||
Engineering words that became AI flavor once they leaked into training data around late 2024.
|
||||
|
||||
| Banned | Why | Use instead |
|
||||
|---|---|---|
|
||||
| `load-bearing` | Almost always vague. The literal sense is rare. | Name the specific thing it does. "The decision that shapes the rest", "carries the brand", "matters specifically". |
|
||||
| `highest-leverage` | Vague claim of impact. | Say what specifically pays off. "The change that moves the design most". |
|
||||
| `biggest unlock` | Marketing-speak. | Describe the actual change. |
|
||||
|
||||
### Internal jargon leaking out
|
||||
Words that work in a research notebook and fail in user copy.
|
||||
|
||||
| Banned | Why | Use instead |
|
||||
|---|---|---|
|
||||
| `reflex defaults` | Eval-team jargon. | "Instincts", "first guesses", "default reaches". |
|
||||
| `collapses into monoculture` | Eval-paper voice. | Describe what specifically went wrong (e.g. "every model picked the same three fonts"). |
|
||||
| `data-driven` | Empty marketing adjective. | Cite the data. "Validated against 15 briefs across two models". |
|
||||
|
||||
### Marketing voice
|
||||
Adjectives and verbs that gesture at quality without doing the work.
|
||||
|
||||
| Banned | Why | Use instead |
|
||||
|---|---|---|
|
||||
| `seamless`, `seamlessly` | Hollow positive. | Say what specifically works without friction. |
|
||||
| `robust`, `robustness` | Hollow positive. | Cite the failure mode handled. |
|
||||
| `elevate`, `elevates` | Marketing verb. | Use the specific verb (improve, raise, sharpen). |
|
||||
| `empower`, `empowers` | Marketing verb. | "Let you", "make possible". |
|
||||
| `underscore`, `underscores` | AI tell. | "Show", "make clear". |
|
||||
| `pivotal` | Hollow positive. | "Central", "key", or describe the role. |
|
||||
| `tapestry` | AI scenery noun. | Cut. |
|
||||
|
||||
### Verbs
|
||||
| Banned | Why | Use instead |
|
||||
|---|---|---|
|
||||
| `delve`, `delves`, `delved`, `delving` | The most-flagged AI tell of all. | "Look at", "explore", or just delete the throat-clearing verb. |
|
||||
|
||||
### Throat-clearing
|
||||
Sentences that delay the point. Cut them; almost nothing of value is lost.
|
||||
|
||||
| Banned | Why | Use instead |
|
||||
|---|---|---|
|
||||
| `in today's …` | Generic opener. | Start at the actual point. |
|
||||
| `gone are the days` | Cliché opener. | Make the point directly. |
|
||||
| `whether you're …` | Audience-pandering; addresses no one. | Pick one reader. Write to them. |
|
||||
| `let's dive in` | Throat-clearing. | Just start. |
|
||||
|
||||
### Closers
|
||||
| Banned | Why | Use instead |
|
||||
|---|---|---|
|
||||
| `in summary`, `in conclusion` | Restates what was just said. | End on the strongest sentence. Trust the reader. |
|
||||
|
||||
### Transitions
|
||||
| Banned | Why | Use instead |
|
||||
|---|---|---|
|
||||
| `moreover`, `furthermore` | Metronome transition crutch. | Drop, or use "also", or restructure. |
|
||||
|
||||
### Punctuation
|
||||
| Banned | Why | Use instead |
|
||||
|---|---|---|
|
||||
| Em dash `—` (and HTML entities `—`, `—`, `—`) | Decision-avoidance: writer didn't pick a relationship between the clauses. | Comma, colon, semicolon, period, parentheses. Pick the relationship. |
|
||||
| ` -- ` (double hyphen as em-dash substitute) | Worse than the em dash. Signals failed cleanup. | Real punctuation. |
|
||||
|
||||
## Patterns the validator can't catch
|
||||
|
||||
The above are the easy wins. The deeper issues require human judgment on every paragraph.
|
||||
|
||||
- **Negation pivot.** "It's not just X, it's Y." "Less about X, more about Y." This is now a stronger AI tell than any vocabulary item. Use sparingly. Most instances should be replaced with a direct positive claim.
|
||||
- **Triadic everything.** Every list exactly three items. Every adjective in groups of three ("fast, simple, and powerful"). Vary count: use 2 or 4. Use 1.
|
||||
- **The five-paragraph essay shape.** Intro → 3 sections → conclusion, on every page. Mix it up. Lead with the example. Skip the conclusion. Let some sections be one sentence.
|
||||
- **Uniform paragraph length.** Insert a 4-word sentence. Insert a one-line paragraph.
|
||||
- **Synthetic balance.** Pros and cons of equal length when one is clearly right. Write the recommendation; note real exceptions briefly.
|
||||
- **Hollow confidence.** "Powerful" without numbers. Replace with a concrete fact.
|
||||
- **Hedging stacks.** "It might potentially be useful to consider..." Each hedge is fine; stacked, they sound trained.
|
||||
- **Interchangeable copy.** Swap "Impeccable" for a competitor name. If nothing becomes false, the copy is generic.
|
||||
|
||||
## When in doubt
|
||||
|
||||
Read the paragraph aloud. If you stumble, rewrite. If a sentence describes nothing specific to this product, cut it.
|
||||
@@ -1,48 +0,0 @@
|
||||
# Issue 150 Live Preview Plan
|
||||
|
||||
## Current Bug Summary
|
||||
|
||||
Live preview can lose framework state when variants are written directly into watched component source. The Svelte reproduction is a stateful expense row: after adding an expense, generating variants for the row should not reset the component or render raw Svelte expressions such as `{expenses[0].name}`.
|
||||
|
||||
The current branch uses **Svelte component injection** for `.svelte` targets: variants are real components under `src/lib/impeccable/<id>/`, mounted in the browser via Svelte 5 `mount()`, and inlined back into the route source on live exit. Accept keeps the mounted component visible immediately while deferring the route write until `live-server stop`.
|
||||
|
||||
## Manual test apps (home directory)
|
||||
|
||||
Stateful framework repros live outside this repo:
|
||||
|
||||
- **Svelte:** `~/impeccable-live-svelte` (see its `README.md`)
|
||||
- **React:** `~/impeccable-live-react` (see its `README.md`)
|
||||
|
||||
Each app includes a copied `.cursor/skills/impeccable` build from the local impeccable branch for live-server / inject / poll.
|
||||
|
||||
## Current Status
|
||||
|
||||
- Svelte component-injection Accept fix is implemented.
|
||||
- Svelte manual pass with the user is complete (`~/impeccable-live-svelte`).
|
||||
- React manual user check and the DeepSeek-backed final run are still pending (`~/impeccable-live-react`).
|
||||
- Focused live tests, build, and full test suite should pass after harness refresh.
|
||||
|
||||
## Svelte Fix Plan
|
||||
|
||||
- Keep component injection scoped to `.svelte` targets.
|
||||
- Extract `propContract` from the picked route markup and author variants as real `.svelte` files with `{propName}` bindings.
|
||||
- Mount compiled variants in the browser with the app's shared Svelte runtime.
|
||||
- On Accept, keep the chosen mounted variant visible immediately.
|
||||
- Defer the real route source inline until live shutdown to avoid accept-time remounts.
|
||||
- On live shutdown, inline accepted markup + CSS into the route and remove temp component files.
|
||||
- Keep the connected indicator stable while an event is leased or actively being handled.
|
||||
|
||||
## React Parity Test Plan
|
||||
|
||||
- Same shape as the Svelte case in `~/impeccable-live-react`.
|
||||
- Run the same Go, cycle, Accept flow against the React row.
|
||||
- React already uses direct source wrap + Fast Refresh; keep that path unchanged unless manual testing shows state loss.
|
||||
|
||||
## Validation Checklist (impeccable repo)
|
||||
|
||||
- [ ] `node --test tests/live-browser-regression.test.mjs tests/live-accept.test.mjs tests/live-poll.test.mjs tests/live-server.test.mjs tests/live-svelte-component.test.mjs`
|
||||
- [ ] Manual Svelte run in `~/impeccable-live-svelte`
|
||||
- [ ] `bun run build`
|
||||
- [ ] `bun run test`
|
||||
|
||||
The PR should remain draft until the React manual check in `~/impeccable-live-react` passes.
|
||||
@@ -1,132 +0,0 @@
|
||||
# Handoff: Svelte live mode bar stuck at `0/0` (CYCLING with no variants)
|
||||
|
||||
Date: 2026-05-30
|
||||
Branch: `codex/issue-150-svelte-live`
|
||||
Status: **Unresolved.** Two rounds of fixes landed and were synced to the manual test repo, but the symptom still reproduces.
|
||||
|
||||
---
|
||||
|
||||
## Symptom
|
||||
|
||||
In `~/impeccable-live-svelte`, after running a live action (Polish/Bolder) on the expense row, the floating live bar shows the **CYCLING** layout (prev/next arrows, two faint dots, `✓ Accept`, `✕`) but the counter reads `0/0` and every control is disabled (`opacity: 0.3`, `pointer-events: none`). No variant is mounted. The bar persists across reloads.
|
||||
|
||||
Captured DOM (trimmed): `<div id="impeccable-live-bar">` … `<span>0/0</span>` … `✓ Accept` (disabled) `✕`. Full markup is in the chat history if needed.
|
||||
|
||||
`0/0` = `visibleVariant=0 / arrivedVariants=0`. The two dots = `expectedVariants=2` rendered as "pending" (see `buildDots`, both unfilled because `arrivedVariants=0`).
|
||||
|
||||
---
|
||||
|
||||
## Context: what this feature is
|
||||
|
||||
Issue 150 replaced the Svelte "source-shadow" live preview with **real component injection**. See `docs/issue-150-live-preview-plan.md` and `skill/reference/live.md` (the `svelte-component` paragraph). Key pieces:
|
||||
|
||||
- `skill/scripts/live-svelte-component.mjs` — scaffolds `src/lib/impeccable/<id>/` with `manifest.json`, `v1.svelte`…`vN.svelte`, and a one-time `__runtime.js`. Inlines the accepted variant back into the route on exit.
|
||||
- `skill/scripts/live-wrap.mjs` — `.svelte` targets return `previewMode: "svelte-component"`, `file` = manifest path.
|
||||
- `skill/scripts/live-browser.js` — mounts compiled variants via Svelte 5 `mount()` into a `display:contents` slot that replaces the original element. Cycling = unmount + remount.
|
||||
- `skill/scripts/live-accept.mjs` / `live-server.mjs` — defer the route source write to `live-server stop`.
|
||||
|
||||
The Svelte wrapper in the live DOM is **runtime-injected** and holds a single mount target (`[data-impeccable-component-mount]`), **not** `[data-impeccable-variant]` children like the HTML/JSX path. This distinction is the source of most of the trouble.
|
||||
|
||||
---
|
||||
|
||||
## What has already been fixed (and synced to the test repo)
|
||||
|
||||
All in `skill/scripts/live-browser.js`, rebuilt via `bun run build:skills` and rsync'd to `~/impeccable-live-svelte/.cursor/skills/impeccable/`.
|
||||
|
||||
1. **Params sidecar (the original compile error).** Svelte parses `{` inside an attribute value as an expression, so `data-impeccable-params='[{…}]'` broke compilation (`Expected token }`). Params now load from `componentDir/params.json` keyed by variant number:
|
||||
- `loadSvelteComponentParams(manifest)` fetches `params.json`.
|
||||
- `parseVariantParams()` reads from `svelteComponentSession.paramsByVariant` for the component path instead of the DOM attribute.
|
||||
- Agent contract updated in `live-svelte-component.mjs` (`buildSvelteComponentCssAuthoring`) and `skill/reference/live.md`.
|
||||
|
||||
2. **Resume guard.** `resumeSession()` now drops an orphaned `svelte-component` wrapper (no live in-memory mount) instead of resuming it into an empty bar. Without this, every reload resumed `arrivedVariants=0`.
|
||||
|
||||
3. **Abort-on-failure.** New `abortSvelteComponentInjection(sessionId, message)` resets the bar to PICKING (restores the original element, clears session, toast) when the picked element can't be found OR the initial `mountSvelteComponentVariant` returns false (compile/mount throw). Called from `injectSvelteComponentsFromManifest`.
|
||||
|
||||
Tests added in `tests/live-browser-regression.test.mjs` (all green): orphan reset, abort helper, sidecar params. Full focused suite passes: `node --test tests/live-browser-regression.test.mjs tests/live-svelte-component.test.mjs tests/live-accept.test.mjs`.
|
||||
|
||||
---
|
||||
|
||||
## Why it probably STILL reproduces (hypotheses, in priority order)
|
||||
|
||||
### H1. The served `live.js` is stale (verify FIRST)
|
||||
The browser loads `live.js` from the running live-server, not from disk directly. If the server wasn't restarted, or the `<script src=".../live.js">` is cached, the page is still running the OLD code without the abort/resume guards.
|
||||
- **Check:** in the page console, search the loaded script for `abortSvelteComponentInjection` / `loadSvelteComponentParams`. If absent, the fix isn't loaded.
|
||||
- **Fix:** `live-server.mjs --stop` then `--background`, hard-reload (Cmd+Shift+R), reload Cursor.
|
||||
- Confirm the test repo copy is current: `grep -l abortSvelteComponentInjection ~/impeccable-live-svelte/.cursor/skills/impeccable/scripts/live-browser.js`.
|
||||
|
||||
### H2. The variant still fails to compile, and a path other than `injectSvelteComponentsFromManifest` sets CYCLING
|
||||
The abort only fires inside `injectSvelteComponentsFromManifest`. If the agent-authored `v1.svelte` still has a compile error (another `{` in an attribute, a bad expression, etc.), the mount fails. Confirm the abort path is actually reached:
|
||||
- Look for the console line `[impeccable] Failed to mount Svelte variant N` and the toast.
|
||||
- If CYCLING is being set somewhere else, audit every `updateBarContent('cycling')` / `state = 'CYCLING'` site (grep finds ~12) for one that runs with `arrivedVariants===0`.
|
||||
|
||||
### H3. Server-side event replay re-injects after reset
|
||||
`live-server.mjs` redelivers unacknowledged events and persists a session journal. After the browser aborts to PICKING, a redelivered `done` event (SSE reconnect, or the agent reply not acked) could re-trigger injection. The `done` handler (`live-browser.js` ~line 4868) only injects when `state === 'GENERATING'`, so a clean PICKING state should be safe — but verify the state at the moment of replay. The durable session may also need clearing: check `.impeccable/live/sessions/` and `.impeccable/live/deferred-svelte-component-accepts.json` in the test repo.
|
||||
|
||||
### H4. Stale localStorage session keeps re-priming the bar
|
||||
The bar persists `state`/`id`/`visible` to localStorage (keys prefixed `impeccable-live`). A reload can rehydrate CYCLING before any wrapper exists.
|
||||
- **Check:** `Object.keys(localStorage).filter(k=>k.startsWith('impeccable-live'))` in the console.
|
||||
- **Clear:** remove those keys and reload (see "Reset procedure" below).
|
||||
|
||||
### H5. Defect-class root cause: empty CYCLING is representable at all
|
||||
The deepest fix is to make `0/0 CYCLING` an impossible state. A cheap, robust guard: in the cycling render path (`buildCyclingRow` / wherever `updateBarContent('cycling')` resolves), if `arrivedVariants === 0`, refuse to render CYCLING and fall back to PICKING (or hide). That self-heals regardless of which upstream path is buggy. Consider adding this as belt-and-suspenders even after the specific path is found.
|
||||
|
||||
---
|
||||
|
||||
## Reset procedure (clears the currently-stuck bar)
|
||||
|
||||
```js
|
||||
// page console
|
||||
Object.keys(localStorage).filter(k => k.startsWith('impeccable-live')).forEach(k => localStorage.removeItem(k));
|
||||
location.reload();
|
||||
```
|
||||
|
||||
```bash
|
||||
cd ~/impeccable-live-svelte
|
||||
node .cursor/skills/impeccable/scripts/live-server.mjs --stop
|
||||
node .cursor/skills/impeccable/scripts/live-server.mjs --background
|
||||
# also clear any orphaned session state if H3 suspected:
|
||||
rm -rf .impeccable/live/sessions/* .impeccable/live/deferred-svelte-component-accepts.json
|
||||
rm -rf src/lib/impeccable/*/ # leftover variant component dirs (keep __runtime.js)
|
||||
```
|
||||
Reload Cursor so `/impeccable` picks up the synced skill.
|
||||
|
||||
---
|
||||
|
||||
## Repro
|
||||
|
||||
1. `cd ~/impeccable-live-svelte && npm run dev`
|
||||
2. New terminal: `node .cursor/skills/impeccable/scripts/live-server.mjs --background`, note the port.
|
||||
3. `node .cursor/skills/impeccable/scripts/live-inject.mjs --port <port>` then `node .cursor/skills/impeccable/scripts/live-poll.mjs`.
|
||||
4. In the browser: add an expense, pick the expense row, run Polish/Bolder, let variants generate.
|
||||
5. Observe whether the bar reaches `1/3` (good) or `0/0` (the bug).
|
||||
6. With `IMPECCABLE_E2E_DEBUG`-style logging: open devtools console and watch for `[impeccable]` lines during step 4.
|
||||
|
||||
The expense row source is `src/routes/+page.svelte` lines ~34-37 (`<article class="expense-row …">{expenses[0].name}…`). `propContract` derives `name` and `amount`.
|
||||
|
||||
---
|
||||
|
||||
## Suggested investigation order for tomorrow
|
||||
|
||||
1. **Confirm the new code is actually live** (H1) — single biggest time-saver. Grep the loaded script + the test-repo file for `abortSvelteComponentInjection`.
|
||||
2. With confirmed-fresh code, reproduce and **capture the console**. Determine whether `injectSvelteComponentsFromManifest` runs and whether `mountSvelteComponentVariant` throws. Read the actual `src/lib/impeccable/<id>/v1.svelte` the agent wrote — is it valid Svelte?
|
||||
3. If mount throws: the agent is still authoring an invalid component. Tighten the `live.md` contract / scaffold stub, OR make the inline-accept/scaffold validate. Capture the exact error.
|
||||
4. If mount succeeds but bar still `0/0`: trace which `state='CYCLING'` site runs with `arrivedVariants=0` (H2) and add the H5 guard.
|
||||
5. Add an E2E regression once root cause is known. Note `tests/live-e2e.test.mjs` still references the old `source-shadow` markers for the Svelte fixture (lines ~311, ~891, ~918) — that opt-in suite needs updating to the component-injection model regardless.
|
||||
|
||||
---
|
||||
|
||||
## Key files / line anchors (as of this handoff)
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `skill/scripts/live-browser.js` ~4242 | `injectSvelteComponentsFromManifest` |
|
||||
| `skill/scripts/live-browser.js` (`abortSvelteComponentInjection`) | clean-reset helper |
|
||||
| `skill/scripts/live-browser.js` (`mountSvelteComponentVariant`) | dynamic import + `mount()` |
|
||||
| `skill/scripts/live-browser.js` (`loadSvelteComponentParams`, `parseVariantParams`) | sidecar params |
|
||||
| `skill/scripts/live-browser.js` (`resumeSession`) | orphan-wrapper guard |
|
||||
| `skill/scripts/live-browser.js` ~4868 | SSE `done` handler |
|
||||
| `skill/scripts/live-browser.js` (`buildCyclingRow`, `buildDots`, ~2102/2262) | bar render — candidate for H5 guard |
|
||||
| `skill/scripts/live-svelte-component.mjs` | scaffold / inline-accept / cssAuthoring contract |
|
||||
| `skill/reference/live.md` (`svelte-component` paragraph + Parameters §7) | agent contract |
|
||||
|
||||
Remember: source of truth is `skill/`. After any edit run `bun run build:skills`, then rsync to `~/impeccable-live-svelte/.cursor/skills/impeccable/`. Do not hand-edit the harness copies.
|
||||
Reference in New Issue
Block a user