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.
Files changed (72) hide show
  1. package/README.md +31 -4
  2. package/bin/handoff-mcp.js +56 -13
  3. package/bin/resolve-binary.js +122 -0
  4. package/package.json +14 -9
  5. package/Cargo.lock +0 -686
  6. package/Cargo.toml +0 -30
  7. package/scripts/cargo-env.sh +0 -29
  8. package/scripts/handoff-memory-hook.py +0 -208
  9. package/scripts/install-local.sh +0 -109
  10. package/scripts/postinstall.js +0 -50
  11. package/scripts/sync-plugin-skills.sh +0 -35
  12. package/scripts/sync-plugin-version.sh +0 -85
  13. package/scripts/sync-workflow-inline.sh +0 -138
  14. package/src/cli.rs +0 -551
  15. package/src/context/injection.rs +0 -276
  16. package/src/context/mod.rs +0 -129
  17. package/src/lib.rs +0 -5
  18. package/src/main.rs +0 -157
  19. package/src/mcp/handlers/assignees.rs +0 -254
  20. package/src/mcp/handlers/auto_schedule.rs +0 -489
  21. package/src/mcp/handlers/bulk_update.rs +0 -155
  22. package/src/mcp/handlers/calendar.rs +0 -196
  23. package/src/mcp/handlers/capacity.rs +0 -318
  24. package/src/mcp/handlers/check_criterion.rs +0 -70
  25. package/src/mcp/handlers/config.rs +0 -402
  26. package/src/mcp/handlers/config_crud.rs +0 -183
  27. package/src/mcp/handlers/dashboard.rs +0 -214
  28. package/src/mcp/handlers/docs.rs +0 -2288
  29. package/src/mcp/handlers/docs_query.rs +0 -1335
  30. package/src/mcp/handlers/fork_session.rs +0 -91
  31. package/src/mcp/handlers/get_session.rs +0 -48
  32. package/src/mcp/handlers/get_task.rs +0 -53
  33. package/src/mcp/handlers/import_context.rs +0 -470
  34. package/src/mcp/handlers/init.rs +0 -28
  35. package/src/mcp/handlers/list_sessions.rs +0 -187
  36. package/src/mcp/handlers/list_tasks.rs +0 -308
  37. package/src/mcp/handlers/load_context.rs +0 -361
  38. package/src/mcp/handlers/log_time.rs +0 -67
  39. package/src/mcp/handlers/memory.rs +0 -961
  40. package/src/mcp/handlers/merge_sessions.rs +0 -103
  41. package/src/mcp/handlers/metrics.rs +0 -196
  42. package/src/mcp/handlers/milestones.rs +0 -102
  43. package/src/mcp/handlers/mod.rs +0 -140
  44. package/src/mcp/handlers/refer.rs +0 -307
  45. package/src/mcp/handlers/referrals.rs +0 -74
  46. package/src/mcp/handlers/save_context.rs +0 -354
  47. package/src/mcp/handlers/task_checklist.rs +0 -507
  48. package/src/mcp/handlers/timer.rs +0 -529
  49. package/src/mcp/handlers/update_session.rs +0 -197
  50. package/src/mcp/handlers/update_task.rs +0 -452
  51. package/src/mcp/mod.rs +0 -6
  52. package/src/mcp/protocol.rs +0 -41
  53. package/src/mcp/resources.rs +0 -57
  54. package/src/mcp/router.rs +0 -154
  55. package/src/mcp/tools.rs +0 -1522
  56. package/src/mcp/types.rs +0 -108
  57. package/src/setup.rs +0 -1212
  58. package/src/storage/config.rs +0 -578
  59. package/src/storage/docs/frontmatter.rs +0 -509
  60. package/src/storage/docs/mod.rs +0 -835
  61. package/src/storage/docs/model.rs +0 -708
  62. package/src/storage/docs/reassemble.rs +0 -167
  63. package/src/storage/docs/split.rs +0 -377
  64. package/src/storage/git.rs +0 -47
  65. package/src/storage/memory/injected.rs +0 -340
  66. package/src/storage/memory/mod.rs +0 -236
  67. package/src/storage/memory/model.rs +0 -127
  68. package/src/storage/mod.rs +0 -96
  69. package/src/storage/referrals.rs +0 -248
  70. package/src/storage/sessions.rs +0 -859
  71. package/src/storage/tasks.rs +0 -957
  72. package/templates/claude-md-section.md +0 -12
