Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
---
name: add-feature-flag
description: Add a new feature flag to gate code changes in the Warp codebase.
---
# add-feature-flag
Add a new feature flag to gate code changes in the Warp codebase.
## Overview
Feature flags in Warp are compile-time flags that allow features to be selectively enabled for different channels (e.g.: Dev, Stable). They use a small runtime plumbing layer that checks if a flag is enabled.
## Steps
### 1. Add to Cargo.toml
Add the feature to `app/Cargo.toml` under the `[features]` section, but **NOT** under the `default` nested stanza:
```toml
[features]
your_feature_name = []
```
### 2. Add to FeatureFlag enum
Add a new variant to the `FeatureFlag` enum in `warp_core/src/features.rs`:
```rust
#[derive(Sequence)]
pub enum FeatureFlag {
YourFeatureName,
}
```
### 3. Add conditional compilation directive
Add the feature to `app/src/lib.rs` with a corresponding `#[cfg(feature = "...")]` attribute to ensure it's only included when enabled:
```rust
#[cfg(feature = "your_feature_name")]
YourFeatureName,
```
### 4. Gate code with runtime checks
In your code, use the runtime check to conditionally execute feature-gated code:
```rust
if FeatureFlag::YourFeatureName.is_enabled() {
// feature-gated behavior
}
```
### 5. (Optional) Enable for dogfood builds
To enable the feature by default for Dev/dogfood builds, add it to the `DOGFOOD_FLAGS` array in `features.rs`:
```rust
pub const DOGFOOD_FLAGS: &[FeatureFlag] = &[
FeatureFlag::YourFeatureName,
];
```
### 6. Running with feature flags
To test locally with the feature enabled:
```bash
cargo run --features your_feature_name
# Multiple features:
cargo run --features your_feature_name,another_feature
```
## Keybindings with Feature Flags
If adding an `EditableBinding` or `FixedBinding` that's part of a gated feature, include an enabled predicate that checks the feature flag. This prevents the keybinding from appearing in keyboard settings when the feature is disabled.
Example:
```rust
EditableBinding::new(
"action:name",
"Action description",
YourAction::Variant
)
.with_enabled(|| FeatureFlag::YourFeatureName.is_enabled())
.with_key_binding("cmdorctrl-key")
```
## Rolling Out to Stable
When ready to enable the feature for all Warp Stable users, add it to the `default` array in `app/Cargo.toml`:
```toml
[features]
default = [
"your_feature_name",
# other default features...
]
```
## Best Practices
- **Prefer runtime checks over cfg directives**: Use `FeatureFlag::YourFeatureName.is_enabled()` instead of `#[cfg(...)]` when possible, so flags can be toggled without recompilation and are easier to clean up later
- Use `#[cfg(...)]` only when code cannot compile without the flag (e.g., platform-specific code or missing dependencies)
- Keep flags high-level and product-focused rather than per-call-site
- Remove flags and dead branches after launch has stabilized
+167
View File
@@ -0,0 +1,167 @@
---
name: add-telemetry
description: Add telemetry events to track user behavior or system events in the Warp codebase. Use when instrumenting new features, debugging issues, or measuring product metrics.
---
# add-telemetry
## Overview
Warp uses a trait-based telemetry system where feature-specific enums implement the `TelemetryEvent` trait. This approach keeps telemetry events organized by domain rather than in one giant enum.
**Important**: Before implementing telemetry, collaborate with the user to:
- Define what events should be tracked and when
- Determine what data should be included in each event
- Clarify the purpose and expected usage of the telemetry
Adding telemetry code is straightforward, but designing meaningful instrumentation requires careful thought.
## Steps
### 1. Identify or create a telemetry module
Find an existing feature-specific telemetry file (e.g., `app/src/antivirus/telemetry.rs`) or create a new one for your feature area.
### 2. Define the telemetry event enum
Add a new variant to an enum that implements `TelemetryEvent`, or create a new enum:
```rust
use serde_json::{json, Value};
use strum_macros::{EnumDiscriminants, EnumIter};
use warp_core::telemetry::{EnablementState, TelemetryEvent, TelemetryEventDesc};
#[derive(Debug, EnumDiscriminants)]
#[strum_discriminants(derive(EnumIter))]
pub enum YourFeatureTelemetryEvent {
ActionStarted {
duration_ms: u64,
},
ActionCompleted {
success: bool,
error: Option<String>,
},
}
```
### 3. Implement the TelemetryEvent trait
`EnablementState` allows you to control when events are sent:
- `EnablementState::Always` - Always send the event
- `EnablementState::Flag(FeatureFlag::YourFeature)` - Only send when the feature flag is enabled
- `EnablementState::Channel(Channel::Dev)` - Only send in specific build channels
```rust
impl TelemetryEvent for YourFeatureTelemetryEvent {
fn name(&self) -> &'static str {
YourFeatureTelemetryEventDiscriminants::from(self).name()
}
fn payload(&self) -> Option<Value> {
match self {
Self::ActionStarted { duration_ms } => Some(json!({
"duration_ms": duration_ms,
})),
Self::ActionCompleted { success, error } => Some(json!({
"success": success,
"error": error,
})),
}
}
fn description(&self) -> &'static str {
YourFeatureTelemetryEventDiscriminants::from(self).description()
}
fn enablement_state(&self) -> EnablementState {
YourFeatureTelemetryEventDiscriminants::from(self).enablement_state()
}
fn contains_ugc(&self) -> bool {
match self {
Self::ActionStarted { .. } => false,
Self::ActionCompleted { .. } => false,
}
}
fn event_descs() -> impl Iterator<Item = Box<dyn TelemetryEventDesc>> {
warp_core::telemetry::enum_events::<Self>()
}
}
```
### 4. Implement TelemetryEventDesc for the discriminants
```rust
impl TelemetryEventDesc for YourFeatureTelemetryEventDiscriminants {
fn name(&self) -> &'static str {
match self {
Self::ActionStarted => "YourFeature.Action.Started",
Self::ActionCompleted => "YourFeature.Action.Completed",
}
}
fn description(&self) -> &'static str {
match self {
Self::ActionStarted => "User started the action",
Self::ActionCompleted => "User completed the action",
}
}
fn enablement_state(&self) -> EnablementState {
match self {
Self::ActionStarted | Self::ActionCompleted => EnablementState::Always,
// Or gate behind a feature flag:
// EnablementState::Flag(FeatureFlag::YourFeature)
}
}
}
```
### 5. Register the telemetry event
At the end of your telemetry module, register the event:
```rust
warp_core::register_telemetry_event!(YourFeatureTelemetryEvent);
```
### 6. Send telemetry events from your code
Use `send_telemetry_from_ctx!` in views or models with a `ViewContext` or `ModelContext`:
```rust
use warp_core::send_telemetry_from_ctx;
// In a view update or model method
send_telemetry_from_ctx!(
YourFeatureTelemetryEvent::ActionStarted {
duration_ms: 150,
},
ctx
);
```
For code with only `AppContext`, use `send_telemetry_from_app_ctx!` instead.
### 7. Test locally
Run Warp with the `log_named_telemetry_events` feature flag to see telemetry events logged to the console:
```bash
cargo run --features log_named_telemetry_events
```
## Best Practices
- Keep telemetry enums feature-specific rather than adding to a global enum
- Set `contains_ugc()` to `true` if the payload includes user-generated content
- Use descriptive event names following the pattern `Feature.Action.Result`
- Include only necessary data in payloads to minimize bandwidth and storage
- Consider privacy implications when deciding what data to include
- Avoid exhaustive matching with wildcards; handle all variants explicitly
## Example Reference
See `app/src/antivirus/telemetry.rs` for a complete example of a feature-specific telemetry implementation.
+231
View File
@@ -0,0 +1,231 @@
---
name: create-pr
description: Create a pull request in the warp repository for the current branch. Use when the user mentions opening a PR, creating a pull request, submitting changes for review, or preparing code for merge.
---
# create-pr
## Overview
This guide covers best practices for creating pull requests in the warp repository, including merging master, running presubmit checks, linking Linear tasks, ensuring appropriate test coverage, and structuring your PR for effective review.
## Related Skills
- `fix-errors` - Fix presubmit failures (formatting, linting, tests) before opening PR
- `rust-unit-tests` - Write unit tests for your changes, if applicable (see "Testing Requirements" below)
- `warp-integration-test` - Add or update integration coverage for user-visible flows, regressions, and P0 use cases
- `add-feature-flag` - Gate changes behind feature flags
## Pre-PR Checklist
### 1. Merge master into your feature branch
**Always merge master into your feature branch before starting the review process.**
```bash
git fetch origin
git merge origin/master
```
Resolve any merge conflicts locally before opening the PR.
### 2. Run presubmit checks for code changes
If the PR includes code changes, run the relevant presubmit checks before opening or updating it:
```bash
./script/presubmit
```
`./script/presubmit` runs:
- `cargo fmt` - Code formatting
- `cargo clippy` - Linting with all warnings as errors
- All tests (unit, doc, and integration)
If the PR is documentation-only (for example, skills, markdown, or other non-code content), you do not need to run `cargo fmt` or `cargo clippy` just to open or update the PR.
If presubmit fails for a code-changing PR, use the `fix-errors` skill to resolve issues.
**You must run `cargo fmt` and `cargo clippy` before:**
- Opening a new PR that includes code changes
- Pushing new commits that include code changes to an existing PR branch
- Any reviewed branch update that changes code
### 3. Review your changes
Before creating a PR, review what changes you're about to submit:
```bash
# View commits in your branch (comparing against base branch)
git --no-pager log <base-branch>..HEAD --oneline
# View file statistics for changes
git --no-pager diff <base-branch>...HEAD --stat
# View full diff
git --no-pager diff <base-branch>...HEAD
```
This helps you:
- Verify all intended changes are included
- Catch unintended changes before review
- Write an accurate PR description
- Ensure you're comparing against the correct base branch
- **Tests:** Include tests when required—bug fixes (regression test), algorithmic code (unit tests), UI components (layout test), P0 use cases (integration test). See Testing Requirements below.
### 4. Link to Linear task
When possible, PRs should be associated with a Linear task. Use the Linear MCP tool (if available) to find corresponding issues.
**Branch naming convention:**
Remote branches should be prefixed with your name (e.g., `zheng/feature`, `alice/fix-bug`).
**How to link PRs to Linear:**
Include the issue ID in the PR title (e.g., `[WARP-1234] Add new feature`). Do this **before** creating the PR for automatic linking.
### 5. Open the PR
Use the PR template at `.github/pull_request_template.md` when opening PRs.
Add changelog entries when appropriate using the format at the bottom of the PR template. Some examples:
- Feature: "Global search in files across your current directories. Use CMD-F/CTRL-SHIFT-F to open."
- Improvement: "Added horizontal autoscrolling when jumping to line/column."
- Bug fix: "Fixed session viewer input being cleared when agent runs commands.
**CLI workflow:**
- **Check if PR exists** for current branch:
```bash
gh pr view --json number,url
```
Exit code 0 if PR exists, 1 if not.
- **Create a new PR:**
```bash
# With title and body
gh pr create --title "Title" --body "Description" --draft
# Auto-fill from commits
gh pr create --fill --draft
# Use PR template file
gh pr create --body-file .github/pull_request_template.md --title "Title" --draft
```
Key flags: `--draft` / `-d`, `--fill` / `-f`, `--body-file` / `-F`, `--web` / `-w`
- **Update an existing PR:**
```bash
gh pr edit --title "New title" --body "New body"
gh pr edit --add-reviewer username --add-label bug
```
- **Mark PR ready for review:**
```bash
gh pr ready
```
### 6. Include co-author attribution
When committing changes or creating a PR, include attribution at the end of every commit message or PR description:
```
Co-Authored-By: Warp <agent@warp.dev>
```
## Testing Requirements
### Bug fixes require regression tests
**All bug fixes should be accompanied by a regression test.** This helps prevent re-breaking something that was already broken once.
The test should:
- Reproduce the original bug (would fail before the fix)
- Pass after the fix is applied
- Be clearly named to indicate what bug it's preventing
### Algorithmic code requires unit tests
Code with non-trivial logic should have unit tests to validate functionality:
**Examples of what needs unit tests:**
- Custom data structures (e.g., `SumTree`)
- Search-related APIs that should return expected results for a given query
- Core layout code in the UI framework
- Any algorithmic or computational logic
**Not required for:**
- Sufficiently-simple functions
- Trivial getters/setters
See the `rust-unit-tests` skill for guidance on writing unit tests.
### UI components need layout validation tests
**All UI components (implementations of `View`) should have a simple unit test** to validate that they can be laid out without a panic.
This provides high-level coverage over rendering "safety" (though not "correctness"):
```rust
#[test]
fn test_component_can_layout() {
use warpui::App;
use warp::test_util::{terminal::initialize_app_for_terminal_view, add_window_with_terminal};
App::test((), |mut app| async move {
initialize_app_for_terminal_view(&mut app);
let term = add_window_with_terminal(&mut app, None);
// Render the component - should not panic
term.update(&mut app, |view, ctx| {
// Create and layout your component
});
})
}
```
### Ask before skipping integration coverage
If the PR changes a user-visible flow, fixes an end-to-end regression, or otherwise looks like it would benefit from integration coverage, use the `ask_user_question` tool before creating or updating the PR to ask whether the user wants an integration test added as part of the work.
Prefer a direct choice such as:
- `Yes, add an integration test before creating the PR`
- `No, continue without an integration test`
If the user chooses to add one, use the `warp-integration-test` skill.
### P0 use cases require integration tests
**All "P0 use cases" require an integration test** that covers the behavior/flow in question.
**A "P0 use case" is defined as:** Any behavior of the application that, if broken, warrants an out-of-band release.
Integration tests should:
- Exercise the full user-facing flow
- Validate end-to-end functionality
- Be placed in the `integration/` directory
Use the `warp-integration-test` skill for implementation details, test registration steps, and validation workflow.
## PR Description Guidelines
Your PR summary under the "Description" section should include:
1. **What** - What changes are being made
2. **Why** - Why these changes are necessary (link to Linear task if applicable)
3. **How** - Brief explanation of the approach taken
## After Opening the PR
1. **Monitor CI checks** - Ensure all automated checks pass
2. **Respond to review comments** - Address feedback promptly
3. **Keep the PR up to date** - Merge master if conflicts arise
4. **Re-run relevant validation** - After making changes based on review feedback. For code changes, re-run `cargo fmt`/`cargo clippy` (and other relevant checks); for documentation-only changes, this is not required.
## Best Practices
- **Keep PRs focused** - One logical change per PR when possible
- **Write clear commit messages** - Explain what and why, not just what
- **Self-review first** - Review your own diff before requesting review
- **Update tests** - Ensure test coverage reflects your changes
- **Document breaking changes** - Call out any API changes or breaking modifications
- **Use feature flags** - Gate risky changes behind feature flags when appropriate (see the `add-feature-flag` skill)
@@ -0,0 +1,25 @@
---
name: dedupe-issue-local
specializes: dedupe-issue
description: Repo-specific dedupe guidance for warp-external. Only the categories declared overridable by the core dedupe-issue skill may be specialized here.
---
# Repo-specific dedupe guidance for `warp-external`
This file is a companion to the core `dedupe-issue` skill. It does not
redefine the duplicate-detection algorithm, the similarity thresholds,
or the output contract. It only specializes the override categories the
core skill marks as overridable.
## Repo-specific normalizations
- Strip low-signal title prefixes such as `Bug:`, `Feature:`, `Request:`, `[Bug]`, `[Feature]`, `Warp:`, and platform tags like `[macOS]`, `[Linux]`, or `[Windows]` before comparing titles.
- Treat app channel/version, OS version, and shell name as supporting evidence, not as duplicate blockers, when the core symptom and reproduction path are otherwise the same.
- Do not collapse distinct Warp surfaces just because they share a word like "agent", "terminal", "MCP", "settings", "search", or "sync". Require overlap in the actual failing behavior or requested capability.
- For terminal issues, compare shell/session context, command output behavior, prompt rendering, input behavior, and remote/tmux involvement before treating two reports as duplicates.
- For agent or MCP issues, compare the trigger path, local vs cloud execution, MCP server/tool, visible error, and expected workflow before treating two reports as duplicates.
- For UI/rendering issues, compare the affected surface and visible symptom. Similar screenshots or recordings are strong duplicate evidence when the title is vague.
## Known-duplicate clusters
No known-duplicate clusters have been captured for this repository yet. The weekly `update-dedupe` loop will propose additions here over time when maintainers repeatedly close issues as duplicates of the same canonical thread.
@@ -0,0 +1,112 @@
---
name: diagnose-ci-failures
description: Diagnose CI failures for a PR using the GitHub CLI, extract error logs, and generate a plan to fix them. Use when the user asks to check CI status, pull CI issues, triage test failures, or investigate PR build failures.
---
# diagnose-ci-failures
Programmatically diagnose CI failures for a PR and generate a plan to fix them.
## Overview
This skill provides a deterministic workflow to check CI status for a PR, extract failure logs, analyze errors, and create a plan (not code changes) to resolve issues. The output is always a plan document that can be reviewed before execution.
## Workflow
### 1. Verify PR exists for current branch
Get the current branch and check if a PR exists:
```bash
# Get current branch
git branch --show-current
# Check for PR
gh --no-pager pr view <branch-name> --json number,title,url,state
```
If no PR exists, inform the user and offer to create one using the `create-pr` skill.
### 2. Check CI status
Fetch the status of all CI checks:
```bash
gh pr view <branch-name> --json statusCheckRollup
```
Parse the output to identify:
- Completed checks vs. in-progress checks
- Successful checks
- Failed checks with their names and details URLs
If CI is still running, inform the user which checks have already failed or passed, highlight the checks that are still running, and suggest waiting for completion before diagnosis.
### 3. Extract failure logs
For each failed check, pull the logs using the run ID from the status check:
```bash
gh run view <run-id> --log-failed
```
Focus on extracting:
- Error messages and their locations (file paths, line numbers)
- Compilation errors (unused imports, type mismatches, etc.)
- Linting/clippy errors with specific lint names
- Test failure messages and stack traces
- Build failures and their root causes
### 4. Categorize errors
Group errors by type:
- **Formatting issues**: `cargo fmt` failures
- **Linting issues**: `cargo clippy` warnings/errors
- **Compilation errors**: Type errors, missing imports, signature mismatches
- **Test failures**: Failing tests with their names and failure reasons
- **Platform-specific issues**: WASM, Linux, macOS, Windows-specific failures
### 5. Generate fix plan
Create a plan document (using `create_plan` tool) with:
- **Problem Statement**: Summary of failing checks
- **Current State**: What errors were found and where
- **Proposed Changes**: Specific fixes needed for each error category
- **Validation Steps**: Commands to verify fixes (fmt, clippy, tests, presubmit)
The plan should reference the `fix-errors` skill for detailed guidance on resolving specific error types.
## Important Notes
- **Always create a plan first**: Never make code changes directly. Generate a plan for user review
- **Check test status in CI**: Even if tests fail locally, verify they passed in CI before flagging as issues
- **Unrelated test failures**: If tests passed in CI but fail locally, they may be environment-specific or flaky
- **Multiple error types**: Fix one category at a time (e.g., all clippy errors before tests)
- **Cross-reference fix-errors skill**: For detailed error resolution strategies, use the `fix-errors` skill
## Common CI Check Names
- `Formatting + Clippy (MacOS)`
- `Formatting + Clippy (Linux)`
- `Run MacOS tests`
- `Run Linux tests`
- `Run Windows tests`
- `Check CI results` (summary check)
- `WASM build`
## Example Commands
**Get PR status with details:**
```bash
gh --no-pager pr view --json number,title,state,statusCheckRollup
```
**Get logs from specific failed run:**
```bash
gh run view 12345678 --log-failed
```
**Check for specific error in logs:**
```bash
gh run view 12345678 --log-failed 2>&1 | grep -A 5 "error:"
```
+180
View File
@@ -0,0 +1,180 @@
---
name: fix-errors
description: Fix compilation errors, linting issues, and test failures in the warp Rust codebase. Covers presubmit checks, WASM-specific errors, and running specific tests. Use when the user hits build errors, clippy or fmt failures, test failures, or needs to run or interpret presubmit before a PR.
---
# fix-errors
Fix compilation errors, linting issues, and test failures in the warp Rust codebase.
## Overview
This skill helps resolve common issues encountered during development, including:
- Compilation errors (unused imports, type mismatches, etc.)
- Linting failures (clippy warnings)
- Formatting violations
- WASM-specific errors
- Test failures
Before opening or updating a pull request, all presubmit checks must pass.
## Presubmit Checks
Run all presubmit checks at once:
```bash
./script/presubmit
```
This runs formatting, linting, and all tests. If it passes, you're ready to open a PR.
### Individual Checks
Run checks separately when debugging specific issues:
**Rust formatting:**
```bash
cargo fmt -- --check
```
**Clippy (full workspace):**
```bash
cargo clippy --workspace --exclude warp_completer --all-targets --all-features --tests -- -D warnings
cargo clippy -p warp_completer --all-targets --tests -- -D warnings
```
**WASM Clippy:**
```bash
cargo clippy --target wasm32-unknown-unknown --profile release-wasm-debug_assertions --no-deps
```
**Objective-C/C/C++ formatting:**
```bash
./script/run-clang-format.py -r --extensions 'c,h,cpp,m' ./crates/warpui/src/ ./app/src/
```
**All tests:**
```bash
cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2
cargo nextest run -p warp_completer --features v2
```
**Doc tests:**
```bash
cargo test --doc
```
## Running Specific Tests
**Single package:**
```bash
cargo nextest run -p <package_name>
```
**Filter by test name:**
```bash
cargo nextest run -E 'test(<substring>)'
```
**Specific package with filter:**
```bash
cargo nextest run -p <package_name> -E 'test(<substring>)'
```
**With output (no capture):**
```bash
cargo nextest run -p <package> --nocapture
```
## Common Error Types
### Unused Imports
Remove unused `use` statements identified by the compiler.
### Unused Constants
Remove constants that are defined but never used.
### Unknown Imports
Add the correct `use` statement for undefined types. Search the codebase to find the correct module path.
### Type Mismatches
Update function calls to pass arguments of the correct type. Common fixes:
- Use `.as_str()` instead of `.clone()` when a `&str` is expected
- Use `&value` when a reference is needed
- Use `.to_string()` when `String` is expected but `&str` is provided
### Struct Field Changes
When a struct adds/removes fields, update all places where it's constructed or destructured:
- Struct initialization
- Pattern matching (`match`, `if let`)
- Destructuring assignments
### Function Signature Changes
When a function adds a new parameter, update all call sites to provide the new argument:
- For `bool` params: pass `true` or `false` based on context
- For `Option<T>` params: pass `None` as default or `Some(value)` if needed
### Enum Variant Changes
When adding a new enum variant, update exhaustive `match` statements:
- Add a new match arm with appropriate handling
- Mirror the implementation pattern of similar variants
### Incorrect Trait Implementation
Fix trait implementations that return the wrong type or don't satisfy trait bounds.
### WASM-Specific Errors
WASM builds (`wasm32-unknown-unknown` target) don't support filesystem operations. Code that uses filesystem APIs must be gated behind the `local_fs` feature flag.
**Common WASM errors:**
- Dead code warnings for code only used in non-WASM builds
- Unused code that's only relevant when `local_fs` is available
- Tests that require filesystem access
**Fixes:**
**Gate tests behind `local_fs`:**
```rust
#[test]
#[cfg(feature = "local_fs")]
fn test_find_git_repo_with_worktree() {
// Test that uses filesystem operations
}
```
**Conditionally allow dead code for types only used when `local_fs` is enabled:**
```rust
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
#[derive(Clone, EnumDiscriminants, Serialize)]
pub enum ExampleType {
// Variants only used when local_fs is enabled
Variant1,
Variant2,
Variant3,
}
```
WASM errors are discovered by running:
```bash
cargo clippy --target wasm32-unknown-unknown --profile release-wasm-debug_assertions --no-deps
```
## Best Practices
**Before fixing:**
- Read the full error message to understand the root cause
- Check if multiple errors are related (fixing one may resolve others)
- For trait/type errors, verify you understand the expected vs actual types
- For WASM errors, check if code needs to be gated behind `local_fs`
**When fixing:**
- Fix one error type at a time when there are multiple issues
- Run `cargo check` frequently to verify fixes
- For WASM errors, run WASM clippy to verify the fix
- For complex changes, run relevant tests after fixing
**After fixing:**
- Always run `cargo fmt` and `cargo clippy` before pushing
- Run the full presubmit script before opening or updating a PR. Use the `create-pr` skill for more detailed instructions
- Verify tests pass in the areas you modified
+91
View File
@@ -0,0 +1,91 @@
---
name: implement-specs
description: Implement an approved feature from PRODUCT.md and TECH.md, keeping specs and code aligned in the same PR as implementation evolves. Use after the product and tech specs are approved and the next step is building the feature.
---
# implement-specs
Implement an approved feature from `PRODUCT.md` and `TECH.md`.
## Overview
Use this skill after the product and tech specs are approved. The goal is to build the feature described by the specs while keeping the checked-in specs and the implementation aligned as the work evolves.
Approved specs should live directly under a ticket-named directory in `specs/`, for example `specs/APP-1234/PRODUCT.md` and `specs/APP-1234/TECH.md`.
In many cases, the implementation should be pushed in the same PR as the product and tech specs. As the engineer iterates, changes to `PRODUCT.md`, `TECH.md`, and the code should all be pushed in that same PR so review stays anchored to the feature that will actually ship.
## Prerequisites
Before using this skill:
- confirm that `PRODUCT.md` exists
- confirm that `TECH.md` exists when the feature warranted one
- confirm that the relevant specs have been reviewed and approved enough to start implementation
## Workflow
### 1. Read the approved specs first
Treat:
- `PRODUCT.md` as the source of truth for user-facing behavior
- `TECH.md` as the source of truth for architecture, sequencing, and implementation shape
Make sure you understand the expected behavior, constraints, risks, and validation plan before writing code.
### 2. Offer optional implementation aids for large features
For large or long-running features, optionally offer one of these aids to the user before implementation begins:
- `PROJECT_LOG.md` to track checkpoints, explored paths, partial findings, and current implementation state
- `DECISIONS.md` to capture concrete product and technical decisions made during the PRD and tech design process
These are optional aids, not required deliverables. Offer them when they would reduce confusion or help future agents avoid re-exploring the same paths.
### 3. Plan and implement against the specs
Break the work into concrete implementation steps, then implement the feature against the approved specs.
During implementation:
- keep behavior aligned with `PRODUCT.md`
- keep architecture and sequencing aligned with `TECH.md`
- add or update tests and verification artifacts as the work lands
Use the same PR for the specs and implementation when practical so the full feature evolution is reviewable in one place.
### 4. Update specs as the implementation evolves
If implementation reveals that the intended behavior or design should change, update the checked-in specs rather than letting them go stale.
In particular:
- update `PRODUCT.md` when user-facing behavior, UX, edge cases, or success criteria change
- update `TECH.md` when architecture, sequencing, module boundaries, or validation strategy change
- keep those updates in the same PR as the corresponding code changes
The PR should describe the feature that actually ships, not just the initial draft of the specs.
### 5. Verify against the specs
Before considering the work complete, verify that the code matches the current specs.
Prefer:
- `rust-unit-tests` for unit tests and regression coverage
- integration or end-to-end tests for important user flows
## Best Practices
- Keep specs and code synchronized throughout implementation.
- Prefer updating the spec immediately when decisions change rather than batching spec cleanup until the end.
- Use optional tracking documents only when they add real value for a complex feature.
- Keep the same PR coherent: spec updates, code changes, tests, and optional tracking docs should all support the same feature narrative.
## Related Skills
- `spec-driven-implementation`
- `write-product-spec`
- `write-tech-spec`
- `rust-unit-tests`
+101
View File
@@ -0,0 +1,101 @@
---
name: promote-feature
description: Promote a feature-flagged feature to Dogfood, Preview, or Stable in the Warp codebase. Use when a feature behind a FeatureFlag is ready to roll out to a broader audience, including wiring up the compile-time/runtime bridge and deferring flag cleanup safely.
---
# promote-feature
Guides the staged promotion of a gated `FeatureFlag` variant to Dogfood, Preview, or Stable, and schedules the follow-up cleanup.
## Overview
Feature flags have two interacting layers:
- **Runtime** (`warp_core/src/features.rs`): `DOGFOOD_FLAGS`, `PREVIEW_FLAGS`, `RELEASE_FLAGS` — enabled per-channel at startup.
- **Compile-time** (`app/Cargo.toml` + `app/src/lib.rs`): Cargo features in `[features]`. The `default = [...]` array enables a feature for all builds. `enabled_features()` in `app/src/lib.rs` bridges each Cargo feature to its `FeatureFlag` variant via `#[cfg(feature = "...")]`.
**Do not remove the flag immediately after promoting to Stable.** Keep it for at least 1–2 release cycles so a rollback is a one-line PR (remove the entry from `default`). Use the `remove-feature-flag` skill for the cleanup step later.
## Promote to Dogfood
Add the flag to `DOGFOOD_FLAGS` in `warp_core/src/features.rs`:
```rust
pub const DOGFOOD_FLAGS: &[FeatureFlag] = &[
// ...
FeatureFlag::YourFeature,
];
```
No other file changes needed.
## Promote to Preview
1. Add to `PREVIEW_FLAGS` in `warp_core/src/features.rs`.
2. Remove from `DOGFOOD_FLAGS` if present — Preview flags are automatically included in Dogfood builds.
```rust
pub const PREVIEW_FLAGS: &[FeatureFlag] = &[
// ...
FeatureFlag::YourFeature,
];
```
## Promote to Stable
This requires changes in **three files**.
### 1. `app/Cargo.toml` — add to `default`
Add the snake_case feature name to the `default = [...]` array:
```toml
default = [
# ...
"your_feature_name",
]
```
Prefer this over adding to `RELEASE_FLAGS` (see comment at `warp_core/src/features.rs:787-790`). It compiles the feature into all builds and enables a one-line rollback.
### 2. `app/src/lib.rs` — add to `enabled_features()` bridge
Add a `#[cfg(...)]` entry inside the `flags.extend([...])` block in `enabled_features()`, following the existing pattern:
```rust
#[cfg(feature = "your_feature_name")]
FeatureFlag::YourFeature,
```
Place it near logically related entries.
### 3. `warp_core/src/features.rs` — remove from `PREVIEW_FLAGS` / `DOGFOOD_FLAGS`
Remove the variant from whichever arrays it currently lives in:
```rust
pub const PREVIEW_FLAGS: &[FeatureFlag] = &[
// Remove FeatureFlag::YourFeature,
];
```
### Validate
```bash
cargo fmt
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
```
### Create a follow-up Linear issue
After the PR lands, create a Linear issue to remind the team to remove the flag. Use the Linear MCP tool:
```
save_issue(
title: "Remove FeatureFlag::YourFeature after stabilization",
team: <your team>,
assignee: "me",
description: "FeatureFlag::YourFeature was promoted to Stable in <PR link>. Remove the flag and dead code branches after 1–2 release cycles. Follow the `remove-feature-flag` skill.",
labels: ["tech-debt"],
priority: 4 // Low
)
```
+148
View File
@@ -0,0 +1,148 @@
---
name: remove-feature-flag
description: Remove a feature flag after it has been rolled out and stabilized in the Warp codebase.
---
# remove-feature-flag
Remove a feature flag after it has been rolled out and stabilized in the Warp codebase.
## Overview
After a feature flag has been enabled for all users and has stabilized in production, the flag should be removed to reduce technical debt and simplify the codebase. This involves removing the flag definition and all conditional checks.
## When to Remove
Remove a feature flag when:
- The feature has been enabled in `default` features in `app/Cargo.toml`
- The feature has been stable in production for a reasonable period
- There are no plans to disable the feature or provide configuration options
- The team agrees the feature is permanent
## Steps
### 1. Remove from app/Cargo.toml
Remove the feature from both the `[features]` section and the `default` array:
```toml
[features]
default = [
# Remove "your_feature_name" from here
]
# Remove this line:
# your_feature_name = []
```
### 2. Remove from FeatureFlag enum
Remove the variant from the `FeatureFlag` enum in `warp_core/src/features.rs`:
```rust
#[derive(Sequence)]
pub enum FeatureFlag {
// Remove YourFeatureName,
}
```
### 3. Remove from app/src/lib.rs
Remove the conditional compilation directive:
```rust
// Remove these lines:
// #[cfg(feature = "your_feature_name")]
// YourFeatureName,
```
### 4. Remove from DOGFOOD_FLAGS/PREVIEW_FLAGS/RELEASE_FLAGS
If the flag was listed in any of these arrays in `features.rs`, remove it:
```rust
pub const DOGFOOD_FLAGS: &[FeatureFlag] = &[
// Remove FeatureFlag::YourFeatureName,
];
```
### 5. Remove all runtime checks and dead code
Find and remove all `FeatureFlag::YourFeatureName.is_enabled()` checks throughout the codebase:
**Before:**
```rust
if FeatureFlag::YourFeatureName.is_enabled() {
// new behavior
} else {
// old behavior (dead code)
}
```
**After:**
```rust
// new behavior (unconditionally enabled)
```
Use ripgrep to find all occurrences:
```bash
rg "YourFeatureName" app/ warp_core/
```
### 6. Remove keybinding predicates
If the feature flag was used in keybinding enabled predicates, remove the predicate:
**Before:**
```rust
EditableBinding::new(
"action:name",
"Action description",
YourAction::Variant
)
.with_enabled(|| FeatureFlag::YourFeatureName.is_enabled())
.with_key_binding("cmdorctrl-key")
```
**After:**
```rust
EditableBinding::new(
"action:name",
"Action description",
YourAction::Variant
)
.with_key_binding("cmdorctrl-key")
```
### 7. Clean up dead code branches
Remove any code paths that were only executed when the feature was disabled (the `else` branches in feature checks). These are now dead code.
### 8. Run tests and validation
After removing the flag:
```bash
# Format and lint
cargo fmt
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
# Run tests
cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2
# Build the app
cargo run
```
## Best Practices
- Remove feature flags promptly after they're no longer needed to reduce technical debt
- When removing a flag, remove ALL related code (checks, dead branches, keybinding predicates)
- Use grep/ripgrep to ensure you've found all occurrences
- Test thoroughly after removal to ensure no regressions
- Consider doing flag removal in a separate PR for easier review
## Example Search Commands
```bash
# Find all occurrences of the flag name
rg "YourFeatureName" app/ warp_core/
# Find feature flag checks
rg "FeatureFlag::YourFeatureName" app/
# Find cfg attributes
rg 'cfg\(feature = "your_feature_name"\)' app/
```
@@ -0,0 +1,89 @@
---
name: resolve-merge-conflicts
description: Resolve Git merge conflicts by extracting only unresolved paths, conflict hunks, and compact diffs instead of loading whole files into context. Use when a merge, rebase, cherry-pick, or stash pop stops on conflicts, when `git status` shows unmerged paths, or when files contain conflict markers.
---
# Resolve Merge Conflicts
## Overview
Resolve conflicts without opening full files unless the compact view is insufficient. Start with a summary, then inspect one conflicted file at a time.
## Workflow
1. Start with a summary.
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py
```
Use the summary to identify which files are unresolved, which index stages exist, and how many text hunks each file contains.
2. Drill into one file.
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py --file path/to/file
```
Prefer this over reading the whole file. The script prints only nearby context, the `ours` / `base` / `theirs` sections for each hunk, and a compact unified diff between `ours` and `theirs`.
3. Resolve the file.
- Take one side wholesale with `git checkout --ours -- path/to/file` or `git checkout --theirs -- path/to/file` when appropriate.
- Otherwise edit the file directly and remove the conflict markers.
- Read more of the file only if the compact output is not enough to decide the correct merge.
4. Re-check unresolved files.
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py
git diff --name-only --diff-filter=U
```
5. Validate the resolution.
- Ensure no unmerged paths remain.
- Ensure no `<<<<<<<`, `=======`, or `>>>>>>>` markers remain in the resolved files.
- Run targeted tests, builds, or linters for the touched area.
- Stage the resolved files.
## Commands
### Summary only
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py
```
### Detailed view for one file
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py --file path/to/file
```
### Detailed view for all conflicted files
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py --all
```
### JSON output
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py --file path/to/file --json
```
### Tune output size
```bash
python3 .agents/skills/resolve-merge-conflicts/scripts/extract_conflict_context.py \
--file path/to/file \
--context 3 \
--max-lines 60
```
## Notes
- Use the script before opening conflicted files directly.
- Resolve one file at a time to keep context small.
- Expect marker-based text conflicts and index-only conflicts such as add/add or modify/delete. The script summarizes both, and it falls back to index-stage previews when the worktree file has no conflict markers.
@@ -0,0 +1,468 @@
#!/usr/bin/env python3
"""Summarize and extract compact merge-conflict context from a Git repository."""
from __future__ import annotations
import argparse
import difflib
import json
import re
import subprocess
import sys
from pathlib import Path
START_RE = re.compile(r"^<<<<<<<(?: (.*))?$")
BASE_RE = re.compile(r"^\|\|\|\|\|\|\|(?: (.*))?$")
END_RE = re.compile(r"^>>>>>>>(?: (.*))?$")
def run_git(repo_root: Path, *args: str) -> str:
result = subprocess.run(
["git", "-C", str(repo_root), *args],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
message = result.stderr.strip() or result.stdout.strip() or "unknown git error"
raise RuntimeError(f"git {' '.join(args)} failed: {message}")
return result.stdout
def find_repo_root(start: Path) -> Path:
result = subprocess.run(
["git", "-C", str(start), "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
message = result.stderr.strip() or result.stdout.strip() or "not a git repository"
raise RuntimeError(message)
return Path(result.stdout.strip()).resolve()
def get_unmerged_entries(repo_root: Path) -> dict[str, dict[int, dict[str, str]]]:
entries: dict[str, dict[int, dict[str, str]]] = {}
output = run_git(repo_root, "ls-files", "-u", "-z")
for record in output.split("\0"):
if not record:
continue
metadata, path = record.split("\t", 1)
mode, object_id, stage_text = metadata.split()
file_entry = entries.setdefault(path, {})
file_entry[int(stage_text)] = {"mode": mode, "object_id": object_id}
return entries
def read_text_file(path: Path) -> list[str] | None:
if not path.exists() or path.is_dir():
return None
try:
text = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return None
if "\x00" in text:
return None
return text.splitlines()
def read_stage_text(repo_root: Path, path: str, stage: int) -> list[str] | None:
result = subprocess.run(
["git", "-C", str(repo_root), "show", f":{stage}:{path}"],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
return None
if "\x00" in result.stdout:
return None
return result.stdout.splitlines()
def truncate_lines(lines: list[str], max_lines: int) -> list[str]:
if len(lines) <= max_lines:
return lines
omitted = len(lines) - max_lines
return [*lines[:max_lines], f"... ({omitted} more lines omitted)"]
def build_diff(
left_lines: list[str],
right_lines: list[str],
left_label: str,
right_label: str,
max_lines: int,
) -> list[str]:
diff = list(
difflib.unified_diff(
left_lines,
right_lines,
fromfile=left_label,
tofile=right_label,
lineterm="",
)
)
if not diff:
diff = ["(no textual diff)"]
return truncate_lines(diff, max_lines)
def classify_conflict(stages: list[int], marker_hunks: int) -> str:
if marker_hunks:
return "text"
stage_set = set(stages)
if stage_set == {2, 3}:
return "add/add"
if stage_set == {1, 2}:
return "deleted-by-them"
if stage_set == {1, 3}:
return "deleted-by-us"
if stage_set == {1, 2, 3}:
return "index-only"
return "unmerged"
def normalize_requested_path(repo_root: Path, raw_path: str) -> str:
path = Path(raw_path)
candidate = path.resolve() if path.is_absolute() else (repo_root / path).resolve()
try:
return str(candidate.relative_to(repo_root))
except ValueError as error:
raise RuntimeError(f"path is outside repository: {raw_path}") from error
def parse_conflict_hunks(lines: list[str], context: int) -> tuple[list[dict[str, object]], str | None]:
hunks: list[dict[str, object]] = []
index = 0
while index < len(lines):
start_match = START_RE.match(lines[index])
if not start_match:
index += 1
continue
start_index = index
ours_label = start_match.group(1) or "ours"
index += 1
ours: list[str] = []
base: list[str] = []
theirs: list[str] = []
base_label: str | None = None
theirs_label = "theirs"
while index < len(lines):
base_match = BASE_RE.match(lines[index])
if base_match:
base_label = base_match.group(1) or "base"
index += 1
while index < len(lines) and lines[index] != "=======":
base.append(lines[index])
index += 1
break
if lines[index] == "=======":
break
ours.append(lines[index])
index += 1
if index >= len(lines) or lines[index] != "=======":
return hunks, f"unterminated conflict starting at line {start_index + 1}"
index += 1
end_index = index
while index < len(lines):
end_match = END_RE.match(lines[index])
if end_match:
theirs_label = end_match.group(1) or "theirs"
end_index = index
index += 1
break
theirs.append(lines[index])
index += 1
else:
return hunks, f"unterminated conflict starting at line {start_index + 1}"
hunks.append(
{
"start_line": start_index + 1,
"end_line": end_index + 1,
"before_context": lines[max(0, start_index - context):start_index],
"ours": ours,
"ours_label": ours_label,
"base": base or None,
"base_label": base_label,
"theirs": theirs,
"theirs_label": theirs_label,
"after_context": lines[index:index + context],
}
)
return hunks, None
def build_summary_report(repo_root: Path, path: str, stage_entries: dict[int, dict[str, str]], context: int) -> dict[str, object]:
worktree_lines = read_text_file(repo_root / path)
hunks: list[dict[str, object]] = []
parse_error = None
if worktree_lines is not None:
hunks, parse_error = parse_conflict_hunks(worktree_lines, context)
stages = sorted(stage_entries)
return {
"path": path,
"stages": stages,
"conflict_type": classify_conflict(stages, len(hunks)),
"marker_hunks": len(hunks),
"parse_error": parse_error,
"worktree_present": worktree_lines is not None,
"hunks": hunks,
}
def build_index_preview(repo_root: Path, report: dict[str, object], max_lines: int) -> dict[str, object]:
path = str(report["path"])
ours = read_stage_text(repo_root, path, 2)
theirs = read_stage_text(repo_root, path, 3)
base = read_stage_text(repo_root, path, 1)
preview: dict[str, object] = {
"ours": truncate_lines(ours, max_lines) if ours else None,
"theirs": truncate_lines(theirs, max_lines) if theirs else None,
"base": truncate_lines(base, max_lines) if base else None,
}
if ours and theirs:
preview["ours_vs_theirs_diff"] = build_diff(ours, theirs, "ours", "theirs", max_lines)
return preview
def section_lines(title: str, lines: list[str] | None) -> list[str]:
if lines is None:
return [f"{title}:", " (not present)"]
if not lines:
return [f"{title}:", " (empty)"]
return [f"{title}:", *[f" {line}" for line in lines]]
def render_summary_text(repo_root: Path, reports: list[dict[str, object]]) -> str:
lines = [f"repo: {repo_root}", f"conflicted files: {len(reports)}"]
for report in reports:
stages = ",".join(str(stage) for stage in report["stages"])
lines.append(
f"- {report['path']} | type={report['conflict_type']} | stages={stages} | hunks={report['marker_hunks']}"
)
if report["parse_error"]:
lines.append(f" parse-error: {report['parse_error']}")
lines.append("use --file <path> for compact hunk details or --all for every file")
return "\n".join(lines)
def render_detail_text(
repo_root: Path,
report: dict[str, object],
max_lines: int,
) -> str:
lines = [
f"== {report['path']} ==",
f"type: {report['conflict_type']}",
f"stages: {', '.join(str(stage) for stage in report['stages'])}",
]
parse_error = report["parse_error"]
if parse_error:
lines.append(f"parse-error: {parse_error}")
hunks = report["hunks"]
if hunks:
lines.append(f"hunks: {len(hunks)}")
for index, hunk in enumerate(hunks, start=1):
ours = list(hunk["ours"])
theirs = list(hunk["theirs"])
diff = build_diff(
ours,
theirs,
str(hunk["ours_label"]),
str(hunk["theirs_label"]),
max_lines,
)
lines.extend(
[
"",
f"[hunk {index}] current lines {hunk['start_line']}-{hunk['end_line']}",
*section_lines("before", truncate_lines(list(hunk["before_context"]), max_lines)),
*section_lines(
f"ours ({hunk['ours_label']})",
truncate_lines(ours, max_lines),
),
]
)
if hunk["base"] is not None:
lines.extend(
section_lines(
f"base ({hunk['base_label'] or 'base'})",
truncate_lines(list(hunk["base"]), max_lines),
)
)
lines.extend(
[
*section_lines(
f"theirs ({hunk['theirs_label']})",
truncate_lines(theirs, max_lines),
),
*section_lines("ours vs theirs diff", diff),
*section_lines("after", truncate_lines(list(hunk["after_context"]), max_lines)),
]
)
return "\n".join(lines)
preview = build_index_preview(repo_root, report, max_lines)
lines.append("hunks: 0")
lines.append("index preview:")
lines.extend(section_lines("ours", preview["ours"]))
lines.extend(section_lines("base", preview["base"]))
lines.extend(section_lines("theirs", preview["theirs"]))
if "ours_vs_theirs_diff" in preview:
lines.extend(section_lines("ours vs theirs diff", preview["ours_vs_theirs_diff"]))
return "\n".join(lines)
def render_json(
repo_root: Path,
reports: list[dict[str, object]],
include_details: bool,
max_lines: int,
) -> str:
files: list[dict[str, object]] = []
for report in reports:
file_entry: dict[str, object] = {
"path": report["path"],
"conflict_type": report["conflict_type"],
"stages": report["stages"],
"marker_hunks": report["marker_hunks"],
"parse_error": report["parse_error"],
}
if include_details:
if report["hunks"]:
file_entry["hunks"] = [
{
"start_line": hunk["start_line"],
"end_line": hunk["end_line"],
"before_context": truncate_lines(list(hunk["before_context"]), max_lines),
"ours_label": hunk["ours_label"],
"ours": truncate_lines(list(hunk["ours"]), max_lines),
"base_label": hunk["base_label"],
"base": truncate_lines(list(hunk["base"]), max_lines) if hunk["base"] else None,
"theirs_label": hunk["theirs_label"],
"theirs": truncate_lines(list(hunk["theirs"]), max_lines),
"after_context": truncate_lines(list(hunk["after_context"]), max_lines),
"ours_vs_theirs_diff": build_diff(
list(hunk["ours"]),
list(hunk["theirs"]),
str(hunk["ours_label"]),
str(hunk["theirs_label"]),
max_lines,
),
}
for hunk in report["hunks"]
]
else:
file_entry["index_preview"] = build_index_preview(repo_root, report, max_lines)
files.append(file_entry)
return json.dumps(
{
"repo_root": str(repo_root),
"conflicted_files": files,
},
indent=2,
)
def main() -> int:
parser = argparse.ArgumentParser(
description="Summarize and extract compact merge-conflict context."
)
parser.add_argument("--repo", default=".", help="Path inside the target repository.")
parser.add_argument(
"--file",
action="append",
default=[],
help="Conflicted file to inspect in detail. Repeat to inspect multiple files.",
)
parser.add_argument(
"--all",
action="store_true",
help="Print detailed output for every conflicted file.",
)
parser.add_argument(
"--json",
action="store_true",
help="Emit JSON instead of text.",
)
parser.add_argument(
"--context",
type=int,
default=2,
help="Lines of surrounding context to include around each conflict hunk.",
)
parser.add_argument(
"--max-lines",
type=int,
default=40,
help="Maximum lines to print for each section before truncating.",
)
args = parser.parse_args()
if args.all and args.file:
parser.error("--all cannot be combined with --file")
if args.context < 0:
parser.error("--context must be non-negative")
if args.max_lines <= 0:
parser.error("--max-lines must be positive")
try:
repo_root = find_repo_root(Path(args.repo).resolve())
entries = get_unmerged_entries(repo_root)
except RuntimeError as error:
print(f"error: {error}", file=sys.stderr)
return 2
reports = [
build_summary_report(repo_root, path, entries[path], args.context)
for path in sorted(entries)
]
if not reports:
message = json.dumps({"repo_root": str(repo_root), "conflicted_files": []}, indent=2) if args.json else f"repo: {repo_root}\nconflicted files: 0"
print(message)
return 0
if args.all:
selected_reports = reports
elif args.file:
try:
requested_paths = {normalize_requested_path(repo_root, path) for path in args.file}
except RuntimeError as error:
print(f"error: {error}", file=sys.stderr)
return 2
known_paths = {str(report["path"]) for report in reports}
missing = sorted(requested_paths - known_paths)
if missing:
for path in missing:
print(f"error: conflicted file not found: {path}", file=sys.stderr)
return 2
selected_reports = [report for report in reports if report["path"] in requested_paths]
else:
selected_reports = []
if args.json:
print(render_json(repo_root, selected_reports or reports, bool(selected_reports), args.max_lines))
return 0
if not selected_reports:
print(render_summary_text(repo_root, reports))
return 0
print("\n\n".join(render_detail_text(repo_root, report, args.max_lines) for report in selected_reports))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+35
View File
@@ -0,0 +1,35 @@
---
name: review-pr-local
specializes: review-pr
description: Repo-specific review guidance for warp-external. Only the categories declared overridable by the core review-pr skill may be specialized here.
---
# Repo-specific review guidance for `warp-external`
This file is a companion to the core `review-pr` skill. It does not
redefine the review output schema, severity labels, safety rules, or
evidence rules. It only specializes the override categories the core
skill marks as overridable.
## Repo-specific style and recurring review patterns
- Do not suggest adding test cases that only vary constructor inputs or struct fields when an existing test already covers the meaningful behavior. Only suggest new tests when they exercise a distinct code path or edge case.
- When a PR is clearly a V0 or initial implementation, frame robustness suggestions such as timeouts, retries, and lifecycle management as optional future work rather than blocking concerns, unless they risk correctness, security, data loss, or a persistent UI hang.
- For Rust changes, apply the repository conventions from `WARP.md`: avoid unnecessary type annotations, prefer imports over long path qualifiers, name context parameters `ctx` and place them last, remove unused parameters instead of prefixing them with `_`, and prefer inline format arguments in macros.
- Avoid wildcard `_` match arms when an enum can reasonably be matched exhaustively; exhaustive matches are preferred so future variants are surfaced during review.
- For new or changed feature flags, prefer high-level runtime checks with `FeatureFlag::YourFlag.is_enabled()` over `#[cfg(...)]` unless the code cannot compile without a compile-time gate.
- Flag nested or redundant `TerminalModel` locking when the call stack may already hold the model lock. Prefer passing locked references down the stack and keeping lock scopes short.
- In WarpUI code, flag inline `MouseStateHandle::default()` usage during render or event handling. Mouse state handles should be created during construction and then cloned/referenced where needed.
- For user-facing UI changes, mention missing validation only when it is tied to a concrete risk or when the PR changes behavior that should be verified visually.
## User-facing strings
- Flag interpolated text that would read unnaturally at runtime or combine sentence fragments with the wrong casing.
- Link text should be descriptive rather than bare URLs or generic "click here" labels.
- Verify that product terminology is consistent across related UI, comments, workflow messages, and errors in the same PR.
## Graceful degradation and observability
- When optional dynamic data such as URLs, session links, workflow links, issue numbers, or metadata may be absent, prefer omitting the element or showing a short fallback over rendering empty or broken output.
- Do not suggest removing session links, workflow URLs, or diagnostic context from error paths. Those links are important for debugging failed automation and user reports.
- Prefer generic, user-safe error text in user-visible surfaces, but keep enough structured logging or diagnostic context for maintainers to investigate failures.
+109
View File
@@ -0,0 +1,109 @@
---
name: review-pr
description: Review a pull request diff and write structured feedback to review.json for the workflow to publish. Use when reviewing a checked-out PR from local artifacts like pr_diff.txt and pr_description.txt and producing machine-readable review output instead of posting directly to GitHub.
---
# Review PR Skill
Review the current pull request and write the output to `review.json`.
## Context
- The working directory is the PR branch checkout.
- The workflow provides an annotated diff in `pr_diff.txt`.
- The workflow provides the PR description in `pr_description.txt`.
- Focus on files and lines changed by this PR.
- Do not post comments or reviews to GitHub directly.
## Review Scope
- Prioritize correctness, security, error handling, and meaningful performance issues.
- Include style or nit comments only when you can provide a concrete suggestion block.
- If a concern involves untouched code, mention it in the summary instead of an inline comment.
- Do not suggest adding test cases that only vary constructor inputs or struct fields when the existing test already covers the meaningful behavior. Only suggest new tests when they exercise a distinct code path or edge case.
- When a PR is clearly a V0 or initial implementation, frame robustness suggestions (timeouts, retries, lifecycle management) as optional future work rather than blocking concerns, unless they risk correctness, security, or data loss.
## Diff Line Annotations
The diff file uses these prefixes:
- `[OLD:n]` for deleted lines on the old side. Use `"LEFT"`.
- `[NEW:n]` for added lines on the new side. Use `"RIGHT"`.
- `[OLD:n,NEW:m]` for unchanged context. Use `"RIGHT"` with line `m`.
## Comment Requirements
Every comment body must start with one of these labels:
- `🚨 [CRITICAL]` for bugs, security issues, crashes, or data loss.
- `⚠️ [IMPORTANT]` for logic problems, edge cases, or missing error handling.
- `💡 [SUGGESTION]` for worthwhile improvements or better patterns.
- `🧹 [NIT]` for cleanup only when the comment includes a suggestion block.
Write comments with these constraints:
- Be concise, direct, and actionable.
- Do not add compliments or hedging.
- Prefer single-line comments.
- Keep ranges to at most 10 lines.
- Restrict inline comments to valid changed lines in this PR.
## Suggestion Blocks
When proposing a code change, use:
```suggestion
<replacement code here>
```
Rules:
- Match the exact indentation of the original file.
- Include only replacement code.
- For multi-line suggestions, set `start_line` to the first line and `line` to the last line.
## Output Format
Create `review.json` with this shape:
```json
{
"summary": "## Overview\n...\n\n## Concerns\n- ...\n\n## Verdict\nFound: 1 critical, 2 important, 3 suggestions\n\n**Request changes**",
"comments": [
{
"path": "path/to/file",
"line": 42,
"side": "RIGHT",
"start_line": 40,
"body": "⚠️ [IMPORTANT] Short explanation\n\n```suggestion\nreplacement\n```"
}
]
}
```
Field rules:
- `path` must be relative to the repository root.
- `line` is required and must target the correct side.
- `start_line` is optional and only for multi-line ranges.
- `side` must be `"LEFT"` or `"RIGHT"`.
## Summary Requirements
The `summary` must include:
- A high-level overview of the PR.
- Important concerns and any untouched-code concerns that could not be commented inline.
- Issue counts in the format `Found: X critical, Y important, Z suggestions`.
- A final recommendation of `Approve`, `Approve with nits`, or `Request changes`.
## Final Checks
Before finishing:
- Validate `review.json` with `jq`.
- Fix invalid JSON if validation fails.
- Confirm line numbers match the annotated diff.
- Do not run `gh pr review`, `gh pr comment`, `gh api`, or any other command that posts to GitHub.
Your only output is the final `review.json`.
+116
View File
@@ -0,0 +1,116 @@
---
name: rust-unit-tests
description: Write, improve, and run Rust unit tests in the warp Rust codebase.
---
# Rust Unit Tests in warp
## Scope
- This skill focuses on crate-level unit tests.
- Favor incremental, well-scoped tests that exercise a single function or behavior per case.
## Where unit tests live
- Put unit tests in separate files named `${filename}_tests.rs` or `mod_test.rs`.
- Include the test module at the end of the corresponding source file:
```rust
#[cfg(test)]
#[path = "filename_tests.rs"] // or "mod_test.rs"
mod tests;
```
## Writing good tests
- Use descriptive names: `fn parses_utf8_sequence_when_valid()`.
- Prefer `assert_eq!`/`assert_ne!` over `assert!` for clearer diffs.
- Use `#[should_panic]` only when panic semantics are intended API.
- Minimize global state; inject dependencies via traits/constructors to make logic testable without heavy mocking.
- When adding enums or expanding behavior, prefer exhaustive matches in code under test and mirror cases in tests.
- Be mindful of terminal model locking: avoid patterns that acquire multiple `model.lock()` calls in the same call stack from tests.
## Async and feature-gated code
- For async logic, use `#[tokio::test]` when the code requires a runtime.
- Prefer runtime feature checks (e.g., `FeatureFlag::X.is_enabled()`) over `#[cfg(...)]` so tests don’t require recompilation to toggle behavior.
## Quickstart harness (UI/model tests)
- Prefer `warpui::App::test` for deterministic unit tests around views/models.
- Initialize app models once, then mutate via `update` and assert via `read`.
```rust
use warpui::App;
// In app crate tests prefer `crate::test_util::...`; from other crates use `warp::test_util::...`.
use warp::test_util::{terminal::initialize_app_for_terminal_view, add_window_with_terminal};
#[test]
fn example() {
App::test((), |mut app| async move {
// One-time app setup for terminal/view tests
initialize_app_for_terminal_view(&mut app); // includes settings init
let term = add_window_with_terminal(&mut app, None);
// Act
term.update(&mut app, |view, _ctx| {
view.model.lock().simulate_block("ls", "out");
});
// Assert
term.read(&app, |view, _ctx| {
assert!(view.model.lock().block_list().len() > 0);
});
})
}
```
## Common helpers to use
- Terminal model shortcuts: `TerminalModel::mock(..)`, `.simulate_block(..)`, `.finish_block()`, `.simulate_cmd(..)`.
- Builders for focused tests: `terminal::model::test_utils::{TestBlockListBuilder, TestBlockBuilder}`.
- Virtual filesystem for IO-heavy code:
```rust
use virtual_fs::{VirtualFS, Stub};
VirtualFS::test("case", |_dirs, mut fs| {
fs.with_files(vec![Stub::FileWithContent("path/file.txt", "contents")]);
// run logic and assert
});
```
- Feature flags (scoped):
```rust
use warp::features::FeatureFlag; // or `use crate::features::FeatureFlag;` inside the app crate
let _flag = FeatureFlag::CreatingSharedSessions.override_enabled(true);
```
- UI numeric assertions (lines):
```rust
assert_lines_approx_eq!(actual_lines, INLINE_BANNER_HEIGHT);
```
- Concurrency: keep `model.lock()` scopes minimal; avoid nested/re-entrant locks in the same call chain.
- Don’t call `initialize_settings_for_tests` directly when using `initialize_app_for_terminal_view` (it already calls it).
- Async needs: use `#[tokio::test]` when a real runtime is required; otherwise prefer `App::test`.
- Tests touching global/external state: consider `serial_test`'s `#[serial]` or local mocking instead of parallelism.
## Running unit tests
- Workspace (parallel):
```bash
cargo nextest run --no-fail-fast --workspace --exclude command-signatures-v2
```
- Single crate:
```bash
cargo nextest run -p <crate_name>
```
- Single test (filter by name):
```bash
cargo nextest run -E 'test(<substring>)'
```
- Doc tests:
```bash
cargo test --doc
```
## Linting and formatting
Run before submitting changes:
```bash
cargo fmt
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
```
For a full local check before a PR, you can also run:
```bash
./script/presubmit
```
@@ -0,0 +1,142 @@
---
name: spec-driven-implementation
description: Drive a spec-first workflow for substantial features by writing PRODUCT.md before implementation, writing TECH.md when warranted, and keeping both specs updated as implementation evolves. Use when starting a significant feature, planning agent-driven implementation, or when the user wants product and tech specs checked into source control.
---
# spec-driven-implementation
Drive a spec-first workflow for substantial features in Warp.
## Overview
Use this skill for significant features where a written spec will improve implementation quality, reduce ambiguity, or make review easier. Be pragmatic: not every change needs specs.
Specs should usually live in:
- `specs/<linear-ticket-number>/PRODUCT.md`
- `specs/<linear-ticket-number>/TECH.md`
For example:
- `specs/APP-1234/PRODUCT.md`
- `specs/APP-1234/TECH.md`
`specs/` should contain only ticket-named directories as direct children. Do not create engineer-named subdirectories or feature-slug directories there.
If a relevant Linear issue does not already exist, create one before writing specs. Use the Linear MCP tools directly:
- `list_teams` to find the appropriate team
- `list_issue_labels` to inspect the expected labels/tags
- `save_issue` to create the issue with the appropriate team and labels
If the correct team or labels are not obvious from the request and surrounding context, use `ask_user_question` to clarify rather than guessing.
These specs should largely be written by agents, not by hand, and should be checked into source control so they can be reviewed and kept current with the code.
## When specs are required
Strongly prefer specs when the change is substantial, such as:
- product or architectural ambiguity
- expected implementation size around 1k+ LOC
- deep or cross-cutting stack changes
- risky behavior changes where regressions would be expensive
- work where agent quality will improve materially from clearer inputs
Specs are often unnecessary for:
- small, local bug fixes
- straightforward refactors
- narrow UI tweaks with little ambiguity
For pure UI changes, the product spec is often useful while the tech spec may be unnecessary.
## Workflow
### 1. Decide whether the feature needs specs
Evaluate the size, ambiguity, and risk of the feature. If specs will not meaningfully improve execution or review, skip them and focus on verification instead.
### 2. Write the product spec first
Before implementation, create `PRODUCT.md` describing the desired user-facing behavior.
Use the `write-product-spec` skill to produce it. The product spec should define:
- what problem is being solved
- the desired user experience
- invariants and edge cases
- success criteria
- how the behavior will be validated
If the feature has UI or interaction design, ask for a Figma mock if one exists. If there is no mock, continue but call that out explicitly in the product spec.
Reference the Linear issue in the spec when one exists. Because specs live under `specs/<linear-ticket-number>/...`, this should usually be straightforward.
### 3. Write the tech spec when warranted
Use the `write-tech-spec` skill for substantial or ambiguous implementation work.
Prefer a tech spec when:
- the implementation spans multiple subsystems
- architecture or extensibility matters
- there are meaningful tradeoffs to document
- reviewers will benefit more from reviewing the plan than the raw code
It is acceptable to write the tech spec after an e2e prototype if that leads to a more accurate implementation plan. Do not force a premature tech spec when the implementation details are still too uncertain.
### 4. Implement approved specs
After the specs are approved, use the `implement-specs` skill to build from the approved `PRODUCT.md` and `TECH.md`.
The implementation can often be pushed in the same PR as the product and tech specs. As the engineer iterates, keep `PRODUCT.md`, `TECH.md`, code changes, and tests in that same PR so the review reflects the feature that will actually ship.
For large features, the implementer may optionally offer:
- `PROJECT_LOG.md` to track explored paths, checkpoints, and current implementation state
- `DECISIONS.md` to capture concrete product and technical decisions made during design and implementation
These are optional aids, not required outputs.
### 5. Keep specs current during implementation
If implementation changes from the spec, update the spec rather than leaving it stale.
Update `PRODUCT.md` when:
- user-facing behavior changes
- success criteria change
- UX details or edge cases change
Update `TECH.md` when:
- the implementation approach changes
- architectural boundaries move
- risks, dependencies, or rollout details change
- the testing or validation plan changes
The checked-in specs should describe the feature that actually ships, not just the initial intent. Keep those spec updates in the same PR as the related code changes whenever practical.
### 6. Verify behavior against the spec
Before considering the work complete, make sure verification maps back to the specs. Prefer tests and artifacts that validate the product behavior directly:
- use the `rust-unit-tests` skill for crate-level unit tests and regression coverage
- integration tests for critical user flows
- loom walkthroughs or equivalent feature demonstrations when appropriate
- screenshots or videos when useful for UI-heavy work
## Best Practices
- Be pragmatic above all else.
- Write specs to improve input quality for agents, not as ceremony.
- Keep product specs behavior-oriented and implementation-light.
- Keep tech specs implementation-oriented and grounded in current codebase patterns.
- Use review time to validate specs and behavior, not to over-index on code style nits.
## Related Skills
- `implement-specs`
- `write-product-spec`
- `write-tech-spec`
@@ -0,0 +1,69 @@
---
name: triage-issue-local
specializes: triage-issue
description: Repo-specific triage guidance for warp-external. Only the categories declared overridable by the core triage-issue skill may be specialized here.
---
# Repo-specific triage guidance for `warp-external`
This file is a companion to the core `triage-issue` skill. It does not
redefine the triage output schema, safety rules, or follow-up-question
contract. It only specializes the override categories the core skill
marks as overridable.
## Heuristics
- `warp-external` is the public-facing Warp desktop client repository. Treat public issue reports as potentially incomplete and avoid asking for secrets, tokens, private workspace names, private repository names, or account identifiers in the public issue thread.
- Distinguish the user's observed Warp behavior from their guesses about Rust modules, UI components, server behavior, feature flags, or product intent.
- For issue reports that mention another terminal, editor, shell, or CLI tool, identify whether the problem is Warp-specific or generally reproducible outside Warp before assigning Warp ownership.
- When the issue includes screenshots, videos, logs, stack traces, or command output, use them as primary evidence and ask follow-up questions only for missing details that cannot be inferred from that evidence.
- Before asking any follow-up questions, check the Warp documentation and the repository's existing feature set to determine whether the desired behavior the reporter is describing is already supported. If an existing feature, setting, or workflow satisfies the request, recommend it to the reporter instead of treating the issue as a bug or feature gap.
- If the report is about billing (pricing, plans, subscriptions, payments, refunds, invoices, AI request quotas, charges) or about appeals (account suspensions, bans, takedowns, abuse decisions, or other account-status disputes), do not attempt to triage it as an actionable bug or feature request. Instead, notify the reporter that these requests must go through Warp's support channels (https://docs.warp.dev/support-and-community/troubleshooting-and-support/sending-us-feedback) and direct them there for resolution. Apply the relevant `area:billing` or `area:auth` label as appropriate so the issue is still routed correctly.
## Follow-up question limit
Ask **at most 2 follow-up questions** per triage response. Each question must be high-value: it should meaningfully change the label assignment, owner routing, or reproduction confidence if answered. Do not ask questions whose answers can be inferred from existing evidence, and do not bundle multiple sub-questions into a single bullet. If more than 2 unknowns exist, prioritize the two that are most likely to unblock triage.
## Label taxonomy
The label taxonomy for this repository is managed in `.github/issue-triage/config.json`. Prefer labels from that configuration, especially the `area:*`, `os:*`, `repro:*`, `accessibility`, `needs-info`, `duplicate`, and primary issue-type labels. Do not invent new labels unless the prompt explicitly allows it.
Use area labels based on the user's reported surface:
- `area:shell-terminal` for terminal output, block rendering, shell integration, prompt rendering, command execution display, and terminal-emulation behavior.
- `area:terminal-input` for command-line input editing, cursor movement, key handling, and typed text behavior.
- `area:window-tabs-panes` for window, tab, pane, split, layout, and focus behavior.
- `area:editor-notebooks` for editors, notebooks, markdown rendering, LSP, and code display.
- `area:agent` for agent conversations, agent mode, cloud/local agent execution, prompts, and AI-specific UI.
- `area:code-review` for git diff views, review UI, review comments, and PR-focused agent flows.
- `area:mcp` for MCP server connection, tool/resource discovery, OAuth, and integration issues.
- `area:settings-keybindings` for settings UI, preferences, keyboard shortcuts, and keybinding configuration.
- `area:warp-drive` for Warp Drive objects, sync, sharing, workflows, notebooks, tab configs, and persisted artifacts.
- `area:performance:*` when the report includes CPU, memory, GPU, startup, rendering, latency, or responsiveness symptoms. Add the more specific CPU, memory, or GPU label when the evidence points to that resource.
## Information to check for before asking follow-up questions
Before asking the reporter for more information, check the issue body, comments, attachments, logs, labels, and repository context for:
- Warp channel and version/build number, especially whether the report is for Dev, Canary, Preview, Beta, or Stable.
- OS and version, architecture, display setup, window manager or desktop environment on Linux, and whether the issue is platform-specific.
- Shell and terminal context: shell name/version, prompt framework, shell integration status, command being run, terminal mode, local vs SSH/remote/tmux, and whether the behavior reproduces in a fresh session.
- Clear reproduction steps, expected behavior, actual behavior, frequency, regression timing, and whether the user can reproduce outside Warp.
- Visual evidence for UI, rendering, layout, font, cursor, focus, window, pane, tab, and accessibility issues. Prefer a screenshot or short recording when the symptom is visual.
- Logs and diagnostics for crashes, hangs, startup failures, update failures, authentication failures, MCP failures, and agent execution failures. Ask for redacted logs only when the report lacks actionable evidence.
- For AI/agent reports: whether the agent is local or cloud, the model if known, relevant conversation/session link, repository context, tool or MCP server involved, and the exact user action that triggered the failure.
- For performance reports: approximate project/session size, command output size, CPU/memory/GPU observations, profile or diagnostics if provided, and whether the issue appears after long-running sessions.
- For keyboard or input reports: keyboard layout, custom keybindings, IME usage, conflicting OS shortcuts, focused surface, and whether the same keys work in other apps.
- For account, billing, or auth reports: account tier or authentication method only if the user already provided it. Do not ask for private identifiers in public; direct the user to support when private account details are required. For billing or appeals reports specifically, do not pursue further triage questions in the public thread—redirect the reporter to Warp's support channels per the heuristic above.
## Recurring follow-up patterns
- Visual UI/rendering issue with no media: ask for a screenshot or short screen recording first.
- Environment-sensitive terminal issue: ask for Warp version/channel, OS/version, shell, and whether it reproduces in a fresh local session.
- SSH/tmux/remote issue: ask for local OS, remote OS, shell, whether tmux is involved, and the minimal command or workflow that reproduces it.
- Agent/MCP issue: ask for the failing workflow, local vs cloud execution, relevant session link, MCP server/tool name, and any redacted error text.
- Performance issue: ask for approximate scale, how long Warp has been running, what action triggers the spike or hang, and whether logs or a profile are available.
## Owner-inference hints
Prefer `.github/STAKEHOLDERS` for owner inference. When no path-level match exists, use the label and issue surface to choose likely owners rather than defaulting to broad app ownership.
+114
View File
@@ -0,0 +1,114 @@
---
name: update-skill
description: Create or update skills by generating, editing, or refining SKILL.md files in this repository. Use when authoring new skills or revising the structure, frontmatter, or guidance for existing ones.
---
# update-skill
This guide provides instructions for creating or updating skills in this repository. It covers the required structure, frontmatter, and best practices for skills.
## Quick Start
Every skill is a directory containing a `SKILL.md` file with YAML frontmatter and markdown body:
```markdown
---
name: pdf-processing
description: Extract text and tables from PDF files, fill forms, merge documents.
---
# PDF Processing
## When to use this skill
Use this skill when the user needs to work with PDF files...
## How to extract text
1. Use pdfplumber for text extraction...
## How to fill forms
...
```
## Requirements
### Frontmatter (Required)
Every SKILL.md must start with YAML frontmatter containing:
- **name**: Kebab-case identifier (lowercase letters, numbers, hyphens only)
- Example: `add-feature-flag`, `rust-unit-tests`, `update-skill`
- **description**: Specific description of what the skill does and when to use it
- Must be non-empty
- Should include key terms for skill discovery
- Begin with an action verb to clearly state what the skill accomplishes (e.g., "Adds feature flags..." instead of "Helps with features..."), and immediately follow with a specific use case or context (e.g., "Use when working with feature flags")
- Write in third person (e.g., "Adds feature flags..." not "I can help you add...")
### Writing Effective Descriptions
The description field is critical for skill discovery. Include both **what** the skill does and **when** to use it. Some good examples:
- `git-commit`: "Generate descriptive commit messages by analyzing git diffs. Use when the user asks for help writing commit messages or reviewing staged changes."
- `pdf-processing`: "Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction."
Avoid vague descriptions like "Helps with code" or "Does development tasks". For more context, see "Description Best Practices" in [references/best-practices.md](references/best-practices.md).
### Skill Structure
Typical sections in Warp skills:
1. **Title and brief summary** – Clear title and a concise overview of the skill's purpose and primary use cases. Link to sections, reference files or related skills if useful
2. **Overview** - Context about the skill's purpose (optional but common), extends the summary with more details and context
3. **Main content** - Steps, usage instructions, or workflow guidance
4. **Best Practices** - Guidelines and recommendations (optional)
5. **Examples / Reference PRs** - Links to real examples (optional)
Keep the structure flexible based on the skill's needs. Simple skills can omit the optional sections.
### Validation
Optionally, use the [skills-ref](https://github.com/agentskills/agentskills/tree/main/skills-ref) reference library to validate your skills:
```bash
skills-ref validate ./my-skill
```
This checks that your SKILL.md frontmatter is valid and follows all naming conventions. If not installed, use the WebSearch tool to get context around this package.
### Main Content Best Practices
- For guidance on what qualifies as good main content, see "Conciseness Principles" in [references/best-practices.md](references/best-practices.md)
- When formatting code examples, see "Code Example Formatting" in [references/best-practices.md](references/best-practices.md).
### File Organization
- **Simple skills** (<=200 lines): Keep everything in SKILL.md
- **Complex skills** (>200 lines): Split detailed content into `references/` subdirectory
- Reference files from SKILL.md with clear links
- Example: "See [references/best-practices.md](references/best-practices.md) for detailed guidance"
## When to Split Content
Create `references/` subdirectory when:
- SKILL.md approaches 200+ lines
- Skill covers multiple domains or workflows that can be loaded independently
- Detailed reference material would clutter the main instructions
Keep only essential workflow and procedural instructions in SKILL.md. Move detailed reference material, schemas, and extensive examples to `references/` files.
## Examples from Existing Skills
For reference on structure and style:
- `.agents/skills/add-feature-flag/SKILL.md` - Multi-step workflow with clear sequential steps
- `.agents/skills/rust-unit-tests/SKILL.md` - Comprehensive guide with code examples and helper utilities
- `.agents/skills/remove-feature-flag/SKILL.md` - Cleanup workflow with search commands
## Best Practices
See [references/best-practices.md](references/best-practices.md) for detailed authoring guidance including:
- Progressive disclosure patterns
- Writing concise, effective instructions
- Code example formatting
- Common anti-patterns to avoid
@@ -0,0 +1,310 @@
# Best Practices for Warp Skills
Detailed authoring guidance for creating effective skills in `.agents/skills/`.
## Progressive Disclosure
Skills use a loading system to manage context efficiently:
1. **Metadata (name + description)** - Always loaded at startup
2. **SKILL.md body** - Loaded when skill triggers
3. **Reference files** - Loaded only when needed
### When to Use References
Keep SKILL.md under 150-200 lines. When content grows beyond this:
**Pattern 1: High-level guide with references**
SKILL.md contains the core workflow and points to detailed references:
```markdown
## Advanced Features
- **Detailed configuration**: See [references/config.md](references/config.md)
- **API reference**: See [references/api.md](references/api.md)
- **Examples**: See [references/examples.md](references/examples.md)
```
**Pattern 2: Domain-specific organization**
For skills with multiple independent domains, organize by domain:
```
skill-name/
├── SKILL.md (overview and navigation)
└── references/
├── domain-a.md
├── domain-b.md
└── domain-c.md
```
When the user works with domain-a, the agent only loads domain-a.md, not the others.
**Pattern 3: Conditional details**
Show basic content inline, link to advanced content:
```markdown
## Basic Usage
[Core instructions here]
**For advanced configuration**: See [references/advanced.md](references/advanced.md)
```
### Important Guidelines
- **Keep references one level deep** - All reference files should link directly from SKILL.md
- **Avoid nested references** - Don't create references that reference other files
- **Add table of contents** - For reference files >100 lines, include TOC at the top
## Writing Effective Descriptions
The description field enables skill discovery. the agent uses it to decide when to load the skill.
### Description Best Practices
1. **Be specific and include key terms**
- Good: "Add a new feature flag to gate code changes in the Warp codebase."
- Avoid: "Helps with features."
2. **Include both what and when**
- What the skill does: "Write, improve, and run Rust unit tests"
- When to use it: "in the warp Rust codebase"
3. **Write in third person**
- Good: "Adds feature flags to gate code changes"
- Avoid: "I can help you add feature flags"
- Avoid: "You can use this to add feature flags"
4. **Include trigger terms**
- Mention specific files, commands, or concepts
- Example: "Use when working with PDF files, forms, or document extraction"
## Conciseness Principles
Context window is shared across all skills, conversation history, and the system prompt. Every token matters.
### Default Assumption: Agent is Already Smart
Only add context the agent doesn't already have. Challenge each piece:
- "Does the agent really need this explanation?"
- "Can I assume the agent knows this?"
- "Does this paragraph justify its token cost?"
**Good (concise):**
```markdown
## Extract PDF text
Use pdfplumber for text extraction:
\`\`\`python
import pdfplumber
with pdfplumber.open("file.pdf") as pdf:
text = pdf.pages[0].extract_text()
\`\`\`
```
**Bad (verbose):**
```markdown
## Extract PDF text
PDF (Portable Document Format) files are a common file format that contains
text, images, and other content. To extract text from a PDF, you'll need to
use a library. There are many libraries available for PDF processing, but we
recommend pdfplumber because it's easy to use and handles most cases well.
First, you'll need to install it using pip. Then you can use the code below...
```
The concise version assumes the agent knows what PDFs are and how libraries work.
## Code Example Formatting
### Syntax Highlighting
Always specify the language for code blocks:
```rust
pub fn example() {
println!("Always specify language");
}
```
```bash
cargo nextest run --workspace
```
### Example Structure
For workflow-based skills, show before/after or step-by-step:
```markdown
### Before:
\`\`\`rust
if FeatureFlag::YourFeature.is_enabled() {
// new behavior
} else {
// old behavior (dead code)
}
\`\`\`
### After:
\`\`\`rust
// new behavior (unconditionally enabled)
\`\`\`
```
### Inline Commands
For shell commands, show the complete command with flags:
```bash
cargo clippy --workspace --all-targets --all-features --tests -- -D warnings
```
Explain non-obvious flags if necessary, but prefer self-documenting commands.
## Workflows vs Simple Instructions
### When to Use Workflows
Use numbered steps for multi-step processes where order matters:
```markdown
## Workflow
1. Analyze the form structure
2. Create field mapping
3. Validate mapping
4. Fill the form
5. Verify output
```
Include a checklist for complex workflows:
```markdown
Copy this checklist and track progress:
\`\`\`
Task Progress:
- [ ] Step 1: Analyze form
- [ ] Step 2: Create mapping
- [ ] Step 3: Validate
- [ ] Step 4: Fill form
- [ ] Step 5: Verify
\`\`\`
```
### When to Use Simple Instructions
For straightforward tasks, skip the workflow structure:
```markdown
## Adding a Feature Flag
Add the feature to `app/Cargo.toml`:
\`\`\`toml
[features]
your_feature_name = []
\`\`\`
Then gate code with the runtime check:
\`\`\`rust
if FeatureFlag::YourFeatureName.is_enabled() {
// feature-gated behavior
}
\`\`\`
```
## Common Anti-Patterns
### ❌ Windows-Style Paths
Always use forward slashes:
- ✓ Good: `scripts/helper.py`, `references/guide.md`
- ✗ Avoid: `scripts\helper.py`, `references\guide.md`
### ❌ Vague Descriptions
Be specific:
- ✗ Avoid: "Helps with documents"
- ✓ Good: "Extract text and tables from PDF files"
### ❌ Too Many Options
Don't present multiple approaches unless necessary:
- ✗ Avoid: "You can use pypdf, or pdfplumber, or PyMuPDF, or..."
- ✓ Good: "Use pdfplumber for text extraction. For scanned PDFs requiring OCR, use pdf2image with pytesseract instead."
### ❌ Time-Sensitive Information
Don't include dates or version-specific guidance:
- ✗ Avoid: "If you're doing this before August 2025, use the old API."
- ✓ Good: Use a "Current method" and "Old patterns" section with deprecation notes
### ❌ Inconsistent Terminology
Choose one term and use it throughout:
- ✗ Avoid: Mix "API endpoint", "URL", "API route", "path"
- ✓ Good: Always "API endpoint"
### ❌ Explaining the Obvious
Skip explanations for concepts the agent already knows:
- ✗ Avoid: "Git is a version control system that tracks changes in files..."
- ✓ Good: "Use `git --no-pager diff` to see changes without pagination"
### ❌ Over-Structuring Simple Skills
Not every skill needs an Overview, Best Practices, and Examples section. Use only what adds value:
- Simple skills: Title + instructions
- Medium skills: Title + Overview + instructions
- Complex skills: Full structure with multiple sections
## Naming Conventions
Use consistent naming patterns for skills:
**Recommended: Gerund form (verb + -ing)**
- `processing-pdfs`
- `analyzing-spreadsheets`
- `managing-databases`
- `testing-code`
**Acceptable alternatives:**
- Noun phrases: `pdf-processing`, `spreadsheet-analysis`
- Action-oriented: `process-pdfs`, `analyze-spreadsheets`
**Avoid:**
- Vague names: `helper`, `utils`, `tools`
- Overly generic: `documents`, `data`, `files`
Consistent naming makes skills easier to reference, understand at a glance, and organize.
## Skill Iteration
Skills improve through usage. When updating a skill:
1. **Observe usage** - Note where the agent struggles or succeeds
2. **Identify gaps** - What information was missing or unclear?
3. **Update targeted sections** - Fix specific issues without over-explaining
4. **Test changes** - Use the skill on similar tasks to verify improvements
Keep iterations focused. Don't add content preemptively—only add what's proven necessary through real usage.
@@ -0,0 +1,387 @@
---
name: warp-integration-test
description: Writes, runs, and debugs Warp integration tests using the custom Builder/TestStep framework in `crates/integration`. Use when adding a new integration test, fixing a failing integration test, wiring a test into the manual runner or nextest suite, or verifying end-to-end UI and terminal behavior in Warp.
---
# Warp Integration Tests
Use this skill for Rust integration tests in Warp's custom framework under `crates/integration/`.
These are not ordinary unit tests. They boot a real Warp app instance, give it an isolated test home directory, drive it with synthetic UI and terminal events, and poll assertions until success or timeout.
## Framework map
The core pieces are:
- `crates/integration/src/bin/integration.rs`
- Manual integration test runner binary.
- Registers test names to `Builder` factories.
- Runs exactly one named test per invocation.
- `crates/integration/tests/common/mod.rs`
- The outer Rust test harness used by `cargo test` and `cargo nextest`.
- Shells out to the integration binary.
- Forwards a limited set of env vars (`PATH`, `RUST_*`, `WARP_*`, `WARPUI_*`, `WGPU_*`, display-related vars).
- Re-runs tests up to 10 times when the integration binary exits with the special rerun code.
- `crates/integration/src/test.rs`
- Module hub for integration tests.
- Add new test modules here and `pub use` their functions so the runner can see them.
- `crates/integration/tests/integration/ui_tests.rs`
- List of UI-oriented integration tests that nextest should run.
- `crates/integration/tests/integration/shell_integration_tests.rs`
- List of tests that must run against every shell or a specific shell matrix.
- `crates/integration/src/builder.rs`
- Warp-specific wrapper around the lower-level WarpUI integration builder.
- Sets default timeout, hermetic home directory, shell rc files, user prefs, and real-display mode when requested.
- `crates/warpui_core/src/integration/driver.rs`
- Executes steps, handles retries, precondition reruns, screenshots, video capture, artifact export, and `on_finish`.
- `crates/warpui_core/src/integration/step.rs`
- Defines `TestStep`, input/event APIs, assertion polling, step-to-step data passing, and screenshot/recording hooks.
- `app/src/integration_testing/`
- High-level helpers and assertions for common Warp behaviors.
- Prefer these helpers over raw low-level event plumbing whenever they fit.
## How the framework actually runs a test
1. A Rust test from `crates/integration/tests/integration/*.rs` calls `run_integration_test("test_name")`.
2. That harness launches the `integration` binary with the test name.
3. The binary in `crates/integration/src/bin/integration.rs` looks up the name in `register_tests()`, builds the `Builder`, and turns it into a `TestDriver`.
4. `Builder::build(...)` creates an isolated temp directory, points `HOME` at it, writes minimal rc files, and initializes file-backed user preferences.
5. The driver runs each `TestStep` in order:
- setup callbacks
- synthetic events
- actions
- assertion polling until success or timeout
6. If an assertion returns `PreconditionFailed`, the binary exits with the rerun code and the outer harness retries the whole test.
7. On success, failure, or cancellation, the driver can run `on_finish` and export artifacts/runtime tags.
This means integration tests should be written for a hermetic environment. Do not rely on the developer's real shell dotfiles, home directory contents, or persisted Warp settings.
## Where to put a new test
Add the actual test function in a module under `crates/integration/src/test/`.
Use these heuristics:
- Put the test in an existing module when it matches that feature area.
- Create a new module when the feature does not fit an existing one cleanly.
- Add the test to `crates/integration/tests/integration/ui_tests.rs` if it is primarily a UI/app behavior test.
- Add the test to `crates/integration/tests/integration/shell_integration_tests.rs` if it needs to run against every shell, or depends on a specific shell/set of shells.
Being present in `crates/integration/src/test/*.rs` is not enough. For a test to run under `cargo nextest`, it also needs to be listed in one of the macro files in `crates/integration/tests/integration/`.
## Authoring checklist for a new test
When adding a new integration test, do all of the following:
1. Implement `pub fn test_name() -> Builder` in a module under `crates/integration/src/test/`.
2. Add the module to `crates/integration/src/test.rs`.
3. `pub use` the new module's exports from `crates/integration/src/test.rs`.
4. Add `register_test!(test_name);` in `crates/integration/src/bin/integration.rs`.
5. Add `test_name` to either:
- `crates/integration/tests/integration/ui_tests.rs`, or
- `crates/integration/tests/integration/shell_integration_tests.rs`
6. Default to making the test run in CI once it is added to one of those macro lists. Only mark it `#[ignore]` when the task explicitly calls for manual-only coverage or there is a concrete, documented reason it cannot run reliably in CI.
7. Run the test manually first, then through nextest once it is stable enough for the suite you chose.
## Writing the test body
The normal shape is:
```rust
use crate::Builder;
use warp::integration_testing::step::new_step_with_default_assertions;
use warp::integration_testing::terminal::{
clear_blocklist_to_remove_bootstrapped_blocks,
execute_command_for_single_terminal_in_tab,
wait_until_bootstrapped_single_pane_for_tab,
util::ExpectedExitStatus,
};
pub fn test_example() -> Builder {
Builder::new()
.with_step(wait_until_bootstrapped_single_pane_for_tab(0))
.with_step(clear_blocklist_to_remove_bootstrapped_blocks())
.with_step(execute_command_for_single_terminal_in_tab(
0,
"echo hello".to_string(),
ExpectedExitStatus::Success,
"hello".to_string(),
))
.with_step(
new_step_with_default_assertions("Assert some UI state")
.add_named_assertion("specific assertion name", |app, window_id| {
// inspect app state and return AssertionOutcome
warpui::integration::AssertionOutcome::Success
}),
)
}
```
Prefer a small number of focused steps with descriptive names over a huge monolithic test.
## Builder guidance
### `Builder::new()`
Start here almost every time.
Warp's wrapper automatically gives you:
- a per-test root directory
- isolated `HOME`
- generated rc files for Bash, Zsh, and Fish
- file-backed user preferences
- a default 2-minute hard timeout
- real-display support if `WARPUI_USE_REAL_DISPLAY_IN_INTEGRATION_TESTS` is present
### `with_setup(...)`
Use this for filesystem or environment setup before the app runs.
Common patterns:
- `utils.set_env("NAME", Some(value))`
- creating files under `utils.test_dir()`
- writing fixture config files
Prefer this over reaching into the real filesystem.
### `with_user_defaults(...)`
Use this to set persisted Warp preferences before the test starts.
This is the right tool for settings backed by user preferences rather than environment variables.
### `set_should_run_test(...)`
Use this to gate tests on shell/platform/runtime capabilities when the test genuinely cannot run everywhere.
### `with_on_finish(...)`
Use this for final verification or artifact inspection that should happen after all steps complete, such as checking that screenshots or recordings were written.
### `with_real_display()`
Use this explicitly when the test needs a real display for frame capture or visual workflows. Video/screenshot tests should normally be manual or ignored in CI unless there is a stable real-display path.
## `TestStep` guidance
`TestStep` is the unit of execution. Each step can have:
- setup callbacks
- input events
- actions
- assertions
- a timeout
- retry count
- failure handling
### Start from helper constructors
Prefer:
- `wait_until_bootstrapped_single_pane_for_tab(0)`
- `new_step_with_default_assertions("...")`
- `new_step_with_default_assertions_for_pane("...", tab, pane)`
The default step helpers already assert:
- no pending model events
- no block executing
These are good baseline invariants for most UI interactions.
### Prefer helper APIs over raw event plumbing
Use high-level helpers from `app/src/integration_testing/` whenever possible:
- terminal command execution helpers
- block list helpers
- command palette helpers
- navigation helpers
- settings helpers
- workflow/file tree/notebook helpers
Drop to raw `with_event(...)`, `with_event_fn(...)`, or saved-position mouse events only when there is no suitable helper.
### Use named assertions
Prefer `add_named_assertion(...)` over unnamed assertions. Named assertions make failure output and runtime tags much easier to interpret.
### Use polling assertions instead of sleeps
Assertions are polled until success or timeout. Lean on that model instead of hardcoding sleeps.
Good pattern:
- trigger an event or action
- assert on the eventual UI/model state
Avoid brittle timing assumptions.
### Use step data when one step computes something for the next
If a later step needs data from an earlier one, use:
- `add_named_assertion_with_data_from_prior_step(...)`
- `StepDataMap`
This is useful for saving measured positions, counts, IDs, or other values from prior frames.
### Use retries sparingly
`set_retries(...)` can help for a legitimately retryable step, but do not use it to hide deterministic failures. Prefer making the step more robust first.
### Use `PreconditionFailed` for known environmental flakes
If the environment reaches a state where the rest of the test is invalid, return `AssertionOutcome::PreconditionFailed(...)` instead of failing hard. The outer harness can rerun the entire test up to 10 times.
The existing bootstrap helper is a good model for this.
## Common test-writing patterns
### 1. Wait for bootstrap first
For most terminal-facing tests, the first real step should be:
- `wait_until_bootstrapped_single_pane_for_tab(0)`
Do not start asserting on terminal UI before bootstrap completes.
### 2. Clear the bootstrapped blocks if block indices matter
If the test relies on saved positions like `block_index:0`, clear the block list after bootstrap:
- `clear_blocklist_to_remove_bootstrapped_blocks()`
Otherwise the first user-generated block index depends on bootstrap output and the active shell.
### 3. Use helper command runners
Prefer helpers like:
- `execute_command_for_single_terminal_in_tab(...)`
- `execute_echo(...)`
- `execute_echo_str(...)`
- `execute_long_running_command(...)`
These helpers already handle a lot of correctness and output validation.
### 4. Assert visible behavior, not just internal mutation
A good integration test verifies the user-observable behavior:
- output visible in the terminal
- focus moved where expected
- UI element opened/closed
- selection changed
- settings applied
Internal state assertions are still useful, but they should support the visible behavior rather than replace it.
### 5. Keep tests feature-focused
Write a test for one behavior or one closely related flow. If you need to cover multiple scenarios, consider multiple tests instead of one giant script.
## Running tests
### Run one test directly through the integration binary
Use this first while authoring:
```bash
cargo run -p integration --bin integration -- test_name
```
This is the fastest way to iterate on a specific test because it bypasses the outer Rust test wrapper and runs the named test directly.
### Run one test through nextest
Once it is wired into one of the `tests/integration/*.rs` macro lists, run it with nextest:
```bash
cargo nextest run --no-fail-fast --workspace test_name
```
### Run with a real display when needed
For screenshot/video or other real-display flows:
```bash
WARPUI_USE_REAL_DISPLAY_IN_INTEGRATION_TESTS=1 cargo run -p integration --bin integration -- test_name
```
Or with nextest:
```bash
WARPUI_USE_REAL_DISPLAY_IN_INTEGRATION_TESTS=1 cargo nextest run --no-fail-fast --workspace test_name
```
## Debugging and investigation
### Get a backtrace on failures
```bash
RUST_BACKTRACE=1 cargo run -p integration --bin integration -- test_name
```
### Pause on failure
This is useful when running locally and you want to inspect the failed UI state:
```bash
WARPUI_PAUSE_INTEGRATION_TEST_ON_FAILURE=1 cargo run -p integration --bin integration -- test_name
```
### Pause after every step
Useful for understanding exactly what the test is doing:
```bash
WARPUI_PAUSE_INTEGRATION_TEST_AT_EVERY_STEP=1 cargo run -p integration --bin integration -- test_name
```
### Video and screenshots
If the task is specifically about recording a test, collecting screenshots, or validating overlay/video artifacts, also use the `integration-test-video` skill (located at `.warp/skills/integration-test-video/SKILL.md`).
### Environment variable gotcha
`utils.set_env(...)` affects runtime environment lookups such as `std::env::var(...)`.
It does not affect compile-time lookups like `option_env!(...)`. If the product code uses `option_env!`, changing the env var inside the test will not change that behavior without rebuilding.
## Verification checklist
Before considering a new integration test done, verify all of the following:
- The test function lives under `crates/integration/src/test/`.
- The module is added and re-exported in `crates/integration/src/test.rs`.
- The test is registered in `crates/integration/src/bin/integration.rs`.
- The test is listed in the correct nextest macro file and will run in CI by default, unless it was explicitly made manual-only with a documented reason.
- The test passes when run directly through the integration binary.
- The test passes through nextest if it is meant to be part of the automated suite.
- The assertions check the intended user-visible behavior.
- The test does not depend on the developer's real home directory, shell config, or machine state.
- If the test uses screenshots/video, the produced artifacts were actually inspected rather than only assuming they exist.
## Anti-patterns to avoid
- Writing a test only in `src/test/*.rs` and forgetting the nextest macro list.
- Asserting on bootstrap-sensitive block indices without clearing the bootstrapped blocks first.
- Using raw events everywhere when a helper already exists.
- Adding sleeps instead of assertion polling.
- Making the test depend on personal dotfiles, real settings, or non-hermetic filesystem state.
- Using retries to paper over a deterministic bug.
- Leaving a real-display/manual test enabled in CI without a stable path.
## Good workflow for agents
When asked to add or fix an integration test:
1. Find the closest existing integration test module for the feature.
2. Reuse helper assertions and step constructors before inventing new low-level plumbing.
3. Register the test in all required places, not just the implementation file.
4. Run the test manually first.
5. If it belongs in automation, run it with nextest too.
6. If the test exercises visual behavior, verify the resulting UI behavior or artifacts directly.
@@ -0,0 +1,35 @@
---
name: warp-ui-guidelines
description: Catalog of guidelines for writing UI code in the Warp client. Read whenever doing any UI work in this repo, up front before writing the change, so the relevant guidelines shape the implementation.
---
# warp-ui-guidelines
This skill is a growing catalog of guidelines for working on Warp's UI code. Each guideline captures a lesson that would otherwise be re-learned through review — typically because an agent or contributor reinvented a component, drifted from the design system, or bypassed a shared abstraction.
**How to use this skill:**
- Read through the guidelines below once at the start of any UI task, then keep them in mind while implementing. The list is short enough to scan.
- Each guideline is self-contained. Not every one will apply to every task — use judgment. But if a guideline *does* apply, follow it.
- When in doubt, prefer reusing an existing abstraction over introducing a new one. The Warp UI has accumulated a well-factored set of shared components and themes; new one-offs almost always drift.
New guidelines get added here over time. If you discover a recurring UI mistake that would have been caught by a written rule, add it.
---
## Guideline: Reuse button themes
Button colors come from a shared set of `ActionButtonTheme` impls in `app/src/view_components/action_button.rs` (and the parallel `Theme` impls in `crates/ui_components/src/button/themes.rs`) — `PrimaryTheme`, `SecondaryTheme`, `NakedTheme`, `DangerPrimaryTheme`, etc. These encode the design system and keep button colors consistent across the app.
When styling a button, **use one of the existing themes unchanged**. The shared themes are well-established and vetted; if one looks "wrong" for your use case, the most likely explanation is that you're reaching for the wrong theme, not that the theme is buggy.
Do **not** modify a shared theme on your own initiative. Changing `PrimaryTheme`, `SecondaryTheme`, etc. affects every button in the app, and a tweak that fixes your screen can silently regress others. Only edit a shared theme when the user has explicitly confirmed that the design-system component itself needs to change.
Red flags that you're about to make buttons inconsistent:
- Writing a new `impl ActionButtonTheme for FooPrimaryTheme` that delegates to `PrimaryTheme` and only tweaks one method (usually `text_color`). Almost always the right move is to use `PrimaryTheme` directly and accept the result.
- Hard-coding `ColorU::new(...)` instead of using `appearance.theme()` accessors (`accent`, `font_color(bg)`, `foreground`, etc.).
- Setting `should_opt_out_of_contrast_adjustment` to `true` to force a specific label color.
- Naming a theme after a feature or view (`FooPrimaryTheme`, `BarSubmitTheme`) rather than a design-system role.
If an existing theme genuinely doesn't fit and you think the shared theme should change, surface that to the user before editing it, rather than either editing it unilaterally or papering over it with a one-off.
+164
View File
@@ -0,0 +1,164 @@
---
name: write-product-spec
description: Write a PRODUCT.md spec for a significant user-facing feature in Warp, focused on detailed behavior and validation. Use when the user asks for a product spec, desired behavior doc, or PRD, wants to define feature behavior before implementation, or when the feature is substantial or behaviorally ambiguous enough that a written spec would improve implementation or review.
---
# write-product-spec
Write a `PRODUCT.md` spec for a significant feature in Warp.
## Overview
The product spec should make the desired behavior unambiguous enough that an agent can implement it correctly and avoid regressions. Describe the feature purely from the user's perspective — what the user sees, does, and experiences, and the invariants that must hold for them. Do not include implementation details (internal types, state layout, module boundaries, data flow, algorithms).
"User" is not limited to the end user of the Warp app. It means whoever consumes the surface being designed:
- For UI / UX features: the human using Warp.
- For a data model: the code that reads and writes that model.
- For an API, protocol, or library: the callers of that API — other services, client code, plugins, or agents.
- For a CLI tool or developer-facing surface: the developer invoking it.
The spec should describe behavior from that consumer's perspective: the shape of the surface, the operations they can perform, what they see back, invariants they can rely on, and edge cases they must handle — without prescribing how the surface is implemented underneath.
Implementation details, validation, and test planning live in a companion `TECH.md`, produced by the `write-tech-spec` skill. Writing the product spec is usually the first step of a two-step process: once `PRODUCT.md` is agreed on, invoke `write-tech-spec` to produce `TECH.md` for the same feature (or let the user know that's the expected next step). The product spec should be written so the tech spec can be written directly from it.
Write specs to `specs/<id>/PRODUCT.md`, where `<id>` is one of:
- a Linear ticket number (e.g. `specs/APP-1234/PRODUCT.md`)
- a GitHub issue id, prefixed with `gh-` (e.g. `specs/gh-4567/PRODUCT.md`)
- a short kebab-case feature name (e.g. `specs/vertical-tabs-hover-sidecar/PRODUCT.md`)
`specs/` should contain only id-named directories as direct children — no engineer-named subdirectories.
Ticket / issue references are optional. If the user has a Linear ticket or GitHub issue, use its id. If they don't, ask them for a feature name to use as the directory. Only create a new Linear ticket or GitHub issue when the user explicitly asks for one; in that case use the Linear MCP tools or `gh` CLI respectively (and `ask_user_question` if team, labels, or repo are unclear).
## Before writing
Gather only the context you need: directory id (Linear ticket, GitHub issue, or feature name), feature summary, target users, key behaviors, edge cases, and how the feature will be validated. Use `ask_user_question` for missing context rather than guessing.
### Figma mocks
If the feature has any UI or interaction design, ask the user whether a Figma mock exists before drafting the Behavior section, and include the link in the spec when one is provided. A mock is often the most reliable source of truth for visual states, spacing, and edge-case layouts — not asking can cause the Behavior section to guess at intent the designer already settled.
- If the user provides a link, include it under a short `## Figma` section (or inline near the top of Behavior) as `Figma: <link>`.
- If the user confirms no mock exists, note `Figma: none provided` so the absence is explicit rather than ambiguous.
- If the feature is purely backend (data model, API, CLI with no visual surface), skip the question and omit the section.
Do not silently drop design context; an explicit "none" is preferable to no mention at all on features where design would normally be expected.
## Structure
Required sections:
1. **Summary** — 1–3 sentences describing the feature and desired outcome.
2. **Behavior** — The meat of the spec. An exhaustive English description of how the feature works, written as numbered, testable invariants. See "The Behavior section" below — this is where the spec earns its length, and everything else should stay thin to avoid duplicating it.
Optional sections — include only when they add signal beyond the core. Omit the heading entirely if empty; do not write "None" as a placeholder.
- **Problem** — Include only when the motivation isn't obvious from Summary.
- **Goals / Non-goals** — Include when scope is ambiguous or has been contested.
- **Figma** — Include with a link when one exists, or an explicit `Figma: none provided` note when design matters but no mock exists. Omit entirely for non-visual features. See "Figma mocks" above.
- **Open questions** — Prefer inline `**Open question:** …` next to the relevant behavior. Include a dedicated section only if there are multiple unresolved questions worth collecting.
Do not include Validation, Success criteria, or Testing sections. Validation and test planning live in the companion `TECH.md` (produced by `write-tech-spec`). Write Behavior as numbered invariants that are testable on their own — the tech spec can reference them directly.
## The Behavior section
Behavior is the spec. Everything else is framing.
The goal of Behavior is a complete English description of how the feature works, detailed enough that a tech spec can be written directly from it without the author having to guess or re-derive product intent. If a reader finishes Behavior with questions about what the feature does in some situation, the section is not done.
Describe, at minimum:
- Default behavior and the happy-path user flow.
- Every user-visible state and the transitions between them.
- All inputs the user can provide and how the feature responds.
- Empty states, error states, loading / pending states, and cancellation.
- Edge cases a reasonable implementer would not think to ask about — permission denied, offline, timeouts, races between state changes, multiple concurrent instances, stale or missing data, focus loss mid-interaction, interactions with adjacent features.
- Keyboard, accessibility, and focus expectations where relevant.
- Invariants that must hold at all times and behaviors that must not regress.
Length Behavior to match the feature. Trivial features may need a handful of invariants; complex features may need many, with sub-sections per flow or state. The rest of the spec should stay thin so Behavior can be as exhaustive as the feature requires without producing a bloated document overall. Err toward enumerating one more edge case rather than one fewer.
## Length heuristic
Behavior should be as long as the feature requires — do not truncate edge cases to hit a line target. The heuristic below applies to everything around Behavior (Summary, optional sections): keep that framing thin so the spec's total length reflects the feature's actual complexity, not structural overhead.
- Trivial fix or narrow UI tweak: no spec.
- Small feature (single module, few edge cases): framing plus Behavior typically ~30–60 lines total.
- Medium feature (cross-module, multiple states): typically ~80–150 lines total.
- Large or behaviorally rich feature: longer is fine, and most of the length should live in Behavior.
If you find yourself writing the same idea in Summary, Problem, Goals, and Behavior, collapse the framing — not the Behavior content.
## Writing guidance
- Prefer concrete, observable behavior over aspirational wording.
- Write Behavior as a list of invariants rather than prose when possible.
- Capture invariants that must not regress and edge cases that are easy to miss.
- Avoid implementation details unless unavoidable for the UX.
- Each section should earn its place — if a section would repeat another or contain only boilerplate, omit it.
## Keep the spec current
Approved specs may ship in the same PR as the implementation. As implementation evolves, update `PRODUCT.md` in the same PR when user-facing behavior or UX details change. The checked-in spec should describe the feature that actually ships.
For large features, the implementer may optionally keep a `DECISIONS.md` file summarizing concrete decisions made during design and implementation. Offer it when it would help future agents; otherwise skip it.
## Related Skills
- `implement-specs`
- `write-tech-spec`
- `spec-driven-implementation`
## Example Behavior section
A sample Behavior section for a hypothetical feature: rendering GitHub-flavored Markdown tables in the Warp block list. It demonstrates the expected shape — numbered, testable, user-perspective invariants that enumerate defaults, edge cases, malformed input, streaming, selection/copy, search, sharing, theming, and cross-surface consistency, with one inline open question.
````markdown
## Behavior
1. When a terminal output block contains a GitHub-flavored Markdown table (a header row, a separator row of one or more `---` segments, and one or more body rows, all delimited by `|`), that table renders as a visually formatted table in the block — not as raw pipe-delimited text.
2. The table renders with:
- A visually distinct header row.
- Aligned columns based on the separator row: `|:---|` left-align, `|:---:|` center, `|---:|` right-align. `|---|` with no colons falls back to the default alignment (left for text, right for numeric-looking values).
- Visible row separators (or equivalent spacing) consistent with the active theme.
3. Inline markdown inside a cell renders inline: bold, italic, inline code, strikethrough, and links all render the same way they do in the surrounding block output. Line breaks inside a cell (`<br>` or escaped `\n`) render as in-cell line breaks.
4. Column widths are chosen to fit the table's natural content when it fits inside the block. If a single cell's content is very long, that cell wraps its text within its column rather than forcing the column to an unreasonable width.
- **Open question:** when a wrapped cell would produce an unreasonably tall row, do we clip with an "expand" affordance, or let the row grow unbounded?
5. Horizontal scrolling: when the table's total width exceeds the block width — many columns, or wide columns that can't reasonably be narrowed — the table becomes horizontally scrollable within the block. Scrolling horizontally reveals off-screen columns without clipping or truncating them. Vertical scrolling of the block continues to work independently of table scroll.
6. When the block is resized (terminal resize, pane split, sidebar open/close), the table reflows to the new width without losing row or column order.
7. Empty cells render as visibly empty (same row height as surrounding cells, no placeholder text). A row with all empty cells still renders as a row.
8. A table with only a header and separator (zero body rows) renders as a header-only table, not as raw text.
9. A single-column table renders as a single-column table (not collapsed to a bullet list or similar).
10. Malformed tables fall back gracefully:
- Missing separator row → rendered as preformatted text, not as a table.
- Ragged rows (some rows have fewer or more cells than the header) → missing cells render empty; extra cells are shown, with the header row extended visually if possible. The block should never silently drop data.
- Unclosed table (last row truncated mid-stream) → rendered as a partial table; see (11).
11. Streaming output: while a command is still producing rows, the table renders incrementally. New rows append as they arrive. The header row locks in as soon as the separator line is received; rows before the separator render as plain text until the table is recognized.
12. Selection and copy:
- Selecting across cells with the mouse or keyboard selects their visible text content.
- Copying the selection produces tab-separated plain text by default (one row per line, cells separated by tabs). An affordance (context menu, shortcut) lets the user copy the original markdown source instead.
- Copying the entire block preserves the original markdown source verbatim.
13. Search within a block (find-in-block) matches against cell text content. Matches highlight in place in the rendered cell; navigating matches scrolls the table into view, including horizontally if the match is in an off-screen column.
14. Sharing or exporting a block (Warp Drive, share link, save as file) preserves the original markdown source, not the rendered form.
15. Theming: table borders, header backgrounds, alternating row shading (if any), and link/code styles all come from the active Warp theme. No hard-coded colors.
16. Markdown tables render consistently wherever block-list markdown already renders — command output, agent responses, and any other block type that supports inline markdown. The same input produces the same table in each surface.
17. Non-table pipe content is not misrendered as a table. Text that contains `|` characters but no valid header-separator line remains plain text, even if it visually resembles a table.
````
+81
View File
@@ -0,0 +1,81 @@
---
name: write-tech-spec
description: Write a TECH.md spec for a significant Warp feature after researching the current codebase and implementation constraints. Use when the user asks for a technical spec, implementation plan, or architecture doc tied to a product spec.
---
# write-tech-spec
Write a `TECH.md` spec for a significant feature in Warp.
## Overview
The tech spec should translate product intent into an implementation plan that fits the existing codebase, documents architectural choices, and makes the work easier for agents to execute and reviewers to evaluate.
Write specs to `specs/<id>/TECH.md`, where `<id>` is one of:
- a Linear ticket number (e.g. `specs/APP-1234/TECH.md`)
- a GitHub issue id, prefixed with `gh-` (e.g. `specs/gh-4567/TECH.md`)
- a short kebab-case feature name (e.g. `specs/vertical-tabs-hover-sidecar/TECH.md`)
Match the id used by the sibling `PRODUCT.md` when one exists. `specs/` should contain only id-named directories as direct children.
Ticket / issue references are optional. If the user has a Linear ticket or GitHub issue, use its id. If they don't, ask them for a feature name to use as the directory. Only create a new Linear ticket or GitHub issue when the user explicitly asks for one; in that case use the Linear MCP tools or `gh` CLI respectively (and `ask_user_question` if team, labels, or repo are unclear).
## When to use
Use this skill when the implementation spans multiple modules, has meaningful architectural tradeoffs, or when reviewers will benefit from seeing the plan before or alongside the code. For pure UI changes or straightforward fixes, a tech spec is often unnecessary.
Prefer to have a `PRODUCT.md` first so the technical plan is anchored to agreed behavior. If the implementation is still too uncertain, build an e2e prototype first and then write the tech spec from what was learned.
## Research before writing
Before drafting, read the product spec (if any), inspect the relevant code, and identify the main files, types, data flow, and ownership boundaries. Do not guess about current architecture when the code can be inspected directly.
## Structure
Required sections:
1. **Context** — What's being built, how the current system works in the area being changed, and the most relevant files with line references. Combine the "problem," "current state," and "relevant code" into one grounded section. Example references:
- `app/src/workspace/mod.rs:42` — entry point for the user flow
- `app/src/workspace/workspace.rs (120-220)` — state and event handling that will likely change
Reference `PRODUCT.md` for user-visible behavior rather than restating it.
2. **Proposed changes** — The implementation plan: which modules change, new types/APIs/state being introduced, data flow, ownership boundaries, and how the design follows existing patterns. Call out tradeoffs when there is more than one reasonable path.
3. **Testing and validation** — How the implementation will be verified against the product behavior. Owns everything about proving the feature works: unit tests, integration tests, manual steps, screenshots, videos, and any other verification. Reference the numbered Behavior invariants from `PRODUCT.md` directly rather than restating them; each important invariant should map to a concrete test or verification step. This section is where validation lives — `PRODUCT.md` intentionally does not have a Validation section.
Optional sections — include only when they add signal. Omit the heading entirely if empty; do not write "None" as a placeholder.
- **End-to-end flow** — Include only when tracing the path through the system tells you something the Proposed changes list doesn't.
- **Diagram** — Include a Mermaid diagram only when a visual will explain the design faster than prose (data flow, state transitions, sequence across layers). Prefer one or two focused diagrams over decorative ones.
- **Risks and mitigations** — Include when there are real failure modes, regressions, migration concerns, or rollout hazards worth calling out.
- **Parallelization** — Include when work can cleanly split across multiple agents and that split is non-obvious.
- **Follow-ups** — Include when there is deferred cleanup or future work worth naming.
## Length heuristic
Right-size the spec to the feature:
- Single-file change with clear approach: skip the tech spec or keep it under ~40 lines.
- Multi-module change with some ambiguity: target ~80–150 lines.
- Large cross-cutting or architecturally novel change: longer is fine when every section earns its place.
If Context and Proposed changes end up describing the same files and state from different angles, collapse them.
## Writing guidance
- Ground the plan in actual codebase structure and patterns.
- Prefer concrete implementation guidance over generic architecture language.
- Explain why the proposed design fits this repo.
- Reference `PRODUCT.md` for behavior instead of restating it.
- Each section should earn its place — if a section would repeat another or contain only boilerplate, omit it.
## Keep the spec current
Approved specs may ship in the same PR as the implementation. Update `TECH.md` in the same PR when module boundaries, implementation sequencing, risks, validation strategy, or rollout assumptions change. The checked-in spec should describe the implementation that actually ships.
For large features, the implementer may optionally keep a `DECISIONS.md` file summarizing concrete decisions. Offer it when it would help future agents; otherwise skip it.
## Related Skills
- `implement-specs`
- `write-product-spec`
- `spec-driven-implementation`