gojira

package module
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 19 Imported by: 0

README

gojira

CI CD Coverage Go Reference Go Report Card

Status badges populate once the GitHub Actions CI/CD workflows have run on the repository; on a fresh fork they render a neutral "no status" state until the first workflow run. The coverage figure is the first-party statement coverage reported by go test (see Testing and coverage).

A Go CLI and library for working with Jira Cloud from the command line. Its flagship gojira crawl recursively mirrors a Jira issue graph into Markdown — including the issue's hierarchy, development metadata (branches, commits, pull requests, builds, repositories), and human-labelled custom fields — and can export the discovered graph as JSON and D2. Beyond crawling, gojira also ships a gRPC server (gojira serve), a Model Context Protocol server for AI hosts (gojira mcp), and write operations to create and update issues, add comments, and run workflow transitions. The same engine is available as an embeddable Go library.

Pre-1.0 status. APIs may change between minor versions until v1.0 is tagged.

[!WARNING] Breaking changes in v0.2.0. Two changes break compatibility with v0.1.0:

  1. Package layout — the reusable classify, client, and log packages moved under pkg/, so their import paths gained a /pkg/ segment (e.g. github.com/neumachen/gojira/pkg/client). The library facade github.com/neumachen/gojira is unchanged.
  2. Protocol servicesinternal/grpcserver became internal/grpc and internal/mcpserver became internal/mcp, each behind an encapsulated Serve(ctx, cfg). The gojira.ServerConfig / gojira.LoadServerConfig accessors were removed — read Config.ServerAddress from a loaded Config instead.

See the v0.2.0 changelog for the full list. This is a pre-1.0 release; there is no migration shim, but no external consumers depend on the previous paths.

Install

Go install
go install github.com/neumachen/gojira/cmd/gojira@v0.2.0
Docker
# Pull (when published to a registry) or build locally:
docker build -t gojira:v0.2.0 .

A docker-compose.yml is provided for convenience; see "Docker Compose" below.

Shell completion

gojira can generate completion scripts for bash, zsh, fish, and PowerShell. Add the matching line to your shell's startup file:

# bash — ~/.bashrc
source <(gojira completion bash)

# zsh — ~/.zshrc (ensure `autoload -U compinit && compinit` runs first)
source <(gojira completion zsh)

# fish
gojira completion fish > ~/.config/fish/completions/gojira.fish

# PowerShell — add to $PROFILE
gojira completion pwsh | Out-String | Invoke-Expression

Completion works without any gojira configuration, so you can set it up before running gojira init.

Quick start

# 1. Generate a Jira Cloud API token at:
#      https://id.atlassian.com/manage-profile/security/api-tokens

# 2. Configure credentials. The simplest path is `gojira init`, which
#    writes a 0600 config file at ~/.config/gojira/config.yaml — see
#    "First run: gojira init" below. The CLI also reads env vars
#    directly: either export them or use a .env file.
gojira init   # interactive; or pass --site/--user/--token as flags
# alternative: env-only configuration
cp .env.example .env
$EDITOR .env   # fill in GOJIRA_SITE, GOJIRA_USER, GOJIRA_TOKEN

# 3. Crawl a single Jira issue and its reachable graph.
gojira crawl PROJ-123

# 4. The Markdown output appears under ./out by default.
ls out/PROJ-123/
Docker Compose
# After ./out is created by your first run, this works as a
# convenient wrapper:
docker compose run --rm gojira crawl PROJ-123

What gojira crawl does

Starting from one Jira issue key, the crawler:

  1. Fetches the issue via the Jira Cloud REST API v3 (GET /rest/api/3/issue/{key}?expand=names).
  2. Parses the issue's description from Atlassian Document Format into Markdown.
  3. Renders an index.md for the issue under <output-dir>/<KEY>/index.md with metadata, description, relationships, the ## Development panel (pull requests, branches, commits, repositories, builds), and human-labelled custom fields.
  4. Recursively follows links: subtasks, parents, issue links, hierarchy children (via JQL parent = "KEY" plus Epic Link), and Jira-flavoured links inside the description. Each discovered key is fetched and rendered the same way.
  5. Recognizes GitHub pull request URLs as PR references even when no Jira key is present in the PR title or branch.
  6. Writes a per-issue references/outbound.md summarising every outbound reference the issue produced.
  7. Honours configurable caps on depth, issue count, time, and API concurrency.

CLI flags

The crawl subcommand accepts these flags. Each maps to an env var of the same name in uppercase with a GOJIRA_ prefix; the flag overrides the env var when both are set.

Flag Env var Default What it does
--site GOJIRA_SITE (required) Jira Cloud site URL, e.g. https://your-site.atlassian.net.
--user GOJIRA_USER (required) Atlassian account email.
--token GOJIRA_TOKEN (required) Atlassian API token.
--output-dir GOJIRA_OUTPUT_DIR ./out Output root directory.
--depth-limit GOJIRA_DEPTH_LIMIT 0 (no cap) Max crawl depth from the start issue.
--issue-cap GOJIRA_ISSUE_CAP 500 Max issues to fetch per run.
--time-cap GOJIRA_TIME_CAP_SECONDS 0 (no cap) Max wall-clock seconds per run.
--concurrency GOJIRA_CONCURRENCY 3 Concurrent Jira API requests.
--refetch GOJIRA_REFETCH false Re-fetch issues that already exist on disk.
--include-comments GOJIRA_INCLUDE_COMMENTS false Fetch issue comments (not yet rendered as of v0.2.0; the field is fetched but the current renderer ignores it).
--include-children GOJIRA_INCLUDE_CHILDREN true Discover hierarchy children via JQL parent search.
--child-search-limit GOJIRA_CHILD_SEARCH_LIMIT 100 Max children to discover per parent.
--epic-link-field GOJIRA_EPIC_LINK_FIELD (auto-detect) Tenant's Epic Link custom-field ID.
--include-dev-status GOJIRA_INCLUDE_DEV_STATUS true Query the Jira Dev Status API for development metadata.
--dev-status-applications GOJIRA_DEV_STATUS_APPLICATIONS GitHub Comma-separated Dev Status integration types.
--dev-status-data-types GOJIRA_DEV_STATUS_DATA_TYPES pullrequest,branch,commit,repository,build Comma-separated dataType values to query.
--render-null-custom-fields GOJIRA_RENDER_NULL_CUSTOM_FIELDS false Include custom fields whose value is JSON null.
--graph GOJIRA_EMIT_GRAPH false Write graph.json and graph.d2 (D2 source) at the output-dir root.
--log-level GOJIRA_LOG_LEVEL info One of: error, warn, info, debug, trace.
--log-format GOJIRA_LOG_FORMAT text One of: text (human-readable), json (one JSON object per line).
--config GOJIRA_CONFIG_FILE (discovered) Path to a YAML config file (see Configuration).

First run: gojira init

Every Jira-touching command (crawl, serve, create, update, comment, transitions, transition) requires some form of configuration before it will contact Jira. The simplest way to provide it is the gojira init subcommand, which scaffolds a config file with 0600 permissions (it contains your Jira API token).

