-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscan.go
More file actions
117 lines (110 loc) · 3.49 KB
/
scan.go
File metadata and controls
117 lines (110 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Copyright (c) 2025 by Developer Network.
//
// For more details, see the LICENSE file in the root directory of this
// source code repository or contact Developer Network at info@devnw.com.
package canary
import (
"encoding/csv"
"os"
"sort"
"time"
"go.devnw.com/canary/gate"
)
// Scan now delegates to the generic gate.Scanner, converting the result into the legacy report.
// Backward-compatible: existing tests and callers expect auto-promotion logic preserved.
func Scan(root string) (Report, error) {
sc := gate.NewScanner() // default with promotion
res, err := sc.ScanRepository(root)
if err != nil {
return Report{}, err
}
// Convert gate result to legacy report shape
var rows []requirementRow
for _, r := range res.Requirements {
feats := make([]featureEntry, len(r.Features))
for i, f := range r.Features {
feats[i] = featureEntry{
Feature: f.Feature,
Aspect: f.Aspect,
Status: f.Status,
Files: append([]string(nil), f.Files...),
Tests: append([]string(nil), f.Tests...),
Benches: append([]string(nil), f.Benches...),
Owner: f.Owner,
Updated: f.Updated,
}
}
// Already sorted by gate; defensive sort for determinism
sort.Slice(feats, func(i, j int) bool {
if feats[i].Feature == feats[j].Feature {
return feats[i].Aspect < feats[j].Aspect
}
return feats[i].Feature < feats[j].Feature
})
rows = append(rows, requirementRow{ID: r.ID, Features: feats})
}
sort.Slice(rows, func(i, j int) bool { return rows[i].ID < rows[j].ID })
return Report{Requirements: rows, Summary: summary{ByStatus: res.Summary.ByStatus, ByAspect: res.Summary.ByAspect}}, nil
}
// CheckStaleness delegates to gate.CheckStalenessTokens for consistency.
func CheckStaleness(rep Report, dur time.Duration) error {
// Convert report -> ScanResult minimal for staleness check
var reqs []gate.RequirementTokens
for _, r := range rep.Requirements {
var feats []gate.FeatureTokens
for _, f := range r.Features {
feats = append(feats, gate.FeatureTokens{Feature: f.Feature, Aspect: f.Aspect, Status: f.Status, Files: f.Files, Tests: f.Tests, Benches: f.Benches, Owner: f.Owner, Updated: f.Updated})
}
reqs = append(reqs, gate.RequirementTokens{ID: r.ID, Features: feats})
}
sr := gate.ScanResult{Requirements: reqs, Summary: gate.SummaryTokens{ByStatus: rep.Summary.ByStatus, ByAspect: rep.Summary.ByAspect}}
return gate.CheckStalenessTokens(sr, int64(dur/time.Second))
}
// WriteCSV unchanged; moved here after refactor for legacy consumers.
func WriteCSV(rep Report, path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
w := csv.NewWriter(f)
defer w.Flush()
_ = w.Write([]string{"req", "feature", "aspect", "status", "file", "test", "bench", "owner", "updated"})
for _, r := range rep.Requirements {
for _, f := range r.Features {
rowBase := []string{r.ID, f.Feature, f.Aspect, f.Status, "", "", "", f.Owner, f.Updated}
max := max3(len(f.Files), len(f.Tests), len(f.Benches))
for i := 0; i < max; i++ {
row := make([]string, len(rowBase))
copy(row, rowBase)
if i < len(f.Files) {
row[4] = f.Files[i]
}
if i < len(f.Tests) {
row[5] = f.Tests[i]
}
if i < len(f.Benches) {
row[6] = f.Benches[i]
}
if err := w.Write(row); err != nil {
return err
}
}
if max == 0 {
if err := w.Write(rowBase); err != nil {
return err
}
}
}
}
return nil
}
func max3(a, b, c int) int {
if a < b {
a = b
}
if a < c {
a = c
}
return a
}