relcmd

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 27, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package relcmd provides the "rel" parent command, which groups relationship management operations under a single namespace. Subcommands cover adding and removing relationships, listing all relationships across active issues (rel list, with --rel filter for blocking, refs, or parent-child), per-issue relationship views (rel issue), parent-ancestry operations (rel parent), and tree and graph rendering (rel tree, rel graph).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewCmd

func NewCmd(f *cmdutil.Factory) *cli.Command

NewCmd constructs the "rel" parent command with subcommands for managing issue relationships by type.

func RenderBlockingSection added in v0.3.0

func RenderBlockingSection(ctx context.Context, svc driving.Service, ios *iostreams.IOStreams) error

RenderBlockingSection renders the blocking dependency section of np rel list. It builds a directed acyclic graph (DAG) of blocking relationships across all non-closed issues, detects cycles, and writes the result as a columnar table using the shared RenderTreeText renderer.

Roots are non-closed issues that block at least one other non-closed issue but are not themselves blocked. Cycle nodes are promoted to roots. The walk proceeds depth-first from each root sorted by priority (P0 first) then ID. Issues reached via multiple paths appear in full at their first occurrence; subsequent occurrences emit a back-reference marker showing where they first appeared.

The section header always appears even when there are no blocking edges. Its format is "Blocking (N chains, M edges, K cycles)". Cycle banners appear between the section header and the tree listing.

func RenderParentChildSection added in v0.3.0

func RenderParentChildSection(ctx context.Context, svc driving.Service, ios *iostreams.IOStreams) error

RenderParentChildSection renders the parent-child hierarchy section of np rel list. It builds a forest of non-closed issue trees and writes them as a single aligned table — one header row covering all trees — using the existing RenderTreeText renderer. A root qualifies when it is non-closed, has no non-closed parent, and has at least one non-closed child.

The section header always appears, even when no qualifying roots exist. The format is "Parent-child (N roots, M issues)" where N is the number of roots rendered and M is the total non-closed issue count across all subtrees.

Title truncation is applied on TTY output using the terminal width from ios; non-TTY output emits full titles.

func RenderRefsSection added in v0.3.0

func RenderRefsSection(ctx context.Context, svc driving.Service, ios *iostreams.IOStreams) error

RenderRefsSection renders the contextual reference section of np rel list. It loads all non-deleted issues and their refs relationships via a single GetGraphData call, computes connected components of the undirected refs graph (ignoring edges where either endpoint is closed), and writes the result as a flat edge list grouped by component.

Components are printed largest first (by issue count); ties are broken by the lexicographically smallest issue ID in the component. Within each component, edges are listed one per line with endpoints sorted lexicographically (smaller ID on the left). Each undirected edge appears exactly once.

On TTY, each title is truncated to half of the available terminal width. On non-TTY, full titles are emitted.

The section header always appears, even when there are no refs edges.

func RenderTreeJSON added in v0.3.0

func RenderTreeJSON(ctx context.Context, w io.Writer, svc treeService, focusID string, full bool) error

RenderTreeJSON builds the nested JSON tree for the given focus issue and writes it as indented JSON to w.

The output shape is:

  • A single top-level JSON object representing the root ancestor.
  • Each expanded node carries a "children" array containing either fully expanded child nodes (with all issue fields) or placeholder entries ({"id": "<sibling-id>"} only) for unexpanded siblings.
  • Without full: the tree contains the ancestry path from root to focus, the focus's full subtree, and placeholders for unexpanded siblings at each ancestor tier. Placeholders appear at their sorted position within the children array (ascending by issue ID).
  • With full: all nodes are expanded; no placeholders appear.

The per-issue field shape is byte-compatible with cmdutil.ConvertListItems: id, role, state, secondary_state, display_status, priority, title, blocker_ids, parent_id, and created_at. The children field is added on top.

JSON output is always plain text; no ANSI escape sequences are emitted regardless of whether w is a TTY.

svc must not be nil. focusID must be a valid issue ID present in the database.

func RenderTreeText added in v0.3.0

func RenderTreeText(ios *iostreams.IOStreams, nodes []TreeNode) error

RenderTreeText writes the tree as a columnar text table to ios.Out.

The table has five columns: TREE (issue ID indented two spaces per depth), P (priority), ROLE, STATE, and TITLE. The focus issue's row is bold when stdout is a TTY. Sibling placeholder rows are indented to the depth of the siblings they summarize and read "and N siblings" (or "and 1 sibling" for N=1). Column coloration matches np list (cs.Yellow for priority, cs.Dim for role, cmdutil.FormatState for state).

When stdout is not a TTY, no ANSI escape sequences are emitted and alignment is preserved via cmdutil.TableWriter, which strips ANSI bytes before measuring column widths.

func RunAdd

func RunAdd(ctx context.Context, input RunAddInput) error

RunAdd executes the rel add workflow. It parses the relationship argument, dispatches to the appropriate service method, and writes output.

func RunDetach