gojira init writes the global config by default; pass --local to write a project-local file instead. The two flags are mutually exclusive, and --local never creates or modifies the global config.

  • gojira init (or gojira init --global) — writes the global config at the XDG path ($XDG_CONFIG_HOME/gojira/config.yaml or, when XDG_CONFIG_HOME is unset, ~/.config/gojira/config.yaml).
  • gojira init --local — writes a project-local ./gojira.yaml in the current working directory (similar in spirit to git init for the project's gojira config). The file is complete and self-sufficient: it carries every required field so it loads on its own, without any global config present.
# Global config (default; writes the XDG config.yaml):
gojira init \
  --site https://your.atlassian.net \
  --user you@example.com \
  --token <api-token>

# Project-local config (writes ./gojira.yaml, adds it to .gitignore):
gojira init --local \
  --site https://your.atlassian.net \
  --user you@example.com \
  --token <api-token>

Omitted required values are prompted for interactively. The token is read without echo on a real terminal (via golang.org/x/term) and falls back to a warned-echo read on non-terminals (CI pipes, etc.). The --output-dir and --server-address flags accept the documented defaults (./out and 127.0.0.1:50051) when omitted.

gojira init refuses to overwrite an existing config file unless you pass --force. The written YAML has the same shape as gojira.example.yaml and is consumed by the same loader the cascade below documents.

Security: do not commit ./gojira.yaml. The project-local file contains your Jira API token. gojira init --local writes it with 0600 permissions and, when a ./.gitignore is present, appends gojira.yaml to it (and prints what it did). When no .gitignore exists, it warns you to ignore the file yourself. Treat ./gojira.yaml like any other credentials file.

How a command finds its configuration

A guarded command will run iff any one of the following is true:

  1. A config file is discovered through the cascade (see Config-file discovery below): an explicit --config value, $GOJIRA_CONFIG_FILE, ./gojira.yaml, or the XDG global config file.
  2. The --config <path> flag was passed.
  3. The --site, --user, and --token flags were ALL passed.
  4. The required env vars are present: GOJIRA_CONFIG_FILE, or all three of GOJIRA_SITE, GOJIRA_USER, and GOJIRA_TOKEN.

If none of (1)-(4) hold, the command exits with a clear error pointing at gojira init. The init, help, and --version invocations are exempt from this guard so they remain usable from a no-config state.

Configuration

gojira supports three configuration surfaces — embedded defaults, one or two YAML config files (a global and an optional project-local file), and GOJIRA_* environment variables — plus the CLI flags documented above. They compose into one effective configuration through a documented cascade:

embedded defaults < XDG global config.yaml < local ./gojira.yaml < GOJIRA_* env vars < CLI flags

A value at any layer overrides the same value at every lower layer; a value absent at every layer keeps its embedded default.

When no --config flag and no $GOJIRA_CONFIG_FILE are given, gojira layers the global config (~/.config/gojira/config.yaml) and the project-local ./gojira.yaml field-by-field: the local file overrides the global on a per-field basis, and any field absent from the local file is inherited from the global config. This is true XDG-style layering with local-first precedence, so a project can pin just the handful of values it cares about (e.g. a different output.dir or crawl.concurrency) and inherit the rest of the global config unchanged.

An explicit --config <path> (or $GOJIRA_CONFIG_FILE) pins exactly that one file with no layering: gojira loads only that file and does not also read the global or local config. This is the escape hatch for CI, scripts, and any case where you want a fully deterministic single-file load.

Config-file discovery

There are two discovery modes, depending on whether the load is explicitly pinned or left to discovery:

Explicit single-file pin (first match wins, no layering). When either of the explicit candidates is set, gojira uses exactly that file and stops — it does NOT also read the global or local config:

  1. --config <path> (CLI flag)
  2. $GOJIRA_CONFIG_FILE

Both are explicit: when set but the file does not exist, gojira exits with a hard error so a misconfigured invocation fails fast.

Pure discovery (global + local layered). When neither explicit candidate is set, gojira layers the following files (each missing file is silently skipped; a fully absent chain falls through to defaults plus environment variables, not an error):

  1. ./gojira.yaml — project-local (current working directory). Written by gojira init --local.
  2. $XDG_CONFIG_HOME/gojira/config.yaml — XDG global, when XDG_CONFIG_HOME is set.
  3. ~/.config/gojira/config.yaml — XDG global fallback.

In pure discovery, the global config (4 or 5, whichever exists) is loaded first and the local ./gojira.yaml (3) is layered on top field-by-field. Either may be absent; if both exist, local fields override global ones and global fills any gaps the local file leaves.

Behavior change in v0.2.0. Previously a ./gojira.yaml in pure discovery fully shadowed the global config (winner-takes-all). The two files are now merged: global is read first and local is layered over it per field. To restore the old single-file load semantics, point --config (or $GOJIRA_CONFIG_FILE) at the file you want.

A starter file lives at gojira.example.yaml. Copy it to one of the locations above, edit the values you care about, and delete the blocks you want to leave at their defaults. The file's first line embeds a yaml-language-server directive so editors like VS Code (with the YAML extension) and Neovim get autocomplete and live validation against the embedded JSON Schema at internal/config/config.schema.json.

Quick start with a config file:

# Copy the example and edit it.
cp gojira.example.yaml gojira.yaml
$EDITOR gojira.yaml

# Supply the secret out of band so it never lands in version control.
export GOJIRA_JIRA_API_TOKEN="$(security find-generic-password -s gojira -w)"

# Crawl. --config is optional; ./gojira.yaml is auto-discovered.
gojira crawl --config gojira.yaml PROJ-1
Canonical environment variables

The table below lists the canonical GOJIRA_* keys gojira reads. Every key here has a YAML equivalent under the corresponding section of gojira.yaml; pick whichever surface fits the deployment best.

Env var YAML path Default
GOJIRA_SCHEMA schema gojira.config.v1
GOJIRA_CONFIG_FILE (resolver only) (discovered)
GOJIRA_JIRA_BASE_URL jira.base_url (required)
GOJIRA_JIRA_EMAIL jira.email (required)
GOJIRA_JIRA_API_TOKEN jira.api_token (required)
GOJIRA_OUTPUT_DIR output.dir (required)
GOJIRA_CRAWL_DEPTH_LIMIT crawl.depth_limit 0
GOJIRA_CRAWL_ISSUE_CAP crawl.issue_cap 500
GOJIRA_CRAWL_TIME_CAP_SECONDS crawl.time_cap_seconds 0
GOJIRA_CRAWL_CONCURRENCY crawl.concurrency 3
GOJIRA_CRAWL_REFETCH crawl.refetch false
GOJIRA_CRAWL_INCLUDE_COMMENTS crawl.include_comments false
GOJIRA_CRAWL_INCLUDE_CHILDREN crawl.include_children true
GOJIRA_CRAWL_CHILD_SEARCH_LIMIT crawl.child_search_limit 100
GOJIRA_CRAWL_EPIC_LINK_FIELD crawl.epic_link_field (auto-detect)
GOJIRA_CRAWL_INCLUDE_DEV_STATUS crawl.include_dev_status true
GOJIRA_CRAWL_DEV_STATUS_APPLICATIONS crawl.dev_status_applications GitHub
GOJIRA_CRAWL_DEV_STATUS_DATA_TYPES crawl.dev_status_data_types pullrequest,branch,commit,repository,build
GOJIRA_CRAWL_RENDER_NULL_CUSTOM_FIELDS crawl.render_null_custom_fields false
GOJIRA_CRAWL_EMIT_GRAPH crawl.emit_graph false
GOJIRA_LOG_LEVEL log.level info
GOJIRA_LOG_FORMAT log.format text
Deprecated aliases (still honored)

The v0.1 flat GOJIRA_* keys documented under CLI flags continue to work; gojira resolves them to their canonical Phase 0 equivalents during load. When both the canonical key and its alias are set, the canonical key wins — set both only during a migration.

Deprecated alias Canonical replacement
GOJIRA_SITE GOJIRA_JIRA_BASE_URL
GOJIRA_USER GOJIRA_JIRA_EMAIL
GOJIRA_TOKEN GOJIRA_JIRA_API_TOKEN
GOJIRA_DEPTH_LIMIT GOJIRA_CRAWL_DEPTH_LIMIT
GOJIRA_ISSUE_CAP GOJIRA_CRAWL_ISSUE_CAP
GOJIRA_TIME_CAP_SECONDS GOJIRA_CRAWL_TIME_CAP_SECONDS
GOJIRA_CONCURRENCY GOJIRA_CRAWL_CONCURRENCY
GOJIRA_REFETCH GOJIRA_CRAWL_REFETCH
GOJIRA_INCLUDE_COMMENTS GOJIRA_CRAWL_INCLUDE_COMMENTS
GOJIRA_INCLUDE_CHILDREN GOJIRA_CRAWL_INCLUDE_CHILDREN
GOJIRA_CHILD_SEARCH_LIMIT GOJIRA_CRAWL_CHILD_SEARCH_LIMIT
GOJIRA_EPIC_LINK_FIELD GOJIRA_CRAWL_EPIC_LINK_FIELD
GOJIRA_INCLUDE_DEV_STATUS GOJIRA_CRAWL_INCLUDE_DEV_STATUS
GOJIRA_DEV_STATUS_APPLICATIONS GOJIRA_CRAWL_DEV_STATUS_APPLICATIONS
GOJIRA_DEV_STATUS_DATA_TYPES GOJIRA_CRAWL_DEV_STATUS_DATA_TYPES
GOJIRA_RENDER_NULL_CUSTOM_FIELDS GOJIRA_CRAWL_RENDER_NULL_CUSTOM_FIELDS
GOJIRA_EMIT_GRAPH GOJIRA_CRAWL_EMIT_GRAPH

GOJIRA_OUTPUT_DIR, GOJIRA_LOG_LEVEL, and GOJIRA_LOG_FORMAT already use canonical names in v0.1 and need no migration.

Output layout

out/
└── PROJ-123/
    ├── index.md
    └── references/
        └── outbound.md

Each fetched issue lives at <KEY>/index.md. Outbound references discovered in that issue are summarised at <KEY>/references/outbound.md. The references/ directory keeps the per-issue reference index out of the issue's own rendered Markdown so a reader who wants the full graph view can find it, but a reader who just wants the issue content sees only index.md.

Library usage

The same engine is available as a Go library. Third-party programs can embed gojira in their pipelines without invoking the CLI binary:

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/neumachen/gojira"
)

