Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dec5758
refactor(mcp): complete trait-ization of remaining MCP tool layer
juice094 May 11, 2026
4faeca5
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 11, 2026
8a36e72
refactor(knowledge_engine): extract Config load + prepare_repos, elim…
juice094 May 11, 2026
83663ba
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 11, 2026
a9bea88
perf(knowledge_engine): reuse Tantivy writer across daemon batch inde…
juice094 May 11, 2026
abe30c1
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 11, 2026
507fc44
docs: module rustdoc + ADR-004/005 + i18n dead_code clarification
juice094 May 11, 2026
d0d2297
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 11, 2026
a3fef4c
docs: update project facade files (README ecosystem)
juice094 May 11, 2026
8bea167
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 11, 2026
3b7446b
test(knowledge_engine): 补齐 index.rs 与 index_state.rs 单元测试
juice094 May 11, 2026
69eb364
chore: ignore coverage_report.txt
juice094 May 11, 2026
ed51994
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 11, 2026
195f648
ci: upgrade actions/checkout v4 → v6 (Node.js 24)
juice094 May 12, 2026
3c2f2a9
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 12, 2026
fabba96
test(vault): 补齐 indexer.rs 单元测试,修复 id field 分词导致删除失效
juice094 May 12, 2026
9bc8017
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 12, 2026
2d62826
test(semantic_index): 补齐 persist.rs 单元测试
juice094 May 12, 2026
a0fd872
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 12, 2026
122fcbf
test(discovery_engine): 补齐 package_json、go_mod、similar_projects 测试
juice094 May 12, 2026
3118425
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 12, 2026
55ed67e
chore(deps): patch upgrade assert_cmd/blake3/tantivy/tokio
juice094 May 12, 2026
e8d0fe4
docs: 对齐 README Tool 矩阵与实际代码(48 tools)
juice094 May 12, 2026
afee7c4
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 12, 2026
5b008d4
feat(mcp): add devkit_evaluate — AI self-evaluation tool (Claude Comp…
juice094 May 12, 2026
324e662
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 12, 2026
7e96947
feat(agent-contexts): v0.16.0 P1 — persistent AI sessions with typed …
juice094 May 12, 2026
60bb8ed
feat(agent-contexts): v0.16.0 P2/P2 — context-aware execution + memor…
juice094 May 12, 2026
6c61e2f
Merge branch 'main' of https://github.com/juice094/devbase
juice094 May 12, 2026
62f2cee
feat(agent-contexts): v0.16.1 Workflow-Session binding
juice094 May 12, 2026
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Skill Runtime 全生命周期已落地(含依赖管理 Schema v15),Schema
- **Workspace**:`%LOCALAPPDATA%\devbase\workspace/` —— 文件系统 = source of truth
- `vault/` —— PARA 结构:00-Inbox, 01-Projects, 02-Areas, 03-Resources, 04-Archives, 99-Meta
- `assets/` —— 二进制资源
- **MCP Server**:stdio only,**57 个 tools**(含 5 个 vault tools + 8 个代码分析工具 + 4 个 embedding/搜索工具 + 4 个 Skill Runtime tools + 3 个 Workflow/评分 tools + 1 个报告工具 + 1 个 arXiv 工具 + 2 个 KnownLimit tools + 3 个 Relation tools + 2 个 Agent 状态工具 + 8 个 Agent Context tools + 1 个 streaming index 工具 + 1 个 oplog 工具);配置见 `mcp.json`
- **MCP Server**:stdio only,**58 个 tools**(含 5 个 vault tools + 8 个代码分析工具 + 4 个 embedding/搜索工具 + 4 个 Skill Runtime tools + 3 个 Workflow/评分 tools + 1 个报告工具 + 1 个 arXiv 工具 + 2 个 KnownLimit tools + 3 个 Relation tools + 2 个 Agent 状态工具 + 9 个 Agent Context tools + 1 个 streaming index 工具 + 1 个 oplog 工具);配置见 `mcp.json`
- **Kimi CLI 集成**:MCP server 已通过 `kimi mcp add` 注册,端到端验证通过(`kimi --print` 成功调用 `devkit_health`);项目级 skill 位于 `.kimi/skills/devbase-project/SKILL.md`
- **统一节点模型**:`core::node::{Node, NodeType, Edge}` —— GitRepo / VaultNote / Asset / ExternalLink
- **当前测试**:490+ workspace passed / 0 failed / 4 ignored(主 crate 390 + symbol-links 4 + sync-protocol 12 + core-types 3 + syncthing-client 2 + vault-frontmatter 5 + vault-wikilink 5 + workflow-interpolate 9 + workflow-model 2 + registry-health 3 + registry-metrics 4 + registry-workspace 5 + embedding 5 + skill-runtime-types 7 + skill-runtime-parser 3 + 其他 crates ~30);11/11 passed(integration `tests/cli.rs`)
Expand Down
2 changes: 2 additions & 0 deletions src/commands/workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@ pub fn run_workflow(
));
}
}
let active_ctx = crate::registry::agent_context::resolve_active_context();
let exec_id = crate::workflow::create_execution(
&conn,
&workflow_id,
&serde_json::to_string(&input_map)?,
active_ctx.as_deref(),
)?;
crate::workflow::update_execution(
&conn,
Expand Down
6 changes: 6 additions & 0 deletions src/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ pub enum McpToolEnum {
SessionActivate(DevkitSessionActivateTool),
SessionSearch(DevkitSessionSearchTool),
SessionCapture(DevkitSessionCaptureTool),
SessionWorkflows(DevkitSessionWorkflowsTool),
WorkflowList(DevkitWorkflowListTool),
WorkflowRun(DevkitWorkflowRunTool),
WorkflowStatus(DevkitWorkflowStatusTool),
Expand Down Expand Up @@ -193,6 +194,7 @@ impl McpToolEnum {
McpToolEnum::SessionActivate(_) => ToolTier::Beta,
McpToolEnum::SessionSearch(_) => ToolTier::Beta,
McpToolEnum::SessionCapture(_) => ToolTier::Beta,
McpToolEnum::SessionWorkflows(_) => ToolTier::Beta,
McpToolEnum::WorkflowList(_) => ToolTier::Beta,
McpToolEnum::WorkflowRun(_) => ToolTier::Beta,
McpToolEnum::WorkflowStatus(_) => ToolTier::Beta,
Expand Down Expand Up @@ -257,6 +259,7 @@ impl McpTool for McpToolEnum {
McpToolEnum::SessionActivate(t) => t.name(),
McpToolEnum::SessionSearch(t) => t.name(),
McpToolEnum::SessionCapture(t) => t.name(),
McpToolEnum::SessionWorkflows(t) => t.name(),
McpToolEnum::WorkflowList(t) => t.name(),
McpToolEnum::WorkflowRun(t) => t.name(),
McpToolEnum::WorkflowStatus(t) => t.name(),
Expand Down Expand Up @@ -319,6 +322,7 @@ impl McpTool for McpToolEnum {
McpToolEnum::SessionActivate(t) => t.schema(),
McpToolEnum::SessionSearch(t) => t.schema(),
McpToolEnum::SessionCapture(t) => t.schema(),
McpToolEnum::SessionWorkflows(t) => t.schema(),
McpToolEnum::WorkflowList(t) => t.schema(),
McpToolEnum::WorkflowRun(t) => t.schema(),
McpToolEnum::WorkflowStatus(t) => t.schema(),
Expand Down Expand Up @@ -385,6 +389,7 @@ impl McpTool for McpToolEnum {
McpToolEnum::SessionActivate(t) => t.invoke(args, ctx).await,
McpToolEnum::SessionSearch(t) => t.invoke(args, ctx).await,
McpToolEnum::SessionCapture(t) => t.invoke(args, ctx).await,
McpToolEnum::SessionWorkflows(t) => t.invoke(args, ctx).await,
McpToolEnum::WorkflowList(t) => t.invoke(args, ctx).await,
McpToolEnum::WorkflowRun(t) => t.invoke(args, ctx).await,
McpToolEnum::WorkflowStatus(t) => t.invoke(args, ctx).await,
Expand Down Expand Up @@ -641,6 +646,7 @@ pub fn build_server_with_tiers(tiers: Option<&HashSet<ToolTier>>) -> McpServer {
McpToolEnum::SessionActivate(DevkitSessionActivateTool),
McpToolEnum::SessionSearch(DevkitSessionSearchTool),
McpToolEnum::SessionCapture(DevkitSessionCaptureTool),
McpToolEnum::SessionWorkflows(DevkitSessionWorkflowsTool),
McpToolEnum::WorkflowList(DevkitWorkflowListTool),
McpToolEnum::WorkflowRun(DevkitWorkflowRunTool),
McpToolEnum::WorkflowStatus(DevkitWorkflowStatusTool),
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async fn test_tools_list() {
let (mut ctx, _tmp) = test_ctx();
let resp = server.handle_request(req, &mut ctx).await.unwrap();
let tools = resp.get("result").unwrap().get("tools").unwrap().as_array().unwrap();
assert_eq!(tools.len(), 57);
assert_eq!(tools.len(), 58);
let names: Vec<&str> = tools.iter().map(|t| t.get("name").unwrap().as_str().unwrap()).collect();
assert!(names.contains(&"devkit_session_save"));
assert!(names.contains(&"devkit_session_list"));
Expand Down
1 change: 1 addition & 0 deletions src/mcp/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@ mod tests {
let _ = super::session::DevkitSessionActivateTool;
let _ = super::session::DevkitSessionSearchTool;
let _ = super::session::DevkitSessionCaptureTool;
let _ = super::session::DevkitSessionWorkflowsTool;
}
}
65 changes: 65 additions & 0 deletions src/mcp/tools/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,70 @@ This is a lightweight append-only operation. No validation is performed on conte
}
}

#[derive(Clone)]
pub struct DevkitSessionWorkflowsTool;

impl McpTool for DevkitSessionWorkflowsTool {
fn name(&self) -> &'static str {
"devkit_session_workflows"
}

fn schema(&self) -> serde_json::Value {
json!({
"description": r#"List workflow executions associated with an agent session.

Use this when the user wants to:
- Review what automated workflows were run in a project context
- Audit the execution history of a session
- Check workflow status for a specific project"#,
"inputSchema": {
"type": "object",
"properties": {
"context_id": { "type": "string", "description": "Session ID" },
"limit": { "type": "integer", "description": "Maximum results", "default": 20 }
},
"required": ["context_id"]
}
})
}

async fn invoke(
&self,
args: serde_json::Value,
ctx: &mut AppContext,
) -> anyhow::Result<serde_json::Value> {
let context_id = args.get("context_id").and_then(|v| v.as_str()).unwrap_or("");
let limit = args.get("limit").and_then(|v| v.as_i64()).unwrap_or(20);
if context_id.is_empty() {
anyhow::bail!("Missing required argument: context_id");
}

let conn = ctx.conn()?;
let executions =
crate::workflow::state::list_executions_by_context(&conn, context_id, limit)?;
let results: Vec<serde_json::Value> = executions
.into_iter()
.map(|(id, wf_id, status, current_step, started_at, duration_ms)| {
json!({
"execution_id": id,
"workflow_id": wf_id,
"status": status,
"current_step": current_step,
"started_at": started_at,
"duration_ms": duration_ms,
})
})
.collect();

Ok(json!({
"success": true,
"context_id": context_id,
"count": results.len(),
"executions": results,
}))
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -606,6 +670,7 @@ mod tests {
assert_eq!(DevkitSessionActivateTool.name(), "devkit_session_activate");
assert_eq!(DevkitSessionSearchTool.name(), "devkit_session_search");
assert_eq!(DevkitSessionCaptureTool.name(), "devkit_session_capture");
assert_eq!(DevkitSessionWorkflowsTool.name(), "devkit_session_workflows");
}

#[test]
Expand Down
16 changes: 16 additions & 0 deletions src/registry/agent_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,22 @@ fn log_op(
);
}

/// Resolve the active agent context ID from environment or workspace state file.
pub fn resolve_active_context() -> Option<String> {
if let Ok(ctx) = std::env::var("DEVBASE_ACTIVE_CONTEXT")
&& !ctx.is_empty()
{
return Some(ctx);
}
let state_file = crate::registry::WorkspaceRegistry::workspace_dir()
.ok()?
.join(".active_context");
std::fs::read_to_string(state_file)
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/registry/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::*;
use crate::storage::StorageBackend;
use std::path::PathBuf;

pub const CURRENT_SCHEMA_VERSION: i32 = 32;
pub const CURRENT_SCHEMA_VERSION: i32 = 33;

impl WorkspaceRegistry {
pub fn db_path() -> anyhow::Result<PathBuf> {
Expand Down
4 changes: 4 additions & 0 deletions src/registry/migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub mod v29_compensation_log;
pub mod v30_code_symbol_attributes;
pub mod v31_agent_contexts;
pub mod v32_context_links;
pub mod v33_workflow_context;

pub fn run_all(conn: &mut Connection) -> anyhow::Result<()> {
let user_version: i32 = conn.query_row("PRAGMA user_version", [], |row| row.get(0))?;
Expand Down Expand Up @@ -134,6 +135,9 @@ pub fn run_all(conn: &mut Connection) -> anyhow::Result<()> {
if user_version < 32 {
v32_context_links::run(conn)?;
}
if user_version < 33 {
v33_workflow_context::run(conn)?;
}

Ok(())
}
20 changes: 20 additions & 0 deletions src/registry/migrations/v33_workflow_context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
use rusqlite::Connection;

pub fn run(conn: &Connection) -> anyhow::Result<()> {
let cols: Vec<String> = {
let mut stmt = conn.prepare("PRAGMA table_info(workflow_executions)")?;
let rows = stmt.query_map([], |row| row.get::<_, String>(1))?;
rows.filter_map(Result::ok).collect()
};
if !cols.iter().any(|c| c == "context_id") {
conn.execute("ALTER TABLE workflow_executions ADD COLUMN context_id TEXT", [])?;
}
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_workflow_execs_context ON workflow_executions(context_id)",
[],
)?;
conn.execute("PRAGMA user_version = 33", [])?;
Ok(())
}
3 changes: 2 additions & 1 deletion src/registry/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ CREATE TABLE IF NOT EXISTS workflow_executions (
current_step TEXT,
started_at TEXT NOT NULL,
finished_at TEXT,
duration_ms INTEGER
duration_ms INTEGER,
context_id TEXT
);

-- v18: Known Limits (L3 risk layer)
Expand Down
18 changes: 1 addition & 17 deletions src/skill_runtime/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub fn run_skill(
cmd.env("DEVBASE_HOME", devbase_home()?);

// P2-B: Inject active session context memories if available
if let Some(ctx_id) = resolve_active_context() {
if let Some(ctx_id) = crate::registry::agent_context::resolve_active_context() {
if let Ok(memories) = crate::registry::agent_context::list_memories(conn, &ctx_id)
&& !memories.is_empty()
{
Expand Down Expand Up @@ -254,22 +254,6 @@ pub(crate) fn check_hard_vetoes_for_skill(
))
}

/// Resolve the active agent context ID from environment or state file.
fn resolve_active_context() -> Option<String> {
if let Ok(ctx) = std::env::var("DEVBASE_ACTIVE_CONTEXT")
&& !ctx.is_empty()
{
return Some(ctx);
}
let state_file = crate::registry::WorkspaceRegistry::workspace_dir()
.ok()?
.join(".active_context");
std::fs::read_to_string(state_file)
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}

fn resolve_interpreter(path: &std::path::Path) -> (Option<String>, String) {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
let path_str = path.to_string_lossy().to_string();
Expand Down
4 changes: 3 additions & 1 deletion src/workflow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ impl crate::clients::WorkflowClient for AppContext {
};

let inputs_json = inputs.to_string();
let exec_id = state::create_execution(&conn, workflow_id, &inputs_json)?;
let active_ctx = crate::registry::agent_context::resolve_active_context();
let exec_id =
state::create_execution(&conn, workflow_id, &inputs_json, active_ctx.as_deref())?;
state::update_execution(&conn, exec_id, &model::ExecutionStatus::Running, None, None)?;

let pool = self.pool();
Expand Down
38 changes: 33 additions & 5 deletions src/workflow/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,44 @@ pub fn create_execution(
conn: &Connection,
workflow_id: &str,
inputs_json: &str,
context_id: Option<&str>,
) -> anyhow::Result<i64> {
let now = chrono::Utc::now().to_rfc3339();
conn.execute(
"INSERT INTO workflow_executions (workflow_id, inputs_json, status, current_step, started_at)
VALUES (?1, ?2, 'Pending', NULL, ?3)",
params![workflow_id, inputs_json, now],
"INSERT INTO workflow_executions (workflow_id, inputs_json, status, current_step, started_at, context_id)
VALUES (?1, ?2, 'Pending', NULL, ?3, ?4)",
params![workflow_id, inputs_json, now, context_id],
)?;
Ok(conn.last_insert_rowid())
}

/// List workflow executions bound to a session context.
#[allow(clippy::type_complexity)]
pub fn list_executions_by_context(
conn: &Connection,
context_id: &str,
limit: i64,
) -> anyhow::Result<Vec<(i64, String, String, Option<String>, String, Option<i64>)>> {
let mut stmt = conn.prepare(
"SELECT id, workflow_id, status, current_step, started_at, duration_ms
FROM workflow_executions
WHERE context_id = ?1
ORDER BY started_at DESC
LIMIT ?2",
)?;
let rows = stmt.query_map(params![context_id, limit], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, Option<String>>(3)?,
row.get::<_, String>(4)?,
row.get::<_, Option<i64>>(5)?,
))
})?;
rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
}

pub fn update_execution(
conn: &Connection,
exec_id: i64,
Expand Down Expand Up @@ -162,7 +190,7 @@ mod tests {
let conn = WorkspaceRegistry::init_in_memory().unwrap();
let wf = dummy_wf();
save_workflow(&conn, &wf).unwrap();
let exec_id = create_execution(&conn, "test-wf", r#"{"repo_path":"/tmp"}"#).unwrap();
let exec_id = create_execution(&conn, "test-wf", r#"{"repo_path":"/tmp"}"#, None).unwrap();
assert!(exec_id > 0);
update_execution(&conn, exec_id, &ExecutionStatus::Running, Some("step1"), None).unwrap();
let exec = get_execution(&conn, exec_id).unwrap().unwrap();
Expand Down Expand Up @@ -204,7 +232,7 @@ mod tests {
validate_workflow(&wf).unwrap();
save_workflow(&conn, &wf).unwrap();

let exec_id = create_execution(&conn, "e2e-wf", "{}").unwrap();
let exec_id = create_execution(&conn, "e2e-wf", "{}", None).unwrap();
update_execution(&conn, exec_id, &ExecutionStatus::Running, None, None).unwrap();

// Execution should fail because skill does not exist
Expand Down
Loading