From e72b6ecbc07fe329b5d6a8f24da7a30619b409ae Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Wed, 16 Sep 2026 12:18:02 +0530 Subject: [PATCH] refactor(tool): migrate RequestCredential/Debugger/DependencyAudit/Git/GitHub/ImportOrganizer to typed schemas Single-source Schema()+Parameters() pattern, Execute parses via DecodeInput[T], wire-shape parity tests covering enums, array items, and numeric bounds. DebuggerInput exported (was debugParams). --- internal/tool/credential_gate.go | 38 ++++++------ internal/tool/debugger.go | 58 ++++++++---------- internal/tool/debugger_test.go | 26 ++++---- internal/tool/dependency_audit.go | 48 +++++++++------ internal/tool/git.go | 43 +++++++------ internal/tool/github.go | 51 +++++++++------- internal/tool/import_organizer.go | 37 ++++++----- internal/tool/schema_batch_test.go | 98 ++++++++++++++++++++++++++++++ 8 files changed, 264 insertions(+), 135 deletions(-) diff --git a/internal/tool/credential_gate.go b/internal/tool/credential_gate.go index f056eef03..0d3744c0c 100644 --- a/internal/tool/credential_gate.go +++ b/internal/tool/credential_gate.go @@ -42,32 +42,36 @@ func (RequestCredentialTool) Description() string { "available inside the sandbox. Use this when a command fails due to missing credentials." } -func (RequestCredentialTool) Parameters() map[string]interface{} { - return map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "credential": map[string]interface{}{ - "type": "string", - "description": "Credential ID to request. One of: gitconfig, kube, aws, gh, docker, gnupg, terraform.", - }, - "reason": map[string]interface{}{ - "type": "string", - "description": "Why this credential is needed (e.g. 'run kubectl get pods').", - }, +// Schema returns the typed input schema. Parameters() delegates to it so the +// two cannot diverge. +func (RequestCredentialTool) Schema() ToolSchema { + return ToolSchema{ + Type: "object", + Properties: map[string]SchemaProperty{ + "credential": {Type: "string", Description: "Credential ID to request. One of: gitconfig, kube, aws, gh, docker, gnupg, terraform."}, + "reason": {Type: "string", Description: "Why this credential is needed (e.g. 'run kubectl get pods')."}, }, - "required": []string{"credential", "reason"}, + Required: []string{"credential", "reason"}, } } -type credentialInput struct { +func (RequestCredentialTool) Parameters() map[string]interface{} { + return requestCredentialSchema.ToJSONSchema() +} + +// requestCredentialSchema is the single source of truth for RequestCredential's input schema. +var requestCredentialSchema = RequestCredentialTool{}.Schema() + +// RequestCredentialInput is the typed input for RequestCredentialTool. +type RequestCredentialInput struct { Credential string `json:"credential"` Reason string `json:"reason"` } func (t RequestCredentialTool) Execute(ctx context.Context, input json.RawMessage) (string, error) { - var p credentialInput - if err := json.Unmarshal(input, &p); err != nil { - return "", fmt.Errorf("invalid RequestCredential input: %w", err) + p, err := DecodeInput[RequestCredentialInput]("RequestCredential", input) + if err != nil { + return "", err } if p.Credential == "" { return "", fmt.Errorf("credential is required") diff --git a/internal/tool/debugger.go b/internal/tool/debugger.go index e578b579e..68c7911b0 100644 --- a/internal/tool/debugger.go +++ b/internal/tool/debugger.go @@ -25,34 +25,30 @@ func (DebuggerTool) Description() string { Prefer this over adding print statements when you need to understand runtime state.` } -func (DebuggerTool) Parameters() map[string]interface{} { - return map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "action": map[string]interface{}{ - "type": "string", - "description": "Action: breakpoint, run, inspect, step, continue, stack", - "enum": []string{"breakpoint", "run", "inspect", "step", "continue", "stack"}, - }, - "file": map[string]interface{}{ - "type": "string", - "description": "File path (for breakpoint action)", - }, - "line": map[string]interface{}{ - "type": "integer", - "description": "Line number (for breakpoint action)", - }, - "expression": map[string]interface{}{ - "type": "string", - "description": "Expression to evaluate (for inspect action)", - }, +// Schema returns the typed input schema. Parameters() delegates to it so the +// two cannot diverge. +func (DebuggerTool) Schema() ToolSchema { + return ToolSchema{ + Type: "object", + Properties: map[string]SchemaProperty{ + "action": {Type: "string", Enum: []interface{}{"breakpoint", "run", "inspect", "step", "continue", "stack"}, Description: "Action: breakpoint, run, inspect, step, continue, stack"}, + "file": {Type: "string", Description: "File path (for breakpoint action)"}, + "line": {Type: "integer", Description: "Line number (for breakpoint action)"}, + "expression": {Type: "string", Description: "Expression to evaluate (for inspect action)"}, }, - "required": []string{"action"}, + Required: []string{"action"}, } } -// debugParams holds the parsed input parameters. -type debugParams struct { +func (DebuggerTool) Parameters() map[string]interface{} { + return debuggerSchema.ToJSONSchema() +} + +// debuggerSchema is the single source of truth for Debugger's input schema. +var debuggerSchema = DebuggerTool{}.Schema() + +// DebuggerInput is the typed input for DebuggerTool. +type DebuggerInput struct { Action string `json:"action"` File string `json:"file"` Line int `json:"line"` @@ -60,9 +56,9 @@ type debugParams struct { } func (DebuggerTool) Execute(ctx context.Context, input json.RawMessage) (string, error) { - var p debugParams - if err := json.Unmarshal(input, &p); err != nil { - return "", fmt.Errorf("invalid input: %w", err) + p, err := DecodeInput[DebuggerInput]("Debug", input) + if err != nil { + return "", err } if err := validateDebugParams(p); err != nil { @@ -88,7 +84,7 @@ func (DebuggerTool) Execute(ctx context.Context, input json.RawMessage) (string, } // validateDebugParams ensures required fields are present for each action. -func validateDebugParams(p debugParams) error { +func validateDebugParams(p DebuggerInput) error { switch p.Action { case "": return fmt.Errorf("action is required") @@ -126,7 +122,7 @@ func detectDebugLanguage(file string) string { } } -func debugBreakpoint(ctx context.Context, p debugParams) (string, error) { +func debugBreakpoint(ctx context.Context, p DebuggerInput) (string, error) { lang := detectDebugLanguage(p.File) switch lang { case "go": @@ -148,7 +144,7 @@ func debugBreakpoint(ctx context.Context, p debugParams) (string, error) { } } -func debugRun(ctx context.Context, p debugParams) (string, error) { +func debugRun(ctx context.Context, p DebuggerInput) (string, error) { file := p.File if file == "" { file = "." @@ -184,7 +180,7 @@ func debugRun(ctx context.Context, p debugParams) (string, error) { } } -func debugInspect(ctx context.Context, p debugParams) (string, error) { +func debugInspect(ctx context.Context, p DebuggerInput) (string, error) { // For Go, use dlv eval. cmd := exec.CommandContext(ctx, "dlv", "eval", p.Expression) // #nosec G204 -- debugger/interpreter invocation with file path or expression from tool params out, err := cmd.CombinedOutput() diff --git a/internal/tool/debugger_test.go b/internal/tool/debugger_test.go index 6d9e8d9e1..f14d362ad 100644 --- a/internal/tool/debugger_test.go +++ b/internal/tool/debugger_test.go @@ -42,73 +42,73 @@ func TestDebuggerTool_Metadata(t *testing.T) { func TestDebuggerTool_ValidateParams(t *testing.T) { tests := []struct { name string - params debugParams + params DebuggerInput wantErr bool errMsg string }{ { name: "empty action", - params: debugParams{}, + params: DebuggerInput{}, wantErr: true, errMsg: "action is required", }, { name: "breakpoint without file", - params: debugParams{Action: "breakpoint", Line: 10}, + params: DebuggerInput{Action: "breakpoint", Line: 10}, wantErr: true, errMsg: "file is required", }, { name: "breakpoint without line", - params: debugParams{Action: "breakpoint", File: "main.go"}, + params: DebuggerInput{Action: "breakpoint", File: "main.go"}, wantErr: true, errMsg: "line must be a positive integer", }, { name: "breakpoint with negative line", - params: debugParams{Action: "breakpoint", File: "main.go", Line: -1}, + params: DebuggerInput{Action: "breakpoint", File: "main.go", Line: -1}, wantErr: true, errMsg: "line must be a positive integer", }, { name: "inspect without expression", - params: debugParams{Action: "inspect"}, + params: DebuggerInput{Action: "inspect"}, wantErr: true, errMsg: "expression is required", }, { name: "valid breakpoint", - params: debugParams{Action: "breakpoint", File: "main.go", Line: 10}, + params: DebuggerInput{Action: "breakpoint", File: "main.go", Line: 10}, wantErr: false, }, { name: "valid inspect", - params: debugParams{Action: "inspect", Expression: "x + 1"}, + params: DebuggerInput{Action: "inspect", Expression: "x + 1"}, wantErr: false, }, { name: "valid run", - params: debugParams{Action: "run"}, + params: DebuggerInput{Action: "run"}, wantErr: false, }, { name: "valid step", - params: debugParams{Action: "step"}, + params: DebuggerInput{Action: "step"}, wantErr: false, }, { name: "valid continue", - params: debugParams{Action: "continue"}, + params: DebuggerInput{Action: "continue"}, wantErr: false, }, { name: "valid stack", - params: debugParams{Action: "stack"}, + params: DebuggerInput{Action: "stack"}, wantErr: false, }, { name: "unknown action", - params: debugParams{Action: "dance"}, + params: DebuggerInput{Action: "dance"}, wantErr: true, errMsg: "unknown action", }, diff --git a/internal/tool/dependency_audit.go b/internal/tool/dependency_audit.go index ad5df3b82..14954ef6e 100644 --- a/internal/tool/dependency_audit.go +++ b/internal/tool/dependency_audit.go @@ -24,30 +24,38 @@ func (DependencyAuditTool) Description() string { return "Audit dependency integrity and report outdated packages without installing or changing anything. Supports Go, npm, Python, and Cargo projects with structured results." } -func (DependencyAuditTool) Parameters() map[string]interface{} { - return map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "action": map[string]interface{}{ - "type": "string", - "enum": []string{"check", "outdated", "all"}, - "description": "check validates dependency integrity; outdated reports available updates; all runs both.", - }, - "path": map[string]interface{}{"type": "string", "description": "Project directory (default: session working directory)."}, - "timeout_seconds": map[string]interface{}{"type": "integer", "minimum": 1, "maximum": 300, "description": "Per-command timeout (default 60 seconds)."}, +// Schema returns the typed input schema. Parameters() delegates to it so the +// two cannot diverge. +func (DependencyAuditTool) Schema() ToolSchema { + return ToolSchema{ + Type: "object", + Properties: map[string]SchemaProperty{ + "action": {Type: "string", Enum: []interface{}{"check", "outdated", "all"}, Description: "check validates dependency integrity; outdated reports available updates; all runs both."}, + "path": {Type: "string", Description: "Project directory (default: session working directory)."}, + "timeout_seconds": {Type: "integer", Minimum: 1, Maximum: 300, Description: "Per-command timeout (default 60 seconds)."}, }, - "required": []string{"action"}, + Required: []string{"action"}, } } +func (DependencyAuditTool) Parameters() map[string]interface{} { + return dependencyAuditSchema.ToJSONSchema() +} + +// dependencyAuditSchema is the single source of truth for DependencyAudit's input schema. +var dependencyAuditSchema = DependencyAuditTool{}.Schema() + +// DependencyAuditInput is the typed input for DependencyAuditTool. +type DependencyAuditInput struct { + Action string `json:"action"` + Path string `json:"path"` + TimeoutSeconds int `json:"timeout_seconds"` +} + func (DependencyAuditTool) Execute(ctx context.Context, input json.RawMessage) (string, error) { - var params struct { - Action string `json:"action"` - Path string `json:"path"` - TimeoutSeconds int `json:"timeout_seconds"` - } - if err := json.Unmarshal(input, ¶ms); err != nil { - return "", fmt.Errorf("invalid input: %w", err) + params, err := DecodeInput[DependencyAuditInput]("DependencyAudit", input) + if err != nil { + return "", err } params.Action = strings.ToLower(strings.TrimSpace(params.Action)) if params.Action != "check" && params.Action != "outdated" && params.Action != "all" { @@ -67,7 +75,7 @@ func (DependencyAuditTool) Execute(ctx context.Context, input json.RawMessage) ( root, _ = os.Getwd() } } - root, err := filepath.Abs(root) + root, err = filepath.Abs(root) if err != nil { return "", fmt.Errorf("resolve project path: %w", err) } diff --git a/internal/tool/git.go b/internal/tool/git.go index 9aff79c92..59a348536 100644 --- a/internal/tool/git.go +++ b/internal/tool/git.go @@ -44,30 +44,35 @@ func (GitTool) Description() string { return "Run git commands in the project worktree. Supports: status, diff, log, show, branch, checkout, add, commit, pull, push, fetch, stash, rebase, merge, reset, tag." } -func (GitTool) Parameters() map[string]interface{} { - return map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "subcommand": map[string]interface{}{ - "type": "string", - "description": "Git subcommand to run (e.g. status, diff, add, commit)", - }, - "args": map[string]interface{}{ - "type": "array", - "items": map[string]interface{}{"type": "string"}, - "description": "Arguments for the subcommand (e.g. [\"-m\", \"fix bug\"])", - }, +// Schema returns the typed input schema. Parameters() delegates to it so the +// two cannot diverge. +func (GitTool) Schema() ToolSchema { + return ToolSchema{ + Type: "object", + Properties: map[string]SchemaProperty{ + "subcommand": {Type: "string", Description: "Git subcommand to run (e.g. status, diff, add, commit)"}, + "args": {Type: "array", Items: &SchemaProperty{Type: "string"}, Description: "Arguments for the subcommand (e.g. [\"-m\", \"fix bug\"])"}, }, - "required": []string{"subcommand"}, + Required: []string{"subcommand"}, } } +func (GitTool) Parameters() map[string]interface{} { + return gitSchema.ToJSONSchema() +} + +// gitSchema is the single source of truth for Git's input schema. +var gitSchema = GitTool{}.Schema() + +// GitInput is the typed input for GitTool. +type GitInput struct { + Subcommand string `json:"subcommand"` + Args []string `json:"args,omitempty"` +} + func (t GitTool) Execute(ctx context.Context, input json.RawMessage) (string, error) { - var in struct { - Subcommand string `json:"subcommand"` - Args []string `json:"args,omitempty"` - } - if err := json.Unmarshal(input, &in); err != nil { + in, err := DecodeInput[GitInput]("Git", input) + if err != nil { return "", err } diff --git a/internal/tool/github.go b/internal/tool/github.go index 29c593ff2..4594f8edc 100644 --- a/internal/tool/github.go +++ b/internal/tool/github.go @@ -23,31 +23,40 @@ func (GitHubTool) Description() string { return "Inspect GitHub repositories, pull requests, issues, checks, and workflow runs through the authenticated gh CLI. Read-only; creating, merging, commenting, and pushing require explicit Git/Bash workflows." } -func (GitHubTool) Parameters() map[string]interface{} { - return map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "action": map[string]interface{}{ - "type": "string", - "enum": []string{"auth_status", "repo", "pr_list", "pr_view", "pr_diff", "pr_checks", "issue_list", "issue_view", "run_list"}, - }, - "ref": map[string]interface{}{"type": "string", "description": "PR, issue, or workflow reference (number, URL, or branch where supported)."}, - "limit": map[string]interface{}{"type": "integer", "minimum": 1, "maximum": 50, "description": "Maximum records for list actions (default 20)."}, - "path": map[string]interface{}{"type": "string", "description": "Repository working directory (default: session working directory)."}, +// Schema returns the typed input schema. Parameters() delegates to it so the +// two cannot diverge. +func (GitHubTool) Schema() ToolSchema { + return ToolSchema{ + Type: "object", + Properties: map[string]SchemaProperty{ + "action": {Type: "string", Enum: []interface{}{"auth_status", "repo", "pr_list", "pr_view", "pr_diff", "pr_checks", "issue_list", "issue_view", "run_list"}}, + "ref": {Type: "string", Description: "PR, issue, or workflow reference (number, URL, or branch where supported)."}, + "limit": {Type: "integer", Minimum: 1, Maximum: 50, Description: "Maximum records for list actions (default 20)."}, + "path": {Type: "string", Description: "Repository working directory (default: session working directory)."}, }, - "required": []string{"action"}, + Required: []string{"action"}, } } +func (GitHubTool) Parameters() map[string]interface{} { + return gitHubSchema.ToJSONSchema() +} + +// gitHubSchema is the single source of truth for GitHub's input schema. +var gitHubSchema = GitHubTool{}.Schema() + +// GitHubInput is the typed input for GitHubTool. +type GitHubInput struct { + Action string `json:"action"` + Ref string `json:"ref"` + Limit int `json:"limit"` + Path string `json:"path"` +} + func (GitHubTool) Execute(ctx context.Context, input json.RawMessage) (string, error) { - var params struct { - Action string `json:"action"` - Ref string `json:"ref"` - Limit int `json:"limit"` - Path string `json:"path"` - } - if err := json.Unmarshal(input, ¶ms); err != nil { - return "", fmt.Errorf("invalid input: %w", err) + params, err := DecodeInput[GitHubInput]("GitHub", input) + if err != nil { + return "", err } params.Action = strings.ToLower(strings.TrimSpace(params.Action)) if params.Limit <= 0 { @@ -68,7 +77,7 @@ func (GitHubTool) Execute(ctx context.Context, input json.RawMessage) (string, e root, _ = os.Getwd() } } - root, err := filepath.Abs(root) + root, err = filepath.Abs(root) if err != nil { return "", fmt.Errorf("resolve repository path: %w", err) } diff --git a/internal/tool/import_organizer.go b/internal/tool/import_organizer.go index 91f28b069..1a1311897 100644 --- a/internal/tool/import_organizer.go +++ b/internal/tool/import_organizer.go @@ -783,25 +783,34 @@ func (ImportOrganizerTool) Description() string { return "Organize and fix imports in Go and TypeScript files. Groups imports by category, sorts alphabetically, and removes unused imports." } -func (ImportOrganizerTool) Parameters() map[string]interface{} { - return map[string]interface{}{ - "type": "object", - "properties": map[string]interface{}{ - "path": map[string]interface{}{ - "type": "string", - "description": "Absolute path to the file to organize imports in", - }, +// Schema returns the typed input schema. Parameters() delegates to it so the +// two cannot diverge. +func (ImportOrganizerTool) Schema() ToolSchema { + return ToolSchema{ + Type: "object", + Properties: map[string]SchemaProperty{ + "path": {Type: "string", Description: "Absolute path to the file to organize imports in"}, }, - "required": []string{"path"}, + Required: []string{"path"}, } } +func (ImportOrganizerTool) Parameters() map[string]interface{} { + return importOrganizerSchema.ToJSONSchema() +} + +// importOrganizerSchema is the single source of truth for ImportOrganizer's input schema. +var importOrganizerSchema = ImportOrganizerTool{}.Schema() + +// ImportOrganizerInput is the typed input for ImportOrganizerTool. +type ImportOrganizerInput struct { + Path string `json:"path"` +} + func (ImportOrganizerTool) Execute(ctx context.Context, input json.RawMessage) (string, error) { - var p struct { - Path string `json:"path"` - } - if err := json.Unmarshal(input, &p); err != nil { - return "", fmt.Errorf("invalid input: %w", err) + p, err := DecodeInput[ImportOrganizerInput]("ImportOrganizer", input) + if err != nil { + return "", err } if p.Path == "" { return "", fmt.Errorf("path is required") diff --git a/internal/tool/schema_batch_test.go b/internal/tool/schema_batch_test.go index ffcae7e4d..93bbde204 100644 --- a/internal/tool/schema_batch_test.go +++ b/internal/tool/schema_batch_test.go @@ -478,3 +478,101 @@ func TestAppVerifySchemaProvider(t *testing.T) { t.Fatalf("required = %v, want [action]", AppVerifyTool{}.Parameters()["required"]) } } + +func TestRequestCredentialSchemaProvider(t *testing.T) { + compat := &RequestCredentialTool{} + var _ SchemaProvider = compat + props := schemaProps(t, compat.Parameters()) + if props["credential"].(map[string]interface{})["type"] != "string" { + t.Fatal("credential type wrong") + } + if props["reason"].(map[string]interface{})["type"] != "string" { + t.Fatal("reason type wrong") + } + req, _ := compat.Parameters()["required"].([]string) + if len(req) != 2 || req[0] != "credential" || req[1] != "reason" { + t.Fatalf("required = %v, want [credential reason]", compat.Parameters()["required"]) + } +} + +func TestDebuggerSchemaProvider(t *testing.T) { + var _ SchemaProvider = DebuggerTool{} + props := schemaProps(t, DebuggerTool{}.Parameters()) + action, ok := props["action"].(map[string]interface{}) + if !ok || action["type"] != "string" { + t.Fatalf("action prop = %v, want string", props["action"]) + } + enum, ok := action["enum"].([]interface{}) + if !ok || len(enum) != 6 || enum[0] != "breakpoint" || enum[5] != "stack" { + t.Fatalf("action enum = %v, want 6 options", action["enum"]) + } + req, _ := DebuggerTool{}.Parameters()["required"].([]string) + if len(req) != 1 || req[0] != "action" { + t.Fatalf("required = %v, want [action]", DebuggerTool{}.Parameters()["required"]) + } +} + +func TestDependencyAuditSchemaProvider(t *testing.T) { + var _ SchemaProvider = DependencyAuditTool{} + props := schemaProps(t, DependencyAuditTool{}.Parameters()) + enum, ok := props["action"].(map[string]interface{})["enum"].([]interface{}) + if !ok || len(enum) != 3 || enum[0] != "check" { + t.Fatalf("action enum = %v, want [check outdated all]", props["action"]) + } + ts := props["timeout_seconds"].(map[string]interface{}) + if ts["minimum"] != 1 || ts["maximum"] != 300 { + t.Fatalf("timeout_seconds bounds = %v/%v, want 1/300", ts["minimum"], ts["maximum"]) + } + req, _ := DependencyAuditTool{}.Parameters()["required"].([]string) + if len(req) != 1 || req[0] != "action" { + t.Fatalf("required = %v, want [action]", DependencyAuditTool{}.Parameters()["required"]) + } +} + +func TestGitSchemaProvider(t *testing.T) { + var _ SchemaProvider = GitTool{} + props := schemaProps(t, GitTool{}.Parameters()) + if props["subcommand"].(map[string]interface{})["type"] != "string" { + t.Fatal("subcommand type wrong") + } + args, ok := props["args"].(map[string]interface{}) + if !ok || args["type"] != "array" { + t.Fatalf("args prop = %v, want array", props["args"]) + } + if args["items"].(map[string]interface{})["type"] != "string" { + t.Fatalf("args items = %v, want string", args["items"]) + } + req, _ := GitTool{}.Parameters()["required"].([]string) + if len(req) != 1 || req[0] != "subcommand" { + t.Fatalf("required = %v, want [subcommand]", GitTool{}.Parameters()["required"]) + } +} + +func TestGitHubSchemaProvider(t *testing.T) { + var _ SchemaProvider = GitHubTool{} + props := schemaProps(t, GitHubTool{}.Parameters()) + enum, ok := props["action"].(map[string]interface{})["enum"].([]interface{}) + if !ok || len(enum) != 9 { + t.Fatalf("action enum = %v, want 9 options", props["action"]) + } + l := props["limit"].(map[string]interface{}) + if l["minimum"] != 1 || l["maximum"] != 50 { + t.Fatalf("limit bounds = %v/%v, want 1/50", l["minimum"], l["maximum"]) + } + req, _ := GitHubTool{}.Parameters()["required"].([]string) + if len(req) != 1 || req[0] != "action" { + t.Fatalf("required = %v, want [action]", GitHubTool{}.Parameters()["required"]) + } +} + +func TestImportOrganizerSchemaProvider(t *testing.T) { + var _ SchemaProvider = ImportOrganizerTool{} + props := schemaProps(t, ImportOrganizerTool{}.Parameters()) + if props["path"].(map[string]interface{})["type"] != "string" { + t.Fatal("path type wrong") + } + req, _ := ImportOrganizerTool{}.Parameters()["required"].([]string) + if len(req) != 1 || req[0] != "path" { + t.Fatalf("required = %v, want [path]", ImportOrganizerTool{}.Parameters()["required"]) + } +}