func main() {
    cfg, err := gojira.LoadConfig(map[string]string{
        "GOJIRA_SITE":       os.Getenv("GOJIRA_SITE"),
        "GOJIRA_USER":       os.Getenv("GOJIRA_USER"),
        "GOJIRA_TOKEN":      os.Getenv("GOJIRA_TOKEN"),
        "GOJIRA_OUTPUT_DIR": "./out",
    })
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    summary, err := gojira.Crawl(
        context.Background(), cfg, []string{"PROJ-123"}, nil,
    )
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Printf("fetched %d issues; %d PRs discovered\n",
        summary.Fetched, summary.PRsFound)
}

The library facade groups its capabilities into classification (Classify), configuration (LoadConfig and friends), fetch/render (GetIssue, FetchAndRender), crawl (Crawl, CrawlGraph), and write operations (CreateIssue, UpdateIssue, AddComment, ListTransitions, TransitionIssue), plus type aliases for Config, Summary, Sink, Event, and the graph model (GraphModel/GraphNode/GraphEdge). All are documented in gojira.go's package doc.

Observability and tracing

gojira crawl ships a verbose, structured, correlatable observability instrument designed for answering "where did the wall-clock go?" on a large crawl. It's measurement-first: enabling it does not change traversal, fetch logic, or on-disk output — only what is observed.

Levels

gojira extends slog's standard four levels (error, warn, info, debug) with a fifth, trace. The five levels carry intent-based meaning:

Level Meaning
error Failures.
warn Degraded enrichment, partial failures.
info Operationally significant facts and all measurement data — phase/issue spans, per-HTTP-request summaries, the end-of-run crawl.measurement totals. A normal --log-level info run already shows time attribution.
debug Durable diagnostics worth keeping even after a problem is solved — resolved state and decisions (skip-if-exists hits, epic-link field resolution).
trace Traceability woven into the code — span lifecycles, the "because X therefore Y" fan-out reasoning, raw response bodies and full net/http/httptrace timings.

Select with --log-level trace or GOJIRA_LOG_LEVEL=trace. Use --log-format json to get machine-filterable JSON lines (one record per line).

Correlation attributes

Every traced log line carries structured attributes so a single grep/jq filter can reconstruct any subset of the run's work:

Attribute Meaning
run_id Opaque short UID for this crawl invocation; on every line.
ticket_id Jira issue key (e.g. PROJ-1417) — named to mirror Jira. Present on every line whose work concerns a specific issue.
span_id / parent_span_id Opaque short IDs per unit of work, linking each unit to whoever enqueued it. Opaque, not hierarchical, because crawls can bleed across projects or boards.
phase One of fetch, parse, hierarchy_jql, dev_status, render, store, enqueue.
trace_stream response (HTTP/data side, from the round-tripper) or stream (orchestration side, from the crawl).
depth, discovered_from, relation Fan-out lineage — present on crawl.fanout TRACE lines explaining why a key entered the queue.
Measurement summary

At end of run, gojira crawl emits a single INFO crawl.measurement line with the per-phase wall-clock attribution:

{"msg":"crawl.measurement","total_api_time_ms":31872,"total_duration_ms":32114,"call_counts":{"fetch":48,"parse":48,"hierarchy_jql":12,"dev_status":48,"render":48,"store":48},"time_by_phase_ms":{"fetch":18204,"hierarchy_jql":7411,"dev_status":4012,"parse":612,"render":1031,"store":602}}

The same totals are also folded into the crawl.Summary returned to library callers as APICallCounts, APITimeByPhase, and TotalAPITime.

Filtering examples
# All response-stream traces for one issue:
gojira crawl PROJ-1417 --log-level trace --log-format json 2>&1 \
  | jq 'select(.trace_stream=="response" and .ticket_id=="PROJ-1417")'

# Only the per-phase measurement summary:
gojira crawl PROJ-1417 --log-level info --log-format json 2>&1 \
  | jq 'select(.msg=="crawl.measurement")'

# Reconstruct the fan-out tree (TRACE):
gojira crawl PROJ-1417 --log-level trace --log-format json 2>&1 \
  | jq 'select(.msg=="crawl.fanout") | "\(.discovered_from) -[\(.relation)]-> \(.ticket_id)"'
Credential redaction

Authorization, Cookie, Proxy-Authorization, Set-Cookie, and X-Atlassian-Token headers are ALWAYS redacted in trace output, even at --log-level trace. The raw token is never written to logs by design; the redaction is audited by a unit test (TestRoundTripper_RedactsAuthorizationEvenAtTrace).

gRPC service (gojira serve)

In addition to the one-shot crawl subcommand, gojira can run as a long-lived gRPC server that exposes its full capability surface — classification, issue fetch, recursive crawl, in-memory graph export, and write operations (create/update/comment/transitions) — to other front-ends such as a TUI or the gojira mcp bridge.

