feat: Add tracks relation type for convoy tracking (bd-3roq)

Adds non-blocking tracks dependency type for convoy to issue relationships:
- Non-blocking: does not affect ready work calculation
- Cross-prefix capable: convoys in hq-* can track issues in gt-*, bd-*
- Reverse lookup: bd dep list <id> --direction=up -t tracks

Also adds bd dep list command with direction and type filtering for
querying dependencies/dependents.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-29 21:04:28 -08:00
parent 2b90f51d0c
commit b8a5ee162b
5 changed files with 189 additions and 4 deletions

View File

@@ -73,7 +73,7 @@
{"id":"bd-3bsz","title":"gt mail send: support reading message body from stdin","description":"Currently gt mail send -m requires the message as a command-line argument, which causes shell escaping issues with backticks, quotes, and special characters.\n\nAdd support for reading message body from stdin:\n- gt mail send addr -s 'Subject' --stdin # Read body from stdin\n- echo 'body' | gt mail send addr -s 'Subject' -m - # Convention: -m - means stdin\n\nThis would allow:\ncat \u003c\u003c'EOF' | gt mail send addr -s 'Subject' --stdin\nMessage with `backticks` and 'quotes' safely\nEOF\n\nWithout this, agents struggle to send handoff messages containing code snippets.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-23T03:21:39.496208-08:00","updated_at":"2025-12-23T12:19:44.443554-08:00","closed_at":"2025-12-23T12:19:44.443554-08:00"}
{"id":"bd-3ggb","title":"Rebuild local binary","description":"Build and verify: go build -o bd ./cmd/bd \u0026\u0026 ./bd version","status":"tombstone","priority":1,"issue_type":"task","created_at":"2025-12-18T22:43:03.101428-08:00","updated_at":"2025-12-24T16:25:30.089869-08:00","dependencies":[{"issue_id":"bd-3ggb","depends_on_id":"bd-qqc","type":"parent-child","created_at":"2025-12-18T22:43:16.748289-08:00","created_by":"daemon"},{"issue_id":"bd-3ggb","depends_on_id":"bd-4y4g","type":"blocks","created_at":"2025-12-18T22:43:20.950376-08:00","created_by":"daemon"}],"deleted_at":"2025-12-24T16:25:30.089869-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
{"id":"bd-3jcw","title":"activity.go: Missing test coverage","description":"The new activity.go command (from bd-xo1o.3) has no test coverage. At minimum, tests should cover:\n- parseDurationString() for various formats (5m, 1h, 2d, invalid)\n- filterEvents() for --mol and --type filtering\n- formatEvent() and getEventDisplay() for all mutation types\n\nDiscovered during code review of bd-xo1o implementation.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-23T04:06:15.563579-08:00","updated_at":"2025-12-23T04:14:56.150151-08:00","closed_at":"2025-12-23T04:14:56.150151-08:00"}
{"id":"bd-3roq","title":"Add 'tracks' relation type for convoy tracking","description":"Add a new relation type 'tracks' for convoy → issue relationships.\n\nUnlike 'depends_on':\n- Non-blocking (tracked issue doesn't block convoy)\n- Cross-prefix capable (convoy in hq-* tracks issues in gt-*, bd-*)\n- Supports reverse lookup ('what convoys track this issue?')\n\nUsed by convoys to track issues across project chains without creating dependencies.\n\nRelated: hq-7h8jx (Convoy System epic in town beads)","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-29T18:47:00.581639-08:00","created_by":"mayor","updated_at":"2025-12-29T18:47:00.581639-08:00"}
{"id":"bd-3roq","title":"Add 'tracks' relation type for convoy tracking","description":"Add a new relation type 'tracks' for convoy → issue relationships.\n\nUnlike 'depends_on':\n- Non-blocking (tracked issue doesn't block convoy)\n- Cross-prefix capable (convoy in hq-* tracks issues in gt-*, bd-*)\n- Supports reverse lookup ('what convoys track this issue?')\n\nUsed by convoys to track issues across project chains without creating dependencies.\n\nRelated: hq-7h8jx (Convoy System epic in town beads)","status":"in_progress","priority":1,"issue_type":"task","created_at":"2025-12-29T18:47:00.581639-08:00","created_by":"mayor","updated_at":"2025-12-29T20:58:37.388899-08:00"}
{"id":"bd-3sz0","title":"Auto-repair stale merge driver configs with invalid placeholders","description":"Old bd versions (\u003c0.24.0) installed merge driver with invalid placeholders %L %R instead of %A %B. Add detection to bd doctor --fix: check if git config merge.beads.driver contains %L or %R, auto-repair to 'bd merge %A %O %A %B'. One-time migration for users who initialized with old versions.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-21T23:16:10.762808-08:00","updated_at":"2025-12-23T23:48:18.11858-08:00","closed_at":"2025-12-23T23:48:18.11858-08:00","dependencies":[{"issue_id":"bd-3sz0","depends_on_id":"bd-tbz3","type":"parent-child","created_at":"2025-11-21T23:16:10.763612-08:00","created_by":"daemon"}]}
{"id":"bd-3u8m","title":"Create bd admin parent command and nest cleanup/compact/reset","description":"## Task\nCreate new `bd admin` parent command and move:\n- `bd cleanup` → `bd admin cleanup`\n- `bd compact` → `bd admin compact`\n- `bd reset` → `bd admin reset`\n\n## Implementation\n\n### 1. Create admin.go\nNew file with parent command:\n```go\nvar adminCmd = \u0026cobra.Command{\n Use: \"admin\",\n Short: \"Administrative commands for database maintenance\",\n Long: `Administrative commands for beads database maintenance.\n\nThese commands are for advanced users and should be used carefully:\n cleanup Delete closed issues and prune expired tombstones\n compact Compact old closed issues to save space\n reset Remove all beads data and configuration\n\nFor routine operations, prefer 'bd doctor --fix'.`,\n}\n\nfunc init() {\n rootCmd.AddCommand(adminCmd)\n adminCmd.AddCommand(cleanupCmd)\n adminCmd.AddCommand(compactCmd)\n adminCmd.AddCommand(resetCmd)\n}\n```\n\n### 2. Update cleanup.go, compact.go, reset.go\n- Remove `rootCmd.AddCommand()` from each init()\n- Keep all existing functionality\n\n### 3. Create hidden aliases for backwards compatibility\nTop-level hidden commands that forward to admin subcommands.\n\n### 4. Update docs (major updates)\n- docs/CLI_REFERENCE.md - cleanup, compact references\n- docs/QUICKSTART.md - compact, cleanup references\n- docs/FAQ.md - compact references\n- docs/TROUBLESHOOTING.md - compact references\n- docs/DELETIONS.md - compact reference\n- docs/CONFIG.md - compact reference\n- skills/beads/SKILL.md - compact reference\n- commands/compact.md - update all examples\n- examples/compaction/README.md - update examples\n\n## Files to create\n- cmd/bd/admin.go\n\n## Files to modify\n- cmd/bd/cleanup.go\n- cmd/bd/compact.go\n- cmd/bd/reset.go\n- cmd/bd/main.go (aliases)\n- Multiple docs (see list above)\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-27T15:10:41.836341-08:00","created_by":"mayor","updated_at":"2025-12-27T16:07:30.707323-08:00","closed_at":"2025-12-27T16:07:30.707323-08:00"}
{"id":"bd-3uje","title":"Test issue for pin --for","description":"Testing the pin --for flag","status":"tombstone","priority":3,"issue_type":"task","created_at":"2025-12-22T02:53:43.075522-08:00","updated_at":"2025-12-22T02:54:07.973855-08:00","deleted_at":"2025-12-22T02:54:07.973855-08:00","deleted_by":"daemon","delete_reason":"delete","original_type":"task"}
@@ -422,7 +422,7 @@
{"id":"bd-jdz3","title":"Add pager support to bd list","description":"Add pager support following gh cli conventions:\n\nFlags:\n- --no-pager: disable pager for this command\n\nEnvironment variables:\n- BD_PAGER / PAGER: pager program (default: less)\n- BD_NO_PAGER: disable pager globally\n\nBehavior:\n- Auto-enable pager when output exceeds terminal height\n- Respect LESS env var for pager options\n- Disable pager when stdout is not a TTY (pipes/scripts)","notes":"## Implementation Plan\n\n### Dependencies\n```go\nimport \"github.com/muesli/termenv\" // or golang.org/x/term\n```\n\n### Code Changes\n\n1. **Add pager helper** (internal/ui/pager.go):\n```go\nfunc ToPager(content string) error {\n // Check BD_NO_PAGER or --no-pager\n if os.Getenv(\"BD_NO_PAGER\") \\!= \"\" {\n fmt.Print(content)\n return nil\n }\n \n // Get pager command\n pager := os.Getenv(\"BD_PAGER\")\n if pager == \"\" {\n pager = os.Getenv(\"PAGER\")\n }\n if pager == \"\" {\n pager = \"less\"\n }\n \n // Check if content exceeds terminal height\n // If not, just print directly\n \n // Pipe to pager\n cmd := exec.Command(pager)\n cmd.Stdin = strings.NewReader(content)\n cmd.Stdout = os.Stdout\n cmd.Stderr = os.Stderr\n return cmd.Run()\n}\n```\n\n2. **Add --no-pager flag** (cmd/bd/list.go init):\n```go\nlistCmd.Flags().Bool(\"no-pager\", false, \"Disable pager output\")\n```\n\n3. **Use pager in list output** (end of Run):\n```go\nif \\!noPager \u0026\u0026 isTerminal(os.Stdout) {\n ui.ToPager(output.String())\n} else {\n fmt.Print(output.String())\n}\n```\n\n### Environment Variables\n- `BD_PAGER`: pager program (overrides PAGER)\n- `BD_NO_PAGER`: set to any value to disable\n- `PAGER`: fallback pager\n- `LESS`: passed through for less options\n\n### Testing\n- `bd list` pipes to pager when output \u003e terminal height\n- `bd list --no-pager` prints directly\n- `BD_NO_PAGER=1 bd list` prints directly\n- `bd list | cat` auto-disables pager (not a TTY)","status":"open","priority":3,"issue_type":"feature","created_at":"2025-12-29T15:25:09.109258-08:00","created_by":"stevey","updated_at":"2025-12-29T15:26:49.025186-08:00"}
{"id":"bd-jgxi","title":"Auto-migrate database on CLI version bump","description":"When CLI is upgraded (e.g., 0.24.0 → 0.24.1), database version becomes stale. Add auto-migration in PersistentPreRun or daemon startup. Check dbVersion != CLIVersion and run bd migrate automatically. Fixes recurring UX issue where bd doctor shows version mismatch after every CLI upgrade.","status":"tombstone","priority":0,"issue_type":"feature","created_at":"2025-11-21T23:16:09.004619-08:00","updated_at":"2025-12-25T01:21:01.952723-08:00","dependencies":[{"issue_id":"bd-jgxi","depends_on_id":"bd-tbz3","type":"parent-child","created_at":"2025-11-21T23:16:09.005513-08:00","created_by":"daemon"}],"deleted_at":"2025-12-25T01:21:01.952723-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"bd-jke6","title":"Add covering index (label, issue_id) for label queries","description":"GetIssuesByLabel joins labels table but requires table lookup after using idx_labels_label.\n\n**Query (labels.go:165):**\n```sql\nSELECT ... FROM issues i\nJOIN labels l ON i.id = l.issue_id\nWHERE l.label = ?\n```\n\n**Problem:** Current idx_labels_label index doesn't cover issue_id, requiring row lookup.\n\n**Solution:** Add migration:\n```sql\nCREATE INDEX IF NOT EXISTS idx_labels_label_issue ON labels(label, issue_id);\n```\n\nThis is a covering index - query can be satisfied entirely from the index without touching the labels table rows.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-22T22:58:51.485354-08:00","updated_at":"2025-12-22T23:15:13.839904-08:00","closed_at":"2025-12-22T23:15:13.839904-08:00","dependencies":[{"issue_id":"bd-jke6","depends_on_id":"bd-h0we","type":"discovered-from","created_at":"2025-12-22T22:58:51.485984-08:00","created_by":"daemon"}]}
{"id":"bd-jsk7","title":"Agent beads: structured labels for filtering","description":"## Agent Beads Need Queryable Labels\n\nCurrently agent beads have role_type/rig in description text, not as labels. This breaks @group resolution in gt mail.\n\n## Current State\n```json\n{\n \"id\": \"gt-gastown-witness\",\n \"issue_type\": \"agent\",\n \"description\": \"...\\\\nrole_type: witness\\\\nrig: gastown\\\\n...\"\n}\n```\n\nCannot query: `bd list --type=agent --label=role_type:witness` returns nothing.\n\n## Required\nAgent bead creation should add labels:\n- `role_type:\u003ctype\u003e` (witness, refinery, crew, polecat, dog, mayor, deacon)\n- `rig:\u003crig\u003e` (gastown, beads, or \"town\" for town-level)\n\n## Where to Fix\ngt polecat/crew/agent creation commands should add labels.\n\n## Acceptance\n- New agent beads created with role_type/rig labels\n- @group patterns work in gt mail router","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-29T20:49:13.444793-08:00","created_by":"gastown/crew/joe","updated_at":"2025-12-29T20:49:13.444793-08:00"}
{"id":"bd-jsk7","title":"Agent beads: structured labels for filtering","description":"## Agent Beads Need Queryable Labels\n\nCurrently agent beads have role_type/rig in description text, not as labels. This breaks @group resolution in gt mail.\n\n## Current State\n```json\n{\n \"id\": \"gt-gastown-witness\",\n \"issue_type\": \"agent\",\n \"description\": \"...\\\\nrole_type: witness\\\\nrig: gastown\\\\n...\"\n}\n```\n\nCannot query: `bd list --type=agent --label=role_type:witness` returns nothing.\n\n## Required\nAgent bead creation should add labels:\n- `role_type:\u003ctype\u003e` (witness, refinery, crew, polecat, dog, mayor, deacon)\n- `rig:\u003crig\u003e` (gastown, beads, or \"town\" for town-level)\n\n## Where to Fix\ngt polecat/crew/agent creation commands should add labels.\n\n## Acceptance\n- New agent beads created with role_type/rig labels\n- @group patterns work in gt mail router","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T20:49:13.444793-08:00","created_by":"gastown/crew/joe","updated_at":"2025-12-29T20:58:04.67866-08:00","closed_at":"2025-12-29T20:58:04.67866-08:00","close_reason":"Duplicate of bd-g7eq"}
{"id":"bd-jv4w","title":"Phase 1.2: Separate bdt executable - Initial structure","description":"Create minimal bdt command structure completely separate from bd. Must not share code, config, or database.\n\n## Subtasks\n1. Create cmd/bdt/ directory with main.go\n2. Implement bdt version, help, and status commands\n3. Configure separate database location: $HOME/.bdt/ (not $HOME/.beads/)\n4. Create separate issues file: issues.toon (not issues.jsonl)\n5. Update build system:\n - Makefile: Add bdt target\n - .goreleaser.yml: Add bdt binary config\n\n## Files to Create\n- cmd/bdt/main.go - Entry point\n- cmd/bdt/version.go - Version handling\n- cmd/bdt/help.go - Help text (separate from bd)\n\n## Success Criteria\n- `make build` produces both `bd` and `bdt` executables\n- `bdt version` shows distinct version output from bd\n- `bdt --help` shows distinct help text\n- bdt uses $HOME/.bdt/ directory (verify with `bdt info`)\n- bd and bdt completely isolated (no shared imports beyond stdlib)","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-19T11:48:34.866877282-07:00","updated_at":"2025-12-19T12:59:11.389296015-07:00","closed_at":"2025-12-19T12:59:11.389296015-07:00"}
{"id":"bd-jvu","title":"Add bd update --parent flag to change issue parent","description":"Allow changing an issue's parent with bd update --parent \u003cnew-parent-id\u003e. Useful for reorganizing tasks under different epics or moving issues between hierarchies. Should update the parent-child dependency relationship.","status":"tombstone","priority":3,"issue_type":"feature","created_at":"2025-12-17T22:24:07.274485-08:00","updated_at":"2025-12-25T01:21:01.952723-08:00","deleted_at":"2025-12-25T01:21:01.952723-08:00","deleted_by":"batch delete","delete_reason":"batch delete","original_type":"feature"}
{"id":"bd-kblo","title":"bd prime should mention when beads is redirected","description":"## Problem\n\nAgents running in a redirected clone don't know they're sharing beads with other clones. This can cause confusion when molecules or issues seem to 'appear' or 'disappear'.\n\n## Proposed Solution\n\nWhen `bd prime` runs and detects a redirect, include it in the output:\n\n```\nBeads: /Users/stevey/gt/beads/mayor/rig/.beads\n (redirected from crew/dave - you share issues with other clones)\n```\n\n## Why\n\nVisibility over magic. If agents can see the redirect, they can reason about it.\n\n## Related\n\n- bd where command (shows this on demand)\n- gt redirect following (ensures gt matches bd behavior)","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-27T21:15:55.026907-08:00","created_by":"beads/crew/dave","updated_at":"2025-12-27T21:33:33.765635-08:00","closed_at":"2025-12-27T21:33:33.765635-08:00"}

View File

@@ -221,6 +221,138 @@ Examples:
},
}
var depListCmd = &cobra.Command{
Use: "list [issue-id]",
Short: "List dependencies or dependents of an issue",
Long: `List dependencies or dependents of an issue with optional type filtering.
By default shows dependencies (what this issue depends on). Use --direction to control:
- down: Show dependencies (what this issue depends on) - default
- up: Show dependents (what depends on this issue)
Use --type to filter by dependency type (e.g., tracks, blocks, parent-child).
Examples:
bd dep list gt-abc # Show what gt-abc depends on
bd dep list gt-abc --direction=up # Show what depends on gt-abc
bd dep list gt-abc --direction=up -t tracks # Show what tracks gt-abc (convoy tracking)`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ctx := rootCtx
// Resolve partial ID first
var fullID string
if daemonClient != nil {
resolveArgs := &rpc.ResolveIDArgs{ID: args[0]}
resp, err := daemonClient.ResolveID(resolveArgs)
if err != nil {
FatalErrorRespectJSON("resolving issue ID %s: %v", args[0], err)
}
if err := json.Unmarshal(resp.Data, &fullID); err != nil {
FatalErrorRespectJSON("unmarshaling resolved ID: %v", err)
}
} else {
var err error
fullID, err = utils.ResolvePartialID(ctx, store, args[0])
if err != nil {
FatalErrorRespectJSON("resolving %s: %v", args[0], err)
}
}
// If daemon is running but doesn't support this command, use direct storage
if daemonClient != nil && store == nil {
var err error
store, err = sqlite.New(rootCtx, dbPath)
if err != nil {
FatalErrorRespectJSON("failed to open database: %v", err)
}
defer func() { _ = store.Close() }()
}
direction, _ := cmd.Flags().GetString("direction")
typeFilter, _ := cmd.Flags().GetString("type")
if direction == "" {
direction = "down"
}
var issues []*types.IssueWithDependencyMetadata
var err error
if direction == "up" {
issues, err = store.GetDependentsWithMetadata(ctx, fullID)
} else {
issues, err = store.GetDependenciesWithMetadata(ctx, fullID)
}
if err != nil {
FatalErrorRespectJSON("%v", err)
}
// Apply type filter if specified
if typeFilter != "" {
var filtered []*types.IssueWithDependencyMetadata
for _, iss := range issues {
if string(iss.DependencyType) == typeFilter {
filtered = append(filtered, iss)
}
}
issues = filtered
}
if jsonOutput {
if issues == nil {
issues = []*types.IssueWithDependencyMetadata{}
}
outputJSON(issues)
return
}
if len(issues) == 0 {
if typeFilter != "" {
if direction == "up" {
fmt.Printf("\nNo issues depend on %s with type '%s'\n", fullID, typeFilter)
} else {
fmt.Printf("\n%s has no dependencies of type '%s'\n", fullID, typeFilter)
}
} else {
if direction == "up" {
fmt.Printf("\nNo issues depend on %s\n", fullID)
} else {
fmt.Printf("\n%s has no dependencies\n", fullID)
}
}
return
}
if direction == "up" {
fmt.Printf("\n%s Issues that depend on %s:\n\n", ui.RenderAccent("📋"), fullID)
} else {
fmt.Printf("\n%s %s depends on:\n\n", ui.RenderAccent("📋"), fullID)
}
for _, iss := range issues {
// Color the ID based on status
var idStr string
switch iss.Status {
case types.StatusOpen:
idStr = ui.StatusOpenStyle.Render(iss.ID)
case types.StatusInProgress:
idStr = ui.StatusInProgressStyle.Render(iss.ID)
case types.StatusBlocked:
idStr = ui.StatusBlockedStyle.Render(iss.ID)
case types.StatusClosed:
idStr = ui.StatusClosedStyle.Render(iss.ID)
default:
idStr = iss.ID
}
fmt.Printf(" %s: %s [P%d] (%s) via %s\n",
idStr, iss.Title, iss.Priority, iss.Status, iss.DependencyType)
}
fmt.Println()
},
}
var depRemoveCmd = &cobra.Command{
Use: "remove [issue-id] [depends-on-id]",
Aliases: []string{"rm"},
@@ -842,7 +974,7 @@ func ParseExternalRef(ref string) (project, capability string) {
}
func init() {
depAddCmd.Flags().StringP("type", "t", "blocks", "Dependency type (blocks|related|parent-child|discovered-from)")
depAddCmd.Flags().StringP("type", "t", "blocks", "Dependency type (blocks|tracks|related|parent-child|discovered-from)")
// Note: --json flag is defined as a persistent flag in main.go, not here
// Note: --json flag is defined as a persistent flag in main.go, not here
@@ -853,12 +985,17 @@ func init() {
depTreeCmd.Flags().String("direction", "", "Tree direction: 'down' (dependencies), 'up' (dependents), or 'both'")
depTreeCmd.Flags().String("status", "", "Filter to only show issues with this status (open, in_progress, blocked, deferred, closed)")
depTreeCmd.Flags().String("format", "", "Output format: 'mermaid' for Mermaid.js flowchart")
depTreeCmd.Flags().StringP("type", "t", "", "Filter to only show dependencies of this type (e.g., tracks, blocks, parent-child)")
// Note: --json flag is defined as a persistent flag in main.go, not here
// Note: --json flag is defined as a persistent flag in main.go, not here
depListCmd.Flags().String("direction", "down", "Direction: 'down' (dependencies), 'up' (dependents)")
depListCmd.Flags().StringP("type", "t", "", "Filter by dependency type (e.g., tracks, blocks, parent-child)")
depCmd.AddCommand(depAddCmd)
depCmd.AddCommand(depRemoveCmd)
depCmd.AddCommand(depListCmd)
depCmd.AddCommand(depTreeCmd)
depCmd.AddCommand(depCyclesCmd)
rootCmd.AddCommand(depCmd)

View File

@@ -695,6 +695,49 @@ func (m *MemoryStorage) GetDependents(ctx context.Context, issueID string) ([]*t
return results, nil
}
// GetDependenciesWithMetadata gets issues that this issue depends on, with dependency type
func (m *MemoryStorage) GetDependenciesWithMetadata(ctx context.Context, issueID string) ([]*types.IssueWithDependencyMetadata, error) {
m.mu.RLock()
defer m.mu.RUnlock()
var results []*types.IssueWithDependencyMetadata
for _, dep := range m.dependencies[issueID] {
if issue, exists := m.issues[dep.DependsOnID]; exists {
issueCopy := *issue
results = append(results, &types.IssueWithDependencyMetadata{
Issue: issueCopy,
DependencyType: dep.Type,
})
}
}
return results, nil
}
// GetDependentsWithMetadata gets issues that depend on this issue, with dependency type
func (m *MemoryStorage) GetDependentsWithMetadata(ctx context.Context, issueID string) ([]*types.IssueWithDependencyMetadata, error) {
m.mu.RLock()
defer m.mu.RUnlock()
var results []*types.IssueWithDependencyMetadata
for id, deps := range m.dependencies {
for _, dep := range deps {
if dep.DependsOnID == issueID {
if issue, exists := m.issues[id]; exists {
issueCopy := *issue
results = append(results, &types.IssueWithDependencyMetadata{
Issue: issueCopy,
DependencyType: dep.Type,
})
}
break
}
}
}
return results, nil
}
// GetDependencyCounts returns dependency and dependent counts for multiple issues
func (m *MemoryStorage) GetDependencyCounts(ctx context.Context, issueIDs []string) (map[string]*types.DependencyCounts, error) {
m.mu.RLock()

View File

@@ -92,6 +92,8 @@ type Storage interface {
RemoveDependency(ctx context.Context, issueID, dependsOnID string, actor string) error
GetDependencies(ctx context.Context, issueID string) ([]*types.Issue, error)
GetDependents(ctx context.Context, issueID string) ([]*types.Issue, error)
GetDependenciesWithMetadata(ctx context.Context, issueID string) ([]*types.IssueWithDependencyMetadata, error)
GetDependentsWithMetadata(ctx context.Context, issueID string) ([]*types.IssueWithDependencyMetadata, error)
GetDependencyRecords(ctx context.Context, issueID string) ([]*types.Dependency, error)
GetAllDependencyRecords(ctx context.Context) (map[string][]*types.Dependency, error)
GetDependencyCounts(ctx context.Context, issueIDs []string) (map[string]*types.DependencyCounts, error)

View File

@@ -519,6 +519,9 @@ const (
DepAuthoredBy DependencyType = "authored-by" // Creator relationship
DepAssignedTo DependencyType = "assigned-to" // Assignment relationship
DepApprovedBy DependencyType = "approved-by" // Approval relationship
// Convoy tracking (non-blocking cross-project references)
DepTracks DependencyType = "tracks" // Convoy → issue tracking (non-blocking)
)
// IsValid checks if the dependency type value is valid.
@@ -534,7 +537,7 @@ func (d DependencyType) IsWellKnown() bool {
switch d {
case DepBlocks, DepParentChild, DepConditionalBlocks, DepWaitsFor, DepRelated, DepDiscoveredFrom,
DepRepliesTo, DepRelatesTo, DepDuplicates, DepSupersedes,
DepAuthoredBy, DepAssignedTo, DepApprovedBy:
DepAuthoredBy, DepAssignedTo, DepApprovedBy, DepTracks:
return true
}
return false