- Added data model for AI-assisted renaming including structures for prompts, responses, and policies. - Created implementation plan detailing the integration of Google Genkit into the CLI for renaming tasks. - Developed quickstart guide for setting up and using the new AI rename functionality. - Documented research decisions regarding Genkit orchestration and prompt composition. - Established tasks for phased implementation, including setup, foundational work, and user stories. - Implemented contract tests to ensure AI rename policies and ledger metadata are correctly applied. - Developed integration tests for validating AI rename flows, including preview, apply, and undo functionalities. - Added tooling to pin Genkit dependency for consistent builds.
109 lines
2.5 KiB
Go
109 lines
2.5 KiB
Go
package contract
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/rogeecn/renamer/internal/ai/plan"
|
|
"github.com/rogeecn/renamer/internal/ai/prompt"
|
|
)
|
|
|
|
func TestAIApplyLedgerMetadataContract(t *testing.T) {
|
|
root := t.TempDir()
|
|
writeFile(t, filepath.Join(root, "sample.txt"))
|
|
|
|
candidate := plan.Candidate{
|
|
OriginalPath: "sample.txt",
|
|
SizeBytes: 4,
|
|
Depth: 0,
|
|
}
|
|
|
|
response := prompt.RenameResponse{
|
|
Items: []prompt.RenameItem{
|
|
{
|
|
Original: "sample.txt",
|
|
Proposed: "001_sample-final.txt",
|
|
Sequence: 1,
|
|
},
|
|
},
|
|
Model: "test-model",
|
|
PromptHash: "prompt-hash-123",
|
|
}
|
|
|
|
policy := prompt.NamingPolicyConfig{Prefix: "proj", Casing: "kebab"}
|
|
|
|
entry, err := plan.Apply(context.Background(), plan.ApplyOptions{
|
|
WorkingDir: root,
|
|
Candidates: []plan.Candidate{candidate},
|
|
Response: response,
|
|
Policies: policy,
|
|
PromptHash: response.PromptHash,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("apply: %v", err)
|
|
}
|
|
|
|
if len(entry.Operations) != 1 {
|
|
t.Fatalf("expected 1 operation, got %d", len(entry.Operations))
|
|
}
|
|
|
|
planFile := filepath.Join(root, "001_sample-final.txt")
|
|
if _, err := os.Stat(planFile); err != nil {
|
|
t.Fatalf("expected renamed file: %v", err)
|
|
}
|
|
|
|
ledgerPath := filepath.Join(root, ".renamer")
|
|
file, err := os.Open(ledgerPath)
|
|
if err != nil {
|
|
t.Fatalf("open ledger: %v", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
var lastLine string
|
|
for scanner.Scan() {
|
|
lastLine = scanner.Text()
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
t.Fatalf("scan ledger: %v", err)
|
|
}
|
|
|
|
var recorded map[string]any
|
|
if err := json.Unmarshal([]byte(lastLine), &recorded); err != nil {
|
|
t.Fatalf("unmarshal ledger entry: %v", err)
|
|
}
|
|
|
|
metaRaw, ok := recorded["metadata"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("expected metadata in ledger entry")
|
|
}
|
|
aiRaw, ok := metaRaw["ai"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("expected ai metadata in ledger entry")
|
|
}
|
|
|
|
if aiRaw["model"] != "test-model" {
|
|
t.Fatalf("expected model test-model, got %v", aiRaw["model"])
|
|
}
|
|
if aiRaw["promptHash"] != "prompt-hash-123" {
|
|
t.Fatalf("expected prompt hash recorded, got %v", aiRaw["promptHash"])
|
|
}
|
|
if aiRaw["batchSize"].(float64) != 1 {
|
|
t.Fatalf("expected batch size 1, got %v", aiRaw["batchSize"])
|
|
}
|
|
}
|
|
|
|
func writeFile(t *testing.T, path string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatalf("mkdir %s: %v", path, err)
|
|
}
|
|
if err := os.WriteFile(path, []byte("data"), 0o644); err != nil {
|
|
t.Fatalf("write file %s: %v", path, err)
|
|
}
|
|
}
|