# Start the server (reads the same GOJIRA_* config as `crawl`).
export GOJIRA_SITE="https://your-site.atlassian.net"
export GOJIRA_USER="you@example.com"
export GOJIRA_TOKEN="<api-token>"
gojira serve --address 127.0.0.1:50051

The server is single-tenant: one Jira identity is loaded at startup from the same configuration cascade as crawl. It accepts concurrent clients (each RPC is isolated) and is intended for a loopback or otherwise trusted network — Phase 1 ships no TLS and no authentication.

Service gojira.v1.Gojira
RPC Type Description
Classify unary Classify a bare key or URL into JiraKey, JiraURL, GitHubPR, or External.
GetIssue unary Fetch one issue. The response is a structured proto Issue, rendered Markdown, or JSON, selected by the request's OutputFormat (STRUCTURED, MARKDOWN, JSON).
Crawl server-streaming Recursively crawl from one or more start keys, streaming a CrawlEvent for each state transition. Issue content is written server-side to the configured output directory (streaming content over the wire is deferred to Phase 2).
GetGraph unary Crawl in-memory from one or more start keys and return the discovered issue graph as nodes and edges, without writing files. Mirrors the library's CrawlGraph and the CLI's crawl --graph.
CreateIssue unary Create an issue (project + type required; fields via summary/description/labels/parent and a raw_fields map). dry_run returns the request body the server would send, without creating anything.
UpdateIssue unary Edit fields on an existing issue. Honors dry_run like CreateIssue.
AddComment unary Append a comment (plain text, converted to ADF server-side) to an issue.
ListTransitions unary List the workflow transitions currently available for an issue (id, name, target status).
TransitionIssue unary Move an issue through a transition, selected by transition_id or by target_status_name (resolved server-side via ListTransitions).

The proto contract is defined in proto/gojira/v1/gojira.proto and the generated Go bindings live under gen/gojira/v1/.

Write operations

The CreateIssue, UpdateIssue, AddComment, and TransitionIssue RPCs let clients mutate Jira through the same single-tenant identity the server loaded at startup. Two design points are worth calling out:

  • Dry-run. CreateIssue and UpdateIssue accept a dry_run flag. When set, the server builds and returns the exact JSON request body it would send to Jira (in dry_run_body) without performing the write — useful for previewing a mutation before committing to it.
  • Extensible fields. Beyond the typed fields (summary, description, labels, parent), any Jira field — including tenant-specific custom fields — can be set through the raw_fields map (field id → raw JSON value). In the Go library this is the WithField / WithRawFields option; new fields never require a new method or signature change.

Errors carry Jira's detail: a 400 validation failure maps to gRPC InvalidArgument with the failing field names in the message; a 409 (e.g. an invalid workflow transition) maps to FailedPrecondition.

Issue deletion is intentionally unsupported — destructive removal is out of scope for this phase.

Server configuration
Flag Env var Default Description
--address GOJIRA_SERVER_ADDRESS 127.0.0.1:50051 gRPC server bind address.
--config GOJIRA_CONFIG_FILE (discovered) Path to a YAML config file.
--site GOJIRA_SITE (required) Jira Cloud site URL.
--user GOJIRA_USER (required) Atlassian account email.
--token GOJIRA_TOKEN (required) Atlassian API token.
--output-dir GOJIRA_OUTPUT_DIR ./out Output root directory for Crawl.
--log-level GOJIRA_LOG_LEVEL info One of error, warn, info, debug, trace.
--log-format GOJIRA_LOG_FORMAT text One of text, json.

The server address can also be set in the YAML config file:

server:
  address: 127.0.0.1:50051

The server stops gracefully on SIGINT/SIGTERM.

Reference client

A minimal reference client ships at cmd/gojira-client for smoke-testing a running server. It is a reference tool, not a production front-end.

go run ./cmd/gojira-client -address 127.0.0.1:50051 -classify PROJ-1147
go run ./cmd/gojira-client -address 127.0.0.1:50051 -key PROJ-1147 -format markdown
go run ./cmd/gojira-client -address 127.0.0.1:50051 -crawl PROJ-1147
go run ./cmd/gojira-client -address 127.0.0.1:50051 -create-project PROJ -create-type Task -create-summary "New task" -dry-run
go run ./cmd/gojira-client -address 127.0.0.1:50051 -comment PROJ-1147 -comment-text "Looks good"
go run ./cmd/gojira-client -address 127.0.0.1:50051 -transitions PROJ-1147
go run ./cmd/gojira-client -address 127.0.0.1:50051 -transition PROJ-1147 -to-status "In Progress"
Regenerating the proto bindings

The generated code under gen/ is committed. To regenerate after editing the proto contract, run buf:

./scripts/gen-proto.sh   # runs `buf lint` then `buf generate`

MCP server (gojira mcp)

gojira mcp runs gojira as a Model Context Protocol server over the stdio transport so AI hosts (Claude Desktop, Cursor, Zed, …) can use gojira's tools directly. The host launches gojira mcp as a subprocess and speaks JSON-RPC over its stdin/stdout. stdout is reserved for the protocol stream; all logs go to stderr.

# Run from your AI host's mcpServers config (see below); for a manual
# smoke, you can drive a one-shot handshake yourself:
gojira mcp --config ~/.config/gojira/config.yaml < your-jsonrpc-frames

gojira mcp is guarded by the same require-config check as crawl/serve (see First run) and additionally requires mcp.mode to be set — it exits 1 with a clear message when mcp.mode is unset or not one of self|bridge.

Modes

The mode is set via mcp.mode in the config file or GOJIRA_MCP_MODE in the environment. It is required for gojira mcp and only for gojira mcp (crawl/serve continue to load configs without an mcp: section).

  • self — gojira does the Jira work in-process via the library facade. The simplest mode; no extra server to run.
  • bridge — gojira forwards each tool call to a running gojira serve gRPC server at server.address (default 127.0.0.1:50051, override with GOJIRA_SERVER_ADDRESS or --address). This is the "one shared gRPC backend, many ephemeral MCP processes" topology: a single long-running gojira serve is the upstream, and every AI host spawns its own short-lived gojira mcp subprocess that bridges to it.
Tools

The following MCP tools are always available (read-only):

  • classify — classify an input as Jira key, Jira URL, GitHub PR URL, or external URL
  • get_issue — fetch a single Jira issue with its outbound references
  • crawl — recursively crawl Jira issues from one or more start keys; returns a summary on completion (emits MCP progress notifications per fetched issue when the host supplies a progress token)
  • get_graph — crawl in-memory and return the discovered issue graph as {nodes, edges} (no files written)
  • list_transitions — list workflow transitions available for an issue

The mutating tools are gated behind mcp.allow_writes: true (default false). When allow_writes is false, the write tools are absent from tools/list — an AI cannot mutate Jira until the operator explicitly opts in:

  • create_issue
  • update_issue
  • add_comment
  • transition_issue
Host configuration

Add an entry to your AI host's MCP servers configuration. The exact file location varies by host (Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json on macOS; Cursor and Zed have their own per-app paths) but the schema is the same:

{
  "mcpServers": {
    "gojira": {
      "command": "gojira",
      "args": ["mcp"],
      "env": {
        "GOJIRA_SITE": "https://your.atlassian.net",
        "GOJIRA_USER": "you@example.com",
        "GOJIRA_TOKEN": "your-api-token",
        "GOJIRA_MCP_MODE": "self"
      }
    }
  }
}

