--- phase: 07-import-cicd plan: 01 type: execute wave: 1 depends_on: [] files_modified: - pkg/importer/importer.go - pkg/importer/trufflehog.go - pkg/importer/trufflehog_test.go - pkg/importer/testdata/trufflehog-sample.json autonomous: true requirements: [IMP-01] must_haves: truths: - "TruffleHog v3 JSON output can be parsed into []engine.Finding" - "Detector names from TruffleHog are normalized to lowercase KeyHunter provider names" - "Verified flag in TruffleHog JSON maps to Finding.Verified + VerifyStatus" artifacts: - path: pkg/importer/importer.go provides: "Importer interface" contains: "type Importer interface" - path: pkg/importer/trufflehog.go provides: "TruffleHog v3 JSON parser" contains: "func (TruffleHogImporter) Import" - path: pkg/importer/testdata/trufflehog-sample.json provides: "Test fixture matching TruffleHog v3 JSON schema" key_links: - from: pkg/importer/trufflehog.go to: pkg/engine/finding.go via: "constructs engine.Finding from TruffleHog records" pattern: "engine\\.Finding\\{" --- Create the pkg/importer package with the Importer interface and the TruffleHog v3 JSON adapter. Purpose: External tool import requires a uniform contract so the CLI command (Plan 07-04) can dispatch by format flag. TruffleHog is the most widely used scanner; its v3 JSON output is the canonical import target (IMP-01). Output: Importer interface, TruffleHogImporter implementation, unit test, JSON fixture. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-import-cicd/07-CONTEXT.md @pkg/engine/finding.go From pkg/engine/finding.go: ```go type Finding struct { ProviderName string KeyValue string KeyMasked string Confidence string Source string SourceType string LineNumber int Offset int64 DetectedAt time.Time Verified bool VerifyStatus string VerifyHTTPCode int VerifyMetadata map[string]string VerifyError string } func MaskKey(key string) string ``` Task 1: Importer interface + TruffleHog parser + fixtures pkg/importer/importer.go, pkg/importer/trufflehog.go, pkg/importer/testdata/trufflehog-sample.json - Importer interface declares: Import(r io.Reader) ([]engine.Finding, error) and Name() string - TruffleHogImporter parses a JSON array of objects with fields {SourceID, SourceName, SourceMetadata (object), DetectorName, DetectorType, Verified (bool), Raw (string), Redacted (string), ExtraData (object)} - Each record maps to engine.Finding: ProviderName = normalizeTruffleHogName(DetectorName) // e.g. "OpenAI" -> "openai", "GitHubV2" -> "github", "AWS" -> "aws" KeyValue = Raw KeyMasked = engine.MaskKey(Raw) Confidence = "high" if Verified else "medium" SourceType = "import:trufflehog" Source = extractSourcePath(SourceMetadata) — traverses SourceMetadata.Data.{Git,Filesystem,Github}.file/link/repository, falls back to SourceName LineNumber = extracted from SourceMetadata.Data.Git.line if present, else 0 Verified = Verified VerifyStatus = "live" if Verified else "unverified" DetectedAt = time.Now() - normalizeTruffleHogName(): lowercase, strip trailing version digits ("GithubV2" -> "github"), map known aliases (AWS -> aws, GCP -> gcp). Unknown names: lowercased as-is. - Invalid JSON returns wrapped error - Empty array returns empty slice, nil error - Name() returns "trufflehog" Create pkg/importer/importer.go: ```go package importer import ( "io" "github.com/salvacybersec/keyhunter/pkg/engine" ) // Importer parses output from an external secret scanner and returns // normalized engine.Finding records. Implementations must be stateless // and safe for reuse across calls. type Importer interface { Name() string Import(r io.Reader) ([]engine.Finding, error) } ``` Create pkg/importer/trufflehog.go: - Define `type TruffleHogImporter struct{}` - Define `type trufflehogRecord struct` with JSON tags matching TruffleHog v3: SourceID, SourceName, SourceMetadata (json.RawMessage), DetectorName, DetectorType int, Verified bool, Raw, Redacted string, ExtraData json.RawMessage. - Implement `Name() string` returning "trufflehog". - Implement `Import(r io.Reader) ([]engine.Finding, error)`: 1. json.NewDecoder(r).Decode(&records) — if error, wrap: fmt.Errorf("decoding trufflehog json: %w", err) 2. For each record: build engine.Finding per behavior spec. Skip records with empty Raw (log-skip count via return? no — just skip silently). 3. Return slice + nil. - Implement `normalizeTruffleHogName(detector string) string`: - lowercased := strings.ToLower(detector) - trim trailing "v\d+" via regexp (package-level var `var tfhVersionSuffix = regexp.MustCompile(`v\d+$`)`) - apply alias map: {"gcp": "gcp", "aws": "aws", "openai": "openai", "anthropic": "anthropic", "huggingface": "huggingface"} - return trimmed - Implement `extractSourcePath(meta json.RawMessage) (path string, line int)`: - Unmarshal into `struct{ Data struct{ Git *struct{ File, Repository, Commit string; Line int } ; Filesystem *struct{ File string }; Github *struct{ File, Link, Repository string } } }` - Return first non-empty in priority: Git.File, Filesystem.File, Github.File, Github.Link, Git.Repository, Github.Repository. Line from Git.Line. - On unmarshal error: return "", 0 (not fatal). Create pkg/importer/testdata/trufflehog-sample.json with a realistic fixture containing 3 records: - record 1: DetectorName "OpenAI", Verified true, Raw "sk-proj-abcdef1234567890abcdef", SourceMetadata.Data.Git.File "src/config.py", Line 42 - record 2: DetectorName "AnthropicV2", Verified false, Raw "sk-ant-api03-xxxxxxxxxxxxxxxx", SourceMetadata.Data.Filesystem.File "/tmp/leaked.env" - record 3: DetectorName "AWS", Verified true, Raw "AKIAIOSFODNN7EXAMPLE", SourceMetadata.Data.Github.Link "https://github.com/foo/bar/blob/main/a.yml" Create pkg/importer/trufflehog_test.go: - TestTruffleHogImporter_Import: open testdata, call Import, assert len==3, assert findings[0].ProviderName=="openai", Confidence=="high", Verified==true, Source=="src/config.py", LineNumber==42. - TestTruffleHogImporter_NormalizeName: table test — {"OpenAI","openai"}, {"GithubV2","github"}, {"AnthropicV2","anthropic"}, {"AWS","aws"}, {"UnknownDetector","unknowndetector"}. - TestTruffleHogImporter_EmptyArray: Import(strings.NewReader("[]")) returns empty slice, nil error. - TestTruffleHogImporter_InvalidJSON: Import(strings.NewReader("not json")) returns error. - TestTruffleHogImporter_Name: asserts "trufflehog". All TruffleHog v3 field decisions per 07-CONTEXT.md decisions block. cd /home/salva/Documents/apikey && go test ./pkg/importer/... -run TruffleHog -v - pkg/importer/importer.go declares Importer interface - pkg/importer/trufflehog.go implements Importer for TruffleHog v3 JSON - All 5 tests pass - go build ./pkg/importer/... succeeds go test ./pkg/importer/... -v passes. go vet ./pkg/importer/... clean. TruffleHog v3 JSON can be loaded from disk and converted to []engine.Finding with correct provider name normalization and verify status mapping. After completion, create `.planning/phases/07-import-cicd/07-01-SUMMARY.md`.