handoff-mcp-server 0.11.0 → 0.13.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/Cargo.lock +42 -1
- package/Cargo.toml +3 -2
- package/README.md +163 -4
- package/package.json +1 -1
- package/scripts/cargo-env.sh +29 -0
- package/scripts/handoff-memory-hook.py +208 -0
- package/skills/handoff/SKILL.md +8 -1
- package/src/mcp/handlers/capacity.rs +21 -6
- package/src/mcp/handlers/config.rs +80 -0
- package/src/mcp/handlers/init.rs +1 -1
- package/src/mcp/handlers/memory.rs +906 -0
- package/src/mcp/handlers/metrics.rs +11 -0
- package/src/mcp/handlers/mod.rs +6 -0
- package/src/mcp/handlers/referrals.rs +20 -1
- package/src/mcp/handlers/update_task.rs +53 -4
- package/src/mcp/tools.rs +77 -0
- package/src/storage/config.rs +69 -0
- package/src/storage/memory/injected.rs +340 -0
- package/src/storage/memory/mod.rs +235 -0
- package/src/storage/memory/model.rs +96 -0
- package/src/storage/mod.rs +2 -0
- package/src/storage/referrals.rs +38 -0
- package/src/storage/tasks.rs +55 -0
|
@@ -5,6 +5,7 @@ use chrono::Utc;
|
|
|
5
5
|
use serde_json::{json, Value};
|
|
6
6
|
|
|
7
7
|
use super::resolve_project_dir;
|
|
8
|
+
use crate::storage::config::read_config;
|
|
8
9
|
use crate::storage::ensure_handoff_exists;
|
|
9
10
|
use crate::storage::tasks::{build_task_index, is_terminal_status, TaskIndex};
|
|
10
11
|
|
|
@@ -50,6 +51,13 @@ pub fn handle(arguments: &Value) -> Result<String> {
|
|
|
50
51
|
|
|
51
52
|
let budget = read_budget(&handoff);
|
|
52
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
|
+
|
|
53
61
|
let milestone_list: Vec<Value> = milestones
|
|
54
62
|
.into_iter()
|
|
55
63
|
.map(|(name, (done, ms_total, est, act))| {
|
|
@@ -58,6 +66,7 @@ pub fn handle(arguments: &Value) -> Result<String> {
|
|
|
58
66
|
"done": done,
|
|
59
67
|
"total": ms_total,
|
|
60
68
|
"estimate_hours": est,
|
|
69
|
+
"adjusted_estimate_hours": est * multiplier,
|
|
61
70
|
"actual_hours": act,
|
|
62
71
|
})
|
|
63
72
|
})
|
|
@@ -68,6 +77,8 @@ pub fn handle(arguments: &Value) -> Result<String> {
|
|
|
68
77
|
"by_status": by_status,
|
|
69
78
|
"completion_percent": (completion_percent * 10.0).round() / 10.0,
|
|
70
79
|
"total_estimate_hours": estimate_sum,
|
|
80
|
+
"ai_estimate_multiplier": multiplier,
|
|
81
|
+
"total_adjusted_estimate_hours": estimate_sum * multiplier,
|
|
71
82
|
"total_actual_hours": actual_sum,
|
|
72
83
|
"total_remaining_hours": remaining_sum,
|
|
73
84
|
"overdue_count": overdue_tasks.len(),
|
package/src/mcp/handlers/mod.rs
CHANGED
|
@@ -15,6 +15,7 @@ pub mod list_sessions;
|
|
|
15
15
|
pub mod list_tasks;
|
|
16
16
|
pub mod load_context;
|
|
17
17
|
pub mod log_time;
|
|
18
|
+
pub mod memory;
|
|
18
19
|
pub mod metrics;
|
|
19
20
|
pub mod milestones;
|
|
20
21
|
pub mod refer;
|
|
@@ -53,6 +54,7 @@ pub fn handle_tool_call(name: &str, arguments: &Value) -> JsonRpcResponse {
|
|
|
53
54
|
"handoff_import_context" => import_context::handle(arguments),
|
|
54
55
|
"handoff_refer" => refer::handle(arguments),
|
|
55
56
|
"handoff_list_referrals" => referrals::handle_list(arguments),
|
|
57
|
+
"handoff_get_referral" => referrals::handle_get(arguments),
|
|
56
58
|
"handoff_update_referral" => referrals::handle_update(arguments),
|
|
57
59
|
"handoff_update_session" => update_session::handle(arguments),
|
|
58
60
|
"handoff_log_time" => log_time::handle(arguments),
|
|
@@ -73,6 +75,10 @@ pub fn handle_tool_call(name: &str, arguments: &Value) -> JsonRpcResponse {
|
|
|
73
75
|
"handoff_update_calendar" => calendar::handle_update_calendar(arguments),
|
|
74
76
|
"handoff_update_labels" => calendar::handle_update_labels(arguments),
|
|
75
77
|
"handoff_start_project" => calendar::handle_start_project(arguments),
|
|
78
|
+
"memory_save" => memory::handle_save(arguments),
|
|
79
|
+
"memory_query" => memory::handle_query(arguments),
|
|
80
|
+
"memory_delete" => memory::handle_delete(arguments),
|
|
81
|
+
"memory_cleanup" => memory::handle_cleanup(arguments),
|
|
76
82
|
_ => Err(anyhow::anyhow!("Tool not implemented: {name}")),
|
|
77
83
|
};
|
|
78
84
|
|
|
@@ -4,7 +4,7 @@ use serde_json::Value;
|
|
|
4
4
|
use super::resolve_project_dir;
|
|
5
5
|
use crate::storage::ensure_handoff_exists;
|
|
6
6
|
use crate::storage::referrals::{
|
|
7
|
-
change_referral_status, is_valid_referral_status, read_referral_summaries,
|
|
7
|
+
change_referral_status, is_valid_referral_status, read_referral_by_id, read_referral_summaries,
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
pub fn handle_list(arguments: &Value) -> Result<String> {
|
|
@@ -32,6 +32,25 @@ pub fn handle_list(arguments: &Value) -> Result<String> {
|
|
|
32
32
|
serde_json::to_string_pretty(&result).context("Failed to serialize referrals")
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
pub fn handle_get(arguments: &Value) -> Result<String> {
|
|
36
|
+
let project_dir = resolve_project_dir(arguments)?;
|
|
37
|
+
let handoff = ensure_handoff_exists(&project_dir)?;
|
|
38
|
+
let referrals_dir = handoff.join("referrals");
|
|
39
|
+
|
|
40
|
+
let referral_id = arguments
|
|
41
|
+
.get("referral_id")
|
|
42
|
+
.and_then(|v| v.as_str())
|
|
43
|
+
.ok_or_else(|| anyhow::anyhow!("'referral_id' is required"))?;
|
|
44
|
+
|
|
45
|
+
let (data, status) = read_referral_by_id(&referrals_dir, referral_id)?
|
|
46
|
+
.ok_or_else(|| anyhow::anyhow!("Referral not found: {referral_id}"))?;
|
|
47
|
+
|
|
48
|
+
let mut result = serde_json::to_value(&data).context("Failed to serialize referral")?;
|
|
49
|
+
result["status"] = Value::String(status);
|
|
50
|
+
|
|
51
|
+
serde_json::to_string_pretty(&result).context("Failed to serialize referral")
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
pub fn handle_update(arguments: &Value) -> Result<String> {
|
|
36
55
|
let project_dir = resolve_project_dir(arguments)?;
|
|
37
56
|
let handoff = ensure_handoff_exists(&project_dir)?;
|
|
@@ -5,6 +5,7 @@ use chrono::Utc;
|
|
|
5
5
|
use serde_json::Value;
|
|
6
6
|
|
|
7
7
|
use super::resolve_project_dir;
|
|
8
|
+
use crate::storage::config::read_config;
|
|
8
9
|
use crate::storage::ensure_handoff_exists;
|
|
9
10
|
use crate::storage::tasks::*;
|
|
10
11
|
|
|
@@ -14,6 +15,10 @@ pub fn handle(arguments: &Value) -> Result<String> {
|
|
|
14
15
|
let handoff = ensure_handoff_exists(&project_dir)?;
|
|
15
16
|
let tasks_dir = handoff.join("tasks");
|
|
16
17
|
|
|
18
|
+
let require_estimate_hours = read_config(&handoff.join("config.toml"))
|
|
19
|
+
.map(|c| c.settings.require_estimate_hours)
|
|
20
|
+
.unwrap_or(true);
|
|
21
|
+
|
|
17
22
|
let task_val = arguments
|
|
18
23
|
.get("task")
|
|
19
24
|
.ok_or_else(|| anyhow::anyhow!("'task' parameter is required"))?;
|
|
@@ -27,9 +32,15 @@ pub fn handle(arguments: &Value) -> Result<String> {
|
|
|
27
32
|
}
|
|
28
33
|
let task_exists = find_task_dir_by_id(&tasks_dir, existing_id)?.is_some();
|
|
29
34
|
if task_exists {
|
|
30
|
-
return handle_update(&tasks_dir, existing_id, task_val);
|
|
35
|
+
return handle_update(&tasks_dir, existing_id, task_val, require_estimate_hours);
|
|
31
36
|
}
|
|
32
|
-
return handle_upsert_create(
|
|
37
|
+
return handle_upsert_create(
|
|
38
|
+
&tasks_dir,
|
|
39
|
+
existing_id,
|
|
40
|
+
task_val,
|
|
41
|
+
arguments,
|
|
42
|
+
require_estimate_hours,
|
|
43
|
+
);
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
let title = task_val
|
|
@@ -37,7 +48,13 @@ pub fn handle(arguments: &Value) -> Result<String> {
|
|
|
37
48
|
.and_then(|v| v.as_str())
|
|
38
49
|
.ok_or_else(|| anyhow::anyhow!("'task.title' is required for new tasks"))?;
|
|
39
50
|
|
|
40
|
-
handle_create(
|
|
51
|
+
handle_create(
|
|
52
|
+
&tasks_dir,
|
|
53
|
+
title,
|
|
54
|
+
task_val,
|
|
55
|
+
arguments,
|
|
56
|
+
require_estimate_hours,
|
|
57
|
+
)
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
fn handle_create(
|
|
@@ -45,6 +62,7 @@ fn handle_create(
|
|
|
45
62
|
title: &str,
|
|
46
63
|
task_val: &Value,
|
|
47
64
|
arguments: &Value,
|
|
65
|
+
require_estimate_hours: bool,
|
|
48
66
|
) -> Result<String> {
|
|
49
67
|
let parent_id = arguments.get("parent_id").and_then(|v| v.as_str());
|
|
50
68
|
|
|
@@ -112,6 +130,14 @@ fn handle_create(
|
|
|
112
130
|
extra: HashMap::new(),
|
|
113
131
|
};
|
|
114
132
|
|
|
133
|
+
// A newly created task is always a leaf (no children yet).
|
|
134
|
+
validate_estimate_required(
|
|
135
|
+
require_estimate_hours,
|
|
136
|
+
status,
|
|
137
|
+
false,
|
|
138
|
+
data.schedule.as_ref(),
|
|
139
|
+
)?;
|
|
140
|
+
|
|
115
141
|
write_task(&task_dir, status, &data)?;
|
|
116
142
|
|
|
117
143
|
Ok(format!("Created task {new_id}: {title} [{status}]"))
|
|
@@ -122,6 +148,7 @@ fn handle_upsert_create(
|
|
|
122
148
|
task_id: &str,
|
|
123
149
|
task_val: &Value,
|
|
124
150
|
arguments: &Value,
|
|
151
|
+
require_estimate_hours: bool,
|
|
125
152
|
) -> Result<String> {
|
|
126
153
|
let title = task_val
|
|
127
154
|
.get("title")
|
|
@@ -195,12 +222,25 @@ fn handle_upsert_create(
|
|
|
195
222
|
extra: HashMap::new(),
|
|
196
223
|
};
|
|
197
224
|
|
|
225
|
+
// Upsert-create: a brand-new task is a leaf.
|
|
226
|
+
validate_estimate_required(
|
|
227
|
+
require_estimate_hours,
|
|
228
|
+
status,
|
|
229
|
+
false,
|
|
230
|
+
data.schedule.as_ref(),
|
|
231
|
+
)?;
|
|
232
|
+
|
|
198
233
|
write_task(&task_dir, status, &data)?;
|
|
199
234
|
|
|
200
235
|
Ok(format!("Created task {task_id}: {title} [{status}]"))
|
|
201
236
|
}
|
|
202
237
|
|
|
203
|
-
fn handle_update(
|
|
238
|
+
fn handle_update(
|
|
239
|
+
tasks_dir: &std::path::Path,
|
|
240
|
+
task_id: &str,
|
|
241
|
+
task_val: &Value,
|
|
242
|
+
require_estimate_hours: bool,
|
|
243
|
+
) -> Result<String> {
|
|
204
244
|
let task_dir = find_task_dir_by_id(tasks_dir, task_id)?
|
|
205
245
|
.ok_or_else(|| anyhow::anyhow!("Task not found: {task_id}"))?;
|
|
206
246
|
|
|
@@ -288,6 +328,15 @@ fn handle_update(tasks_dir: &std::path::Path, task_id: &str, task_val: &Value) -
|
|
|
288
328
|
validate_skipped_transition(&task_dir, &data)?;
|
|
289
329
|
}
|
|
290
330
|
|
|
331
|
+
// Parent tasks (with children) are exempt; only leaf tasks need an estimate.
|
|
332
|
+
let has_children = task_has_children(&task_dir)?;
|
|
333
|
+
validate_estimate_required(
|
|
334
|
+
require_estimate_hours,
|
|
335
|
+
new_status,
|
|
336
|
+
has_children,
|
|
337
|
+
data.schedule.as_ref(),
|
|
338
|
+
)?;
|
|
339
|
+
|
|
291
340
|
data.updated_at = Some(Utc::now().to_rfc3339());
|
|
292
341
|
|
|
293
342
|
if let Some((old_path, _)) = find_task_file(&task_dir)? {
|
package/src/mcp/tools.rs
CHANGED
|
@@ -644,6 +644,24 @@ pub fn all_tool_definitions() -> Vec<ToolDefinition> {
|
|
|
644
644
|
}
|
|
645
645
|
}),
|
|
646
646
|
},
|
|
647
|
+
ToolDefinition {
|
|
648
|
+
name: "handoff_get_referral".to_string(),
|
|
649
|
+
description: "Get the full details of a single incoming referral by ID (summary, details, tasks with done_criteria, priority, context, status). Use this instead of reading .handoff/referrals/*.json directly.".to_string(),
|
|
650
|
+
input_schema: json!({
|
|
651
|
+
"type": "object",
|
|
652
|
+
"properties": {
|
|
653
|
+
"project_dir": {
|
|
654
|
+
"type": "string",
|
|
655
|
+
"description": "Project directory path. Defaults to current working directory."
|
|
656
|
+
},
|
|
657
|
+
"referral_id": {
|
|
658
|
+
"type": "string",
|
|
659
|
+
"description": "ID of the referral to retrieve (full id or a unique prefix)."
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
"required": ["referral_id"]
|
|
663
|
+
}),
|
|
664
|
+
},
|
|
647
665
|
ToolDefinition {
|
|
648
666
|
name: "handoff_update_referral".to_string(),
|
|
649
667
|
description: "Update the status of an incoming referral (open -> acknowledged -> resolved).".to_string(),
|
|
@@ -1005,6 +1023,65 @@ pub fn all_tool_definitions() -> Vec<ToolDefinition> {
|
|
|
1005
1023
|
}
|
|
1006
1024
|
}),
|
|
1007
1025
|
},
|
|
1026
|
+
ToolDefinition {
|
|
1027
|
+
name: "memory_save".to_string(),
|
|
1028
|
+
description: "Save a long-lived project memory (lesson/rule/convention/gotcha) that future sessions should respect. Detects exact and near-duplicate memories: an exact match is reported (not rewritten), a near-duplicate is returned as a 'conflict' with both bodies for you to merge (call again with merge_into=<id> and absorb_ids=[…]) or save separately with force=true. Returns a JSON string.".to_string(),
|
|
1029
|
+
input_schema: json!({
|
|
1030
|
+
"type": "object",
|
|
1031
|
+
"properties": {
|
|
1032
|
+
"project_dir": { "type": "string", "description": "Project directory path. Defaults to current working directory." },
|
|
1033
|
+
"text": { "type": "string", "description": "The memory body (any language). Required, non-empty." },
|
|
1034
|
+
"kind": { "type": "string", "description": "Memory kind.", "enum": ["lesson", "rule", "convention", "gotcha"], "default": "lesson" },
|
|
1035
|
+
"tags": { "type": "array", "items": { "type": "string" }, "description": "Optional tags; also indexed for similarity." },
|
|
1036
|
+
"scope_paths": { "type": "array", "items": { "type": "string" }, "description": "Path prefixes this memory applies to (e.g. 'src/storage/'). Boosts relevance when a query touches a matching file." },
|
|
1037
|
+
"merge_into": { "type": "string", "description": "Commit an AI merge: overwrite this memory id with `text` and absorb `absorb_ids`." },
|
|
1038
|
+
"absorb_ids": { "type": "array", "items": { "type": "string" }, "description": "Memory ids to delete and record as superseded when merging." },
|
|
1039
|
+
"force": { "type": "boolean", "description": "Save even if a near-duplicate exists (skip the conflict response).", "default": false }
|
|
1040
|
+
},
|
|
1041
|
+
"required": ["text"]
|
|
1042
|
+
}),
|
|
1043
|
+
},
|
|
1044
|
+
ToolDefinition {
|
|
1045
|
+
name: "memory_query".to_string(),
|
|
1046
|
+
description: "Return the project memories most relevant to the given text/file (BM25 + scope-path boosting). Intended for automatic injection via hooks, but callable directly. Returns a JSON string {\"memories\":[{id,text,kind,score}],\"injected_count\"}.".to_string(),
|
|
1047
|
+
input_schema: json!({
|
|
1048
|
+
"type": "object",
|
|
1049
|
+
"properties": {
|
|
1050
|
+
"project_dir": { "type": "string", "description": "Project directory path. Defaults to current working directory." },
|
|
1051
|
+
"session_id": { "type": "string", "description": "Hook session id. When given, memories already injected this session (same content hash) are filtered out; an edited memory is re-injected." },
|
|
1052
|
+
"text": { "type": "string", "description": "The current prompt or context text to match against." },
|
|
1053
|
+
"tool_name": { "type": "string", "description": "Name of the tool about to run (e.g. 'Edit'); added to the query." },
|
|
1054
|
+
"file_paths": { "type": "array", "items": { "type": "string" }, "description": "File paths in play; basenames are added to the query and scope_paths are matched against these." },
|
|
1055
|
+
"limit": { "type": "integer", "description": "Maximum memories to return.", "default": 5 },
|
|
1056
|
+
"mark_injected": { "type": "boolean", "description": "Record returned memories in the session sidecar and bump their hit_count/last_referenced_at. Requires session_id.", "default": true }
|
|
1057
|
+
},
|
|
1058
|
+
"required": ["text"]
|
|
1059
|
+
}),
|
|
1060
|
+
},
|
|
1061
|
+
ToolDefinition {
|
|
1062
|
+
name: "memory_delete".to_string(),
|
|
1063
|
+
description: "Delete a project memory by id (full id or unique prefix). Use for AI-driven cleanup of stale memories. Returns a JSON string {\"status\":\"deleted\",\"id\"}.".to_string(),
|
|
1064
|
+
input_schema: json!({
|
|
1065
|
+
"type": "object",
|
|
1066
|
+
"properties": {
|
|
1067
|
+
"project_dir": { "type": "string", "description": "Project directory path. Defaults to current working directory." },
|
|
1068
|
+
"id": { "type": "string", "description": "Memory id to delete (full id or unique prefix)." }
|
|
1069
|
+
},
|
|
1070
|
+
"required": ["id"]
|
|
1071
|
+
}),
|
|
1072
|
+
},
|
|
1073
|
+
ToolDefinition {
|
|
1074
|
+
name: "memory_cleanup".to_string(),
|
|
1075
|
+
description: "Housekeep the project memory store (intended for SessionStart). Silently merges exact duplicates (lossless), then returns recommendations the AI should act on: near-duplicate clusters (merge with memory_save merge_into=…) and stale memories (consider memory_delete). Also garbage-collects old per-session injection sidecars. Returns a JSON string {\"auto_merged_exact\":n,\"cleanup_recommendations\":{\"similar_clusters\":[…],\"stale\":[…]},\"injected_sidecars_removed\":k}.".to_string(),
|
|
1076
|
+
input_schema: json!({
|
|
1077
|
+
"type": "object",
|
|
1078
|
+
"properties": {
|
|
1079
|
+
"project_dir": { "type": "string", "description": "Project directory path. Defaults to current working directory." },
|
|
1080
|
+
"apply_exact_merges": { "type": "boolean", "description": "Auto-merge exact-duplicate memories (same content hash). Lossless and safe.", "default": true },
|
|
1081
|
+
"stale_days": { "type": "integer", "description": "Flag memories not referenced for this many days as stale recommendations.", "default": 60 }
|
|
1082
|
+
}
|
|
1083
|
+
}),
|
|
1084
|
+
},
|
|
1008
1085
|
]
|
|
1009
1086
|
}
|
|
1010
1087
|
|
package/src/storage/config.rs
CHANGED
|
@@ -49,8 +49,37 @@ pub struct SettingsConfig {
|
|
|
49
49
|
pub done_task_limit: u32,
|
|
50
50
|
#[serde(default = "default_auto_git_summary")]
|
|
51
51
|
pub auto_git_summary: bool,
|
|
52
|
+
/// Require `estimate_hours` when creating/updating leaf tasks. Default true.
|
|
53
|
+
#[serde(default = "default_require_estimate_hours")]
|
|
54
|
+
pub require_estimate_hours: bool,
|
|
55
|
+
/// Multiplier applied to AI-entered `estimate_hours` to derive the
|
|
56
|
+
/// adjusted (AI-effort) estimate at aggregation time. Default 0.2.
|
|
57
|
+
#[serde(default = "default_ai_estimate_multiplier")]
|
|
58
|
+
pub ai_estimate_multiplier: f64,
|
|
52
59
|
#[serde(default)]
|
|
53
60
|
pub context_files: Vec<String>,
|
|
61
|
+
/// Master switch for the memory feature (save/query/cleanup). Default true.
|
|
62
|
+
#[serde(default = "default_memory_enabled")]
|
|
63
|
+
pub memory_enabled: bool,
|
|
64
|
+
/// Jaccard threshold above which `memory_save` treats a save as a
|
|
65
|
+
/// near-duplicate `conflict` for the AI to merge. Default 0.72.
|
|
66
|
+
#[serde(default = "default_memory_dup_threshold")]
|
|
67
|
+
pub memory_dup_threshold: f64,
|
|
68
|
+
/// BM25 relevance floor for `memory_query`; scores below are not returned.
|
|
69
|
+
/// Default 0.5.
|
|
70
|
+
#[serde(default = "default_memory_query_min_score")]
|
|
71
|
+
pub memory_query_min_score: f64,
|
|
72
|
+
/// Maximum number of memories `memory_query` returns per call. Default 5.
|
|
73
|
+
#[serde(default = "default_memory_query_limit")]
|
|
74
|
+
pub memory_query_limit: u32,
|
|
75
|
+
/// Days after which an un(re)referenced memory is flagged `stale` by
|
|
76
|
+
/// `memory_cleanup`. Default 60.
|
|
77
|
+
#[serde(default = "default_memory_stale_days")]
|
|
78
|
+
pub memory_stale_days: i64,
|
|
79
|
+
/// Age (days) past which `memory_cleanup` garbage-collects a per-session
|
|
80
|
+
/// `injected/` sidecar. Default 14.
|
|
81
|
+
#[serde(default = "default_memory_injected_gc_days")]
|
|
82
|
+
pub memory_injected_gc_days: i64,
|
|
54
83
|
#[serde(default)]
|
|
55
84
|
pub custom_fields: HashMap<String, toml::Value>,
|
|
56
85
|
}
|
|
@@ -190,6 +219,38 @@ fn default_auto_git_summary() -> bool {
|
|
|
190
219
|
true
|
|
191
220
|
}
|
|
192
221
|
|
|
222
|
+
fn default_require_estimate_hours() -> bool {
|
|
223
|
+
true
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fn default_ai_estimate_multiplier() -> f64 {
|
|
227
|
+
0.2
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fn default_memory_enabled() -> bool {
|
|
231
|
+
true
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn default_memory_dup_threshold() -> f64 {
|
|
235
|
+
0.72
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fn default_memory_query_min_score() -> f64 {
|
|
239
|
+
0.5
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn default_memory_query_limit() -> u32 {
|
|
243
|
+
5
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fn default_memory_stale_days() -> i64 {
|
|
247
|
+
60
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fn default_memory_injected_gc_days() -> i64 {
|
|
251
|
+
14
|
|
252
|
+
}
|
|
253
|
+
|
|
193
254
|
fn default_scan_dirs() -> Vec<String> {
|
|
194
255
|
vec!["~/pro/".to_string()]
|
|
195
256
|
}
|
|
@@ -200,7 +261,15 @@ impl Default for SettingsConfig {
|
|
|
200
261
|
history_limit: default_history_limit(),
|
|
201
262
|
done_task_limit: default_done_task_limit(),
|
|
202
263
|
auto_git_summary: default_auto_git_summary(),
|
|
264
|
+
require_estimate_hours: default_require_estimate_hours(),
|
|
265
|
+
ai_estimate_multiplier: default_ai_estimate_multiplier(),
|
|
203
266
|
context_files: Vec::new(),
|
|
267
|
+
memory_enabled: default_memory_enabled(),
|
|
268
|
+
memory_dup_threshold: default_memory_dup_threshold(),
|
|
269
|
+
memory_query_min_score: default_memory_query_min_score(),
|
|
270
|
+
memory_query_limit: default_memory_query_limit(),
|
|
271
|
+
memory_stale_days: default_memory_stale_days(),
|
|
272
|
+
memory_injected_gc_days: default_memory_injected_gc_days(),
|
|
204
273
|
custom_fields: HashMap::new(),
|
|
205
274
|
}
|
|
206
275
|
}
|