For bridge mode, set "GOJIRA_MCP_MODE": "bridge" and ensure a gojira serve instance is running on the host; add "GOJIRA_SERVER_ADDRESS": "127.0.0.1:50051" (or your chosen address) if you do not use the default. The env block in the host config is not the only configuration channel — gojira init (or a hand-written ~/.config/gojira/config.yaml) is equivalent, and the same require-config guard described in First run applies: at least one of a discovered config file, --config, the --site/--user/--token trio, or the equivalent GOJIRA_* env vars must be present.

To expose the mutating tools to the host, add "GOJIRA_MCP_ALLOW_WRITES": "true" to the env block (or set mcp.allow_writes: true in your config file). Default is off.

Known limitations (v0.2.0)

  • Jira Cloud only. Jira Server / Data Center is out of scope for the entire product.
  • Comments are not yet rendered (the field is fetched but the current renderer ignores it; landing in v0.2).
  • The Jira Dev Status API used for development metadata is semi-undocumented. It has been stable for ~10 years because Atlassian's UI uses it, but no SLA is offered. Disable with --include-dev-status=false to opt out.
  • The gRPC service (gojira serve) is single-tenant and ships without TLS or authentication; run it only on a loopback or trusted network. Streaming issue content over the wire, multi-tenancy, per-request config overrides, and TLS/auth are deferred to Phase 2.
  • gRPC write operations (CreateIssue/UpdateIssue/AddComment/ TransitionIssue) use the server's single startup identity; per-request credentials are deferred to a later phase. Issue deletion is not supported.
  • Observability and tracing (--log-level trace) is opt-in; default info already shows the end-of-run measurement summary. Cross-process tracing (e.g. OpenTelemetry export across the gRPC boundary) is out of scope for this release.

Roadmap

Future releases anticipated:

  • A terminal UI (TUI) front-end over the gRPC API, as a gRPC client. (The MCP server already shipped — see MCP server.)
  • Phase 2 service work: streaming rendered issue content over the wire, multi-tenant identities, per-request configuration overrides, and TLS/authentication.
  • gojira fetch — targeted single-issue (or small-list) retrieval without recursive expansion. The same renderer; just no JQL parent search and no descent into linked issues.
  • Write operations: create issues, update fields, post comments.
  • JQL search: list issues matching a query and crawl the result set.
  • Improved customisation: per-field rendering options, per-tenant field-name overrides.

No timelines committed. Direction only.

Testing and coverage

The entire suite runs under the standard Go toolchain — go test, table- driven tests, golden files, httptest fakes, and bufconn for the gRPC service. There is no third-party test runner; assertions use a mix of stretchr/testify and the standard testing package.

# Run everything (the same command CI runs):
go test ./... -count=1

# With the race detector:
go test ./... -count=1 -race

# First-party statement coverage (attributes cross-package coverage so the
# library facade, exercised only by integtest/, is counted correctly):
go test ./... -count=1 -coverpkg=./... -coverprofile=cover.out
go tool cover -func=cover.out | tail -1

The current first-party coverage is 77.6% of statements (excluding the generated gen/ protobuf bindings). The core paths — the library facade, crawl orchestration, rendering, parsing, and configuration cascade — sit at 80–100%; the lowest-covered first-party packages are the MCP bridge write path and the Dev Status error branches.

Project documentation

License

MIT

Documentation

Overview

Package gojira is the public library facade for the gojira Jira-to-Markdown mirror tool. It exposes the full capability surface as one cohesive package so third-party programs can embed gojira without touching its internal packages or the CLI binary. The exported capabilities group into:

Public surface invariants

  • No flag parsing, CLI argument handling, or process-level signal handling.
  • No os.Exit calls.
  • No hard-coded credentials, Jira domains, or project keys.
  • All internal packages are hidden; only the facade capabilities and their supporting types are exported.
  • The CLI binary (cmd/gojira) is a thin consumer of this package; it is never required to use the library.

Minimal usage example (third-party consumer workflow)

cfg, err := gojira.LoadConfig(map[string]string{
    "GOJIRA_SITE":       "https://mycompany.atlassian.net",
    "GOJIRA_USER":       "me@example.com",
    "GOJIRA_TOKEN":      os.Getenv("JIRA_TOKEN"),
    "GOJIRA_OUTPUT_DIR": "./jira-mirror",
})
if err != nil {
    log.Fatal(err)
}