package/src/cli.rs DELETED
@@ -1,551 +0,0 @@
1
- //! CLI subcommand routing layer.
2
- //!
3
- //! Translates `handoff-mcp <group> <action> [--key value ...]` into a
4
- //! `serde_json::Value` and delegates to the existing MCP handler. All output
5
- //! goes to stdout as JSON for programmatic consumption.
6
-
7
- use serde_json::{json, Value};
8
-
9
- use crate::mcp::handlers;
10
-
11
- /// Entry point called from `main()` when `args[1]` matches a known CLI group.
12
- /// Returns the exit code (0 = success, 1 = error).
13
- pub fn run(args: &[String]) -> i32 {
14
- let result = dispatch(args);
15
- match result {
16
- Ok(output) => {
17
- println!("{output}");
18
- 0
19
- }
20
- Err(e) => {
21
- let err = json!({ "error": format!("{e:#}") });
22
- println!(
23
- "{}",
24
- serde_json::to_string_pretty(&err).unwrap_or_else(|_| err.to_string())
25
- );
26
- 1
27
- }
28
- }
29
- }
30
-
31
- fn dispatch(args: &[String]) -> anyhow::Result<String> {
32
- let group = args.first().map(String::as_str).unwrap_or("");
33
- let second = args.get(1).map(String::as_str).unwrap_or("");
34
-
35
- // If the second arg is a flag (starts with --), treat it as no action.
36
- let (action, flag_args) = if second.starts_with("--") || second.is_empty() {
37
- ("", if args.len() > 1 { &args[1..] } else { &[][..] })
38
- } else {
39
- (second, if args.len() > 2 { &args[2..] } else { &[][..] })
40
- };
41
-
42
- // --help anywhere in the flags → show group help.
43
- if flag_args.iter().any(|a| a == "--help" || a == "-h") {
44
- print_group_help(group);
45
- std::process::exit(0);
46
- }
47
-
48
- let tool_name = resolve_tool_name(group, action)?;
49
- let arguments = parse_flags(flag_args, &tool_name)?;
50
-
51
- // Delegate to the single dispatch table in handlers::handle_tool_call.
52
- // It returns a JsonRpcResponse wrapping the result; we extract the text.
53
- let response = handlers::handle_tool_call(&tool_name, &arguments);
54
- extract_tool_result(response)
55
- }
56
-
57
- /// Extract the content text from a JsonRpcResponse returned by the MCP handler
58
- /// dispatch. Surfaces handler-level errors as `Err` so the CLI prints them with
59
- /// exit code 1.
60
- fn extract_tool_result(response: crate::mcp::types::JsonRpcResponse) -> anyhow::Result<String> {
61
- let result = response.result.ok_or_else(|| {
62
- let msg = response
63
- .error
64
- .map(|e| e.message)
65
- .unwrap_or_else(|| "Unknown error".to_string());
66
- anyhow::anyhow!("{msg}")
67
- })?;
68
-
69
- if result
70
- .get("isError")
71
- .and_then(|v| v.as_bool())
72
- .unwrap_or(false)
73
- {
74
- let text = result
75
- .get("content")
76
- .and_then(|c| c.as_array())
77
- .and_then(|arr| arr.first())
78
- .and_then(|item| item.get("text"))
79
- .and_then(|t| t.as_str())
80
- .unwrap_or("Unknown error");
81
- anyhow::bail!("{text}");
82
- }
83
-
84
- let text = result
85
- .get("content")
86
- .and_then(|c| c.as_array())
87
- .and_then(|arr| arr.first())
88
- .and_then(|item| item.get("text"))
89
- .and_then(|t| t.as_str())
90
- .unwrap_or("")
91
- .to_string();
92
-
93
- Ok(text)
94
- }
95
-
96
- /// Map `(group, action)` to the internal MCP tool name.
97
- fn resolve_tool_name(group: &str, action: &str) -> anyhow::Result<String> {
98
- let name = match (group, action) {
99
- // init (no action needed)
100
- ("init", "") => "handoff_init",
101
-
102
- // task
103
- ("task", "list") => "handoff_list_tasks",
104
- ("task", "get") => "handoff_get_task",
105
- ("task", "update" | "create") => "handoff_update_task",
106
- ("task", "check") => "handoff_check_criterion",
107
- ("task", "bulk-update") => "handoff_bulk_update_tasks",
108
- ("task", "log-time") => "handoff_log_time",
109
- ("task", "import") => "handoff_import_context",
110
-
111
- // session
112
- ("session", "load") => "handoff_load_context",
113
- ("session", "save") => "handoff_save_context",
114
- ("session", "list") => "handoff_list_sessions",
115
- ("session", "get") => "handoff_get_session",
116
- ("session", "update") => "handoff_update_session",
117
-
118
- // config
119
- ("config", "get") => "handoff_get_config",
120
- ("config", "update") => "handoff_update_config",
121
-
122
- // memory
123
- ("memory", "save") => "handoff_memory_save",
124
- ("memory", "query") => "handoff_memory_query",
125
- ("memory", "delete") => "handoff_memory_delete",
126
- ("memory", "cleanup") => "handoff_memory_cleanup",
127
-
128
- // referral
129
- ("referral", "send" | "refer") => "handoff_refer",
130
- ("referral", "list") => "handoff_list_referrals",
131
- ("referral", "get") => "handoff_get_referral",
132
- ("referral", "update") => "handoff_update_referral",
133
-
134
- // assignee
135
- ("assignee", "list") => "handoff_list_assignees",
136
- ("assignee", "add") => "handoff_add_assignee",
137
- ("assignee", "update") => "handoff_update_assignee",
138
- ("assignee", "remove") => "handoff_remove_assignee",
139
-
140
- // milestone
141
- ("milestone", "list") => "handoff_list_milestones",
142
- ("milestone", "add") => "handoff_add_milestone",
143
- ("milestone", "update") => "handoff_update_milestone",
144
- ("milestone", "remove") => "handoff_remove_milestone",
145
-
146
- // calendar / labels / project
147
- ("calendar", "update") => "handoff_update_calendar",
148
- ("labels", "update") => "handoff_update_labels",
149
- ("project", "start") => "handoff_start_project",
150
-
151
- // metrics / capacity / schedule
152
- ("metrics", "" | "get") => "handoff_get_metrics",
153
- ("capacity", "" | "get") => "handoff_get_capacity",
154
- ("schedule", "" | "auto") => "handoff_auto_schedule",
155
-
156
- // dashboard
157
- ("dashboard", "") => "handoff_dashboard",
158
-
159
- // timer
160
- ("timer", "start") => "handoff_timer_start",
161
- ("timer", "stop") => "handoff_timer_stop",
162
- ("timer", "get") => "handoff_timer_get_time",
163
-
164
- _ => {
165
- if action.is_empty() {
166
- anyhow::bail!(
167
- "Unknown command: {group}\n\nRun `handoff-mcp --help` for available commands."
168
- );
169
- } else {
170
- anyhow::bail!(
171
- "Unknown command: {group} {action}\n\nRun `handoff-mcp {group} --help` for available actions."
172
- );
173
- }
174
- }
175
- };
176
- Ok(name.to_string())
177
- }
178
-
179
- /// Parse `--key value` flags into a JSON object.
180
- ///
181
- /// Conventions:
182
- /// - `--key value` → `{"key": "value"}` (string)
183
- /// - `--key 42` → `{"key": 42}` (number if parseable)
184
- /// - `--key true/false` → `{"key": true}` (bool)
185
- /// - `--key a,b,c` → `{"key": ["a","b","c"]}` (comma-separated → array)
186
- /// - `--json-key '{"a":1}'` → parsed as JSON value (for nested objects)
187
- /// - `--flag` (no value or next arg starts with `--`) → `{"flag": true}`
188
- ///
189
- /// Dashes in key names are converted to underscores (e.g. `--project-dir` →
190
- /// `project_dir`) to match the MCP parameter naming.
191
- fn parse_flags(args: &[String], tool_name: &str) -> anyhow::Result<Value> {
192
- let mut map = serde_json::Map::new();
193
- let mut i = 0;
194
-
195
- while i < args.len() {
196
- let arg = &args[i];
197
- if !arg.starts_with("--") {
198
- anyhow::bail!("Unexpected positional argument: {arg}");
199
- }
200
- let key = arg[2..].replace('-', "_");
201
-
202
- let value = if i + 1 < args.len() && !args[i + 1].starts_with("--") {
203
- i += 1;
204
- parse_value(&args[i], &key)
205
- } else {
206
- Value::Bool(true)
207
- };
208
-
209
- // Nest into a `task` or `updates` object for tools that expect it.
210
- insert_value(&mut map, &key, value, tool_name);
211
- i += 1;
212
- }
213
-
214
- Ok(Value::Object(map))
215
- }
216
-
217
- /// Insert a value into the correct position in the JSON object.
218
- ///
219
- /// Some MCP tools expect nested objects (e.g. `handoff_update_task` wants
220
- /// `{"task": {"id": ..., "title": ...}}`). This function routes known fields
221
- /// into the right nesting level so the CLI user writes flat flags:
222
- /// `--id t1 --title "foo"` instead of `--task '{"id":"t1","title":"foo"}'`.
223
- fn insert_value(
224
- map: &mut serde_json::Map<String, Value>,
225
- key: &str,
226
- value: Value,
227
- tool_name: &str,
228
- ) {
229
- match tool_name {
230
- "handoff_update_task" => {
231
- // Fields that go into the top-level `task` object.
232
- let task_fields = [
233
- "id",
234
- "title",
235
- "status",
236
- "notes",
237
- "priority",
238
- "labels",
239
- "links",
240
- "done_criteria",
241
- "assignee",
242
- "dependencies",
243
- "order",
244
- ];
245
- // Fields that go into `task.schedule`.
246
- let schedule_fields = [
247
- "start_date",
248
- "due_date",
249
- "estimate_hours",
250
- "actual_hours",
251
- "remaining_hours",
252
- "milestone",
253
- "pinned",
254
- ];
255
-
256
- if task_fields.contains(&key) {
257
- let task = map
258
- .entry("task")
259
- .or_insert_with(|| json!({}))
260
- .as_object_mut()
261
- .expect("task must be object");
262
- task.insert(key.to_string(), value);
263
- } else if schedule_fields.contains(&key) {
264
- let task = map
265
- .entry("task")
266
- .or_insert_with(|| json!({}))
267
- .as_object_mut()
268
- .expect("task must be object");
269
- let schedule = task
270
- .entry("schedule")
271
- .or_insert_with(|| json!({}))
272
- .as_object_mut()
273
- .expect("schedule must be object");
274
- schedule.insert(key.to_string(), value);
275
- } else {
276
- map.insert(key.to_string(), value);
277
- }
278
- }
279
- _ => {
280
- map.insert(key.to_string(), value);
281
- }
282
- }
283
- }
284
-
285
- /// Fields that are always strings even when they look like numbers. Handlers
286
- /// call `.as_str()` on these, so coercing "42" to `Number(42)` would silently
287
- /// break them.
288
- const STRING_FIELDS: &[&str] = &[
289
- "task_id",
290
- "id",
291
- "session_id",
292
- "referral_id",
293
- "key",
294
- "name",
295
- "text",
296
- "kind",
297
- "summary",
298
- "status",
299
- "status_filter",
300
- "assignee_filter",
301
- "milestone_filter",
302
- "priority_filter",
303
- "label_filter",
304
- "assignee",
305
- "priority",
306
- "project_dir",
307
- "project_name",
308
- "description",
309
- "notes",
310
- "title",
311
- "display_name",
312
- "color",
313
- "target_project",
314
- "target_project_dir",
315
- "referral_type",
316
- "details",
317
- "merge_into",
318
- "tool_name",
319
- "start_date",
320
- "due_date",
321
- "date",
322
- "end_date",
323
- "schedule_mode",
324
- "session_status",
325
- "close_session_id",
326
- "pause_session_id",
327
- "move_to",
328
- "parent_id",
329
- "milestone",
330
- ];
331
-
332
- /// Fields that are always numeric. Only these are coerced from string to number.
333
- const NUMERIC_FIELDS: &[&str] = &[
334
- "hours",
335
- "estimate_hours",
336
- "actual_hours",
337
- "remaining_hours",
338
- "limit",
339
- "criterion_index",
340
- "order",
341
- "work_hours_per_day",
342
- "overwork_limit_percent",
343
- "max_utilization",
344
- "stale_days",
345
- "checklist_index",
346
- ];
347
-
348
- /// Parse a CLI flag value into a JSON type, using the field name to decide
349
- /// whether numeric coercion is appropriate.
350
- fn parse_value(s: &str, key: &str) -> Value {
351
- // Try JSON parse first (handles objects, arrays, quoted strings).
352
- if let Ok(v) = serde_json::from_str::<Value>(s) {
353
- if v.is_object() || v.is_array() {
354
- return v;
355
- }
356
- }
357
-
358
- // Known string fields — never coerce to number/bool.
359
- if STRING_FIELDS.contains(&key) {
360
- return Value::String(s.to_string());
361
- }
362
-
363
- // Boolean.
364
- if s == "true" {
365
- return Value::Bool(true);
366
- }
367
- if s == "false" {
368
- return Value::Bool(false);
369
- }
370
-
371
- // Numeric coercion — only for known numeric fields.
372
- if NUMERIC_FIELDS.contains(&key) {
373
- if let Ok(n) = s.parse::<i64>() {
374
- return Value::Number(n.into());
375
- }
376
- if let Ok(f) = s.parse::<f64>() {
377
- if let Some(n) = serde_json::Number::from_f64(f) {
378
- return Value::Number(n);
379
- }
380
- }
381
- }
382
-
383
- // Comma-separated array (only if no spaces and contains comma).
384
- if s.contains(',') && !s.contains(' ') {
385
- let arr: Vec<Value> = s.split(',').map(|p| Value::String(p.to_string())).collect();
386
- return Value::Array(arr);
387
- }
388
-
389
- Value::String(s.to_string())
390
- }
391
-
392
- /// All known CLI groups (for help display).
393
- pub const GROUPS: &[(&str, &str)] = &[
394
- ("init", "Initialize handoff tracking for a project"),
395
- (
396
- "task",
397
- "Task management (list, get, update, check, bulk-update, log-time, import)",
398
- ),
399
- (
400
- "session",
401
- "Session management (load, save, list, get, update)",
402
- ),
403
- ("config", "Configuration (get, update)"),
404
- ("memory", "Project memory (save, query, delete, cleanup)"),
405
- (
406
- "referral",
407
- "Cross-project referrals (send, list, get, update)",
408
- ),
409
- ("assignee", "Team members (list, add, update, remove)"),
410
- ("milestone", "Milestones (list, add, update, remove)"),
411
- ("calendar", "Calendar settings (update)"),
412
- ("labels", "Project labels (update)"),
413
- ("project", "Project lifecycle (start)"),
414
- ("metrics", "Project metrics"),
415
- ("capacity", "Work capacity"),
416
- ("schedule", "Auto-scheduler"),
417
- ("dashboard", "Cross-project dashboard"),
418
- ("timer", "Timer coordination (start, stop, get)"),
419
- ];
420
-
421
- pub fn print_cli_help() {
422
- println!(
423
- "handoff-mcp v{version} — CLI API
424
-
425
- USAGE:
426
- handoff-mcp <command> <action> [--key value ...]
427
-
428
- COMMANDS:",
429
- version = env!("CARGO_PKG_VERSION")
430
- );
431
- for (name, desc) in GROUPS {
432
- println!(" {name:<16}{desc}");
433
- }
434
- println!(
435
- "
436
- GLOBAL OPTIONS:
437
- --project-dir <path> Project directory (default: current directory)
438
- --help Show help for a command
439
-
440
- EXAMPLES:
441
- handoff-mcp memory save --text \"Always use atomic_write\" --kind lesson
442
- handoff-mcp memory query --text \"atomic\" --limit 5
443
- handoff-mcp task list --status-filter todo
444
- handoff-mcp session load
445
- handoff-mcp metrics"
446
- );
447
- }
448
-
449
- pub fn print_group_help(group: &str) {
450
- let actions: &[(&str, &str)] = match group {
451
- "init" => &[("", "Initialize handoff tracking (--project-name <name>)")],
452
- "task" => &[
453
- ("list", "List tasks (--status-filter, --assignee-filter, --milestone-filter, --priority-filter, --label-filter)"),
454
- ("get", "Get task detail (--task-id <id>)"),
455
- ("update", "Create or update a task (--id, --title, --status, --priority, --assignee, ...)"),
456
- ("check", "Toggle done_criteria item (--task-id, --criterion-index, --checked)"),
457
- ("bulk-update", "Bulk update tasks (--updates '[{...}]')"),
458
- ("log-time", "Log hours worked (--task-id, --hours)"),
459
- ("import", "Import context from document (--source '{...}')"),
460
- ],
461
- "session" => &[
462
- ("load", "Load context at session start (--session-id)"),
463
- ("save", "Save context at session end (--summary, --session-status, ...)"),
464
- ("list", "List sessions (--status-filter, --limit)"),
465
- ("get", "Get session detail (--session-id)"),
466
- ("update", "Update active session (--add-decision '{...}', ...)"),
467
- ],
468
- "config" => &[
469
- ("get", "Read project config"),
470
- ("update", "Update config (--updates '{...}')"),
471
- ],
472
- "memory" => &[
473
- ("save", "Save a memory (--text, --kind, --tags, --scope-paths, --force)"),
474
- ("query", "Query memories (--text, --limit, --session-id)"),
475
- ("delete", "Delete a memory (--id)"),
476
- ("cleanup", "Housekeep memory store (--apply-exact-merges, --stale-days)"),
477
- ],
478
- "referral" => &[
479
- ("send", "Send a referral to another project (--summary, --target-project, ...)"),
480
- ("list", "List incoming referrals (--status-filter)"),
481
- ("get", "Get referral detail (--referral-id)"),
482
- ("update", "Update referral status (--referral-id, --status)"),
483
- ],
484
- "assignee" => &[
485
- ("list", "List team members"),
486
- ("add", "Add assignee (--key, --display-name, ...)"),
487
- ("update", "Update assignee (--key, --display-name, ...)"),
488
- ("remove", "Remove assignee (--key)"),
489
- ],
490
- "milestone" => &[
491
- ("list", "List milestones"),
492
- ("add", "Add milestone (--name, --date, --description)"),
493
- ("update", "Update milestone (--name, --date, --description)"),
494
- ("remove", "Remove milestone (--name)"),
495
- ],
496
- "calendar" => &[
497
- ("update", "Update calendar settings (--work-hours-per-day, --closed-weekdays, ...)"),
498
- ],
499
- "labels" => &[
500
- ("update", "Set project labels (--labels a,b,c)"),
501
- ],
502
- "project" => &[
503
- ("start", "Set project start date (--start-date, --shift-dates)"),
504
- ],
505
- "metrics" => &[
506
- ("", "Get project metrics (--assignee)"),
507
- ],
508
- "capacity" => &[
509
- ("", "Get work capacity (--start-date, --end-date, --assignee)"),
510
- ],
511
- "schedule" => &[
512
- ("", "Run auto-scheduler (--dry-run, --assignee-filter, --start-date)"),
513
- ],
514
- "dashboard" => &[
515
- ("", "Show cross-project dashboard (--scan-dirs, --include-completed)"),
516
- ],
517
- "timer" => &[
518
- ("start", "Start timer for task (--task-id)"),
519
- ("stop", "Stop timer for task (--task-id)"),
520
- ("get", "Get timer state (--task-id)"),
521
- ],
522
- _ => {
523
- eprintln!("Unknown command group: {group}");
524
- eprintln!("Run `handoff-mcp --help` for available commands.");
525
- return;
526
- }
527
- };
528
-
529
- let desc = GROUPS
530
- .iter()
531
- .find(|(n, _)| *n == group)
532
- .map(|(_, d)| *d)
533
- .unwrap_or("");
534
- println!("handoff-mcp {group} — {desc}\n");
535
- println!("ACTIONS:");
536
- for (action, help) in actions {
537
- if action.is_empty() {
538
- println!(" (default) {help}");
539
- } else {
540
- println!(" {action:<16}{help}");
541
- }
542
- }
543
- println!(
544
- "\nGLOBAL OPTIONS:\n --project-dir <path> Project directory (default: current directory)"
545
- );
546
- }
547
-
548
- /// Check if this group/action is a known CLI command.
549
- pub fn is_cli_command(first_arg: &str) -> bool {
550
- GROUPS.iter().any(|(name, _)| *name == first_arg)
551
- }