func RunDetach(ctx context.Context, input RunDetachInput) error

RunDetach removes the parent-child relationship between A and B. The order of A and B does not matter — the command inspects both issues to determine which is the child. Uses one-shot update (atomic claim→update→release) so no explicit claim is needed.

func RunList added in v0.3.0

func RunList(ctx context.Context, input RunListInput) error

RunList dispatches to section renderers based on RelFilter. With an empty filter, all three sections run in order: parent-child, blocking, refs. With a filter set to one of the canonical RelListCategory constants, only the matching section runs. The section header is always included in the renderer's output regardless of whether a filter is active. Returns an error if RelFilter is non-empty and does not match any of the three known sections.

func RunRemove added in v0.3.0

func RunRemove(ctx context.Context, input RunRemoveInput) error

RunRemove executes the rel remove workflow. It parses the relationship argument, dispatches to the appropriate service method, and writes output. This mirrors RunAdd in structure — the same <rel> values are accepted, and the same dispatch rules apply: parent_of/child_of delegate to the detach logic, while blocked_by/blocks/refs delegate to RemoveRelationship.

Types

type NodeKind added in v0.3.0

type NodeKind int

NodeKind distinguishes fully-expanded issue nodes from placeholder entries that represent collapsed siblings.

const (
	// NodeKindIssue is a fully-expanded node that carries complete issue data.
	NodeKindIssue NodeKind = iota

	// NodeKindPlaceholder represents a group of collapsed siblings at a given
	// tier. The Count field tells renderers how many siblings were collapsed.
	// Placeholder nodes always appear after the expanded path element at their
	// tier.
	NodeKindPlaceholder

	// NodeKindBackRef marks an issue that has already been rendered at an
	// earlier position in the output. Used by the blocking section to deduplicate
	// issues reached via multiple paths in the dependency DAG. The IssueID field
	// holds the deduplicated issue's ID; BackRefParentID holds the parent node ID
	// at the issue's first appearance ("" when it first appeared as a root).
	NodeKindBackRef
)

type RelArgResult

type RelArgResult struct {
	// Type classifies the dispatch path.
	Type RelArgType
	// Label is the canonical string form of the relationship (e.g. "blocked_by").
	Label string
	// RelType is the domain relationship type, populated only when Type is
	// RelArgRelationship.
	RelType domain.RelationType
}

RelArgResult holds the parsed result of a relationship argument string.

func ParseRelArg

func ParseRelArg(s string) (RelArgResult, error)

ParseRelArg parses a relationship argument string into a dispatch decision. Returns an error if the argument is not one of the six accepted values.

type RelArgType

type RelArgType int

RelArgType classifies how a relationship argument should be dispatched.

const (
	// RelArgRelationship means the argument maps to a standard
	// AddRelationship call (blocks, blocked_by, refs).
	RelArgRelationship RelArgType = iota + 1

	// RelArgParentOf means A is the parent of B — sets B's parent to A.
	RelArgParentOf

	// RelArgChildOf means A is a child of B — sets A's parent to B.
	RelArgChildOf
)

type RelListCategory added in v0.3.0

type RelListCategory string

RelListCategory is the canonical section identifier used by the np rel list --rel flag. Aliases from np rel add are accepted as input but always resolve to one of these three values.

const (
	// RelListCategoryParentChild selects the parent-child hierarchy section.
	RelListCategoryParentChild RelListCategory = "parent-child"

	// RelListCategoryBlocking selects the blocking dependency section.
	RelListCategoryBlocking RelListCategory = "blocking"

	// RelListCategoryRefs selects the contextual reference section.
	RelListCategoryRefs RelListCategory = "refs"
)

func ParseRelListCategory added in v0.3.0

func ParseRelListCategory(s string) (RelListCategory, error)

ParseRelListCategory maps a raw --rel value to its canonical RelListCategory. It accepts both the canonical names (blocking, refs, parent-child) and the aliases used by np rel add (blocked_by, blocks, parent_of, child_of). The empty string is not accepted; call sites that represent "no filter" should skip calling ParseRelListCategory rather than passing an empty string. Returns an error for any unrecognized value.

type RunAddInput

type RunAddInput struct {
	Service driving.Service
	A       string
	Rel     string
	B       string
	ClaimID string
	Author  string
	JSON    bool
	WriteTo io.Writer
}

RunAddInput holds the parameters for the add command's core logic, decoupled from CLI flag parsing so it can be tested directly.

type RunDetachInput

type RunDetachInput struct {
	Service driving.Service
	A       string
	B       string
	Author  string
	JSON    bool
	WriteTo io.Writer
}

RunDetachInput holds the parameters for the detach command's core logic, decoupled from CLI flag parsing so it can be tested directly.

type RunListInput added in v0.3.0