summary, err := gojira.Crawl(ctx, cfg, []string{"PROJ-1"}, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("fetched=%d stubbed=%d failed=%d\n",
    summary.Fetched, summary.Stubbed, summary.Failed)

Index

Constants

View Source
const (
	KindIssueFetched     = events.KindIssueFetched
	KindIssueStubbed     = events.KindIssueStubbed
	KindIssueFailed      = events.KindIssueFailed
	KindIssueCapReached  = events.KindIssueCapReached
	KindPRReferenceFound = events.KindPRReferenceFound
	KindCrawlSummary     = events.KindCrawlSummary
)

Variables

View Source
var (
	// ErrUnauthorized is returned when Jira responds with 401.
	// This is a total-failure condition: credentials are invalid.
	ErrUnauthorized = client.ErrUnauthorized

	// ErrForbidden is returned when Jira responds with 403.
	// The crawl renders a permission-denied stub and continues.
	ErrForbidden = client.ErrForbidden

	// ErrNotFound is returned when Jira responds with 404.
	// The crawl renders a not-found stub and continues.
	ErrNotFound = client.ErrNotFound

	// ErrRateLimited is returned when Jira responds with 429 and all
	// retry attempts are exhausted.
	ErrRateLimited = client.ErrRateLimited

	// ErrBadRequest is returned when Jira responds with 400 (validation
	// failure). The concrete error may be a [*client.APIError] carrying
	// the failing field names; errors.Is(err, ErrBadRequest) still
	// matches via Unwrap.
	ErrBadRequest = client.ErrBadRequest

	// ErrConflict is returned when Jira responds with 409, e.g. an
	// invalid workflow transition. As with ErrBadRequest, an *APIError
	// may wrap this sentinel while still satisfying errors.Is.
	ErrConflict = client.ErrConflict

	// ErrConfigMissingRequired is returned (wrapped) by [LoadConfig] /
	// [LoadAppConfig] when a required configuration value is absent.
	// It is a re-export of the internal config sentinel so callers can
	// errors.Is without importing the internal package.
	ErrConfigMissingRequired = config.ErrMissingRequired

	// ErrConfigInvalidValue is returned (wrapped) by [LoadConfig] /
	// [LoadAppConfig] when a configuration value fails validation
	// (bad URL, unknown enum, malformed integer, etc.). It is a
	// re-export of the internal config sentinel so callers can
	// errors.Is without importing the internal package.
	ErrConfigInvalidValue = config.ErrInvalidValue
)

Functions

func AddComment added in v0.2.0

func AddComment(ctx context.Context, cfg Config, key string, opts ...client.CommentOption) (client.Comment, error)

AddComment appends a comment to the issue identified by key. The comment body is supplied via client.WithCommentText (plain text → ADF) or client.WithCommentADF (rich, caller-supplied ADF). The returned client.Comment carries Jira's id/author/created fields.

func BuildCreateIssueBody added in v0.2.0

func BuildCreateIssueBody(project, issueType string, opts ...client.CreateOption) ([]byte, error)

BuildCreateIssueBody returns the JSON request body CreateIssue would POST, without contacting Jira. It is a pure pass-through over client.RenderCreateBody, exposed at the facade so CLI / agent callers can preview a write before mutating.

func BuildUpdateIssueBody added in v0.2.0

func BuildUpdateIssueBody(opts ...client.UpdateOption) ([]byte, error)

BuildUpdateIssueBody returns the JSON request body UpdateIssue would PUT, without contacting Jira. Like BuildCreateIssueBody it is a pure pass-through — no network, no client construction.

func Classify

func Classify(input, jiraSite string) classify.Result

Classify determines the kind of link represented by input.

It is a direct re-export of classify.Classify. The returned classify.Result carries the Kind and any extracted fields (IssueKey, Owner, Repo, PRNumber, URL). classify is a public package; callers may import it directly for the Kind constants.

jiraSite is the Jira Cloud base URL (e.g. "https://mycompany.atlassian.net"). Only the host portion is used for matching.

func CrawlGraph added in v0.2.0

func CrawlGraph(ctx context.Context, cfg Config, startKeys []string, sink Sink) (Summary, GraphModel, error)

CrawlGraph runs a recursive crawl exactly like Crawl and returns the collected issue graph IN MEMORY as a GraphModel, without ever writing graph.json or graph.d2 to disk. This is the entry point the gRPC GetGraph handler uses; it is also useful to library callers who want the graph programmatically.

Graph collection is FORCED ON regardless of cfg.EmitGraph (the EmitGraph flag controls the disk-export side of the feature, which CrawlGraph deliberately bypasses). Per-issue Markdown is still produced through the normal output.Store when cfg.OutputDir is configured — only the graph files are suppressed.

The returned Summary is identical to what Crawl would produce for the same inputs. Errors propagate unchanged through the existing sentinel surface (ErrUnauthorized, ErrNotFound, etc.).

func CreateIssue added in v0.2.0

func CreateIssue(ctx context.Context, cfg Config, project, issueType string, opts ...client.CreateOption) (client.CreatedIssue, error)

CreateIssue creates a new Jira issue under the given project key with the supplied issue-type name. Field selection — summary, description, labels, assignee, parent, custom fields — flows through the client.CreateOption set. On success the returned client.CreatedIssue carries Jira's {key, id, self} response. On 400 the error is a *client.APIError that satisfies errors.Is against ErrBadRequest and exposes per-field validation messages via errors.As.

func FetchAndRender

func FetchAndRender(ctx context.Context, cfg Config, key string, opts ...client.Option) (indexMD, outboundMD string, discoveredKeys []string, err error)

FetchAndRender fetches a single Jira issue identified by key, parses its ADF description and relationships, and returns the rendered Markdown content for both the issue page and its outbound reference index.

It is a convenience wrapper over GetIssue + render. It shares all fetch, parse, and extract logic with GetIssue; only the render step is added here.

It does NOT write anything to disk. The caller decides what to do with the returned strings (write them, embed them in a larger pipeline, etc.).

Parameters

  • ctx: controls the lifetime of the HTTP request.
  • cfg: validated runtime configuration (construct via LoadConfig).
  • key: Jira issue key, e.g. "PROJ-1".
  • opts: optional client.Option values (e.g. client.WithHTTPClient for tests). Pass nil or omit for production use.

Return values

  • indexMD: Markdown content for <KEY>/index.md.
  • outboundMD: Markdown content for <KEY>/references/outbound.md. Empty string when the issue has no outbound references.
  • discoveredKeys: Jira issue keys found in the issue's description and relationships, in extract's documented order (description → parent → subtasks → issuelinks → remotelinks). May contain duplicates; the caller is responsible for deduplication.
  • err: non-nil on fetch, parse, extract, or render failure.

Neighbour resolution

Because FetchAndRender fetches a single issue in isolation, the neighbours set passed to the renderer is always empty. Relationship links therefore render as absolute Jira browse URLs rather than relative Markdown paths. Use Crawl for a full recursive crawl where relative links are resolved.

func FullVersion added in v0.4.2

func FullVersion() string

FullVersion returns an image-reference-style identifier suitable for log lines and diagnostic banners. It is "<ref>@<commit>" on a stamped build and just "<commit>" otherwise. It is a thin wrapper over buildinfo.FullVersion.

func GetIssue added in v0.2.0

func GetIssue(ctx context.Context, cfg Config, key string, opts ...client.Option) (parse.Issue, []extract.Reference, error)

GetIssue fetches a single Jira issue identified by key, parses its ADF description and relationships, and returns the structured typed data without rendering anything to Markdown.

It is the "fetch" half of FetchAndRender, exposed independently so callers such as MCP handlers or TUI components can obtain typed data without forcing a Markdown render pass.

Parameters

  • ctx: controls the lifetime of the HTTP request.
  • cfg: validated runtime configuration (construct via LoadConfig).
  • key: Jira issue key, e.g. "PROJ-1".
  • opts: optional client.Option values (e.g. client.WithHTTPClient for tests). Pass nil or omit for production use.

Return values

  • issue: the fully parsed parse.Issue value.
  • refs: outbound references discovered by extract.Extract, in extract's documented order (description → parent → subtasks → issuelinks → remotelinks). May contain duplicates; the caller is responsible for deduplication.
  • err: non-nil on fetch, parse, or extract failure.

func ListTransitions added in v0.2.0

func ListTransitions(ctx context.Context, cfg Config, key string) ([]client.Transition, error)

ListTransitions returns the workflow transitions currently available for the issue identified by key. Jira surfaces only transitions whose preconditions are met for the issue's current state, so the result is workflow- and state-dependent.

func Revision added in v0.4.2

func Revision() string

Revision returns the git SHA the binary was built from, or "dev" when the binary has not been release-stamped. It is a thin wrapper over buildinfo.Revision.

func TransitionIssue added in v0.2.0

func TransitionIssue(ctx context.Context, cfg Config, key, transitionID string, opts ...client.TransitionOption) error

TransitionIssue moves the issue identified by key through the workflow transition with id transitionID. Use client.WithTransitionField and client.WithTransitionCommentText to set fields or append a comment as part of the transition. Jira returns 204 on success.

func TransitionIssueByStatus added in v0.2.0

func TransitionIssueByStatus(ctx context.Context, cfg Config, key, targetStatusName string, opts ...client.TransitionOption) error

TransitionIssueByStatus resolves the workflow transition whose target status name matches targetStatusName (case-insensitive) via ListTransitions, then executes it.

It returns a clear error when no transition matches (typically because the issue is not in a state from which that target is reachable) or when more than one transition shares the same target status — gojira will not silently pick one. The convenience costs one extra GET (the ListTransitions call) relative to passing a transition id directly to TransitionIssue.

func UpdateIssue added in v0.2.0

func UpdateIssue(ctx context.Context, cfg Config, key string, opts ...client.UpdateOption) error

UpdateIssue edits fields on the issue identified by key. Field selection flows through client.UpdateOption. Jira returns 204 on success; this function returns nil. 400 surfaces a *client.APIError wrapping ErrBadRequest; 404 surfaces ErrNotFound.

func UserAgent added in v0.4.2

func UserAgent() string

UserAgent returns the default HTTP User-Agent header value the gojira HTTP client sends ("gojira/" + Version()). It is a thin wrapper over buildinfo.UserAgent.

func Version

func Version() string

Version returns the human-facing version string: the release ref (tag or branch) when stamped, falling back to the commit SHA otherwise. An un-stamped build therefore reports "dev". It is a thin wrapper over buildinfo.Version.

Note: in v0.3 and earlier, Version was a package-level CONST. It is now a FUNC so the value can be set by release tooling at build time without requiring -ldflags (which `go install ...@vX` does not pass). Update callers from `gojira.Version` to `gojira.Version()`.

Types

type Config

type Config = config.Config

Config is the validated runtime configuration for a gojira run. It is an alias for the internal config type; construct it via LoadConfig.

func LoadAppConfig added in v0.2.0

func LoadAppConfig(configPath string, env map[string]string) (Config, error)

LoadAppConfig loads configuration through the full app-level cascade and returns a flattened Config ready for Crawl / FetchAndRender. The cascade order, lowest-to-highest precedence, is:

  1. Embedded defaults (per-entity DefaultX constructors).
  2. YAML config file: when configPath is non-empty, that file is opened; when empty, the discovery chain runs (--config-equivalent: explicit path → $GOJIRA_CONFIG_FILE → ./gojira.yaml → $XDG_CONFIG_HOME/gojira/config.yaml → ~/.config/gojira/config.yaml). An explicit but non-existent configPath is a hard error.
  3. GOJIRA_-prefixed environment variables from env (the caller supplies this map; the CLI passes a filtered snapshot of os.Environ, while library consumers may inject any map). Deprecated v0.1 flat keys (GOJIRA_SITE, GOJIRA_USER, GOJIRA_TOKEN, etc.) continue to work via internal alias resolution.

CLI-flag overrides, if any, are the caller's responsibility to apply to the returned Config. Keeping flags out of LoadAppConfig keeps this package free of CLI-library dependencies.

On failure LoadAppConfig returns a zero Config and a descriptive error. Use errors.Is with ErrConfigMissingRequired or ErrConfigInvalidValue to distinguish failure classes — the exact same sentinels LoadConfig uses.

func LoadConfig

func LoadConfig(kv map[string]string) (Config, error)

LoadConfig validates the key-value pairs in kv against the canonical GOJIRA_* key set defined in PRD §6, applies defaults for optional keys, and returns a populated Config.

kv may come from any source: environment variables, CLI flags, a config file, or a test fixture. This package does not read environment variables itself.

On the first validation failure, LoadConfig returns a zero Config and a descriptive error. Use errors.Is with ErrConfigMissingRequired or ErrConfigInvalidValue to distinguish failure classes.

LoadConfig is the legacy entry point preserved for backward compatibility with library consumers and the existing CLI flag-overlay pattern. New callers SHOULD prefer LoadAppConfig, which loads through the full cascade (embedded defaults < YAML file < GOJIRA_ environment variables) and supports config-file discovery.

func LoadFileConfig added in v0.2.0

func LoadFileConfig(configPath string) (Config, error)

LoadFileConfig runs ONLY the YAML-file layer of the configuration cascade (embedded defaults + optional config file, with Layer-1 schema validation) and returns the flattened Config. It does NOT read environment variables and does NOT run the Layer-2 semantic validator, so a partial/missing-required configuration is NOT an error here. An explicit configPath pointing at a non-existent file IS a hard error wrapping ErrConfigInvalidValue.

LoadFileConfig is the seam the CLI uses when it wants to overlay environment variables and flag values on top of the file's contribution using its own validation path: the CLI calls LoadFileConfig, flattens the result onto a kv map, merges env and flag values, and runs the merged map through LoadConfig. This preserves the v0.1 *ConfigError error messages downstream tests and users depend on while still honoring the YAML file's contribution to the cascade.

When configPath is empty, the standard discovery chain runs (see the LoadAppConfig docstring).

type Event

type Event = events.Event

Event is a single observable occurrence emitted by the library. It is an alias for the internal events.Event type.

type GraphEdge added in v0.2.0

type GraphEdge = graph.Edge

GraphEdge is a directed relationship between two [GraphNode]s. Edge Kinds are: parent, subtask, child, link, remote, description, pull_request, external.

type GraphModel added in v0.2.0

type GraphModel = graph.Model

GraphModel is the in-memory issue graph produced by CrawlGraph. It is a true alias of graph.Model so callers can pass values across the package boundary without conversion.

type GraphNode added in v0.2.0

type GraphNode = graph.Node

GraphNode is a single node in a GraphModel; one of three Kinds: "issue", "github_pr", or "external". Issue-only fields (Status, Type, Assignee, URL) are zero-valued for non-issue nodes.

type OutputFormat added in v0.2.0

type OutputFormat int

OutputFormat selects the presentation form of a fetched Jira issue.

Orthogonality

Format (presentation) and Store (destination) are independent concerns. A caller chooses both independently:

  • Format controls HOW the issue data is represented (typed struct, Markdown text, or JSON bytes).
  • Store controls WHERE the result is written (filesystem, gRPC stream, in-memory buffer, etc.).

For example, a gRPC handler may request FormatStructured and stream the typed data to a client, while the CLI uses FormatMarkdown and writes to disk via an FSStore. Neither choice constrains the other.

const (
	// FormatStructured returns the parsed issue as typed Go values
	// ([parse.Issue] + []extract.Reference). No rendering is performed.
	// Use this when the caller needs to inspect or transform the data
	// programmatically (e.g. a gRPC handler that maps fields to proto
	// messages).
	FormatStructured OutputFormat = iota

	// FormatMarkdown returns the issue rendered as Markdown text via
	// [render.RenderIssue]. This is the format written to disk by the
	// default FSStore and displayed by the CLI.
	FormatMarkdown

	// FormatJSON returns the issue serialised as a JSON string. The JSON
	// representation mirrors the structured data ([parse.Issue] +
	// []extract.Reference) and is suitable for machine consumption or
	// embedding in API responses.
	FormatJSON
)

func ParseOutputFormat added in v0.2.0

func ParseOutputFormat(s string) (OutputFormat, error)

ParseOutputFormat converts a string to an OutputFormat.

Accepted forms (all case-insensitive):

  • "structured", "FORMAT_STRUCTURED"
  • "markdown", "FORMAT_MARKDOWN"
  • "json", "FORMAT_JSON"

The proto-style "FORMAT_*" aliases make it straightforward to map from a proto enum name without an extra translation layer.

On failure the error wraps ErrConfigInvalidValue so callers can use errors.Is for classification.

func (OutputFormat) String added in v0.2.0

func (f OutputFormat) String() string

String returns the canonical lower-case name of the format. Unknown values return "OutputFormat(<n>)" so they are always printable.

type Sink

type Sink = events.Sink

Sink is the interface callers implement to receive structured events from the library. It is an alias for the internal events.Sink interface, so callers can implement their own sink without importing internal/events.

var NoopSink Sink = events.NoopSink{}

NoopSink discards every event. Pass it (or nil) to Crawl when you do not need to observe crawl progress.

func NewSlogSink

func NewSlogSink(logger *slog.Logger) Sink

NewSlogSink returns a Sink that emits each event as a structured slog record through logger. It is the recommended way for callers (including the gojira CLI) to bridge gojira's event stream onto the standard log/slog pipeline without importing internal/events directly.

A nil logger is replaced with slog.Default so the resulting sink is always safe to call. The Event-to-slog mapping (kind → level, attribute names) is documented on the underlying events.SlogSink.

type Summary

type Summary = crawl.Summary

Summary is the structured result returned by Crawl after a run completes. It is an alias for the internal crawl summary type.

func Crawl

func Crawl(ctx context.Context, cfg Config, startKeys []string, sink Sink) (Summary, error)

Crawl executes a full recursive Jira issue crawl starting from startKeys.

It constructs the real HTTP client and fetcher from cfg, then delegates to the internal crawl orchestrator. Output files are written to cfg.OutputDir.

sink receives structured events for every state transition (issue queued, fetched, skipped, stubbed, failed, cap reached, PR found, crawl summary). Pass nil to use NoopSink and discard all events.

Error handling

  • 401 Unauthorized: the crawl is aborted immediately. Crawl returns a partial Summary and an error wrapping ErrUnauthorized. Map to exit 1.
  • 403 / 404: a stub index.md is written; the crawl continues.
  • Rate limited (429, retries exhausted): counted in Summary.Failed.
  • Network/transport error: a stub is written; the crawl continues.
  • Context cancellation: in-flight fetches complete; no new fetches start.

Skip-if-exists

When cfg.Refetch is false (the default), issues whose index.md already exists on disk are skipped without making an API call. This makes repeated runs additive and fast.

Output destination

Crawl output is delivered through an injectable output.Store. This facade constructs the default — an output.FSStore that writes the canonical <key>/index.md and references/outbound.md layout to cfg.OutputDir, honoring skip-if-exists vs. refetch — and passes it through to the crawl orchestrator. Alternative Store implementations can be injected at the crawl layer for callers that need to deliver crawl output somewhere other than the local filesystem.

Observability

Crawl emits no log output. To enable the structured observability instrument (per-issue spans, per-phase wall-clock measurement, HTTP request lifecycle traces, and the end-of-run crawl.measurement summary line) use CrawlWithLogger instead.

func CrawlWithLogger added in v0.2.0

func CrawlWithLogger(ctx context.Context, cfg Config, startKeys []string, sink Sink, logger *slog.Logger) (Summary, error)

CrawlWithLogger is the observability-aware sibling of Crawl. Identical behavior, plus: the supplied logger is wired through BOTH the crawl orchestrator (per-issue spans, per-phase measurement, crawl.measurement summary line) AND the underlying client (HTTP request lifecycle tracing via internal/httplog). The two share run_id/span_id/ticket_id correlation so trace_stream=stream lines and trace_stream=response lines from one invocation can be joined.

A nil logger is equivalent to calling Crawl — no instrumentation is emitted and the on-disk behavior is unchanged. The signature is additive; existing callers of Crawl are unaffected.

See log.LevelTrace and log.ParseLevel for the level ladder, and internal/trace for the correlation attribute keys (AttrRunID, AttrTicketID, AttrSpanID, AttrParentSpanID, AttrPhase, AttrTraceStream).

Directories

Path Synopsis
cmd
gojira command
Command gojira is the CLI binary for the gojira Jira-to-Markdown mirror tool.
Command gojira is the CLI binary for the gojira Jira-to-Markdown mirror tool.
gojira-client command
Command gojira-client is a tiny reference client for the gojira gRPC server, intended as a smoke / interoperability tool, not a production frontend.
Command gojira-client is a tiny reference client for the gojira gRPC server, intended as a smoke / interoperability tool, not a production frontend.
gen
internal
adf
Package adf provides pure, stateless traversal and rendering of Atlassian Document Format (ADF) documents.
Package adf provides pure, stateless traversal and rendering of Atlassian Document Format (ADF) documents.
buildinfo
Package buildinfo is the single source of truth for the gojira build identity (git revision and version ref).
Package buildinfo is the single source of truth for the gojira build identity (git revision and version ref).
cli
Package cli implements the gojira command-line interface (subcommand wiring, flag definitions, the config cascade glue, the run orchestrator, signal handling, and exit-code mapping).
Package cli implements the gojira command-line interface (subcommand wiring, flag definitions, the config cascade glue, the run orchestrator, signal handling, and exit-code mapping).
config
Package config validates and constructs the runtime configuration consumed by every other gojira package.
Package config validates and constructs the runtime configuration consumed by every other gojira package.
crawl
Package crawl is the recursive crawl orchestrator for gojira.
Package crawl is the recursive crawl orchestrator for gojira.
devstatus
Package devstatus enriches an already-fetched Jira issue with all the development metadata Jira's Dev Status API surfaces: pull requests, branches, commits, repositories, and builds.
Package devstatus enriches an already-fetched Jira issue with all the development metadata Jira's Dev Status API surfaces: pull requests, branches, commits, repositories, and builds.
events
Package events defines the structured event sink interface used by every gojira package to report progress, warnings, errors, and partial-success states.
Package events defines the structured event sink interface used by every gojira package to report progress, warnings, errors, and partial-success states.
extract
Package extract discovers all outbound references from a parsed Jira issue.
Package extract discovers all outbound references from a parsed Jira issue.
fetch
Package fetch provides a thin adapter between the crawl orchestrator and the Jira Cloud HTTP client.
Package fetch provides a thin adapter between the crawl orchestrator and the Jira Cloud HTTP client.
graph
Package graph is a pure in-memory model of the Jira issue graph discovered during a gojira crawl.
Package graph is a pure in-memory model of the Jira issue graph discovered during a gojira crawl.
grpc
Package grpc provides the gRPC server implementation for gojira.
Package grpc provides the gRPC server implementation for gojira.
hierarchy
Package hierarchy discovers Jira hierarchy children for an already-fetched issue via JQL search.
Package hierarchy discovers Jira hierarchy children for an already-fetched issue via JQL search.
httplog
Package httplog wraps an http.RoundTripper to emit gojira's structured, correlatable, credential-redacted view of every HTTP request the client makes.
Package httplog wraps an http.RoundTripper to emit gojira's structured, correlatable, credential-redacted view of every HTTP request the client makes.
mcp
Package mcp implements the gojira MCP server: a backend interface with two implementations (facade-backed "self" mode and gRPC-bridge "bridge" mode), a shared tool-registration layer that gates mutating tools behind allow_writes, and the stdio bootstrap the `gojira mcp` command consumes.
Package mcp implements the gojira MCP server: a backend interface with two implementations (facade-backed "self" mode and gRPC-bridge "bridge" mode), a shared tool-registration layer that gates mutating tools behind allow_writes, and the stdio bootstrap the `gojira mcp` command consumes.
output
Package output is the filesystem writer for gojira.
Package output is the filesystem writer for gojira.
parse
Package parse converts raw Jira Cloud REST API v3 issue JSON into a typed Issue value.
Package parse converts raw Jira Cloud REST API v3 issue JSON into a typed Issue value.
render
Package render converts a parsed Jira issue into Markdown content.
Package render converts a parsed Jira issue into Markdown content.
trace
Package trace provides the lightweight correlation primitives the gojira crawl uses to emit a reconstructable fan-out tree of log records.
Package trace provides the lightweight correlation primitives the gojira crawl uses to emit a reconstructable fan-out tree of log records.
pkg
classify
Package classify provides pure, stateless classification of strings into one of four link kinds: a bare Jira issue key, a Jira issue URL, a GitHub pull request URL, or an unclassified external link.
Package classify provides pure, stateless classification of strings into one of four link kinds: a bare Jira issue key, a Jira issue URL, a GitHub pull request URL, or an unclassified external link.
client
Package client implements the Jira Cloud HTTP transport for gojira.
Package client implements the Jira Cloud HTTP transport for gojira.
log
Package log is gojira's small slog-compatible logging facade.
Package log is gojira's small slog-compatible logging facade.

Jump to

Keyboard shortcuts

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