Initial scaffold: GyanDoot v2.0 in Rust

Full Cargo workspace with 11 crates:
- gyandoot-core: Shared types, config, error types
- gyandoot-agent: Agent loop, tool execution, context compression
- gyandoot-providers: LLM provider abstraction (OpenAI, Anthropic, Ollama)
- gyandoot-memory: MemPalace persistent memory system
- gyandoot-platform: Platform adapters (Telegram, Discord, Webhook, CLI)
- gyandoot-security: Credential vault, sanitizer, threat detector
- gyandoot-kanban: Task decomposition, swarm orchestration, diagnostics
- gyandoot-subagents: Sub-agent spawning and orchestration
- gyandoot-skills: Skills discovery, loading, preprocessing, bundles
- gyandoot-profile: Multi-agent profile manager
- gyandoot-gateway: Gateway sessions, routing, access control

Plus: CLI entry point with clap, cross-platform installer script,
KANBAN.md, .gnpl/AGENTS.md

Workspace compiles clean: cargo check passes with 0 errors.
This commit is contained in:
2026-06-11 20:04:12 +05:30
commit 851c838ca5
53 changed files with 8037 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
[package]
name = "gyandoot-skills"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "GyanDoot v2 — Skills discovery, loading, preprocessing, bundles"
[dependencies]
gyandoot-core = { path = "../gyandoot-core" }
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
anyhow.workspace = true
tracing.workspace = true
uuid.workspace = true

View File

@@ -0,0 +1,206 @@
//! # Gyandoot Skills System
//!
//! Skills are reusable procedural knowledge — loaded, preprocessed, and invoked
//! by the agent to perform specific tasks. Supports discovery, bundles, templates, and hubs.
use std::collections::HashMap;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
/// Trust level for a skill.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TrustLevel {
Builtin,
Trusted,
Community,
Unverified,
}
/// Metadata for a discovered skill.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillMeta {
pub name: String,
pub description: String,
pub category: Option<String>,
pub tags: Vec<String>,
pub trust: TrustLevel,
pub path: PathBuf,
}
/// A loaded skill with full content.
#[derive(Debug, Clone)]
pub struct Skill {
pub meta: SkillMeta,
pub content: String,
pub templates: HashMap<String, String>,
}
/// Discovers skills from filesystem locations.
pub struct SkillsManager {
skill_dirs: Vec<PathBuf>,
skills: HashMap<String, Skill>,
}
impl SkillsManager {
pub fn new() -> Self {
Self {
skill_dirs: Vec::new(),
skills: HashMap::new(),
}
}
pub fn with_skill_dir(mut self, dir: PathBuf) -> Self {
self.skill_dirs.push(dir);
self
}
/// Scan all registered directories for skills.
pub fn scan(&mut self) -> Vec<SkillMeta> {
let mut metas = Vec::new();
for dir in &self.skill_dirs {
if dir.exists() {
info!(path = %dir.display(), "Scanning for skills");
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() && path.join("SKILL.md").exists() {
let name = path.file_stem()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string();
let meta = SkillMeta {
name,
description: String::new(), // would parse from SKILL.md
category: dir.file_stem().and_then(|n| n.to_str()).map(String::from),
tags: Vec::new(),
trust: TrustLevel::Builtin,
path,
};
metas.push(meta);
}
}
}
}
}
for m in &metas {
info!(skill = %m.name, "Discovered skill");
}
metas
}
/// Load a specific skill by name.
pub fn load(&mut self, name: &str) -> Option<&Skill> {
if self.skills.contains_key(name) {
return self.skills.get(name);
}
// Search directories for matching skill
for dir in &self.skill_dirs {
let candidate = dir.join(name).join("SKILL.md");
if candidate.exists() {
let content = std::fs::read_to_string(&candidate).ok().unwrap_or_default();
// Parse frontmatter
let (_, body) = if let Some(end) = content.find("---\n") {
content.split_at(end + 4)
} else {
("", &content[..])
};
let meta = SkillMeta {
name: name.to_string(),
description: content.lines().next().unwrap_or("").to_string(),
category: dir.file_stem().and_then(|n| n.to_str()).map(String::from),
tags: Vec::new(),
trust: TrustLevel::Trusted,
path: candidate,
};
let skill = Skill {
meta,
content: body.to_string(),
templates: HashMap::new(),
};
self.skills.insert(name.to_string(), skill);
return self.skills.get(name);
}
}
warn!(skill = %name, "Skill not found");
None
}
pub fn list_skills(&self) -> Vec<SkillMeta> {
self.skills.values().map(|s| s.meta.clone()).collect()
}
/// Build compact skill index for system prompt injection.
pub fn inject_skills(&self) -> String {
let mut lines = Vec::new();
lines.push("Available skills (load with skill_view for full content):".to_string());
for skill in self.skills.values() {
lines.push(format!(
"- {} ({}): {}",
skill.meta.name,
skill.meta.category.as_deref().unwrap_or("general"),
skill.meta.description
));
}
if self.skills.is_empty() {
lines.push("(no skills loaded)".to_string());
}
lines.join("\n")
}
}
impl Default for SkillsManager {
fn default() -> Self {
Self::new()
}
}
/// Processes skill content — resolves templates and variables.
pub struct Preprocessor;
impl Preprocessor {
pub fn new() -> Self {
Self
}
/// Resolve ${VAR} placeholders in skill content.
pub fn process(&self, content: &str, vars: &HashMap<String, String>) -> String {
let mut result = content.to_string();
for (key, value) in vars {
result = result.replace(&format!("${{{}}}", key), value);
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_preprocessor_variable_resolution() {
let pp = Preprocessor::new();
let mut vars = HashMap::new();
vars.insert("NAME".to_string(), "Maya".to_string());
let result = pp.process("Hello ${NAME}!", &vars);
assert_eq!(result, "Hello Maya!");
}
#[test]
fn test_skills_manager_initialization() {
let mut sm = SkillsManager::new();
sm = sm.with_skill_dir(PathBuf::from("/tmp/test-skills"));
let metas = sm.scan();
// Should be empty since dir doesn't exist
assert!(metas.is_empty());
}
#[test]
fn test_preprocessor_no_substitution() {
let pp = Preprocessor::new();
let vars = HashMap::new();
let result = pp.process("No variables here", &vars);
assert_eq!(result, "No variables here");
}
}