type RunListInput struct {
	// Service provides access to the issue tracker data.
	Service driving.Service

	// IOStreams provides output and TTY/color detection to section renderers.
	IOStreams *iostreams.IOStreams

	// RelFilter restricts the run to one section. The zero value (empty
	// string) means all sections run. Any non-empty value must be one of the
	// three canonical RelListCategory constants; callers are responsible for
	// using ParseRelListCategory to validate user-supplied strings before
	// placing them here. An unrecognized non-empty value causes RunList to
	// return an error rather than silently running zero sections.
	RelFilter RelListCategory

	// RenderParentChild renders the parent-child hierarchy section.
	RenderParentChild SectionRenderer

	// RenderBlocking renders the blocking dependency section.
	RenderBlocking SectionRenderer

	// RenderRefs renders the contextual reference section.
	RenderRefs SectionRenderer
}

RunListInput holds all parameters for RunList, decoupled from CLI flag parsing to allow direct invocation from tests with injectable renderers.

type RunRemoveInput added in v0.3.0

type RunRemoveInput struct {
	// Service is the tracker service used to remove the relationship.
	Service driving.Service
	// A is the source issue ID string.
	A string
	// Rel is the relationship type string (e.g., "blocked_by", "blocks", "refs",
	// "parent_of", "child_of").
	Rel string
	// B is the target issue ID string.
	B string
	// Author identifies who is performing the removal.
	Author string
	// JSON, when true, emits a machine-readable JSON response instead of prose.
	JSON bool
	// WriteTo is the writer for command output.
	WriteTo io.Writer
}

RunRemoveInput holds the parameters for the remove command's core logic, decoupled from CLI flag parsing so it can be tested directly.

type SectionRenderer added in v0.3.0

type SectionRenderer func(ctx context.Context, svc driving.Service, ios *iostreams.IOStreams) error

SectionRenderer is the function type for rendering a single section of np rel list output. It receives the service for data access and the IOStreams for TTY-aware, color-enabled output.

type TreeModelService added in v0.3.0

type TreeModelService = treeService

TreeModelService is the subset of driving.Service consumed by BuildTreeModel. It is exported so that tests in the _test package can declare their own helpers that accept the interface without depending on the driving package directly.

type TreeNode added in v0.3.0

type TreeNode struct {
	// Kind distinguishes expanded issue nodes from sibling placeholder entries.
	Kind NodeKind

	// Depth is the zero-based depth in the tree. The root ancestor is at depth
	// zero; each child tier increments the depth by one.
	Depth int

	// IsFocus is true for the single node that corresponds to the focus issue
	// supplied to BuildTreeModel. Renderers use this to apply emphasis (e.g.,
	// bold on TTY). Only meaningful when Kind == NodeKindIssue.
	IsFocus bool

	// IssueID is the string representation of the issue ID (e.g., "NP-a3bxr").
	// Empty when Kind == NodeKindPlaceholder.
	IssueID string

	// IssueItem carries the full list-item DTO for the issue. The zero value
	// when Kind == NodeKindPlaceholder.
	IssueItem driving.IssueListItemDTO

	// Count is the number of collapsed siblings this placeholder represents.
	// Zero when Kind == NodeKindIssue.
	Count int

	// BackRefParentID is the ID of the node that was the parent of this issue at
	// its first appearance in the output. Empty when the issue first appeared as a
	// root (depth 0). Only meaningful when Kind == NodeKindBackRef.
	BackRefParentID string
}

TreeNode is a single entry in the in-memory tree model produced by BuildTreeModel. It is rendering-agnostic — text and JSON renderers both consume this structure and produce their output independently.

When Kind == NodeKindIssue, all Issue* fields are populated and Count is zero. When Kind == NodeKindPlaceholder, only Depth and Count are meaningful; Issue* fields are empty.

func BuildTreeModel added in v0.3.0

func BuildTreeModel(ctx context.Context, svc treeService, focusID string, full bool) ([]TreeNode, error)

BuildTreeModel constructs an ordered, flat slice of TreeNode values that represents the tree view for the given focus issue.

When full is false (default mode), the model contains:

  • every ancestor from the root down to the focus issue (one node each),
  • the full descendant subtree of the focus issue, and
  • placeholder entries at each ancestor tier for siblings of the path element that are NOT on the ancestry path (these always appear after the expanded path element at their tier, reflecting siblings sorted after it).

When full is true, the model contains the complete tree rooted at the root ancestor with no placeholder entries. The result is identical to calling BuildTreeModel with the root ancestor ID and full == false on a tree where the root ancestor has no unexpanded siblings.

Children at every tier are sorted ascending by issue ID (lexicographic on the string representation). Placeholder entries appear after the expanded path element at their tier — they summarize all siblings that sort after the expanded element (the implementation counts siblings on both sides, because sibling-count for the placeholder is all siblings except the path element).

The svc parameter must not be nil. focusID must be a valid issue ID string present in the database.

Directories

Path Synopsis
Package graphcmd implements the "graph" CLI command, which renders the issue hierarchy and relationships as a Graphviz DOT file.
Package graphcmd implements the "graph" CLI command, which renders the issue hierarchy and relationships as a Graphviz DOT file.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL