Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@ crates/live/assets/antipatterns.json linguist-generated
.trae-cn/** linguist-generated
.veto/** linguist-generated
.vibe/** linguist-generated
.zcode/** linguist-generated
plugin/** linguist-generated
cursor-plugin/** linguist-generated
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ assignees: ''
- [ ] OpenCode
- [ ] Qoder
- [ ] Mistral Vibe
- [ ] ZCode
- [ ] All providers

## Alternatives considered
Expand Down
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This repo is issue-first for outside contributions. If you are not `pbakaus` or
- [ ] Source files updated in `source/`
- [ ] `bun run build` ran successfully
- [ ] `bun test` passes
- [ ] Tested with at least one provider (Cursor / Claude Code / Gemini CLI / Codex / Copilot / Grok Build / Kiro / OpenCode / Qoder / Mistral Vibe)
- [ ] Tested with at least one provider (Cursor / Claude Code / Gemini CLI / Codex / Copilot / Grok Build / Kiro / OpenCode / Qoder / Mistral Vibe / ZCode)
- [ ] README / DEVELOP.md updated if needed
- [ ] I reviewed the full diff myself before requesting human review
- [ ] I disclosed any AI assistance in this PR and related commits/comments, or no AI assistance was used
1 change: 1 addition & 0 deletions .github/workflows/sync-generated-output.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ env:
.trae-cn
.veto
.vibe
.zcode
plugin
cursor-plugin

Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ Run `bun run build` after changing anything in `skill/`, transformer code, or us

## Generated Provider Output Policy

The root harness folders (`.agents/skills/`, `.claude/skills/`, `.cursor/skills/`, `.devin/skills/`, `.dsh/skills/`, `.gemini/skills/`, `.github/skills/`, `.grok/skills/`, `.hermes/skills/`, `.kiro/skills/`, `.opencode/skills/`, `.pi/skills/`, `.qoder/skills/`, `.rovodev/skills/`, `.trae*/skills/`, `.vibe/skills/`) and `plugin/` stay tracked so `main` remains installable for direct GitHub, `npx skills`, and submodule users. They are still generated artifacts.

The root harness folders (`.agents/skills/`, `.claude/skills/`, `.cursor/skills/`, `.devin/skills/`, `.dsh/skills/`, `.gemini/skills/`, `.github/skills/`, `.grok/skills/`, `.hermes/skills/`, `.kiro/skills/`, `.opencode/skills/`, `.pi/skills/`, `.qoder/skills/`, `.rovodev/skills/`, `.trae*/skills/`, `.vibe/skills/`, `.zcode/skills/`) and `plugin/` stay tracked so `main` remains installable for direct GitHub, `npx skills`, and submodule users. They are still generated artifacts.


Normal development should be source-first: stage changes in `crates/`, `browser-bundle/`, `skill/`, `scripts/`, `cli/`, `extension/`, and `tests/`; leave generated harness churn unstaged unless the user asked for it. After source changes land on `main`, `.github/workflows/sync-generated-output.yml` runs `bun run build:release` and commits generated provider output directly back to `main`. Treat generated harness diffs as release artifacts and keep them out of feature PRs unless they are the point of the PR. The two tracked engine assets under `crates/live/assets/` follow the rule-change workflow below instead.

Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ git add .gitmodules .impeccable .claude .cursor
git commit -m "Add Impeccable skills"
```

Use the providers your project needs, for example `claude`, `cursor`, `gemini`, `codex`, `github`, `grok`, `devin`, `hermes`, `opencode`, `pi`, `qoder`, `trae`, `trae-cn`, `rovo-dev`, `vibe`, or `veto`. The command links individual skill folders from `.impeccable/dist/universal/` and leaves existing real skill directories untouched unless you pass `--force`.

Use the providers your project needs, for example `claude`, `cursor`, `gemini`, `codex`, `github`, `grok`, `devin`, `hermes`, `opencode`, `pi`, `qoder`, `trae`, `trae-cn`, `rovo-dev`, `vibe`, `zcode`, or `veto`. The command links individual skill folders from `.impeccable/dist/universal/` and leaves existing real skill directories untouched unless you pass `--force`.


To update later:

Expand Down Expand Up @@ -307,6 +309,15 @@ cp -r dist/qoder/.qoder your-project/
cp -r dist/qoder/.qoder/skills/* ~/.qoder/skills/
```

**ZCode:**
```bash
# Project-specific
cp -r dist/zcode/.zcode your-project/

# Or global (applies to all projects)
cp -r dist/zcode/.zcode/skills/* ~/.zcode/skills/
```

**Mistral Vibe:**
```bash
# Project-specific
Expand Down Expand Up @@ -521,6 +532,7 @@ Full detector docs: [impeccable.style/docs/detector](https://impeccable.style/do
- [Mistral Vibe](https://docs.mistral.ai/vibe/code/overview)
- [Veto](https://github.com/oleg-koval/veto)
- [Google Antigravity](https://antigravity.google)
- [ZCode](https://zcode.z.ai)

## Community & Ecosystem

Expand Down
7 changes: 4 additions & 3 deletions crates/context/src/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ use impeccable_common::Io;
/// keep in sync with the public repo).
pub const COMMAND_METADATA_JSON: &str = include_str!("command-metadata.json");

const HARNESS_DIRS: [&str; 20] = [
const HARNESS_DIRS: [&str; 21] = [

".claude", ".cursor", ".devin", ".dsh", ".gemini", ".codex", ".agents", ".agent", ".github", ".grok", ".hermes", ".trae", ".trae-cn",
".pi", ".omp", ".opencode", ".kiro", ".rovodev", ".vibe", ".qoder",
".pi", ".omp", ".opencode", ".kiro", ".rovodev", ".vibe", ".qoder", ".zcode",
];
const CODEX_HARNESSES: [&str; 2] = [".codex", ".agents"];
const CODEX_HARNESSES: [&str; 3] = [".codex", ".agents", ".zcode"];
pub const VALID_COMMANDS: [&str; 24] = [
"craft", "init", "extract", "document", "shape", "critique", "audit", "polish", "bolder", "quieter", "distill",
"harden", "onboard", "live", "animate", "colorize", "typeset", "layout", "delight", "overdrive", "clarify",
Expand Down
3 changes: 2 additions & 1 deletion crates/context/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ fn provider_from_skill_dir(skill_dir: &str) -> Option<&'static str> {
".grok" => "grok",
".agent" => "antigravity",
".hermes" => "hermes",
".zcode" => "zcode",
_ => return None,
})
}
Expand All @@ -100,7 +101,7 @@ pub fn detect(env: &Env, cwd: &str) -> Provider {
.unwrap_or(SOURCE_PROVIDER)
.to_string(),
};
let command_prefix = if id == "codex" { "$" } else { "/" }.to_string();
let command_prefix = if id == "codex" || id == "zcode" { "$" } else { "/" }.to_string();
let command = format!("{}impeccable", command_prefix);
let self_raw = match env.get("IMPECCABLE_SELF").filter(|v| !v.trim().is_empty()) {
Some(v) => v.trim().to_string(),
Expand Down
3 changes: 2 additions & 1 deletion crates/skills/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,8 @@ pub fn list_skill_tree_files(root: &str) -> Vec<String> {
}

static PROVIDER_PATH_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"\.(claude|cursor|devin|dsh|agents|agent|github|gemini|codex|grok|hermes|kiro|opencode|pi|qoder|trae|trae-cn|rovodev|vibe)/skills/").unwrap()

Regex::new(r"\.(claude|cursor|devin|dsh|agents|agent|github|gemini|codex|grok|hermes|kiro|opencode|pi|qoder|trae|trae-cn|rovodev|vibe|zcode)/skills/").unwrap()
});

/// JS: normalizeForHash(content)
Expand Down
26 changes: 24 additions & 2 deletions crates/skills/src/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ use crate::util::{self, jsp, Env};
pub const API_BASE: &str = "https://impeccable.style";

pub const PROVIDER_DIRS: &[&str] = &[

".claude", ".cursor", ".devin", ".dsh", ".gemini", ".agents", ".agent", ".github", ".grok", ".hermes", ".kiro",
".opencode", ".pi", ".omp", ".qoder", ".trae", ".trae-cn", ".rovodev", ".vibe",
".opencode", ".pi", ".omp", ".qoder", ".trae", ".trae-cn", ".rovodev", ".vibe", ".zcode",

];

const PROVIDER_ALIASES: &[(&str, &str)] = &[
Expand Down Expand Up @@ -43,7 +45,10 @@ const PROVIDER_ALIASES: &[(&str, &str)] = &[
("trae", ".trae"),
("trae-cn", ".trae-cn"),
("vibe", ".vibe"),

("windsurf", ".devin"),
("zcode", ".zcode"),

];

const PROVIDER_DISPLAY: &[(&str, &str, &str)] = &[
Expand All @@ -66,6 +71,7 @@ const PROVIDER_DISPLAY: &[(&str, &str, &str)] = &[
(".trae", "Trae", "trae"),
(".trae-cn", "Trae CN", "trae-cn"),
(".vibe", "Mistral Vibe", "vibe"),
(".zcode", "ZCode", "zcode"),
];

/// Harnesses that read another provider's dir: (input name, label, provider
Expand All @@ -74,8 +80,10 @@ const PROVIDER_DISPLAY: &[(&str, &str, &str)] = &[
const SHARED_DIR_HARNESSES: &[(&str, &str, &str)] = &[("replit", "Replit Agent", ".agents")];

pub const PROVIDER_INPUT_ORDER: &[&str] = &[

"antigravity", "claude", "codex", "replit", "cursor", "devin", "dsh", "gemini", "github", "grok", "hermes", "kiro",
"opencode", "pi", "omp", "qoder", "trae", "trae-cn", "rovo-dev", "vibe",
"opencode", "pi", "omp", "qoder", "trae", "trae-cn", "rovo-dev", "vibe", "zcode",

];

pub const DEFAULT_TARGETS: &[&str] = &[".claude", ".agents"];
Expand Down Expand Up @@ -500,11 +508,14 @@ fn opencode_hint_dir(env: &Env, _cwd: &str, home: &str) -> String {
impl Hint {
fn harness(&self) -> &'static str {
match self {

Hint::Project(_, h) | Hint::Home(_, h) | Hint::ConfigDir(_, h) => h,

}
}
}


const HARNESS_HINTS: &[Hint] = &[
// Every Replit project carries `.replit` at its root; Replit Agent reads
// `.agents/skills`, so this marks the `.agents` install as Replit (#1019).
Expand Down Expand Up @@ -533,6 +544,8 @@ const HARNESS_HINTS: &[Hint] = &[
Hint::Home(".qoder", "qoder"),
Hint::Home(".rovodev", "rovo-dev"),
Hint::Home(".vibe", "vibe"),
Hint::Home(".zcode", "zcode"),

];

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -881,6 +894,15 @@ mod tests {
assert_eq!(invalid, vec!["zzz"]);
}

#[test]
fn zcode_provider_resolves() {
assert_eq!(normalize_provider_name("zcode"), Some(".zcode"));
assert_eq!(normalize_provider_name(".zcode"), Some(".zcode"));
assert_eq!(normalize_provider_name("ZCode"), Some(".zcode"));
assert_eq!(provider_display_name(".zcode"), "ZCode");
assert_eq!(provider_input_name(".zcode"), "zcode");
}

#[test]
fn omp_provider_resolves_paths() {
assert_eq!(normalize_provider_name("omp"), Some(".omp"));
Expand Down
18 changes: 18 additions & 0 deletions crates/skills/tests/install_detection_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
//! (DeepSeek Harness) must be detected even when `~/.dsh` itself does not
//! exist, and must be ignored when it points outside home. A project
//! `.replit` file labels the shared `.agents` detection as Replit Agent.
//! Also pins the plain-home `.zcode` hint (ZCode reaches detection through
//! the generic home-hint path, like other plain-home providers).

use std::collections::HashMap;

Expand Down Expand Up @@ -111,6 +113,22 @@ fn opencode_config_dir_only_setup_is_detected() {
assert_eq!(found, vec![opencode_dir]);
}

#[test]
fn zcode_home_hint_dir_is_detected() {
let home = temp_root("zcode-hint-detect");
let project = temp_root("zcode-hint-project");
std::fs::create_dir_all(jsp::join(&[&home, ".zcode"])).unwrap();
let sys = sys_with(&home, &[]);

let found: Vec<String> = sys
.collect_install_detections(&project)
.into_iter()
.filter(|d| d.provider == ".zcode" && d.scope == Scope::User)
.map(|d| d.found_path)
.collect();
assert_eq!(found, vec![jsp::join(&[&home, ".zcode"])]);
}

fn agents_project_detections(sys: &Sys, root: &str) -> Vec<impeccable_skills::providers::Detection> {
sys.collect_install_detections(root)
.into_iter()
Expand Down
Loading