|
@@ -1,7 +1,7 @@
|
|
|
package main
|
|
package main
|
|
|
|
|
|
|
|
-// The bot prompts under .github/workflows/ read .github/claude/repo-context.md
|
|
|
|
|
-// instead of restating repo facts; a stale claim there is invisible, so pin it.
|
|
|
|
|
|
|
+// The issue analyst prompt reads .github/claude/issue-analyst-context.md instead
|
|
|
|
|
+// of restating repo facts; a stale claim there is invisible, so pin it.
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"os"
|
|
"os"
|
|
@@ -12,9 +12,9 @@ import (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
const (
|
|
|
- botContextPath = ".github/claude/repo-context.md"
|
|
|
|
|
- reviewPath = "REVIEW.md"
|
|
|
|
|
- ciWorkflowPath = ".github/workflows/ci.yml"
|
|
|
|
|
|
|
+ analystContextPath = ".github/claude/issue-analyst-context.md"
|
|
|
|
|
+ reviewPath = "REVIEW.md"
|
|
|
|
|
+ ciWorkflowPath = ".github/workflows/ci.yml"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
func readRepoFile(t *testing.T, path string) string {
|
|
func readRepoFile(t *testing.T, path string) string {
|
|
@@ -32,7 +32,7 @@ func section(t *testing.T, doc, from, to string) string {
|
|
|
t.Helper()
|
|
t.Helper()
|
|
|
i := strings.Index(doc, from)
|
|
i := strings.Index(doc, from)
|
|
|
if i < 0 {
|
|
if i < 0 {
|
|
|
- t.Fatalf("%s no longer contains the heading %q", botContextPath, from)
|
|
|
|
|
|
|
+ t.Fatalf("%s no longer contains the heading %q", analystContextPath, from)
|
|
|
}
|
|
}
|
|
|
rest := doc[i+len(from):]
|
|
rest := doc[i+len(from):]
|
|
|
if before, _, ok := strings.Cut(rest, to); ok {
|
|
if before, _, ok := strings.Cut(rest, to); ok {
|
|
@@ -41,18 +41,18 @@ func section(t *testing.T, doc, from, to string) string {
|
|
|
return rest
|
|
return rest
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func TestBotContextLocaleFileCount(t *testing.T) {
|
|
|
|
|
- doc := readRepoFile(t, botContextPath)
|
|
|
|
|
|
|
+func TestAnalystContextLocaleFileCount(t *testing.T) {
|
|
|
|
|
+ doc := readRepoFile(t, analystContextPath)
|
|
|
m := regexp.MustCompile("`internal/web/translation/` \\((\\d+) files\\)").FindStringSubmatch(doc)
|
|
m := regexp.MustCompile("`internal/web/translation/` \\((\\d+) files\\)").FindStringSubmatch(doc)
|
|
|
if m == nil {
|
|
if m == nil {
|
|
|
- t.Fatalf("%s no longer states the locale file count as \"`internal/web/translation/` (N files)\"", botContextPath)
|
|
|
|
|
|
|
+ t.Fatalf("%s no longer states the locale file count as \"`internal/web/translation/` (N files)\"", analystContextPath)
|
|
|
}
|
|
}
|
|
|
files, err := filepath.Glob("internal/web/translation/*.json")
|
|
files, err := filepath.Glob("internal/web/translation/*.json")
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
t.Fatalf("glob locales: %v", err)
|
|
t.Fatalf("glob locales: %v", err)
|
|
|
}
|
|
}
|
|
|
if got := len(files); m[1] != itoa(got) {
|
|
if got := len(files); m[1] != itoa(got) {
|
|
|
- t.Errorf("%s claims %s locale files, internal/web/translation/ holds %d; update the claim and every prompt that relies on it", botContextPath, m[1], got)
|
|
|
|
|
|
|
+ t.Errorf("%s claims %s locale files, internal/web/translation/ holds %d; update the claim and every prompt that relies on it", analystContextPath, m[1], got)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -68,26 +68,26 @@ func itoa(n int) string {
|
|
|
return string(b)
|
|
return string(b)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func TestBotContextNamesRealCIJobs(t *testing.T) {
|
|
|
|
|
- doc := readRepoFile(t, botContextPath)
|
|
|
|
|
|
|
+func TestAnalystContextNamesRealCIJobs(t *testing.T) {
|
|
|
|
|
+ doc := readRepoFile(t, analystContextPath)
|
|
|
ci := readRepoFile(t, ciWorkflowPath)
|
|
ci := readRepoFile(t, ciWorkflowPath)
|
|
|
table := section(t, doc, "## What CI runs", "**What CI does NOT prove.**")
|
|
table := section(t, doc, "## What CI runs", "**What CI does NOT prove.**")
|
|
|
rows := regexp.MustCompile("(?m)^\\| `([a-z0-9-]+)` \\|").FindAllStringSubmatch(table, -1)
|
|
rows := regexp.MustCompile("(?m)^\\| `([a-z0-9-]+)` \\|").FindAllStringSubmatch(table, -1)
|
|
|
if len(rows) < 5 {
|
|
if len(rows) < 5 {
|
|
|
- t.Fatalf("expected the CI table in %s to list at least 5 jobs, found %d", botContextPath, len(rows))
|
|
|
|
|
|
|
+ t.Fatalf("expected the CI table in %s to list at least 5 jobs, found %d", analystContextPath, len(rows))
|
|
|
}
|
|
}
|
|
|
for _, r := range rows {
|
|
for _, r := range rows {
|
|
|
t.Run(r[1], func(t *testing.T) {
|
|
t.Run(r[1], func(t *testing.T) {
|
|
|
if !strings.Contains(ci, "\n "+r[1]+":\n") {
|
|
if !strings.Contains(ci, "\n "+r[1]+":\n") {
|
|
|
- t.Errorf("%s describes a CI job %q that %s does not define", botContextPath, r[1], ciWorkflowPath)
|
|
|
|
|
|
|
+ t.Errorf("%s describes a CI job %q that %s does not define", analystContextPath, r[1], ciWorkflowPath)
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func TestBotContextNamesRealPaths(t *testing.T) {
|
|
|
|
|
- // REVIEW.md briefs the review job the way repo-context.md briefs the
|
|
|
|
|
- // issue bot, so both get their paths pinned.
|
|
|
|
|
|
|
+func TestAnalystContextNamesRealPaths(t *testing.T) {
|
|
|
|
|
+ // REVIEW.md briefs the review job the way issue-analyst-context.md briefs
|
|
|
|
|
+ // the analyst, so both get their paths pinned.
|
|
|
// internal/web/dist and frontend/node_modules are build output: absent from a
|
|
// internal/web/dist and frontend/node_modules are build output: absent from a
|
|
|
// fresh clone, created by `make dist-stub` and `npm ci`.
|
|
// fresh clone, created by `make dist-stub` and `npm ci`.
|
|
|
generated := map[string]bool{
|
|
generated := map[string]bool{
|
|
@@ -97,7 +97,7 @@ func TestBotContextNamesRealPaths(t *testing.T) {
|
|
|
}
|
|
}
|
|
|
seen := map[string]bool{}
|
|
seen := map[string]bool{}
|
|
|
counts := map[string]int{}
|
|
counts := map[string]int{}
|
|
|
- for _, src := range []string{botContextPath, reviewPath} {
|
|
|
|
|
|
|
+ for _, src := range []string{analystContextPath, reviewPath} {
|
|
|
for _, m := range regexp.MustCompile("`([^`]+)`").FindAllStringSubmatch(readRepoFile(t, src), -1) {
|
|
for _, m := range regexp.MustCompile("`([^`]+)`").FindAllStringSubmatch(readRepoFile(t, src), -1) {
|
|
|
p := m[1]
|
|
p := m[1]
|
|
|
if !regexp.MustCompile(`^(internal|frontend|docs|tools|\.github)/`).MatchString(p) ||
|
|
if !regexp.MustCompile(`^(internal|frontend|docs|tools|\.github)/`).MatchString(p) ||
|
|
@@ -113,19 +113,19 @@ func TestBotContextNamesRealPaths(t *testing.T) {
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- if counts[botContextPath] < 20 {
|
|
|
|
|
- t.Errorf("expected the bot context to name at least 20 repository paths, found %d - has the file been gutted?", counts[botContextPath])
|
|
|
|
|
|
|
+ if counts[analystContextPath] < 20 {
|
|
|
|
|
+ t.Errorf("expected the bot context to name at least 20 repository paths, found %d - has the file been gutted?", counts[analystContextPath])
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func TestBotContextSkipGatesExist(t *testing.T) {
|
|
|
|
|
- doc := readRepoFile(t, botContextPath)
|
|
|
|
|
|
|
+func TestAnalystContextSkipGatesExist(t *testing.T) {
|
|
|
|
|
+ doc := readRepoFile(t, analystContextPath)
|
|
|
table := section(t, doc, "**What CI does NOT prove.**", "Mutation testing")
|
|
table := section(t, doc, "**What CI does NOT prove.**", "Mutation testing")
|
|
|
// [A-Z0-9_] and not [A-Z_]: XRAY_E2E_BINARY carries a digit, and excluding it
|
|
// [A-Z0-9_] and not [A-Z_]: XRAY_E2E_BINARY carries a digit, and excluding it
|
|
|
// silently dropped that gate from the check instead of failing.
|
|
// silently dropped that gate from the check instead of failing.
|
|
|
gates := regexp.MustCompile("`((?:XUI|XRAY)_[A-Z0-9_]+)`").FindAllStringSubmatch(table, -1)
|
|
gates := regexp.MustCompile("`((?:XUI|XRAY)_[A-Z0-9_]+)`").FindAllStringSubmatch(table, -1)
|
|
|
if len(gates) < 5 {
|
|
if len(gates) < 5 {
|
|
|
- t.Fatalf("expected at least 5 skip-gate variables in %s, found %d", botContextPath, len(gates))
|
|
|
|
|
|
|
+ t.Fatalf("expected at least 5 skip-gate variables in %s, found %d", analystContextPath, len(gates))
|
|
|
}
|
|
}
|
|
|
var sources []string
|
|
var sources []string
|
|
|
err := filepath.WalkDir("internal", func(path string, d os.DirEntry, err error) error {
|
|
err := filepath.WalkDir("internal", func(path string, d os.DirEntry, err error) error {
|
|
@@ -147,7 +147,7 @@ func TestBotContextSkipGatesExist(t *testing.T) {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- t.Errorf("%s lists %s as a test skip gate, but no .go file under internal/ reads it", botContextPath, g[1])
|
|
|
|
|
|
|
+ t.Errorf("%s lists %s as a test skip gate, but no .go file under internal/ reads it", analystContextPath, g[1])
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|