fix(sling): register hq-cv- prefix for convoy beads (#475)
Instead of changing the convoy ID format, register the hq-cv- prefix as a valid route pointing to town beads. This preserves the semantic meaning of convoy IDs (hq-cv-xxxxx) while fixing the prefix mismatch. Changes: - Register hq-cv- prefix during gt install - Add doctor check and fix for missing convoy route - Update routes_check tests for both hq- and hq-cv- routes Fixes: gt-4nmfh Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -404,6 +404,12 @@ func initTownBeads(townPath string) error {
|
||||
fmt.Printf(" %s Could not update routes.jsonl: %v\n", style.Dim.Render("⚠"), err)
|
||||
}
|
||||
|
||||
// Register hq-cv- prefix for convoy beads (auto-created by gt sling).
|
||||
// Convoys use hq-cv-* IDs for visual distinction from other town beads.
|
||||
if err := beads.AppendRoute(townPath, beads.Route{Prefix: "hq-cv-", Path: "."}); err != nil {
|
||||
fmt.Printf(" %s Could not register convoy prefix: %v\n", style.Dim.Render("⚠"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ func createAutoConvoy(beadID, beadTitle string) (string, error) {
|
||||
|
||||
townBeads := filepath.Join(townRoot, ".beads")
|
||||
|
||||
// Generate convoy ID with cv- prefix
|
||||
// Generate convoy ID with hq-cv- prefix for visual distinction
|
||||
// The hq-cv- prefix is registered in routes during gt install
|
||||
convoyID := fmt.Sprintf("hq-cv-%s", slingGenerateShortID())
|
||||
|
||||
// Create convoy with title "Work: <issue-title>"
|
||||
|
||||
@@ -75,6 +75,7 @@ func (c *RoutesCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
|
||||
var details []string
|
||||
var missingTownRoute bool
|
||||
var missingConvoyRoute bool
|
||||
|
||||
// Check town root route exists (hq- -> .)
|
||||
if _, hasTownRoute := routeByPrefix["hq-"]; !hasTownRoute {
|
||||
@@ -82,16 +83,22 @@ func (c *RoutesCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
details = append(details, "Town root route (hq- -> .) is missing")
|
||||
}
|
||||
|
||||
// Check convoy route exists (hq-cv- -> .)
|
||||
if _, hasConvoyRoute := routeByPrefix["hq-cv-"]; !hasConvoyRoute {
|
||||
missingConvoyRoute = true
|
||||
details = append(details, "Convoy route (hq-cv- -> .) is missing")
|
||||
}
|
||||
|
||||
// Load rigs registry
|
||||
rigsPath := filepath.Join(ctx.TownRoot, "mayor", "rigs.json")
|
||||
rigsConfig, err := config.LoadRigsConfig(rigsPath)
|
||||
if err != nil {
|
||||
// No rigs config - check for missing town route and validate existing routes
|
||||
if missingTownRoute {
|
||||
// No rigs config - check for missing town/convoy routes and validate existing routes
|
||||
if missingTownRoute || missingConvoyRoute {
|
||||
return &CheckResult{
|
||||
Name: c.Name(),
|
||||
Status: StatusWarning,
|
||||
Message: "Town root route is missing",
|
||||
Message: "Required town routes are missing",
|
||||
Details: details,
|
||||
FixHint: "Run 'gt doctor --fix' to add missing routes",
|
||||
}
|
||||
@@ -155,13 +162,16 @@ func (c *RoutesCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
}
|
||||
|
||||
// Determine result
|
||||
if missingTownRoute || len(missingRigs) > 0 || len(invalidRoutes) > 0 {
|
||||
if missingTownRoute || missingConvoyRoute || len(missingRigs) > 0 || len(invalidRoutes) > 0 {
|
||||
status := StatusWarning
|
||||
var messageParts []string
|
||||
|
||||
if missingTownRoute {
|
||||
messageParts = append(messageParts, "town root route missing")
|
||||
}
|
||||
if missingConvoyRoute {
|
||||
messageParts = append(messageParts, "convoy route missing")
|
||||
}
|
||||
if len(missingRigs) > 0 {
|
||||
messageParts = append(messageParts, fmt.Sprintf("%d rig(s) missing routes", len(missingRigs)))
|
||||
}
|
||||
@@ -249,6 +259,14 @@ func (c *RoutesCheck) Fix(ctx *CheckContext) error {
|
||||
modified = true
|
||||
}
|
||||
|
||||
// Ensure convoy route exists (hq-cv- -> .)
|
||||
// Convoys use hq-cv-* IDs for visual distinction from other town beads
|
||||
if !routeMap["hq-cv-"] {
|
||||
routes = append(routes, beads.Route{Prefix: "hq-cv-", Path: "."})
|
||||
routeMap["hq-cv-"] = true
|
||||
modified = true
|
||||
}
|
||||
|
||||
// Load rigs registry
|
||||
rigsPath := filepath.Join(ctx.TownRoot, "mayor", "rigs.json")
|
||||
rigsConfig, err := config.LoadRigsConfig(rigsPath)
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestRoutesCheck_MissingTownRoute(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create routes.jsonl with only a rig route (no hq- route)
|
||||
// Create routes.jsonl with only a rig route (no hq- or hq-cv- routes)
|
||||
routesPath := filepath.Join(beadsDir, "routes.jsonl")
|
||||
routesContent := `{"prefix": "gt-", "path": "gastown/mayor/rig"}
|
||||
`
|
||||
@@ -37,8 +37,8 @@ func TestRoutesCheck_MissingTownRoute(t *testing.T) {
|
||||
t.Errorf("expected StatusWarning, got %v: %s", result.Status, result.Message)
|
||||
}
|
||||
// When no rigs.json exists, the message comes from the early return path
|
||||
if result.Message != "Town root route is missing" {
|
||||
t.Errorf("expected 'Town root route is missing', got %s", result.Message)
|
||||
if result.Message != "Required town routes are missing" {
|
||||
t.Errorf("expected 'Required town routes are missing', got %s", result.Message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -51,9 +51,10 @@ func TestRoutesCheck_MissingTownRoute(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create routes.jsonl with hq- route
|
||||
// Create routes.jsonl with both hq- and hq-cv- routes
|
||||
routesPath := filepath.Join(beadsDir, "routes.jsonl")
|
||||
routesContent := `{"prefix": "hq-", "path": "."}
|
||||
{"prefix": "hq-cv-", "path": "."}
|
||||
`
|
||||
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -103,7 +104,7 @@ func TestRoutesCheck_FixRestoresTownRoute(t *testing.T) {
|
||||
t.Fatalf("Fix failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify routes.jsonl now contains hq- route
|
||||
// Verify routes.jsonl now contains both hq- and hq-cv- routes
|
||||
content, err := os.ReadFile(routesPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read routes.jsonl: %v", err)
|
||||
@@ -115,6 +116,7 @@ func TestRoutesCheck_FixRestoresTownRoute(t *testing.T) {
|
||||
|
||||
contentStr := string(content)
|
||||
if contentStr != `{"prefix":"hq-","path":"."}
|
||||
{"prefix":"hq-cv-","path":"."}
|
||||
` {
|
||||
t.Errorf("unexpected routes.jsonl content: %s", contentStr)
|
||||
}
|
||||
@@ -141,7 +143,7 @@ func TestRoutesCheck_FixRestoresTownRoute(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create routes.jsonl with only a rig route (no hq- route)
|
||||
// Create routes.jsonl with only a rig route (no hq- or hq-cv- routes)
|
||||
routesPath := filepath.Join(beadsDir, "routes.jsonl")
|
||||
routesContent := `{"prefix": "my-", "path": "myrig/mayor/rig"}
|
||||
`
|
||||
@@ -162,16 +164,17 @@ func TestRoutesCheck_FixRestoresTownRoute(t *testing.T) {
|
||||
t.Fatalf("Fix failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify routes.jsonl now contains both routes
|
||||
// Verify routes.jsonl now contains all routes
|
||||
content, err := os.ReadFile(routesPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read routes.jsonl: %v", err)
|
||||
}
|
||||
|
||||
contentStr := string(content)
|
||||
// Should have both the original rig route and the new hq- route
|
||||
// Should have the original rig route plus both hq- and hq-cv- routes
|
||||
if contentStr != `{"prefix":"my-","path":"myrig/mayor/rig"}
|
||||
{"prefix":"hq-","path":"."}
|
||||
{"prefix":"hq-cv-","path":"."}
|
||||
` {
|
||||
t.Errorf("unexpected routes.jsonl content: %s", contentStr)
|
||||
}
|
||||
@@ -186,9 +189,10 @@ func TestRoutesCheck_FixRestoresTownRoute(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create routes.jsonl with hq- route already present
|
||||
// Create routes.jsonl with both hq- and hq-cv- routes already present
|
||||
routesPath := filepath.Join(beadsDir, "routes.jsonl")
|
||||
originalContent := `{"prefix": "hq-", "path": "."}
|
||||
{"prefix": "hq-cv-", "path": "."}
|
||||
`
|
||||
if err := os.WriteFile(routesPath, []byte(originalContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -246,12 +250,12 @@ func TestRoutesCheck_CorruptedRoutesJsonl(t *testing.T) {
|
||||
result := check.Run(ctx)
|
||||
|
||||
// Corrupted/malformed lines are skipped, resulting in empty routes
|
||||
// This triggers the "Town root route is missing" warning
|
||||
// This triggers the "Required town routes are missing" warning
|
||||
if result.Status != StatusWarning {
|
||||
t.Errorf("expected StatusWarning, got %v: %s", result.Status, result.Message)
|
||||
}
|
||||
if result.Message != "Town root route is missing" {
|
||||
t.Errorf("expected 'Town root route is missing', got %s", result.Message)
|
||||
if result.Message != "Required town routes are missing" {
|
||||
t.Errorf("expected 'Required town routes are missing', got %s", result.Message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -283,7 +287,7 @@ func TestRoutesCheck_CorruptedRoutesJsonl(t *testing.T) {
|
||||
t.Fatalf("Fix failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify routes.jsonl now contains hq- route
|
||||
// Verify routes.jsonl now contains both hq- and hq-cv- routes
|
||||
content, err := os.ReadFile(routesPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read routes.jsonl: %v", err)
|
||||
@@ -291,6 +295,7 @@ func TestRoutesCheck_CorruptedRoutesJsonl(t *testing.T) {
|
||||
|
||||
contentStr := string(content)
|
||||
if contentStr != `{"prefix":"hq-","path":"."}
|
||||
{"prefix":"hq-cv-","path":"."}
|
||||
` {
|
||||
t.Errorf("unexpected routes.jsonl content after fix: %s", contentStr)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user