fix(skills): reconcile team-template skill names so role bindings actually bind
Every skill reference in every team template was failing to resolve. The TOMLs used snake_case slugs (`write_rust`, `index_selection`) while the authored skills under `skills/**/*.md` declare kebab-case names (`write-rust-current-edition`, `postgres-index-selection`), so `get_by_name` missed on all of them: 128 skipped bindings across 51 distinct names, and no mission agent received any of its template's skills. The mirror-image half was equally invisible: ten authored skills — including `int-xx-marker-protocol`, whose own `when_to_use` says "pin on every coding role" — were referenced by no role at all, so nothing could ever load them. - Rename the 14 references that have authored skills behind them, and dedupe the two that now collapse onto the commit-protocol skill. - Attach all ten orphaned skills to the roles their `when_to_use` names. All 23 authored skills now reach at least one role. - Aggregate the loader's per-name logging into one line per template. The old per-name spam is why this went unnoticed; a bound/unresolved count is noticeable. References with no authored skill are kept and listed — they record intent for skills not yet written. - Two regression tests: no authored skill may be orphaned, and every authored skill must be referenced by its exact name. Also clears the two standing clippy warnings: group `mint_team_from_template`'s eight positional args into `TeamMint`, and make `provider_alias_for` branch on `is_exact_provider_match` so the helper is live code and the two can't disagree about what counts as an exact family match. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
95bd65540c
commit
2eb0880fc0
@@ -186,13 +186,15 @@ pub async fn on_launch(
|
|||||||
mission.title, purpose, template.template.name
|
mission.title, purpose, template.template.name
|
||||||
);
|
);
|
||||||
let team_id = mint_team_from_template(
|
let team_id = mint_team_from_template(
|
||||||
|
TeamMint {
|
||||||
pool,
|
pool,
|
||||||
workspace_id,
|
workspace_id,
|
||||||
user_id,
|
user_id,
|
||||||
provisioner.as_ref(),
|
provisioner: provisioner.as_ref(),
|
||||||
&template,
|
template: &template,
|
||||||
&team_name,
|
team_name: &team_name,
|
||||||
"claude-sonnet-5",
|
default_model: "claude-sonnet-5",
|
||||||
|
},
|
||||||
&mut provisioned_claws,
|
&mut provisioned_claws,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -298,16 +300,33 @@ pub async fn on_launch(
|
|||||||
Ok(Some(team_id))
|
Ok(Some(team_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mint_team_from_template(
|
/// The read-only inputs for minting a team. Grouped into a struct so the
|
||||||
pool: &PgPool,
|
/// signature stays readable as the orchestrator accumulates context — the
|
||||||
|
/// growing positional list was also easy to mis-order at the call site,
|
||||||
|
/// since `team_name` and `default_model` are both `&str`.
|
||||||
|
struct TeamMint<'a> {
|
||||||
|
pool: &'a PgPool,
|
||||||
workspace_id: WorkspaceId,
|
workspace_id: WorkspaceId,
|
||||||
user_id: cm_domain::UserId,
|
user_id: cm_domain::UserId,
|
||||||
provisioner: Option<&RuntimeProvisioner>,
|
provisioner: Option<&'a RuntimeProvisioner>,
|
||||||
template: &TeamTemplateDetail,
|
template: &'a TeamTemplateDetail,
|
||||||
team_name: &str,
|
team_name: &'a str,
|
||||||
default_model: &str,
|
default_model: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mint_team_from_template(
|
||||||
|
mint: TeamMint<'_>,
|
||||||
provisioned_claws: &mut Vec<cm_domain::AgentId>,
|
provisioned_claws: &mut Vec<cm_domain::AgentId>,
|
||||||
) -> Result<Uuid, String> {
|
) -> Result<Uuid, String> {
|
||||||
|
let TeamMint {
|
||||||
|
pool,
|
||||||
|
workspace_id,
|
||||||
|
user_id,
|
||||||
|
provisioner,
|
||||||
|
template,
|
||||||
|
team_name,
|
||||||
|
default_model,
|
||||||
|
} = mint;
|
||||||
// Build the topology graph from role slots so the team's `graph`
|
// Build the topology graph from role slots so the team's `graph`
|
||||||
// NOT NULL column is satisfied + downstream topology executors
|
// NOT NULL column is satisfied + downstream topology executors
|
||||||
// have a valid shape to iterate over.
|
// have a valid shape to iterate over.
|
||||||
|
|||||||
@@ -29,14 +29,15 @@ pub fn claw_alias(claw_id: Uuid) -> String {
|
|||||||
pub fn provider_alias_for(model: &str) -> &'static str {
|
pub fn provider_alias_for(model: &str) -> &'static str {
|
||||||
let m = model.trim().to_ascii_lowercase();
|
let m = model.trim().to_ascii_lowercase();
|
||||||
// Prefix families first (covers claude-sonnet-5, claude-opus-4-8,
|
// Prefix families first (covers claude-sonnet-5, claude-opus-4-8,
|
||||||
// claude-haiku-4-5-*, etc.) then explicit aliases.
|
// claude-haiku-4-5-*, etc.) then explicit aliases. `is_exact_provider_match`
|
||||||
|
// decides what "its own family" means, so the two can't drift apart.
|
||||||
|
if is_exact_provider_match(&m) {
|
||||||
if m.starts_with("claude") {
|
if m.starts_with("claude") {
|
||||||
return "anthropic.default";
|
return "anthropic.default";
|
||||||
}
|
}
|
||||||
if m.starts_with("gemini") {
|
if m.starts_with("gemini") {
|
||||||
return "gemini.default";
|
return "gemini.default";
|
||||||
}
|
}
|
||||||
if m.starts_with("llama") || m.starts_with("groq") {
|
|
||||||
return "groq.default";
|
return "groq.default";
|
||||||
}
|
}
|
||||||
match m.as_str() {
|
match m.as_str() {
|
||||||
@@ -69,12 +70,13 @@ pub fn provider_alias_for(model: &str) -> &'static str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether `provider_alias_for` resolved this model to its own family, or
|
/// Whether `provider_alias_for` resolves this model to its own family, or
|
||||||
/// substituted a different one.
|
/// substitutes a different one.
|
||||||
///
|
///
|
||||||
/// Callers that surface a model choice to a user can use this to say so rather
|
/// `provider_alias_for` branches on this, so it is the single definition of
|
||||||
/// than letting the substitution be discovered on an invoice. Kept alongside
|
/// "its own family". Also public for callers that surface a model choice to a
|
||||||
/// `provider_alias_for` so the two can't disagree about what counts as a match.
|
/// user, so a substitution can be said out loud rather than discovered on an
|
||||||
|
/// invoice.
|
||||||
pub fn is_exact_provider_match(model: &str) -> bool {
|
pub fn is_exact_provider_match(model: &str) -> bool {
|
||||||
let m = model.trim().to_ascii_lowercase();
|
let m = model.trim().to_ascii_lowercase();
|
||||||
m.starts_with("claude")
|
m.starts_with("claude")
|
||||||
@@ -350,7 +352,12 @@ mod tests {
|
|||||||
"{m} resolves to anthropic.default by substitution, not by family"
|
"{m} resolves to anthropic.default by substitution, not by family"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for m in ["claude-sonnet-5", "gemini-2.5-flash", "groq-llama", "llama3"] {
|
for m in [
|
||||||
|
"claude-sonnet-5",
|
||||||
|
"gemini-2.5-flash",
|
||||||
|
"groq-llama",
|
||||||
|
"llama3",
|
||||||
|
] {
|
||||||
assert!(
|
assert!(
|
||||||
super::is_exact_provider_match(m),
|
super::is_exact_provider_match(m),
|
||||||
"{m} should resolve to its own family"
|
"{m} should resolve to its own family"
|
||||||
|
|||||||
@@ -167,6 +167,13 @@ async fn load_one(pool: &PgPool, path: &std::path::Path) -> Result<String, Strin
|
|||||||
{
|
{
|
||||||
eprintln!("team_template_loader: clear_template_role_skills({key}) failed: {e}");
|
eprintln!("team_template_loader: clear_template_role_skills({key}) failed: {e}");
|
||||||
}
|
}
|
||||||
|
// Unresolved names are aggregated into one line per template rather than
|
||||||
|
// logged individually: the per-name spam (128 lines at last count) scrolled
|
||||||
|
// past unread for long enough that every template's skill bindings were
|
||||||
|
// silently empty, because the TOMLs used snake_case slugs while the authored
|
||||||
|
// skills in `skills/**/*.md` use kebab-case names. A count is noticeable.
|
||||||
|
let mut unresolved: Vec<String> = Vec::new();
|
||||||
|
let mut bound = 0usize;
|
||||||
for role in &file.roles {
|
for role in &file.roles {
|
||||||
for (idx, skill_name) in role.skills.iter().enumerate() {
|
for (idx, skill_name) in role.skills.iter().enumerate() {
|
||||||
match cm_db::repo::skills_catalog::get_by_name(pool, None, skill_name).await {
|
match cm_db::repo::skills_catalog::get_by_name(pool, None, skill_name).await {
|
||||||
@@ -188,20 +195,124 @@ async fn load_one(pool: &PgPool, path: &std::path::Path) -> Result<String, Strin
|
|||||||
"team_template_loader: attach skill {skill_name} → {key}.{}: {e}",
|
"team_template_loader: attach skill {skill_name} → {key}.{}: {e}",
|
||||||
role.slot
|
role.slot
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
bound += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => unresolved.push(format!("{}.{skill_name}", role.slot)),
|
||||||
eprintln!(
|
|
||||||
"team_template_loader: skill '{skill_name}' referenced by {key}.{} not found — skipped",
|
|
||||||
role.slot
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("team_template_loader: lookup skill '{skill_name}' failed: {e}");
|
eprintln!("team_template_loader: lookup skill '{skill_name}' failed: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if unresolved.is_empty() {
|
||||||
|
eprintln!("team_template_loader: {key} — {bound} role skills bound");
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"team_template_loader: {key} — {bound} role skills bound, {} unresolved (no such skill authored under skills/): {}",
|
||||||
|
unresolved.len(),
|
||||||
|
unresolved.join(", "),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(key)
|
Ok(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
fn repo_root() -> PathBuf {
|
||||||
|
// crates/cm-api → repo root
|
||||||
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.ancestors()
|
||||||
|
.nth(2)
|
||||||
|
.expect("repo root above crates/cm-api")
|
||||||
|
.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authored_skill_names(dir: &Path, out: &mut HashSet<String>) {
|
||||||
|
let Ok(entries) = std::fs::read_dir(dir) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for e in entries.flatten() {
|
||||||
|
let p = e.path();
|
||||||
|
if p.is_dir() {
|
||||||
|
authored_skill_names(&p, out);
|
||||||
|
} else if p.extension().is_some_and(|x| x == "md") {
|
||||||
|
let body = std::fs::read_to_string(&p).unwrap_or_default();
|
||||||
|
if let Some(name) = body
|
||||||
|
.lines()
|
||||||
|
.find_map(|l| l.strip_prefix("name:").map(str::trim))
|
||||||
|
{
|
||||||
|
out.insert(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn referenced_skill_names() -> HashSet<String> {
|
||||||
|
let mut refs = HashSet::new();
|
||||||
|
let dir = repo_root().join("templates/teams");
|
||||||
|
for e in std::fs::read_dir(&dir)
|
||||||
|
.expect("templates/teams readable")
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
let body = std::fs::read_to_string(e.path()).unwrap_or_default();
|
||||||
|
let parsed: toml::Value = match body.parse() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => panic!("{:?} is not valid TOML: {e}", e),
|
||||||
|
};
|
||||||
|
if let Some(roles) = parsed.get("roles").and_then(|r| r.as_array()) {
|
||||||
|
for role in roles {
|
||||||
|
if let Some(skills) = role.get("skills").and_then(|s| s.as_array()) {
|
||||||
|
refs.extend(skills.iter().filter_map(|s| s.as_str()).map(str::to_string));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every skill authored under `skills/**/*.md` must be reachable by at
|
||||||
|
/// least one team role.
|
||||||
|
///
|
||||||
|
/// This is the half of the naming drift that was invisible: the TOMLs used
|
||||||
|
/// snake_case slugs (`write_rust`) while the authored skills use kebab-case
|
||||||
|
/// names (`write-rust-current-edition`), so `get_by_name` missed on every
|
||||||
|
/// lookup — no role got any skill, and ten authored skills were reachable
|
||||||
|
/// by nobody. Both halves are silent at runtime; only a test catches them.
|
||||||
|
#[test]
|
||||||
|
fn every_authored_skill_is_referenced_by_some_role() {
|
||||||
|
let mut authored = HashSet::new();
|
||||||
|
authored_skill_names(&repo_root().join("skills"), &mut authored);
|
||||||
|
assert!(
|
||||||
|
!authored.is_empty(),
|
||||||
|
"no authored skills found — check the skills/ path"
|
||||||
|
);
|
||||||
|
let referenced = referenced_skill_names();
|
||||||
|
let orphans: Vec<_> = authored.difference(&referenced).cloned().collect();
|
||||||
|
assert!(
|
||||||
|
orphans.is_empty(),
|
||||||
|
"authored skills no team role references (they can never reach an agent): {orphans:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A referenced name that matches no authored skill binds to nothing. Some
|
||||||
|
/// are deliberately aspirational, so this asserts the *resolvable* ones
|
||||||
|
/// stay resolvable rather than demanding every name exist.
|
||||||
|
#[test]
|
||||||
|
fn referenced_skills_that_exist_use_the_authored_spelling() {
|
||||||
|
let mut authored = HashSet::new();
|
||||||
|
authored_skill_names(&repo_root().join("skills"), &mut authored);
|
||||||
|
let referenced = referenced_skill_names();
|
||||||
|
let resolvable = referenced.intersection(&authored).count();
|
||||||
|
assert_eq!(
|
||||||
|
resolvable,
|
||||||
|
authored.len(),
|
||||||
|
"every authored skill should be referenced by its exact name",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ version = 1
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "api_designer"
|
slot = "api_designer"
|
||||||
order_idx = 0
|
order_idx = 0
|
||||||
skills = ["decompose_int_items", "openapi_schema", "small_focused_commits"]
|
skills = ["decompose-int-items", "openapi_schema", "small-focused-commits", "api-pagination-day-1"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the API DESIGNER of a Backend team.
|
You are the API DESIGNER of a Backend team.
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "db_engineer"
|
slot = "db_engineer"
|
||||||
order_idx = 1
|
order_idx = 1
|
||||||
skills = ["postgres_migrations", "index_selection", "explain_analyze", "write_rust", "workspace_repo_edit"]
|
skills = ["postgres-migrations-forward-only", "postgres-index-selection", "explain_analyze", "write-rust-current-edition", "workspace-repo-commit-protocol"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the DB ENGINEER of a Backend team.
|
You are the DB ENGINEER of a Backend team.
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "coder"
|
slot = "coder"
|
||||||
order_idx = 2
|
order_idx = 2
|
||||||
skills = ["write_rust", "cargo_build", "cargo_test", "workspace_repo_edit", "git_commit_protocol"]
|
skills = ["write-rust-current-edition", "cargo_build", "cargo-test-driven-development", "workspace-repo-commit-protocol", "int-xx-marker-protocol", "rust-error-handling", "rust-async-tokio-idioms"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the CODER of a Backend team.
|
You are the CODER of a Backend team.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "tester"
|
slot = "tester"
|
||||||
order_idx = 3
|
order_idx = 3
|
||||||
skills = ["cargo_test", "integration_tests_pg", "coverage_report"]
|
skills = ["cargo-test-driven-development", "integration_tests_pg", "coverage_report", "tdd-red-green-refactor"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the TESTER of a Backend team.
|
You are the TESTER of a Backend team.
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "committer"
|
slot = "committer"
|
||||||
order_idx = 4
|
order_idx = 4
|
||||||
skills = ["git_commit_protocol", "small_focused_commits"]
|
skills = ["workspace-repo-commit-protocol", "small-focused-commits"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the COMMITTER. Same protocol as rust_sdlc: only run when tests
|
You are the COMMITTER. Same protocol as rust_sdlc: only run when tests
|
||||||
pass and reviewer (implicit here) approved. Emit COMPLETED: INT-<NN>.
|
pass and reviewer (implicit here) approved. Emit COMPLETED: INT-<NN>.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ version = 1
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "designer"
|
slot = "designer"
|
||||||
order_idx = 0
|
order_idx = 0
|
||||||
skills = ["decompose_int_items", "design_system_check", "a11y_checklist"]
|
skills = ["decompose-int-items", "design_system_check", "a11y_checklist"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the DESIGNER of a Frontend team.
|
You are the DESIGNER of a Frontend team.
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "coder"
|
slot = "coder"
|
||||||
order_idx = 1
|
order_idx = 1
|
||||||
skills = ["write_typescript_react", "tailwind_idioms", "workspace_repo_edit", "git_commit_protocol"]
|
skills = ["write_typescript_react", "tailwind-v4-idioms", "workspace-repo-commit-protocol", "int-xx-marker-protocol", "component-4-state-model", "react-19-server-components"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the CODER of a Frontend team.
|
You are the CODER of a Frontend team.
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "tester"
|
slot = "tester"
|
||||||
order_idx = 2
|
order_idx = 2
|
||||||
skills = ["playwright_e2e", "vitest_unit", "a11y_axe"]
|
skills = ["playwright_e2e", "vitest_unit", "a11y_axe", "tdd-red-green-refactor"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the TESTER of a Frontend team.
|
You are the TESTER of a Frontend team.
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "committer"
|
slot = "committer"
|
||||||
order_idx = 3
|
order_idx = 3
|
||||||
skills = ["git_commit_protocol", "small_focused_commits"]
|
skills = ["workspace-repo-commit-protocol", "small-focused-commits"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the COMMITTER. Only run on green tests + a11y pass. Emit
|
You are the COMMITTER. Only run on green tests + a11y pass. Emit
|
||||||
COMPLETED: INT-<NN>.
|
COMPLETED: INT-<NN>.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ version = 1
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "arch_analyst"
|
slot = "arch_analyst"
|
||||||
order_idx = 0
|
order_idx = 0
|
||||||
skills = ["decompose_int_items", "gpu_arch_reference", "roofline_model"]
|
skills = ["decompose-int-items", "gpu-coalescing-and-occupancy", "roofline-model"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the ARCHITECTURE ANALYST of a GPU team.
|
You are the ARCHITECTURE ANALYST of a GPU team.
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "kernel_author"
|
slot = "kernel_author"
|
||||||
order_idx = 1
|
order_idx = 1
|
||||||
skills = ["write_cuda", "write_metal", "write_rocm", "write_rust_ffi", "workspace_repo_edit", "git_commit_protocol"]
|
skills = ["write_cuda", "write_metal", "write_rocm", "write_rust_ffi", "workspace-repo-commit-protocol"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the KERNEL AUTHOR of a GPU team.
|
You are the KERNEL AUTHOR of a GPU team.
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "coder"
|
slot = "coder"
|
||||||
order_idx = 3
|
order_idx = 3
|
||||||
skills = ["write_rust", "cargo_build", "cargo_test", "workspace_repo_edit"]
|
skills = ["write-rust-current-edition", "cargo_build", "cargo-test-driven-development", "workspace-repo-commit-protocol", "int-xx-marker-protocol"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the RUST-SIDE CODER. Integrate the kernel + FFI into the Rust
|
You are the RUST-SIDE CODER. Integrate the kernel + FFI into the Rust
|
||||||
library, add safe wrappers, and expose ergonomic APIs. Own the
|
library, add safe wrappers, and expose ergonomic APIs. Own the
|
||||||
@@ -76,7 +76,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "committer"
|
slot = "committer"
|
||||||
order_idx = 4
|
order_idx = 4
|
||||||
skills = ["git_commit_protocol", "small_focused_commits"]
|
skills = ["workspace-repo-commit-protocol", "small-focused-commits"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the COMMITTER. Only run when the kernel meets the roofline
|
You are the COMMITTER. Only run when the kernel meets the roofline
|
||||||
target OR a specific reason to defer is documented.
|
target OR a specific reason to defer is documented.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ version = 1
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "designer"
|
slot = "designer"
|
||||||
order_idx = 0
|
order_idx = 0
|
||||||
skills = ["decompose_int_items", "ios_hig_check", "material_you_check"]
|
skills = ["decompose-int-items", "ios_hig_check", "material_you_check"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the DESIGNER of a Mobile team.
|
You are the DESIGNER of a Mobile team.
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "coder"
|
slot = "coder"
|
||||||
order_idx = 1
|
order_idx = 1
|
||||||
skills = ["write_typescript_react_native", "expo_managed_workflow", "workspace_repo_edit", "git_commit_protocol"]
|
skills = ["write_typescript_react_native", "expo-managed-vs-bare", "workspace-repo-commit-protocol", "int-xx-marker-protocol", "component-4-state-model", "rn-flashlist-perf"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the CODER of a Mobile team.
|
You are the CODER of a Mobile team.
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "tester"
|
slot = "tester"
|
||||||
order_idx = 2
|
order_idx = 2
|
||||||
skills = ["detox_e2e", "jest_unit", "ios_simulator_check", "android_emulator_check"]
|
skills = ["detox_e2e", "jest_unit", "ios_simulator_check", "android_emulator_check", "tdd-red-green-refactor"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the TESTER of a Mobile team.
|
You are the TESTER of a Mobile team.
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "committer"
|
slot = "committer"
|
||||||
order_idx = 3
|
order_idx = 3
|
||||||
skills = ["git_commit_protocol", "small_focused_commits"]
|
skills = ["workspace-repo-commit-protocol", "small-focused-commits"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the COMMITTER. Only run on green tests both platforms.
|
You are the COMMITTER. Only run on green tests both platforms.
|
||||||
Emit COMPLETED: INT-<NN>.
|
Emit COMPLETED: INT-<NN>.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ version = 1
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "planner"
|
slot = "planner"
|
||||||
order_idx = 0
|
order_idx = 0
|
||||||
skills = ["read_roadmap", "decompose_int_items", "estimate_effort", "small_focused_commits"]
|
skills = ["read_roadmap", "decompose-int-items", "estimate_effort", "small-focused-commits"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the PLANNER of a Rust SDLC team.
|
You are the PLANNER of a Rust SDLC team.
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "coder"
|
slot = "coder"
|
||||||
order_idx = 1
|
order_idx = 1
|
||||||
skills = ["write_rust", "cargo_build", "cargo_test", "workspace_repo_edit", "small_focused_commits", "git_commit_protocol"]
|
skills = ["write-rust-current-edition", "cargo_build", "cargo-test-driven-development", "workspace-repo-commit-protocol", "small-focused-commits", "int-xx-marker-protocol", "rust-error-handling", "rust-async-tokio-idioms", "react-19-server-components"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the CODER of a Rust SDLC team.
|
You are the CODER of a Rust SDLC team.
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "tester"
|
slot = "tester"
|
||||||
order_idx = 2
|
order_idx = 2
|
||||||
skills = ["cargo_test", "cargo_nextest", "coverage_report", "criterion_bench"]
|
skills = ["cargo-test-driven-development", "cargo_nextest", "coverage_report", "criterion_bench", "tdd-red-green-refactor"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the TESTER of a Rust SDLC team.
|
You are the TESTER of a Rust SDLC team.
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "reviewer"
|
slot = "reviewer"
|
||||||
order_idx = 3
|
order_idx = 3
|
||||||
skills = ["code_review_checklist", "read_diff", "small_focused_commits"]
|
skills = ["code-review-checklist", "read_diff", "small-focused-commits", "cargo-audit-workflow", "secret-scanning-gitleaks"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the REVIEWER of a Rust SDLC team.
|
You are the REVIEWER of a Rust SDLC team.
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "committer"
|
slot = "committer"
|
||||||
order_idx = 4
|
order_idx = 4
|
||||||
skills = ["git_commit_protocol", "workspace_repo_edit", "small_focused_commits"]
|
skills = ["workspace-repo-commit-protocol", "small-focused-commits"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the COMMITTER of a Rust SDLC team.
|
You are the COMMITTER of a Rust SDLC team.
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ version = 1
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "scene_designer"
|
slot = "scene_designer"
|
||||||
order_idx = 0
|
order_idx = 0
|
||||||
skills = ["decompose_int_items", "scene_graph_planning"]
|
skills = ["decompose-int-items", "scene_graph_planning"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the SCENE DESIGNER of a three.js team.
|
You are the SCENE DESIGNER of a three.js team.
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ brain_seed = """
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "coder"
|
slot = "coder"
|
||||||
order_idx = 1
|
order_idx = 1
|
||||||
skills = ["write_typescript", "threejs_idioms", "workspace_repo_edit", "git_commit_protocol"]
|
skills = ["write_typescript", "threejs-perf-and-teardown", "workspace-repo-commit-protocol", "int-xx-marker-protocol"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the CODER of a three.js team.
|
You are the CODER of a three.js team.
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ brain_seed = ""
|
|||||||
[[roles]]
|
[[roles]]
|
||||||
slot = "committer"
|
slot = "committer"
|
||||||
order_idx = 4
|
order_idx = 4
|
||||||
skills = ["git_commit_protocol", "small_focused_commits"]
|
skills = ["workspace-repo-commit-protocol", "small-focused-commits"]
|
||||||
system_prompt = """
|
system_prompt = """
|
||||||
You are the COMMITTER. Only run when perf targets met. Emit
|
You are the COMMITTER. Only run when perf targets met. Emit
|
||||||
COMPLETED: INT-<NN>.
|
COMPLETED: INT-<NN>.
|
||||||
|
|||||||
Reference in New Issue
Block a user