@tanagram/cli 0.1.3 → 0.1.5

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/bin/tanagram CHANGED
Binary file
package/commands/run.go CHANGED
@@ -4,13 +4,34 @@ import (
4
4
  "context"
5
5
  "fmt"
6
6
  "os"
7
+ "path/filepath"
8
+ "sync"
9
+ "time"
7
10
 
8
11
  "github.com/tanagram/cli/checker"
9
12
  "github.com/tanagram/cli/extractor"
10
13
  "github.com/tanagram/cli/git"
14
+ "github.com/tanagram/cli/parser"
11
15
  "github.com/tanagram/cli/storage"
12
16
  )
13
17
 
18
+ // spinner shows a simple spinner animation
19
+ func spinner(stop chan bool, message string) {
20
+ chars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
21
+ i := 0
22
+ for {
23
+ select {
24
+ case <-stop:
25
+ fmt.Print("\r")
26
+ return
27
+ default:
28
+ fmt.Printf("\r%s %s", chars[i%len(chars)], message)
29
+ i++
30
+ time.Sleep(100 * time.Millisecond)
31
+ }
32
+ }
33
+ }
34
+
14
35
  // Run executes the main policy check with auto-sync
15
36
  func Run() error {
16
37
  // Find git root
@@ -50,28 +71,98 @@ func Run() error {
50
71
 
51
72
  // Auto-sync if any files changed
52
73
  if needsSync {
53
- fmt.Println("Instruction files changed, syncing with LLM...")
54
- totalPolicies := 0
74
+ fmt.Printf("\nSyncing policies with LLM (processing %d files in parallel)...\n", len(instructionFiles))
75
+
55
76
  ctx := context.Background()
56
77
 
78
+ // Result type for collecting sync results
79
+ type syncResult struct {
80
+ file string
81
+ relPath string
82
+ policies []parser.Policy
83
+ err error
84
+ }
85
+
86
+ // Channel to collect results
87
+ results := make(chan syncResult, len(instructionFiles))
88
+ var wg sync.WaitGroup
89
+
90
+ // Start spinner
91
+ stop := make(chan bool)
92
+ var completed int
93
+ var mu sync.Mutex
94
+ go func() {
95
+ chars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
96
+ i := 0
97
+ for {
98
+ select {
99
+ case <-stop:
100
+ fmt.Print("\r")
101
+ return
102
+ default:
103
+ mu.Lock()
104
+ c := completed
105
+ mu.Unlock()
106
+ fmt.Printf("\r%s Processing files... (%d/%d completed)", chars[i%len(chars)], c, len(instructionFiles))
107
+ i++
108
+ time.Sleep(100 * time.Millisecond)
109
+ }
110
+ }
111
+ }()
112
+
113
+ // Launch goroutines for each file
57
114
  for _, file := range instructionFiles {
58
- policies, err := extractor.ExtractPoliciesFromFile(ctx, file)
59
- if err != nil {
60
- return fmt.Errorf("failed to extract policies from %s: %w", file, err)
115
+ wg.Add(1)
116
+ go func(file string) {
117
+ defer wg.Done()
118
+ relPath, _ := filepath.Rel(gitRoot, file)
119
+ policies, err := extractor.ExtractPoliciesFromFile(ctx, file)
120
+ results <- syncResult{file, relPath, policies, err}
121
+ }(file)
122
+ }
123
+
124
+ // Close results channel when all goroutines complete
125
+ go func() {
126
+ wg.Wait()
127
+ close(results)
128
+ }()
129
+
130
+ // Collect results
131
+ totalPolicies := 0
132
+ for result := range results {
133
+ mu.Lock()
134
+ completed++
135
+ mu.Unlock()
136
+
137
+ if result.err != nil {
138
+ stop <- true
139
+ close(stop)
140
+ time.Sleep(50 * time.Millisecond)
141
+ fmt.Printf("\r✗ Failed to process %s\n", result.relPath)
142
+ return fmt.Errorf("failed to extract policies from %s: %w", result.file, result.err)
61
143
  }
62
144
 
63
- if err := cache.UpdateFile(file, policies); err != nil {
64
- return fmt.Errorf("failed to update cache for %s: %w", file, err)
145
+ if err := cache.UpdateFile(result.file, result.policies); err != nil {
146
+ stop <- true
147
+ close(stop)
148
+ time.Sleep(50 * time.Millisecond)
149
+ return fmt.Errorf("failed to update cache for %s: %w", result.file, err)
65
150
  }
66
151
 
67
- totalPolicies += len(policies)
152
+ totalPolicies += len(result.policies)
153
+ fmt.Printf("\r✓ %s - %d policies\n", result.relPath, len(result.policies))
68
154
  }
69
155
 
156
+ // Stop spinner
157
+ stop <- true
158
+ close(stop)
159
+ time.Sleep(50 * time.Millisecond)
160
+
70
161
  if err := cache.Save(); err != nil {
71
162
  return fmt.Errorf("failed to save cache: %w", err)
72
163
  }
73
164
 
74
- fmt.Printf("Synced %d policies from %d file(s)\n", totalPolicies, len(instructionFiles))
165
+ fmt.Printf("\n✓ Synced %d policies from %d file(s)\n", totalPolicies, len(instructionFiles))
75
166
  }
76
167
 
77
168
  // Load all policies from cache
package/commands/sync.go CHANGED
@@ -5,8 +5,11 @@ import (
5
5
  "fmt"
6
6
  "os"
7
7
  "path/filepath"
8
+ "sync"
9
+ "time"
8
10
 
9
11
  "github.com/tanagram/cli/extractor"
12
+ "github.com/tanagram/cli/parser"
10
13
  "github.com/tanagram/cli/storage"
11
14
  )
12
15
 
@@ -36,33 +39,100 @@ func Sync() error {
36
39
  return fmt.Errorf("failed to load cache: %w", err)
37
40
  }
38
41
 
39
- // Parse and sync each file using LLM
40
- totalPolicies := 0
42
+ // Parse and sync each file using LLM in parallel
43
+ fmt.Printf("\nSyncing policies with LLM (processing %d files in parallel)...\n", len(instructionFiles))
44
+
41
45
  ctx := context.Background()
42
46
 
47
+ // Result type for collecting sync results
48
+ type syncResult struct {
49
+ file string
50
+ relPath string
51
+ policies []parser.Policy
52
+ err error
53
+ }
54
+
55
+ // Channel to collect results
56
+ results := make(chan syncResult, len(instructionFiles))
57
+ var wg sync.WaitGroup
58
+
59
+ // Start spinner
60
+ stop := make(chan bool)
61
+ var completed int
62
+ var mu sync.Mutex
63
+ go func() {
64
+ chars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
65
+ i := 0
66
+ for {
67
+ select {
68
+ case <-stop:
69
+ fmt.Print("\r")
70
+ return
71
+ default:
72
+ mu.Lock()
73
+ c := completed
74
+ mu.Unlock()
75
+ fmt.Printf("\r%s Processing files... (%d/%d completed)", chars[i%len(chars)], c, len(instructionFiles))
76
+ i++
77
+ time.Sleep(100 * time.Millisecond)
78
+ }
79
+ }
80
+ }()
81
+
82
+ // Launch goroutines for each file
43
83
  for _, file := range instructionFiles {
44
- relPath, _ := filepath.Rel(gitRoot, file)
45
- fmt.Printf("Syncing %s (using LLM)...\n", relPath)
84
+ wg.Add(1)
85
+ go func(file string) {
86
+ defer wg.Done()
87
+ relPath, _ := filepath.Rel(gitRoot, file)
88
+ policies, err := extractor.ExtractPoliciesFromFile(ctx, file)
89
+ results <- syncResult{file, relPath, policies, err}
90
+ }(file)
91
+ }
46
92
 
47
- policies, err := extractor.ExtractPoliciesFromFile(ctx, file)
48
- if err != nil {
49
- return fmt.Errorf("failed to extract policies from %s: %w", file, err)
93
+ // Close results channel when all goroutines complete
94
+ go func() {
95
+ wg.Wait()
96
+ close(results)
97
+ }()
98
+
99
+ // Collect results
100
+ totalPolicies := 0
101
+ for result := range results {
102
+ mu.Lock()
103
+ completed++
104
+ mu.Unlock()
105
+
106
+ if result.err != nil {
107
+ stop <- true
108
+ close(stop)
109
+ time.Sleep(50 * time.Millisecond)
110
+ fmt.Printf("\r✗ Failed to process %s\n", result.relPath)
111
+ return fmt.Errorf("failed to extract policies from %s: %w", result.file, result.err)
50
112
  }
51
113
 
52
- if err := cache.UpdateFile(file, policies); err != nil {
53
- return fmt.Errorf("failed to update cache for %s: %w", file, err)
114
+ if err := cache.UpdateFile(result.file, result.policies); err != nil {
115
+ stop <- true
116
+ close(stop)
117
+ time.Sleep(50 * time.Millisecond)
118
+ return fmt.Errorf("failed to update cache for %s: %w", result.file, err)
54
119
  }
55
120
 
56
- fmt.Printf(" ✓ Extracted %d policies\n", len(policies))
57
- totalPolicies += len(policies)
121
+ totalPolicies += len(result.policies)
122
+ fmt.Printf("\r✓ %s - %d policies\n", result.relPath, len(result.policies))
58
123
  }
59
124
 
125
+ // Stop spinner
126
+ stop <- true
127
+ close(stop)
128
+ time.Sleep(50 * time.Millisecond)
129
+
60
130
  // Save cache
61
131
  if err := cache.Save(); err != nil {
62
132
  return fmt.Errorf("failed to save cache: %w", err)
63
133
  }
64
134
 
65
- fmt.Printf("\nSync complete! Total: %d policies from %d file(s)\n", totalPolicies, len(instructionFiles))
135
+ fmt.Printf("\n✓ Synced %d policies from %d file(s)\n", totalPolicies, len(instructionFiles))
66
136
  return nil
67
137
  }
68
138
 
package/go.mod CHANGED
@@ -5,6 +5,7 @@ go 1.23.0
5
5
  require github.com/anthropics/anthropic-sdk-go v1.17.0
6
6
 
7
7
  require (
8
+ github.com/stretchr/testify v1.9.0 // indirect
8
9
  github.com/tidwall/gjson v1.18.0 // indirect
9
10
  github.com/tidwall/match v1.1.1 // indirect
10
11
  github.com/tidwall/pretty v1.2.1 // indirect
package/go.sum CHANGED
@@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
4
4
  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5
5
  github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6
6
  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7
- github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
8
- github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7
+ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
8
+ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
9
9
  github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
10
10
  github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
11
11
  github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanagram/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Tanagram - Catch sloppy code before it ships",
5
5
  "main": "index.js",
6
6
  "bin": {