handoff-mcp-server 0.26.0 → 0.27.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,214 +0,0 @@
|
|
|
1
|
-
use std::path::{Path, PathBuf};
|
|
2
|
-
|
|
3
|
-
use anyhow::{Context, Result};
|
|
4
|
-
use serde_json::Value;
|
|
5
|
-
|
|
6
|
-
use crate::storage::config::{read_config, DashboardConfig};
|
|
7
|
-
use crate::storage::expand_tilde;
|
|
8
|
-
use crate::storage::referrals::read_referral_summaries;
|
|
9
|
-
use crate::storage::sessions::{read_active_sessions, read_open_sessions, read_paused_sessions};
|
|
10
|
-
use crate::storage::tasks::build_task_index;
|
|
11
|
-
|
|
12
|
-
pub fn handle(arguments: &Value) -> Result<String> {
|
|
13
|
-
let scan_dirs: Vec<String> = arguments
|
|
14
|
-
.get("scan_dirs")
|
|
15
|
-
.and_then(|v| v.as_array())
|
|
16
|
-
.map(|arr| {
|
|
17
|
-
arr.iter()
|
|
18
|
-
.filter_map(|v| v.as_str().map(String::from))
|
|
19
|
-
.collect()
|
|
20
|
-
})
|
|
21
|
-
.unwrap_or_else(|| vec!["~/pro/".to_string()]);
|
|
22
|
-
|
|
23
|
-
let mut projects = Vec::new();
|
|
24
|
-
let mut total_active = 0u32;
|
|
25
|
-
let mut total_blocked = 0u32;
|
|
26
|
-
|
|
27
|
-
for scan_dir in &scan_dirs {
|
|
28
|
-
let expanded = expand_tilde(scan_dir);
|
|
29
|
-
let expanded_path = Path::new(&expanded);
|
|
30
|
-
|
|
31
|
-
if !expanded_path.exists() {
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let (max_depth, exclude_patterns) = resolve_scan_config(expanded_path, arguments);
|
|
36
|
-
|
|
37
|
-
let mut discovered = Vec::new();
|
|
38
|
-
scan_recursive(
|
|
39
|
-
expanded_path,
|
|
40
|
-
1,
|
|
41
|
-
max_depth,
|
|
42
|
-
&exclude_patterns,
|
|
43
|
-
&mut discovered,
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
for project_path in discovered {
|
|
47
|
-
if let Ok(info) = collect_project_info(&project_path) {
|
|
48
|
-
total_active += info["active_tasks"].as_u64().unwrap_or(0) as u32;
|
|
49
|
-
total_blocked += info["blocked_tasks"].as_u64().unwrap_or(0) as u32;
|
|
50
|
-
projects.push(info);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let result = serde_json::json!({
|
|
56
|
-
"projects": projects,
|
|
57
|
-
"total_active_tasks": total_active,
|
|
58
|
-
"total_blocked": total_blocked,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
serde_json::to_string_pretty(&result).context("Failed to serialize dashboard")
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/// Resolves effective `max_depth` / `exclude_patterns` for a single scan_dir.
|
|
65
|
-
///
|
|
66
|
-
/// Precedence: explicit tool `arguments` override (applies uniformly across
|
|
67
|
-
/// all scan_dirs, since it's an explicit user choice), then this scan_dir's
|
|
68
|
-
/// own `.handoff/config.toml` (if present), then — in the common umbrella-
|
|
69
|
-
/// workspace topology where the scan_dir itself is not a handoff project (e.g.
|
|
70
|
-
/// `~/pro/`) — the first *discovered child* project's config within this same
|
|
71
|
-
/// scan_dir's subtree, then built-in defaults.
|
|
72
|
-
///
|
|
73
|
-
/// Scoped to a single `expanded_path` so config discovered under one scan_dir
|
|
74
|
-
/// never leaks into sibling scan_dirs in a multi-root dashboard call.
|
|
75
|
-
fn resolve_scan_config(expanded_path: &Path, arguments: &Value) -> (usize, Vec<String>) {
|
|
76
|
-
let mut defaults = DashboardConfig::default();
|
|
77
|
-
|
|
78
|
-
let own_config_path = expanded_path.join(".handoff").join("config.toml");
|
|
79
|
-
if let Ok(config) = read_config(&own_config_path) {
|
|
80
|
-
defaults = config.dashboard;
|
|
81
|
-
} else {
|
|
82
|
-
// scan_dir itself has no config of its own (typical umbrella-workspace
|
|
83
|
-
// case) — do a discovery pass scoped to this scan_dir's own subtree and
|
|
84
|
-
// look for a child project whose own dashboard config overrides the
|
|
85
|
-
// built-in default, so per-project settings still take effect without
|
|
86
|
-
// requiring an explicit tool argument. Discovery order is filesystem-
|
|
87
|
-
// dependent, so sort child paths for deterministic selection.
|
|
88
|
-
//
|
|
89
|
-
// Probe depth is capped at the caller's explicit max_depth argument
|
|
90
|
-
// (if given) so a shallow-depth request doesn't still pay for a full
|
|
91
|
-
// default-depth (5) filesystem walk just to look for fallback config.
|
|
92
|
-
let probe_depth = arguments
|
|
93
|
-
.get("max_depth")
|
|
94
|
-
.and_then(|v| v.as_u64())
|
|
95
|
-
.map(|n| n as usize)
|
|
96
|
-
.unwrap_or_else(|| DashboardConfig::default().max_depth)
|
|
97
|
-
.min(DashboardConfig::default().max_depth);
|
|
98
|
-
let mut discovered = Vec::new();
|
|
99
|
-
scan_recursive(expanded_path, 1, probe_depth, &[], &mut discovered);
|
|
100
|
-
discovered.sort();
|
|
101
|
-
for child_path in discovered {
|
|
102
|
-
let child_config_path = child_path.join(".handoff").join("config.toml");
|
|
103
|
-
if let Ok(config) = read_config(&child_config_path) {
|
|
104
|
-
if config.dashboard.max_depth != DashboardConfig::default().max_depth
|
|
105
|
-
|| !config.dashboard.exclude_patterns.is_empty()
|
|
106
|
-
{
|
|
107
|
-
defaults = config.dashboard;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let max_depth = arguments
|
|
115
|
-
.get("max_depth")
|
|
116
|
-
.and_then(|v| v.as_u64())
|
|
117
|
-
.map(|n| n as usize)
|
|
118
|
-
.unwrap_or(defaults.max_depth);
|
|
119
|
-
|
|
120
|
-
let exclude_patterns = arguments
|
|
121
|
-
.get("exclude_patterns")
|
|
122
|
-
.and_then(|v| v.as_array())
|
|
123
|
-
.map(|arr| {
|
|
124
|
-
arr.iter()
|
|
125
|
-
.filter_map(|v| v.as_str().map(String::from))
|
|
126
|
-
.collect()
|
|
127
|
-
})
|
|
128
|
-
.unwrap_or(defaults.exclude_patterns);
|
|
129
|
-
|
|
130
|
-
(max_depth, exclude_patterns)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/// Recursively scans `dir` up to `max_depth` levels for `.handoff/config.toml`
|
|
134
|
-
/// markers, skipping directories whose name exactly matches an entry in
|
|
135
|
-
/// `exclude_patterns`. Never descends into a directory literally named
|
|
136
|
-
/// `.handoff` — a project's own bookkeeping tree (tasks/sessions/memory/etc.)
|
|
137
|
-
/// can never contain a nested project marker, so walking it would only waste
|
|
138
|
-
/// I/O proportional to the project's task/session history.
|
|
139
|
-
fn scan_recursive(
|
|
140
|
-
dir: &Path,
|
|
141
|
-
depth: usize,
|
|
142
|
-
max_depth: usize,
|
|
143
|
-
exclude_patterns: &[String],
|
|
144
|
-
results: &mut Vec<PathBuf>,
|
|
145
|
-
) {
|
|
146
|
-
if depth > max_depth {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
let entries = match std::fs::read_dir(dir) {
|
|
150
|
-
Ok(e) => e,
|
|
151
|
-
Err(_) => return,
|
|
152
|
-
};
|
|
153
|
-
for entry in entries.flatten() {
|
|
154
|
-
if !entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
let name = entry.file_name();
|
|
158
|
-
let name_str = name.to_string_lossy();
|
|
159
|
-
if name_str == ".handoff" || exclude_patterns.iter().any(|p| p == name_str.as_ref()) {
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
let path = entry.path();
|
|
163
|
-
if path.join(".handoff").join("config.toml").exists() {
|
|
164
|
-
results.push(path.clone());
|
|
165
|
-
}
|
|
166
|
-
scan_recursive(&path, depth + 1, max_depth, exclude_patterns, results);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
fn collect_project_info(project_path: &Path) -> Result<Value> {
|
|
171
|
-
let handoff_dir = project_path.join(".handoff");
|
|
172
|
-
let config = read_config(&handoff_dir.join("config.toml"))?;
|
|
173
|
-
|
|
174
|
-
let sessions_dir = handoff_dir.join("sessions");
|
|
175
|
-
let mut sessions = read_open_sessions(&sessions_dir)?;
|
|
176
|
-
sessions.extend(read_active_sessions(&sessions_dir)?);
|
|
177
|
-
let paused = read_paused_sessions(&sessions_dir)?;
|
|
178
|
-
let paused_count = paused.len() as u32;
|
|
179
|
-
sessions.extend(paused);
|
|
180
|
-
|
|
181
|
-
let (_, summary) =
|
|
182
|
-
build_task_index(&handoff_dir.join("tasks"), config.settings.done_task_limit)?;
|
|
183
|
-
|
|
184
|
-
let last_session_ended = sessions.last().and_then(|s| s.ended_at.clone());
|
|
185
|
-
|
|
186
|
-
let branch = sessions.last().and_then(|s| s.branch.clone());
|
|
187
|
-
|
|
188
|
-
let active_tasks = *summary.by_status.get("in_progress").unwrap_or(&0)
|
|
189
|
-
+ *summary.by_status.get("todo").unwrap_or(&0)
|
|
190
|
-
+ *summary.by_status.get("review").unwrap_or(&0);
|
|
191
|
-
|
|
192
|
-
let blocked_tasks = *summary.by_status.get("blocked").unwrap_or(&0);
|
|
193
|
-
|
|
194
|
-
let blockers: Vec<String> = sessions
|
|
195
|
-
.iter()
|
|
196
|
-
.flat_map(|s| s.blockers.iter().cloned())
|
|
197
|
-
.collect();
|
|
198
|
-
|
|
199
|
-
let unread_referrals = read_referral_summaries(&handoff_dir.join("referrals"), Some("open"))
|
|
200
|
-
.map(|r| r.len() as u32)
|
|
201
|
-
.unwrap_or(0);
|
|
202
|
-
|
|
203
|
-
Ok(serde_json::json!({
|
|
204
|
-
"name": config.project.name,
|
|
205
|
-
"path": project_path.to_string_lossy(),
|
|
206
|
-
"last_session_ended": last_session_ended,
|
|
207
|
-
"branch": branch,
|
|
208
|
-
"active_tasks": active_tasks,
|
|
209
|
-
"blocked_tasks": blocked_tasks,
|
|
210
|
-
"blockers": blockers,
|
|
211
|
-
"unread_referrals": unread_referrals,
|
|
212
|
-
"paused_sessions": paused_count,
|
|
213
|
-
}))
|
|
214
|
-
}
|