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
package/src/storage/config.rs
DELETED
|
@@ -1,578 +0,0 @@
|
|
|
1
|
-
use std::collections::HashMap;
|
|
2
|
-
use std::path::Path;
|
|
3
|
-
|
|
4
|
-
use anyhow::{Context, Result};
|
|
5
|
-
use serde::de::{self, SeqAccess};
|
|
6
|
-
use serde::{Deserialize, Deserializer, Serialize};
|
|
7
|
-
|
|
8
|
-
/// Convert a weekday name to its number (0=Sun..6=Sat).
|
|
9
|
-
pub fn weekday_to_num(s: &str) -> Option<u32> {
|
|
10
|
-
match s.to_lowercase().as_str() {
|
|
11
|
-
"sun" | "sunday" => Some(0),
|
|
12
|
-
"mon" | "monday" => Some(1),
|
|
13
|
-
"tue" | "tuesday" => Some(2),
|
|
14
|
-
"wed" | "wednesday" => Some(3),
|
|
15
|
-
"thu" | "thursday" => Some(4),
|
|
16
|
-
"fri" | "friday" => Some(5),
|
|
17
|
-
"sat" | "saturday" => Some(6),
|
|
18
|
-
_ => None,
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
fn deserialize_weekdays<'de, D>(deserializer: D) -> std::result::Result<Vec<u32>, D::Error>
|
|
23
|
-
where
|
|
24
|
-
D: Deserializer<'de>,
|
|
25
|
-
{
|
|
26
|
-
struct WeekdayVisitor;
|
|
27
|
-
|
|
28
|
-
impl<'de> de::Visitor<'de> for WeekdayVisitor {
|
|
29
|
-
type Value = Vec<u32>;
|
|
30
|
-
|
|
31
|
-
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
32
|
-
f.write_str("an array of weekday numbers (0-6) or names (\"sun\"..\"sat\")")
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Vec<u32>, A::Error>
|
|
36
|
-
where
|
|
37
|
-
A: SeqAccess<'de>,
|
|
38
|
-
{
|
|
39
|
-
let mut vals = Vec::new();
|
|
40
|
-
while let Some(elem) = seq.next_element::<toml::Value>()? {
|
|
41
|
-
match &elem {
|
|
42
|
-
toml::Value::Integer(n) => {
|
|
43
|
-
let n = *n as u32;
|
|
44
|
-
if n > 6 {
|
|
45
|
-
return Err(de::Error::custom(format!(
|
|
46
|
-
"weekday number {n} out of range 0-6"
|
|
47
|
-
)));
|
|
48
|
-
}
|
|
49
|
-
vals.push(n);
|
|
50
|
-
}
|
|
51
|
-
toml::Value::String(s) => {
|
|
52
|
-
let n = weekday_to_num(s).ok_or_else(|| {
|
|
53
|
-
de::Error::custom(format!("unknown weekday name: \"{s}\""))
|
|
54
|
-
})?;
|
|
55
|
-
vals.push(n);
|
|
56
|
-
}
|
|
57
|
-
_ => {
|
|
58
|
-
return Err(de::Error::custom(
|
|
59
|
-
"closed_weekdays elements must be integers or strings",
|
|
60
|
-
));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
Ok(vals)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
deserializer.deserialize_seq(WeekdayVisitor)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
72
|
-
pub struct Config {
|
|
73
|
-
pub project: ProjectConfig,
|
|
74
|
-
#[serde(default)]
|
|
75
|
-
pub settings: SettingsConfig,
|
|
76
|
-
#[serde(default)]
|
|
77
|
-
pub dashboard: DashboardConfig,
|
|
78
|
-
/// Project-level start timestamp (pre-start mode). RFC3339 string.
|
|
79
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
80
|
-
pub started_at: Option<String>,
|
|
81
|
-
/// Scheduling mode: "manual" or "auto".
|
|
82
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
83
|
-
pub schedule_mode: Option<String>,
|
|
84
|
-
/// Project-level label vocabulary.
|
|
85
|
-
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
86
|
-
pub labels: Vec<String>,
|
|
87
|
-
#[serde(default, skip_serializing_if = "CalendarConfig::is_empty")]
|
|
88
|
-
pub calendar: CalendarConfig,
|
|
89
|
-
/// Team members keyed by stable assignee key.
|
|
90
|
-
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
91
|
-
pub assignees: HashMap<String, AssigneeConfig>,
|
|
92
|
-
/// Milestones keyed by milestone name.
|
|
93
|
-
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
94
|
-
pub milestones: HashMap<String, MilestoneConfig>,
|
|
95
|
-
#[serde(default, skip_serializing_if = "GanttViewConfig::is_empty")]
|
|
96
|
-
pub gantt_view: GanttViewConfig,
|
|
97
|
-
#[serde(default, skip_serializing_if = "EffortBudgetConfig::is_empty")]
|
|
98
|
-
pub effort_budget: EffortBudgetConfig,
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
102
|
-
pub struct ProjectConfig {
|
|
103
|
-
pub name: String,
|
|
104
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
105
|
-
pub description: Option<String>,
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
109
|
-
pub struct SettingsConfig {
|
|
110
|
-
#[serde(default = "default_history_limit")]
|
|
111
|
-
pub history_limit: u32,
|
|
112
|
-
#[serde(default = "default_done_task_limit")]
|
|
113
|
-
pub done_task_limit: u32,
|
|
114
|
-
#[serde(default = "default_auto_git_summary")]
|
|
115
|
-
pub auto_git_summary: bool,
|
|
116
|
-
/// Require `estimate_hours` when creating/updating leaf tasks. Default true.
|
|
117
|
-
#[serde(default = "default_require_estimate_hours")]
|
|
118
|
-
pub require_estimate_hours: bool,
|
|
119
|
-
/// Multiplier applied to AI-entered `estimate_hours` to derive the
|
|
120
|
-
/// adjusted (AI-effort) estimate at aggregation time. Default 0.2.
|
|
121
|
-
#[serde(default = "default_ai_estimate_multiplier")]
|
|
122
|
-
pub ai_estimate_multiplier: f64,
|
|
123
|
-
#[serde(default)]
|
|
124
|
-
pub context_files: Vec<String>,
|
|
125
|
-
/// Master switch for the memory feature (save/query/cleanup). Default true.
|
|
126
|
-
#[serde(default = "default_memory_enabled")]
|
|
127
|
-
pub memory_enabled: bool,
|
|
128
|
-
/// Jaccard threshold above which `memory_save` treats a save as a
|
|
129
|
-
/// near-duplicate `conflict` for the AI to merge. Default 0.72.
|
|
130
|
-
#[serde(default = "default_memory_dup_threshold")]
|
|
131
|
-
pub memory_dup_threshold: f64,
|
|
132
|
-
/// BM25 relevance floor for `memory_query`; scores below are not returned.
|
|
133
|
-
/// Default 2.0.
|
|
134
|
-
#[serde(default = "default_memory_query_min_score")]
|
|
135
|
-
pub memory_query_min_score: f64,
|
|
136
|
-
/// Relative threshold (0.0–1.0) for `memory_query`: after the absolute
|
|
137
|
-
/// `min_score` floor, a candidate is dropped unless its score is at least
|
|
138
|
-
/// `top_score × relative_threshold`. Prevents low-relevance "tail" matches
|
|
139
|
-
/// from riding a strong top hit. 0.0 disables (keep everything above
|
|
140
|
-
/// `min_score`). Default 0.3.
|
|
141
|
-
#[serde(default = "default_memory_query_relative_threshold")]
|
|
142
|
-
pub memory_query_relative_threshold: f64,
|
|
143
|
-
/// Maximum number of memories `memory_query` returns per call. Default 5.
|
|
144
|
-
#[serde(default = "default_memory_query_limit")]
|
|
145
|
-
pub memory_query_limit: u32,
|
|
146
|
-
/// Days after which an un(re)referenced memory is flagged `stale` by
|
|
147
|
-
/// `memory_cleanup`. Default 60.
|
|
148
|
-
#[serde(default = "default_memory_stale_days")]
|
|
149
|
-
pub memory_stale_days: i64,
|
|
150
|
-
/// Age (days) past which `memory_cleanup` garbage-collects a per-session
|
|
151
|
-
/// `injected/` sidecar. Default 14.
|
|
152
|
-
#[serde(default = "default_memory_injected_gc_days")]
|
|
153
|
-
pub memory_injected_gc_days: i64,
|
|
154
|
-
/// Timer provider mode: "auto" (authority-based), "vscode" (always delegate),
|
|
155
|
-
/// "mcp" (always internal), "off" (disabled). Default "auto".
|
|
156
|
-
#[serde(default = "default_timer_provider")]
|
|
157
|
-
pub timer_provider: String,
|
|
158
|
-
/// Heartbeat staleness threshold in seconds for authority.json. Default 30.
|
|
159
|
-
#[serde(default = "default_timer_authority_ttl_secs")]
|
|
160
|
-
pub timer_authority_ttl_secs: u64,
|
|
161
|
-
/// Idle timeout in minutes for MCP fallback timer. Default 10.
|
|
162
|
-
#[serde(default = "default_timer_idle_timeout_minutes")]
|
|
163
|
-
pub timer_idle_timeout_minutes: u64,
|
|
164
|
-
/// Allow multiple active sessions simultaneously. Default false (single-active).
|
|
165
|
-
#[serde(default)]
|
|
166
|
-
pub multi_session: bool,
|
|
167
|
-
#[serde(default)]
|
|
168
|
-
pub custom_fields: HashMap<String, toml::Value>,
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
172
|
-
pub struct DashboardConfig {
|
|
173
|
-
#[serde(default = "default_scan_dirs")]
|
|
174
|
-
pub scan_dirs: Vec<String>,
|
|
175
|
-
#[serde(default)]
|
|
176
|
-
pub exclude_patterns: Vec<String>,
|
|
177
|
-
/// Maximum directory depth for recursive scanning. Default 5 (mirrors VSCode side).
|
|
178
|
-
#[serde(default = "default_max_depth")]
|
|
179
|
-
pub max_depth: usize,
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/// Project-wide working calendar. Mirrors VSCode `CalendarConfig`.
|
|
183
|
-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
184
|
-
pub struct CalendarConfig {
|
|
185
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
186
|
-
pub work_hours_per_day: Option<f64>,
|
|
187
|
-
/// Weekday numbers (0=Sun..6=Sat) that are non-working.
|
|
188
|
-
/// Accepts integers or weekday names ("sun", "sat", etc.) in TOML.
|
|
189
|
-
#[serde(
|
|
190
|
-
default,
|
|
191
|
-
skip_serializing_if = "Vec::is_empty",
|
|
192
|
-
deserialize_with = "deserialize_weekdays"
|
|
193
|
-
)]
|
|
194
|
-
pub closed_weekdays: Vec<u32>,
|
|
195
|
-
/// Specific YYYY-MM-DD dates that are non-working (override weekdays).
|
|
196
|
-
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
197
|
-
pub closed_dates: Vec<String>,
|
|
198
|
-
/// Specific YYYY-MM-DD dates that are working even if normally closed.
|
|
199
|
-
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
200
|
-
pub open_dates: Vec<String>,
|
|
201
|
-
/// Per-weekday / per-date working-hour overrides. Key = weekday name or YYYY-MM-DD.
|
|
202
|
-
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
203
|
-
pub day_hours: HashMap<String, f64>,
|
|
204
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
205
|
-
pub schedule_mode: Option<String>,
|
|
206
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
207
|
-
pub overwork_limit_percent: Option<f64>,
|
|
208
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
209
|
-
pub max_utilization: Option<f64>,
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
impl CalendarConfig {
|
|
213
|
-
pub fn is_empty(&self) -> bool {
|
|
214
|
-
self.work_hours_per_day.is_none()
|
|
215
|
-
&& self.closed_weekdays.is_empty()
|
|
216
|
-
&& self.closed_dates.is_empty()
|
|
217
|
-
&& self.open_dates.is_empty()
|
|
218
|
-
&& self.day_hours.is_empty()
|
|
219
|
-
&& self.schedule_mode.is_none()
|
|
220
|
-
&& self.overwork_limit_percent.is_none()
|
|
221
|
-
&& self.max_utilization.is_none()
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/// A single team member's configuration. Mirrors VSCode `AssigneeConfig`.
|
|
226
|
-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
227
|
-
pub struct AssigneeConfig {
|
|
228
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
229
|
-
pub display_name: Option<String>,
|
|
230
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
231
|
-
pub color: Option<String>,
|
|
232
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
233
|
-
pub work_hours_per_day: Option<f64>,
|
|
234
|
-
#[serde(
|
|
235
|
-
default,
|
|
236
|
-
skip_serializing_if = "Vec::is_empty",
|
|
237
|
-
deserialize_with = "deserialize_weekdays"
|
|
238
|
-
)]
|
|
239
|
-
pub closed_weekdays: Vec<u32>,
|
|
240
|
-
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
241
|
-
pub closed_dates: Vec<String>,
|
|
242
|
-
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
243
|
-
pub open_dates: Vec<String>,
|
|
244
|
-
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
245
|
-
pub day_hours: HashMap<String, f64>,
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/// A milestone definition. Mirrors VSCode `MilestoneConfig`.
|
|
249
|
-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
250
|
-
pub struct MilestoneConfig {
|
|
251
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
252
|
-
pub date: Option<String>,
|
|
253
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
254
|
-
pub color: Option<String>,
|
|
255
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
256
|
-
pub description: Option<String>,
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/// Gantt view UI settings. Mirrors VSCode `GanttViewSettings`.
|
|
260
|
-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
261
|
-
pub struct GanttViewConfig {
|
|
262
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
263
|
-
pub sort: Option<String>,
|
|
264
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
265
|
-
pub zoom: Option<String>,
|
|
266
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
267
|
-
pub mode: Option<String>,
|
|
268
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
269
|
-
pub group_by_milestone: Option<bool>,
|
|
270
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
271
|
-
pub group_by_assignee: Option<bool>,
|
|
272
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
273
|
-
pub show_workload: Option<bool>,
|
|
274
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
275
|
-
pub filter_assignee: Option<String>,
|
|
276
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
277
|
-
pub workload_view: Option<String>,
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
impl GanttViewConfig {
|
|
281
|
-
pub fn is_empty(&self) -> bool {
|
|
282
|
-
self.sort.is_none()
|
|
283
|
-
&& self.zoom.is_none()
|
|
284
|
-
&& self.mode.is_none()
|
|
285
|
-
&& self.group_by_milestone.is_none()
|
|
286
|
-
&& self.group_by_assignee.is_none()
|
|
287
|
-
&& self.show_workload.is_none()
|
|
288
|
-
&& self.filter_assignee.is_none()
|
|
289
|
-
&& self.workload_view.is_none()
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/// Effort budget. Mirrors VSCode `budgetTotalHours`.
|
|
294
|
-
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
295
|
-
pub struct EffortBudgetConfig {
|
|
296
|
-
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
297
|
-
pub total_hours: Option<f64>,
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
impl EffortBudgetConfig {
|
|
301
|
-
pub fn is_empty(&self) -> bool {
|
|
302
|
-
self.total_hours.is_none()
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
fn default_history_limit() -> u32 {
|
|
307
|
-
20
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
fn default_done_task_limit() -> u32 {
|
|
311
|
-
10
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
fn default_auto_git_summary() -> bool {
|
|
315
|
-
true
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
fn default_require_estimate_hours() -> bool {
|
|
319
|
-
true
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
fn default_ai_estimate_multiplier() -> f64 {
|
|
323
|
-
0.2
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
fn default_memory_enabled() -> bool {
|
|
327
|
-
true
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
fn default_memory_dup_threshold() -> f64 {
|
|
331
|
-
0.72
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
fn default_memory_query_min_score() -> f64 {
|
|
335
|
-
2.0
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
fn default_memory_query_relative_threshold() -> f64 {
|
|
339
|
-
0.3
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
fn default_memory_query_limit() -> u32 {
|
|
343
|
-
5
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
fn default_memory_stale_days() -> i64 {
|
|
347
|
-
60
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
fn default_memory_injected_gc_days() -> i64 {
|
|
351
|
-
14
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
fn default_timer_provider() -> String {
|
|
355
|
-
"auto".to_string()
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
fn default_timer_authority_ttl_secs() -> u64 {
|
|
359
|
-
30
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
fn default_timer_idle_timeout_minutes() -> u64 {
|
|
363
|
-
10
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
fn default_scan_dirs() -> Vec<String> {
|
|
367
|
-
vec!["~/pro/".to_string()]
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
fn default_max_depth() -> usize {
|
|
371
|
-
5
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
impl Default for SettingsConfig {
|
|
375
|
-
fn default() -> Self {
|
|
376
|
-
Self {
|
|
377
|
-
history_limit: default_history_limit(),
|
|
378
|
-
done_task_limit: default_done_task_limit(),
|
|
379
|
-
auto_git_summary: default_auto_git_summary(),
|
|
380
|
-
require_estimate_hours: default_require_estimate_hours(),
|
|
381
|
-
ai_estimate_multiplier: default_ai_estimate_multiplier(),
|
|
382
|
-
context_files: Vec::new(),
|
|
383
|
-
memory_enabled: default_memory_enabled(),
|
|
384
|
-
memory_dup_threshold: default_memory_dup_threshold(),
|
|
385
|
-
memory_query_min_score: default_memory_query_min_score(),
|
|
386
|
-
memory_query_relative_threshold: default_memory_query_relative_threshold(),
|
|
387
|
-
memory_query_limit: default_memory_query_limit(),
|
|
388
|
-
memory_stale_days: default_memory_stale_days(),
|
|
389
|
-
memory_injected_gc_days: default_memory_injected_gc_days(),
|
|
390
|
-
timer_provider: default_timer_provider(),
|
|
391
|
-
timer_authority_ttl_secs: default_timer_authority_ttl_secs(),
|
|
392
|
-
timer_idle_timeout_minutes: default_timer_idle_timeout_minutes(),
|
|
393
|
-
multi_session: false,
|
|
394
|
-
custom_fields: HashMap::new(),
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
impl Default for DashboardConfig {
|
|
400
|
-
fn default() -> Self {
|
|
401
|
-
Self {
|
|
402
|
-
scan_dirs: default_scan_dirs(),
|
|
403
|
-
exclude_patterns: Vec::new(),
|
|
404
|
-
max_depth: default_max_depth(),
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
impl Config {
|
|
410
|
-
pub fn new(name: &str, description: &str) -> Self {
|
|
411
|
-
let settings = SettingsConfig {
|
|
412
|
-
multi_session: true,
|
|
413
|
-
..SettingsConfig::default()
|
|
414
|
-
};
|
|
415
|
-
Self {
|
|
416
|
-
project: ProjectConfig {
|
|
417
|
-
name: name.to_string(),
|
|
418
|
-
description: if description.is_empty() {
|
|
419
|
-
None
|
|
420
|
-
} else {
|
|
421
|
-
Some(description.to_string())
|
|
422
|
-
},
|
|
423
|
-
},
|
|
424
|
-
settings,
|
|
425
|
-
dashboard: DashboardConfig::default(),
|
|
426
|
-
started_at: None,
|
|
427
|
-
schedule_mode: None,
|
|
428
|
-
labels: Vec::new(),
|
|
429
|
-
calendar: CalendarConfig::default(),
|
|
430
|
-
assignees: HashMap::new(),
|
|
431
|
-
milestones: HashMap::new(),
|
|
432
|
-
gantt_view: GanttViewConfig::default(),
|
|
433
|
-
effort_budget: EffortBudgetConfig::default(),
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
pub fn read_config(path: &Path) -> Result<Config> {
|
|
439
|
-
let content = std::fs::read_to_string(path)
|
|
440
|
-
.with_context(|| format!("Failed to read config: {}", path.display()))?;
|
|
441
|
-
let config: Config = toml::from_str(&content)
|
|
442
|
-
.with_context(|| format!("Failed to parse config: {}", path.display()))?;
|
|
443
|
-
Ok(config)
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
pub fn write_config(path: &Path, config: &Config) -> Result<()> {
|
|
447
|
-
let content = toml::to_string_pretty(config).context("Failed to serialize config")?;
|
|
448
|
-
crate::storage::atomic_write(path, content.as_bytes())
|
|
449
|
-
.with_context(|| format!("Failed to write config: {}", path.display()))?;
|
|
450
|
-
Ok(())
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
#[cfg(test)]
|
|
454
|
-
mod tests {
|
|
455
|
-
use super::*;
|
|
456
|
-
|
|
457
|
-
fn parse_config(toml_str: &str) -> Config {
|
|
458
|
-
toml::from_str(toml_str).unwrap()
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
#[test]
|
|
462
|
-
fn closed_weekdays_string_names() {
|
|
463
|
-
let cfg = parse_config(
|
|
464
|
-
r#"
|
|
465
|
-
[project]
|
|
466
|
-
name = "test"
|
|
467
|
-
[calendar]
|
|
468
|
-
closed_weekdays = ["sun", "sat"]
|
|
469
|
-
"#,
|
|
470
|
-
);
|
|
471
|
-
assert_eq!(cfg.calendar.closed_weekdays, vec![0, 6]);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
#[test]
|
|
475
|
-
fn closed_weekdays_integer_values() {
|
|
476
|
-
let cfg = parse_config(
|
|
477
|
-
r#"
|
|
478
|
-
[project]
|
|
479
|
-
name = "test"
|
|
480
|
-
[calendar]
|
|
481
|
-
closed_weekdays = [0, 6]
|
|
482
|
-
"#,
|
|
483
|
-
);
|
|
484
|
-
assert_eq!(cfg.calendar.closed_weekdays, vec![0, 6]);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
#[test]
|
|
488
|
-
fn closed_weekdays_mixed() {
|
|
489
|
-
let cfg = parse_config(
|
|
490
|
-
r#"
|
|
491
|
-
[project]
|
|
492
|
-
name = "test"
|
|
493
|
-
[calendar]
|
|
494
|
-
closed_weekdays = ["sun", 6]
|
|
495
|
-
"#,
|
|
496
|
-
);
|
|
497
|
-
assert_eq!(cfg.calendar.closed_weekdays, vec![0, 6]);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
#[test]
|
|
501
|
-
fn closed_weekdays_empty() {
|
|
502
|
-
let cfg = parse_config(
|
|
503
|
-
r#"
|
|
504
|
-
[project]
|
|
505
|
-
name = "test"
|
|
506
|
-
"#,
|
|
507
|
-
);
|
|
508
|
-
assert!(cfg.calendar.closed_weekdays.is_empty());
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
#[test]
|
|
512
|
-
fn closed_weekdays_full_names() {
|
|
513
|
-
let cfg = parse_config(
|
|
514
|
-
r#"
|
|
515
|
-
[project]
|
|
516
|
-
name = "test"
|
|
517
|
-
[calendar]
|
|
518
|
-
closed_weekdays = ["sunday", "saturday"]
|
|
519
|
-
"#,
|
|
520
|
-
);
|
|
521
|
-
assert_eq!(cfg.calendar.closed_weekdays, vec![0, 6]);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
#[test]
|
|
525
|
-
fn assignee_closed_weekdays_strings() {
|
|
526
|
-
let cfg = parse_config(
|
|
527
|
-
r#"
|
|
528
|
-
[project]
|
|
529
|
-
name = "test"
|
|
530
|
-
[assignees.alice]
|
|
531
|
-
closed_weekdays = ["mon", "fri"]
|
|
532
|
-
"#,
|
|
533
|
-
);
|
|
534
|
-
let alice = cfg.assignees.get("alice").unwrap();
|
|
535
|
-
assert_eq!(alice.closed_weekdays, vec![1, 5]);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
#[test]
|
|
539
|
-
fn closed_weekdays_invalid_name() {
|
|
540
|
-
let result = toml::from_str::<Config>(
|
|
541
|
-
r#"
|
|
542
|
-
[project]
|
|
543
|
-
name = "test"
|
|
544
|
-
[calendar]
|
|
545
|
-
closed_weekdays = ["funday"]
|
|
546
|
-
"#,
|
|
547
|
-
);
|
|
548
|
-
assert!(result.is_err());
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
#[test]
|
|
552
|
-
fn closed_weekdays_out_of_range() {
|
|
553
|
-
let result = toml::from_str::<Config>(
|
|
554
|
-
r#"
|
|
555
|
-
[project]
|
|
556
|
-
name = "test"
|
|
557
|
-
[calendar]
|
|
558
|
-
closed_weekdays = [7]
|
|
559
|
-
"#,
|
|
560
|
-
);
|
|
561
|
-
assert!(result.is_err());
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
#[test]
|
|
565
|
-
fn round_trip_preserves_integer_format() {
|
|
566
|
-
let cfg = parse_config(
|
|
567
|
-
r#"
|
|
568
|
-
[project]
|
|
569
|
-
name = "test"
|
|
570
|
-
[calendar]
|
|
571
|
-
closed_weekdays = ["sun", "sat"]
|
|
572
|
-
"#,
|
|
573
|
-
);
|
|
574
|
-
let serialized = toml::to_string_pretty(&cfg).unwrap();
|
|
575
|
-
let re_parsed = parse_config(&serialized);
|
|
576
|
-
assert_eq!(re_parsed.calendar.closed_weekdays, vec![0, 6]);
|
|
577
|
-
}
|
|
578
|
-
}
|