handoff-mcp-server 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -4
- package/bin/handoff-mcp.js +56 -13
- package/bin/resolve-binary.js +122 -0
- package/package.json +14 -9
- package/Cargo.lock +0 -686
- package/Cargo.toml +0 -30
- package/scripts/cargo-env.sh +0 -29
- package/scripts/handoff-memory-hook.py +0 -208
- package/scripts/install-local.sh +0 -109
- package/scripts/postinstall.js +0 -50
- package/scripts/sync-plugin-skills.sh +0 -35
- package/scripts/sync-plugin-version.sh +0 -85
- package/scripts/sync-workflow-inline.sh +0 -138
- package/src/cli.rs +0 -551
- package/src/context/injection.rs +0 -276
- package/src/context/mod.rs +0 -129
- package/src/lib.rs +0 -5
- package/src/main.rs +0 -157
- package/src/mcp/handlers/assignees.rs +0 -254
- package/src/mcp/handlers/auto_schedule.rs +0 -489
- package/src/mcp/handlers/bulk_update.rs +0 -155
- package/src/mcp/handlers/calendar.rs +0 -196
- package/src/mcp/handlers/capacity.rs +0 -318
- package/src/mcp/handlers/check_criterion.rs +0 -70
- package/src/mcp/handlers/config.rs +0 -402
- package/src/mcp/handlers/config_crud.rs +0 -183
- package/src/mcp/handlers/dashboard.rs +0 -214
- package/src/mcp/handlers/docs.rs +0 -2288
- package/src/mcp/handlers/docs_query.rs +0 -1335
- package/src/mcp/handlers/fork_session.rs +0 -91
- package/src/mcp/handlers/get_session.rs +0 -48
- package/src/mcp/handlers/get_task.rs +0 -53
- package/src/mcp/handlers/import_context.rs +0 -470
- package/src/mcp/handlers/init.rs +0 -28
- package/src/mcp/handlers/list_sessions.rs +0 -187
- package/src/mcp/handlers/list_tasks.rs +0 -308
- package/src/mcp/handlers/load_context.rs +0 -361
- package/src/mcp/handlers/log_time.rs +0 -67
- package/src/mcp/handlers/memory.rs +0 -961
- package/src/mcp/handlers/merge_sessions.rs +0 -103
- package/src/mcp/handlers/metrics.rs +0 -196
- package/src/mcp/handlers/milestones.rs +0 -102
- package/src/mcp/handlers/mod.rs +0 -140
- package/src/mcp/handlers/refer.rs +0 -307
- package/src/mcp/handlers/referrals.rs +0 -74
- package/src/mcp/handlers/save_context.rs +0 -354
- package/src/mcp/handlers/task_checklist.rs +0 -507
- package/src/mcp/handlers/timer.rs +0 -529
- package/src/mcp/handlers/update_session.rs +0 -197
- package/src/mcp/handlers/update_task.rs +0 -452
- package/src/mcp/mod.rs +0 -6
- package/src/mcp/protocol.rs +0 -41
- package/src/mcp/resources.rs +0 -57
- package/src/mcp/router.rs +0 -154
- package/src/mcp/tools.rs +0 -1522
- package/src/mcp/types.rs +0 -108
- package/src/setup.rs +0 -1212
- package/src/storage/config.rs +0 -578
- package/src/storage/docs/frontmatter.rs +0 -509
- package/src/storage/docs/mod.rs +0 -835
- package/src/storage/docs/model.rs +0 -708
- package/src/storage/docs/reassemble.rs +0 -167
- package/src/storage/docs/split.rs +0 -377
- package/src/storage/git.rs +0 -47
- package/src/storage/memory/injected.rs +0 -340
- package/src/storage/memory/mod.rs +0 -236
- package/src/storage/memory/model.rs +0 -127
- package/src/storage/mod.rs +0 -96
- package/src/storage/referrals.rs +0 -248
- package/src/storage/sessions.rs +0 -859
- package/src/storage/tasks.rs +0 -957
- package/templates/claude-md-section.md +0 -12
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
use anyhow::Result;
|
|
2
|
-
use serde_json::{json, Value};
|
|
3
|
-
|
|
4
|
-
use super::resolve_project_dir;
|
|
5
|
-
use crate::storage::ensure_handoff_exists;
|
|
6
|
-
use crate::storage::sessions::merge_sessions;
|
|
7
|
-
|
|
8
|
-
pub fn handle(arguments: &Value) -> Result<String> {
|
|
9
|
-
let project_dir = resolve_project_dir(arguments)?;
|
|
10
|
-
let handoff = ensure_handoff_exists(&project_dir)?;
|
|
11
|
-
let sessions_dir = handoff.join("sessions");
|
|
12
|
-
|
|
13
|
-
let source_session_ids: Vec<&str> = arguments
|
|
14
|
-
.get("source_session_ids")
|
|
15
|
-
.and_then(|v| v.as_array())
|
|
16
|
-
.ok_or_else(|| anyhow::anyhow!("'source_session_ids' parameter is required"))?
|
|
17
|
-
.iter()
|
|
18
|
-
.filter_map(|v| v.as_str())
|
|
19
|
-
.collect();
|
|
20
|
-
|
|
21
|
-
if source_session_ids.len() < 2 {
|
|
22
|
-
anyhow::bail!("'source_session_ids' must contain at least 2 session IDs");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let target_session_id = arguments
|
|
26
|
-
.get("target_session_id")
|
|
27
|
-
.and_then(|v| v.as_str())
|
|
28
|
-
.ok_or_else(|| anyhow::anyhow!("'target_session_id' parameter is required"))?;
|
|
29
|
-
|
|
30
|
-
if !source_session_ids.contains(&target_session_id) {
|
|
31
|
-
anyhow::bail!(
|
|
32
|
-
"target_session_id '{}' must be one of the source_session_ids",
|
|
33
|
-
target_session_id
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
let close_sources = arguments
|
|
38
|
-
.get("close_sources")
|
|
39
|
-
.and_then(|v| v.as_bool())
|
|
40
|
-
.unwrap_or(true);
|
|
41
|
-
|
|
42
|
-
let result = merge_sessions(
|
|
43
|
-
&sessions_dir,
|
|
44
|
-
&source_session_ids,
|
|
45
|
-
target_session_id,
|
|
46
|
-
close_sources,
|
|
47
|
-
)?;
|
|
48
|
-
|
|
49
|
-
let conflicts_json: Vec<Value> = result
|
|
50
|
-
.conflicts
|
|
51
|
-
.iter()
|
|
52
|
-
.map(|c| {
|
|
53
|
-
json!({
|
|
54
|
-
"type": c.conflict_type,
|
|
55
|
-
"description": c.description,
|
|
56
|
-
"session_a": c.session_a,
|
|
57
|
-
"session_b": c.session_b,
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
.collect();
|
|
61
|
-
|
|
62
|
-
let output_json = json!({
|
|
63
|
-
"merged_session_id": result.merged_session_id,
|
|
64
|
-
"merged_decisions": result.merged_decisions,
|
|
65
|
-
"merged_notes": result.merged_notes,
|
|
66
|
-
"merged_references": result.merged_references,
|
|
67
|
-
"merged_context_pointers": result.merged_context_pointers,
|
|
68
|
-
"conflicts": conflicts_json,
|
|
69
|
-
"closed_sessions": result.closed_sessions,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
let mut output = format!(
|
|
73
|
-
"Merged {} sessions into {}\n\
|
|
74
|
-
Merged: {} decisions, {} notes, {} references, {} context_pointers",
|
|
75
|
-
source_session_ids.len(),
|
|
76
|
-
result.merged_session_id,
|
|
77
|
-
result.merged_decisions,
|
|
78
|
-
result.merged_notes,
|
|
79
|
-
result.merged_references,
|
|
80
|
-
result.merged_context_pointers,
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
if !result.conflicts.is_empty() {
|
|
84
|
-
output.push_str(&format!("\nConflicts: {}", result.conflicts.len()));
|
|
85
|
-
for c in &result.conflicts {
|
|
86
|
-
output.push_str(&format!("\n - {}: {}", c.conflict_type, c.description));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if !result.closed_sessions.is_empty() {
|
|
91
|
-
output.push_str(&format!(
|
|
92
|
-
"\nClosed source sessions: {}",
|
|
93
|
-
result.closed_sessions.join(", ")
|
|
94
|
-
));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
output.push_str(&format!(
|
|
98
|
-
"\n\n{}",
|
|
99
|
-
serde_json::to_string_pretty(&output_json)?
|
|
100
|
-
));
|
|
101
|
-
|
|
102
|
-
Ok(output)
|
|
103
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
use std::collections::HashMap;
|
|
2
|
-
|
|
3
|
-
use anyhow::Result;
|
|
4
|
-
use chrono::Utc;
|
|
5
|
-
use serde_json::{json, Value};
|
|
6
|
-
|
|
7
|
-
use super::resolve_project_dir;
|
|
8
|
-
use crate::storage::config::read_config;
|
|
9
|
-
use crate::storage::ensure_handoff_exists;
|
|
10
|
-
use crate::storage::tasks::{build_task_index, is_terminal_status, TaskIndex};
|
|
11
|
-
|
|
12
|
-
pub fn handle(arguments: &Value) -> Result<String> {
|
|
13
|
-
let project_dir = resolve_project_dir(arguments)?;
|
|
14
|
-
let handoff = ensure_handoff_exists(&project_dir)?;
|
|
15
|
-
let tasks_dir = handoff.join("tasks");
|
|
16
|
-
|
|
17
|
-
let assignee_filter = arguments.get("assignee").and_then(|v| v.as_str());
|
|
18
|
-
|
|
19
|
-
let (tree, _) = build_task_index(&tasks_dir, u32::MAX)?;
|
|
20
|
-
|
|
21
|
-
let today = Utc::now().format("%Y-%m-%d").to_string();
|
|
22
|
-
|
|
23
|
-
let mut total: u32 = 0;
|
|
24
|
-
let mut by_status: HashMap<String, u32> = HashMap::new();
|
|
25
|
-
let mut estimate_sum: f64 = 0.0;
|
|
26
|
-
let mut actual_sum: f64 = 0.0;
|
|
27
|
-
let mut remaining_sum: f64 = 0.0;
|
|
28
|
-
let mut overdue_tasks: Vec<Value> = Vec::new();
|
|
29
|
-
let mut milestones: HashMap<String, (u32, u32, f64, f64)> = HashMap::new();
|
|
30
|
-
|
|
31
|
-
collect_metrics(
|
|
32
|
-
&tree,
|
|
33
|
-
assignee_filter,
|
|
34
|
-
&today,
|
|
35
|
-
&mut total,
|
|
36
|
-
&mut by_status,
|
|
37
|
-
&mut estimate_sum,
|
|
38
|
-
&mut actual_sum,
|
|
39
|
-
&mut remaining_sum,
|
|
40
|
-
&mut overdue_tasks,
|
|
41
|
-
&mut milestones,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
let done_count = *by_status.get("done").unwrap_or(&0);
|
|
45
|
-
let skipped_count = *by_status.get("skipped").unwrap_or(&0);
|
|
46
|
-
let completion_percent = if total > 0 {
|
|
47
|
-
((done_count + skipped_count) as f64 / total as f64) * 100.0
|
|
48
|
-
} else {
|
|
49
|
-
0.0
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
let budget = read_budget(&handoff);
|
|
53
|
-
|
|
54
|
-
// AI-effort multiplier: the raw estimate is the human-effort estimate;
|
|
55
|
-
// multiplying by ai_estimate_multiplier yields the expected AI-effort hours.
|
|
56
|
-
// Raw estimates are preserved; only the adjusted view is derived here.
|
|
57
|
-
let multiplier = read_config(&handoff.join("config.toml"))
|
|
58
|
-
.map(|c| c.settings.ai_estimate_multiplier)
|
|
59
|
-
.unwrap_or(0.2);
|
|
60
|
-
|
|
61
|
-
let milestone_list: Vec<Value> = milestones
|
|
62
|
-
.into_iter()
|
|
63
|
-
.map(|(name, (done, ms_total, est, act))| {
|
|
64
|
-
json!({
|
|
65
|
-
"name": name,
|
|
66
|
-
"done": done,
|
|
67
|
-
"total": ms_total,
|
|
68
|
-
"estimate_hours": est,
|
|
69
|
-
"adjusted_estimate_hours": est * multiplier,
|
|
70
|
-
"actual_hours": act,
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
.collect();
|
|
74
|
-
|
|
75
|
-
let result = json!({
|
|
76
|
-
"total": total,
|
|
77
|
-
"by_status": by_status,
|
|
78
|
-
"completion_percent": (completion_percent * 10.0).round() / 10.0,
|
|
79
|
-
"total_estimate_hours": estimate_sum,
|
|
80
|
-
"ai_estimate_multiplier": multiplier,
|
|
81
|
-
"total_adjusted_estimate_hours": estimate_sum * multiplier,
|
|
82
|
-
"total_actual_hours": actual_sum,
|
|
83
|
-
"total_remaining_hours": remaining_sum,
|
|
84
|
-
"overdue_count": overdue_tasks.len(),
|
|
85
|
-
"overdue_tasks": overdue_tasks,
|
|
86
|
-
"budget": budget,
|
|
87
|
-
"milestones": milestone_list,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
serde_json::to_string_pretty(&result).map_err(Into::into)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
#[allow(clippy::too_many_arguments)]
|
|
94
|
-
fn collect_metrics(
|
|
95
|
-
tree: &[TaskIndex],
|
|
96
|
-
assignee_filter: Option<&str>,
|
|
97
|
-
today: &str,
|
|
98
|
-
total: &mut u32,
|
|
99
|
-
by_status: &mut HashMap<String, u32>,
|
|
100
|
-
estimate_sum: &mut f64,
|
|
101
|
-
actual_sum: &mut f64,
|
|
102
|
-
remaining_sum: &mut f64,
|
|
103
|
-
overdue_tasks: &mut Vec<Value>,
|
|
104
|
-
milestones: &mut HashMap<String, (u32, u32, f64, f64)>,
|
|
105
|
-
) {
|
|
106
|
-
for node in tree {
|
|
107
|
-
let task_assignee = node.assignee.as_deref();
|
|
108
|
-
let include = match assignee_filter {
|
|
109
|
-
Some(f) => task_assignee == Some(f),
|
|
110
|
-
None => true,
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
if include {
|
|
114
|
-
*total += 1;
|
|
115
|
-
*by_status.entry(node.status.clone()).or_insert(0) += 1;
|
|
116
|
-
|
|
117
|
-
if let Some(ref sched) = node.schedule {
|
|
118
|
-
if let Some(est) = sched.estimate_hours {
|
|
119
|
-
*estimate_sum += est;
|
|
120
|
-
}
|
|
121
|
-
if let Some(act) = sched.actual_hours {
|
|
122
|
-
*actual_sum += act;
|
|
123
|
-
}
|
|
124
|
-
if let Some(rem) = sched.remaining_hours {
|
|
125
|
-
*remaining_sum += rem;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if let Some(ref due) = sched.due_date {
|
|
129
|
-
if !is_terminal_status(&node.status) && due.as_str() < today {
|
|
130
|
-
let days_overdue = days_between(due, today).unwrap_or(0);
|
|
131
|
-
overdue_tasks.push(json!({
|
|
132
|
-
"id": node.id,
|
|
133
|
-
"title": node.title,
|
|
134
|
-
"due_date": due,
|
|
135
|
-
"days_overdue": days_overdue,
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if let Some(ref ms) = sched.milestone {
|
|
141
|
-
let entry = milestones.entry(ms.clone()).or_insert((0, 0, 0.0, 0.0));
|
|
142
|
-
entry.1 += 1;
|
|
143
|
-
if is_terminal_status(&node.status) {
|
|
144
|
-
entry.0 += 1;
|
|
145
|
-
}
|
|
146
|
-
if let Some(est) = sched.estimate_hours {
|
|
147
|
-
entry.2 += est;
|
|
148
|
-
}
|
|
149
|
-
if let Some(act) = sched.actual_hours {
|
|
150
|
-
entry.3 += act;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
collect_metrics(
|
|
157
|
-
&node.children,
|
|
158
|
-
assignee_filter,
|
|
159
|
-
today,
|
|
160
|
-
total,
|
|
161
|
-
by_status,
|
|
162
|
-
estimate_sum,
|
|
163
|
-
actual_sum,
|
|
164
|
-
remaining_sum,
|
|
165
|
-
overdue_tasks,
|
|
166
|
-
milestones,
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
fn days_between(from: &str, to: &str) -> Option<i64> {
|
|
172
|
-
let from_date = chrono::NaiveDate::parse_from_str(from, "%Y-%m-%d").ok()?;
|
|
173
|
-
let to_date = chrono::NaiveDate::parse_from_str(to, "%Y-%m-%d").ok()?;
|
|
174
|
-
Some((to_date - from_date).num_days())
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
fn read_budget(handoff: &std::path::Path) -> Value {
|
|
178
|
-
let config_path = handoff.join("config.toml");
|
|
179
|
-
let content = match std::fs::read_to_string(&config_path) {
|
|
180
|
-
Ok(c) => c,
|
|
181
|
-
Err(_) => return Value::Null,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
for line in content.lines() {
|
|
185
|
-
let trimmed = line.trim();
|
|
186
|
-
if trimmed.starts_with("total_hours") {
|
|
187
|
-
if let Some(val_str) = trimmed.split('=').nth(1) {
|
|
188
|
-
if let Ok(total) = val_str.trim().parse::<f64>() {
|
|
189
|
-
return json!({ "total_hours": total });
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
Value::Null
|
|
196
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
//! Milestone CRUD over the `[milestones.<name>]` config.toml section.
|
|
2
|
-
//! Mirrors the VSCode extension's addMilestone/updateMilestone/removeMilestone.
|
|
3
|
-
|
|
4
|
-
use anyhow::Result;
|
|
5
|
-
use serde_json::{json, Value};
|
|
6
|
-
use toml_edit::{Item, Table};
|
|
7
|
-
|
|
8
|
-
use super::config_crud::{
|
|
9
|
-
config_path, ensure_subtable, ensure_table, load_doc, require_str, save_doc, set_opt_str,
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/// handoff_list_milestones — return every `[milestones.*]` entry.
|
|
13
|
-
pub fn handle_list(arguments: &Value) -> Result<String> {
|
|
14
|
-
let path = config_path(arguments)?;
|
|
15
|
-
let doc = load_doc(&path)?;
|
|
16
|
-
|
|
17
|
-
let mut out = serde_json::Map::new();
|
|
18
|
-
if let Some(table) = doc.get("milestones").and_then(|v| v.as_table()) {
|
|
19
|
-
for (name, item) in table.iter() {
|
|
20
|
-
let sub = match item.as_table() {
|
|
21
|
-
Some(t) => t,
|
|
22
|
-
None => continue,
|
|
23
|
-
};
|
|
24
|
-
out.insert(
|
|
25
|
-
name.to_string(),
|
|
26
|
-
json!({
|
|
27
|
-
"date": sub.get("date").and_then(|v| v.as_str()),
|
|
28
|
-
"color": sub.get("color").and_then(|v| v.as_str()),
|
|
29
|
-
"description": sub.get("description").and_then(|v| v.as_str()),
|
|
30
|
-
}),
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
serde_json::to_string_pretty(&json!({ "milestones": out })).map_err(Into::into)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/// handoff_add_milestone — create a `[milestones.<name>]` entry. Fails if it exists.
|
|
38
|
-
pub fn handle_add(arguments: &Value) -> Result<String> {
|
|
39
|
-
let path = config_path(arguments)?;
|
|
40
|
-
let name = require_str(arguments, "name")?;
|
|
41
|
-
let mut doc = load_doc(&path)?;
|
|
42
|
-
|
|
43
|
-
let table = ensure_table(&mut doc, "milestones")?;
|
|
44
|
-
table.set_implicit(true);
|
|
45
|
-
if table.contains_key(name) {
|
|
46
|
-
anyhow::bail!(
|
|
47
|
-
"Milestone '{name}' already exists. Use handoff_update_milestone to modify it."
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
let mut sub = Table::new();
|
|
51
|
-
apply_fields(&mut sub, arguments);
|
|
52
|
-
doc["milestones"][name] = Item::Table(sub);
|
|
53
|
-
|
|
54
|
-
save_doc(&path, &doc)?;
|
|
55
|
-
Ok(format!("Added milestone '{name}'"))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/// handoff_update_milestone — patch an existing `[milestones.<name>]` entry.
|
|
59
|
-
pub fn handle_update(arguments: &Value) -> Result<String> {
|
|
60
|
-
let path = config_path(arguments)?;
|
|
61
|
-
let name = require_str(arguments, "name")?;
|
|
62
|
-
let mut doc = load_doc(&path)?;
|
|
63
|
-
|
|
64
|
-
let exists = doc
|
|
65
|
-
.get("milestones")
|
|
66
|
-
.and_then(|v| v.as_table())
|
|
67
|
-
.map(|t| t.contains_key(name))
|
|
68
|
-
.unwrap_or(false);
|
|
69
|
-
if !exists {
|
|
70
|
-
anyhow::bail!("Milestone '{name}' not found. Use handoff_add_milestone to create it.");
|
|
71
|
-
}
|
|
72
|
-
let sub = ensure_subtable(&mut doc, "milestones", name)?;
|
|
73
|
-
apply_fields(sub, arguments);
|
|
74
|
-
|
|
75
|
-
save_doc(&path, &doc)?;
|
|
76
|
-
Ok(format!("Updated milestone '{name}'"))
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/// handoff_remove_milestone — delete a `[milestones.<name>]` entry.
|
|
80
|
-
pub fn handle_remove(arguments: &Value) -> Result<String> {
|
|
81
|
-
let path = config_path(arguments)?;
|
|
82
|
-
let name = require_str(arguments, "name")?;
|
|
83
|
-
let mut doc = load_doc(&path)?;
|
|
84
|
-
|
|
85
|
-
let removed = doc
|
|
86
|
-
.get_mut("milestones")
|
|
87
|
-
.and_then(|v| v.as_table_mut())
|
|
88
|
-
.map(|t| t.remove(name).is_some())
|
|
89
|
-
.unwrap_or(false);
|
|
90
|
-
if !removed {
|
|
91
|
-
anyhow::bail!("Milestone '{name}' not found.");
|
|
92
|
-
}
|
|
93
|
-
save_doc(&path, &doc)?;
|
|
94
|
-
Ok(format!("Removed milestone '{name}'"))
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
fn apply_fields(table: &mut Table, arguments: &Value) {
|
|
98
|
-
table.set_implicit(false);
|
|
99
|
-
set_opt_str(table, "date", arguments.get("date"));
|
|
100
|
-
set_opt_str(table, "color", arguments.get("color"));
|
|
101
|
-
set_opt_str(table, "description", arguments.get("description"));
|
|
102
|
-
}
|
package/src/mcp/handlers/mod.rs
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
pub mod assignees;
|
|
2
|
-
pub mod auto_schedule;
|
|
3
|
-
pub mod bulk_update;
|
|
4
|
-
pub mod calendar;
|
|
5
|
-
pub mod capacity;
|
|
6
|
-
pub mod check_criterion;
|
|
7
|
-
pub mod config;
|
|
8
|
-
pub mod config_crud;
|
|
9
|
-
pub mod dashboard;
|
|
10
|
-
pub mod docs;
|
|
11
|
-
pub mod docs_query;
|
|
12
|
-
pub mod fork_session;
|
|
13
|
-
pub mod get_session;
|
|
14
|
-
pub mod get_task;
|
|
15
|
-
pub mod import_context;
|
|
16
|
-
pub mod init;
|
|
17
|
-
pub mod list_sessions;
|
|
18
|
-
pub mod list_tasks;
|
|
19
|
-
pub mod load_context;
|
|
20
|
-
pub mod log_time;
|
|
21
|
-
pub mod memory;
|
|
22
|
-
pub mod merge_sessions;
|
|
23
|
-
pub mod metrics;
|
|
24
|
-
pub mod milestones;
|
|
25
|
-
pub mod refer;
|
|
26
|
-
pub mod referrals;
|
|
27
|
-
pub mod save_context;
|
|
28
|
-
pub mod task_checklist;
|
|
29
|
-
pub mod timer;
|
|
30
|
-
pub mod update_session;
|
|
31
|
-
pub mod update_task;
|
|
32
|
-
|
|
33
|
-
use std::path::PathBuf;
|
|
34
|
-
|
|
35
|
-
use anyhow::{Context, Result};
|
|
36
|
-
use serde_json::Value;
|
|
37
|
-
|
|
38
|
-
use crate::mcp::types::JsonRpcResponse;
|
|
39
|
-
|
|
40
|
-
pub fn resolve_project_dir(arguments: &Value) -> Result<PathBuf> {
|
|
41
|
-
let raw = match arguments
|
|
42
|
-
.get("project_dir")
|
|
43
|
-
.and_then(|v| v.as_str())
|
|
44
|
-
.filter(|s| !s.is_empty() && !s.starts_with("${"))
|
|
45
|
-
{
|
|
46
|
-
Some(dir) => PathBuf::from(dir),
|
|
47
|
-
None => match std::env::var("CLAUDE_PROJECT_DIR") {
|
|
48
|
-
Ok(dir) if !dir.is_empty() => PathBuf::from(dir),
|
|
49
|
-
_ => std::env::current_dir().context("Failed to get current directory")?,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
std::fs::canonicalize(&raw)
|
|
53
|
-
.with_context(|| format!("Invalid project path: {raw}", raw = raw.display()))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
pub fn handle_tool_call(name: &str, arguments: &Value) -> JsonRpcResponse {
|
|
57
|
-
let result = match name {
|
|
58
|
-
"handoff_init" => init::handle(arguments),
|
|
59
|
-
"handoff_update_task" => update_task::handle(arguments),
|
|
60
|
-
"handoff_list_tasks" => list_tasks::handle(arguments),
|
|
61
|
-
"handoff_save_context" => save_context::handle(arguments),
|
|
62
|
-
"handoff_load_context" => load_context::handle(arguments),
|
|
63
|
-
"handoff_dashboard" => dashboard::handle(arguments),
|
|
64
|
-
"handoff_get_config" => config::handle_get(arguments),
|
|
65
|
-
"handoff_update_config" => config::handle_update(arguments),
|
|
66
|
-
"handoff_get_task" => get_task::handle(arguments),
|
|
67
|
-
"handoff_check_criterion" => check_criterion::handle(arguments),
|
|
68
|
-
"handoff_import_context" => import_context::handle(arguments),
|
|
69
|
-
"handoff_refer" => refer::handle(arguments),
|
|
70
|
-
"handoff_list_referrals" => referrals::handle_list(arguments),
|
|
71
|
-
"handoff_get_referral" => referrals::handle_get(arguments),
|
|
72
|
-
"handoff_update_referral" => referrals::handle_update(arguments),
|
|
73
|
-
"handoff_update_session" => update_session::handle(arguments),
|
|
74
|
-
"handoff_log_time" => log_time::handle(arguments),
|
|
75
|
-
"handoff_get_metrics" => metrics::handle(arguments),
|
|
76
|
-
"handoff_list_sessions" => list_sessions::handle(arguments),
|
|
77
|
-
"handoff_list_assignees" => assignees::handle(arguments),
|
|
78
|
-
"handoff_bulk_update_tasks" => bulk_update::handle(arguments),
|
|
79
|
-
"handoff_get_session" => get_session::handle(arguments),
|
|
80
|
-
"handoff_get_capacity" => capacity::handle(arguments),
|
|
81
|
-
"handoff_auto_schedule" => auto_schedule::handle(arguments),
|
|
82
|
-
"handoff_add_assignee" => assignees::handle_add(arguments),
|
|
83
|
-
"handoff_update_assignee" => assignees::handle_update(arguments),
|
|
84
|
-
"handoff_remove_assignee" => assignees::handle_remove(arguments),
|
|
85
|
-
"handoff_list_milestones" => milestones::handle_list(arguments),
|
|
86
|
-
"handoff_add_milestone" => milestones::handle_add(arguments),
|
|
87
|
-
"handoff_update_milestone" => milestones::handle_update(arguments),
|
|
88
|
-
"handoff_remove_milestone" => milestones::handle_remove(arguments),
|
|
89
|
-
"handoff_update_calendar" => calendar::handle_update_calendar(arguments),
|
|
90
|
-
"handoff_update_labels" => calendar::handle_update_labels(arguments),
|
|
91
|
-
"handoff_start_project" => calendar::handle_start_project(arguments),
|
|
92
|
-
"handoff_memory_save" => memory::handle_save(arguments),
|
|
93
|
-
"handoff_memory_query" => memory::handle_query(arguments),
|
|
94
|
-
"handoff_memory_delete" => memory::handle_delete(arguments),
|
|
95
|
-
"handoff_memory_cleanup" => memory::handle_cleanup(arguments),
|
|
96
|
-
"handoff_fork_session" => fork_session::handle(arguments),
|
|
97
|
-
"handoff_merge_sessions" => merge_sessions::handle(arguments),
|
|
98
|
-
"handoff_timer_start" => timer::handle_start(arguments),
|
|
99
|
-
"handoff_timer_stop" => timer::handle_stop(arguments),
|
|
100
|
-
"handoff_timer_get_time" => timer::handle_get_time(arguments),
|
|
101
|
-
"handoff_doc_save" => docs::handle_doc_save(arguments),
|
|
102
|
-
"handoff_doc_update_section" => docs::handle_doc_update_section(arguments),
|
|
103
|
-
"handoff_doc_get" => docs::handle_doc_get(arguments),
|
|
104
|
-
"handoff_doc_list" => docs::handle_doc_list(arguments),
|
|
105
|
-
"handoff_doc_delete" => docs::handle_doc_delete(arguments),
|
|
106
|
-
"handoff_doc_reassemble" => docs::handle_doc_reassemble(arguments),
|
|
107
|
-
"handoff_doc_tree" => docs::handle_doc_tree(arguments),
|
|
108
|
-
"handoff_doc_graph" => docs::handle_doc_graph(arguments),
|
|
109
|
-
"handoff_doc_trace" => docs::handle_doc_trace(arguments),
|
|
110
|
-
"handoff_doc_verify" => docs::handle_doc_verify(arguments),
|
|
111
|
-
"handoff_doc_verify_status" => docs::handle_doc_verify_status(arguments),
|
|
112
|
-
"handoff_doc_query" => docs_query::handle_doc_query(arguments),
|
|
113
|
-
"handoff_doc_analyze" => docs_query::handle_doc_analyze(arguments),
|
|
114
|
-
"handoff_doc_import" => docs_query::handle_doc_import(arguments),
|
|
115
|
-
"handoff_task_checklist" => task_checklist::handle(arguments),
|
|
116
|
-
_ => Err(anyhow::anyhow!("Tool not implemented: {name}")),
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
match result {
|
|
120
|
-
Ok(content) => {
|
|
121
|
-
let tool_result = serde_json::json!({
|
|
122
|
-
"content": [{
|
|
123
|
-
"type": "text",
|
|
124
|
-
"text": content
|
|
125
|
-
}]
|
|
126
|
-
});
|
|
127
|
-
JsonRpcResponse::success(None, tool_result)
|
|
128
|
-
}
|
|
129
|
-
Err(e) => {
|
|
130
|
-
let tool_result = serde_json::json!({
|
|
131
|
-
"isError": true,
|
|
132
|
-
"content": [{
|
|
133
|
-
"type": "text",
|
|
134
|
-
"text": format!("Error: {e}")
|
|
135
|
-
}]
|
|
136
|
-
});
|
|
137
|
-
JsonRpcResponse::success(None, tool_result)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|