@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 +0 -0
- package/commands/run.go +100 -9
- package/commands/sync.go +82 -12
- package/go.mod +1 -0
- package/go.sum +2 -2
- package/package.json +1 -1
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.
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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("\
|
|
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
|
-
github.com/stretchr/testify v1.
|
|
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=
|