Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
35520d3
feat(jtk)!: delete --extended and --all-fields (#455)
rianjs Jul 17, 2026
fc965a0
fix(jtk)!: route pagination prose to stderr and make --max a page siz…
rianjs Jul 17, 2026
09d4136
fix(jtk)!: shape-stable fulltext, fail-closed sprint resolution, dete…
rianjs Jul 17, 2026
6518efc
fix(jtk): address review feedback on pagination batch (#455)
rianjs Jul 17, 2026
146ba04
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
d6489bd
docs(jtk): document --max 100 ceiling for issues list/search (#455)
rianjs Jul 17, 2026
3eaf125
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
76f08f2
docs(jtk): correct flag-deletion documentation accuracy (#455)
rianjs Jul 17, 2026
96b042a
docs(jtk): make --id examples page-accurate with token iteration (#455)
rianjs Jul 17, 2026
3c3f00c
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
b7d075e
docs(jtk): --id bullet reflects stderr continuation contract (#455)
rianjs Jul 17, 2026
d12ad51
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
ecac282
docs(jtk): retire legacy output-mode examples, correct --fields proje…
rianjs Jul 17, 2026
3afac9f
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
fa479f5
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
fbe7657
docs(jtk): runnable examples and projected-column expectations (#455)
rianjs Jul 17, 2026
a56bdbb
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
c1100a5
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
02b8a42
docs(jtk): match issues-types and transitions --fields shapes to pres…
rianjs Jul 17, 2026
06c7b1a
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
45585b4
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
02c6332
test(jtk): cover transitions list --fields expansion and columns (#455)
rianjs Jul 17, 2026
58fcad5
test(jtk): cover transitions list --fields expansion and columns (#455)
rianjs Jul 17, 2026
415ef8a
docs(jtk): identifier-retention accuracy in guardrails and runbook (#…
rianjs Jul 17, 2026
8712a73
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
6da0e8b
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
bd5817a
docs(jtk): TO_STATUS header, runbook verification command, JSON excep…
rianjs Jul 17, 2026
6d1cc16
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
ca1130d
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
0f11aeb
docs(jtk): verified output shapes for projected get, users get, JSON …
rianjs Jul 17, 2026
537b438
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
99b2800
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
0917f2b
test(jtk): cover users expand wiring and fulltext compact contract (#…
rianjs Jul 17, 2026
eedbf20
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
6f39698
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
4c2afa5
docs(jtk): correct automation create flag name (#455)
rianjs Jul 17, 2026
b2f277e
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
07baefc
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
b110912
docs(jtk): correct automation create flag in OUTPUT_SPEC (#455)
rianjs Jul 17, 2026
9ee77b9
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
e84e687
docs(jtk): sweep runnable-example and JSON-scope classes across all d…
rianjs Jul 17, 2026
f08ea3d
docs+test(jtk): doc-class sweep on correct branch, attachments --fiel…
rianjs Jul 17, 2026
76e6cec
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
bd31784
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
4242581
docs(jtk): --id precedence covers --fields in mode table (#455)
rianjs Jul 17, 2026
3c0fd7a
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
9363477
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
bd8729c
docs(jtk): SKILL precedence and scripting sentence accuracy (#455)
rianjs Jul 17, 2026
1106ee4
Merge branch '455-jtk-flag-deletions' into 455-jtk-pagination
rianjs Jul 17, 2026
804c05a
Merge branch '455-jtk-pagination' into 455-jtk-contracts
rianjs Jul 17, 2026
41c5bac
Merge remote-tracking branch 'origin/main' into 455-jtk-contracts
rianjs Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions skills/Jira/CliReference.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Prompts for: Atlassian instance URL, email, API token (from https://id.atlassian

| Flag | Description |
|------|-------------|
| `--fulltext` | Disable truncation of descriptions and comments (`--no-truncate` is a deprecated alias kept during migration) |
| `--fulltext` | Disable truncation of descriptions and comments without changing list output shape |
| `--id` | Emit only the primary identifier (useful for scripting). Takes precedence over `--fulltext` |
| `--no-color` | Disable colored output |
| `-v, --verbose` | Enable verbose output |
Expand Down Expand Up @@ -150,7 +150,7 @@ Common transition names: "To Do", "In Progress", "In Review", "Done" (instance-d

## Sprints

`--board` and sprint positional arguments accept either a numeric ID or a name (resolved via cache — see SKILL.md Cache Warming).
`--board` and sprint positional arguments accept either a numeric ID or a name (resolved via cache — see SKILL.md Cache Warming). Sprint names must resolve uniquely; ambiguity reports candidate IDs, while unresolved names require a cache refresh or numeric ID.

| Command | Description |
|---------|-------------|
Expand Down
2 changes: 1 addition & 1 deletion skills/Jira/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Use `jtk refresh --status` to inspect cache freshness without fetching anything.
`jtk` commands follow a text-first output model (per the repo's [Output Artifact Contract](https://github.com/open-cli-collective/atlassian-cli/blob/main/docs/ARTIFACT_CONTRACT.md)):

- **`--fields <csv>`** — explicitly selects supported fields on commands that expose field projection.
- **`--fulltext`** — disables truncation of descriptions and comments. Use when the user needs full body content (e.g., "show the full description"). `--no-truncate` is a deprecated alias kept during the migration; prefer `--fulltext`.
- **`--fulltext`** — disables truncation of descriptions and comments without changing list output shape. Use when the user needs full body content (e.g., "show the full description").
- **`--id`** — emits only the primary identifier (issue key, account ID, etc.) and takes precedence over `--fulltext` and `--fields` (projection is bypassed entirely). Stdout is identifier-only, one per line, safe to pipe; any continuation notice goes to stderr.

`automation export` is the only resource command that emits JSON (`set-credential --json` is the control-plane exception). For every other command, use `--id` for scripting composition.
Expand Down
4 changes: 2 additions & 2 deletions skills/Jira/Workflows/ManageComments.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ View and add comments on Jira issues.
jtk comments list PROJ-123
```

Optional: `--max N` to control page size (default 50). Use `--fulltext` global flag to disable truncation of long comment bodies.
Optional: `--max N` to control page size (default 50). Use `--fulltext` to disable truncation of long comment bodies without changing the table shape.

### Add Comment

Expand Down Expand Up @@ -55,7 +55,7 @@ The `COMMENT_ID` is a numeric ID (e.g., `12345`). If the user refers to the comm

## Output Format

- **List comments:** Show each comment with author, timestamp, and body
- **List comments:** A table with ID, author, timestamp, and body; `--fulltext` preserves these columns
- **Add comment:** Confirm comment was added with issue key and a snippet of the comment text

## Post-Action
Expand Down
1 change: 1 addition & 0 deletions tools/jtk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Breaking:** Removed global `--extended` and `issues list/search --all-fields`. Use explicit `--fields`; `--fulltext` controls truncation only. ([#455](https://github.com/open-cli-collective/atlassian-cli/issues/455))
- **Breaking:** Pagination and continuation prose now goes to stderr, keeping `--id` stdout identifier-only. `--max` is a positive single-page size; issue list/search no longer auto-exhaust pages. ([#455](https://github.com/open-cli-collective/atlassian-cli/issues/455))
- **Breaking:** `comments list --fulltext` now preserves table shape and only disables truncation; deprecated `--no-truncate` aliases were removed. Sprint-name filters now fail closed on ambiguous or unresolved names. Automation export now validates and deterministically formats API JSON. ([#455](https://github.com/open-cli-collective/atlassian-cli/issues/455))
- **Breaking:** Short alias for `--file` renamed from `-f` to `-F` on `attachments add`, `automation create`, and `automation update`. `-f` continues to mean `--field` on field-setting commands (`issues create`/`update`, `transitions do`). No back-compat alias. ([#339](https://github.com/open-cli-collective/atlassian-cli/issues/339))
- Default page size for paginated commands converged to 50: `issues list` and `issues search` were 25, `users search` was 10. `users search` and `dashboards list` also gain the `-m` short alias for `--max`. ([#340](https://github.com/open-cli-collective/atlassian-cli/issues/340))
- `links types`, `issues types`, `boards list`, `sprints list`, and `users get` now serve from the local instance cache by default — removes per-command API calls in the most common paths. Run `jtk refresh` to update. ([#328](https://github.com/open-cli-collective/atlassian-cli/pull/328), [#329](https://github.com/open-cli-collective/atlassian-cli/pull/329), [#330](https://github.com/open-cli-collective/atlassian-cli/pull/330))
Expand Down
7 changes: 6 additions & 1 deletion tools/jtk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ jtk issues list --project MYPROJECT --fields summary,status,customfield_10005
| `--fields` | | | Comma-separated display columns (headers, Jira field IDs, or human names) |
| `--next-page-token` | | | Token for next page of results |

Sprint names must resolve uniquely from the cache; ambiguous names report candidate IDs, and unresolved names require a cache refresh or numeric sprint ID.

---

### `jtk issues get <issue-key> [issue-key...]`
Expand Down Expand Up @@ -757,6 +759,8 @@ jtk comments list PROJ-123 --fields ID,AUTHOR
| `--fulltext` | | `false` | Show full comment bodies without truncation (global) |
| `--fields` | | | Comma-separated display fields |

`--fulltext` preserves the table columns and row shape; it only disables body truncation.

**Arguments:**
- `<issue-key>` - The issue key (**required**)

Expand Down Expand Up @@ -1215,12 +1219,13 @@ jtk automation export 123 > rule-backup.json

| Flag | Default | Description |
|------|---------|-------------|
| `--compact` | `false` | Output minified JSON |
| `--compact` | `false` | Output whitespace-normalized minified JSON |

**Arguments:**
- `<rule-id>` - The rule ID (**required**)

> Note: Output is always JSON — this is the only resource command that emits JSON directly (the control-plane `set-credential --json` envelope is the other exception).
> Invalid JSON returned by the API fails with empty stdout.

---

Expand Down
21 changes: 10 additions & 11 deletions tools/jtk/internal/cmd/OUTPUT_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ All entity-reference flags (`--assignee`, `--project`, `--board`, `--sprint`, li
- No match + looks like a raw ID → pass through unchanged
- No match + looks like a name → fail with suggestion to `jtk refresh <resource>`

Sprint names must resolve to exactly one cached sprint ID. Ambiguous names list candidate IDs; unresolved names fail with refresh and numeric-ID guidance. `issues list --sprint` never falls back to broader name-based JQL.

```
$ jtk issues assign MON-4820 "John Smith"
Ambiguous user "John Smith" — 3 matches:
Expand Down Expand Up @@ -295,18 +297,11 @@ ID | AUTHOR | CREATED | BODY
21242 | Aaron Wong | 2026-04-16 | Short audit conclusion after the current code changes: The major source-level accessibility findings on CapOne-specific surfaces appear to be addressed or materially improv...
```

**`comments list MON-4810 --fulltext`:** one block per comment:
**`comments list MON-4810 --fulltext`:** the same table with body truncation disabled:
```
ID: 21242
Author: Aaron Wong
Created: 2026-04-16
Body:
Short audit conclusion after the current code changes:
The major source-level accessibility findings on CapOne-specific surfaces
appear to be addressed or materially improved:
- loading / redirect states now expose accessible status messaging
- the unsupported-package modal now exposes both title and description correctly
...
ID | AUTHOR | CREATED | BODY
21242 | Aaron Wong | 2026-04-16 | Short audit conclusion after the current code changes:
The major source-level accessibility findings on CapOne-specific surfaces appear to be addressed or materially improved: ...
```

### `links`
Expand Down Expand Up @@ -406,7 +401,11 @@ Description: Creates Tasks when a new Onboarding Epic is created

**`automation get <id> --show-components`:** dumps the full component tree as indented text (trigger → conditions → actions).

<<<<<<< HEAD
**`automation export <id>`:** validates and emits the rule definition as pretty-printed JSON to stdout. This is the round-trip format consumed by `automation create --from-file`. `--compact` emits whitespace-normalized minified JSON. Invalid API JSON is an error and emits nothing. This command bypasses the global flag system.
=======
**`automation export <id>`:** emits the rule definition as pretty-printed JSON to stdout. This is the round-trip format consumed by `automation create --file`. `--compact` minifies. This command bypasses the global flag system.
>>>>>>> 455-jtk-pagination

### `dashboards`

Expand Down
19 changes: 8 additions & 11 deletions tools/jtk/internal/cmd/automation/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func newExportCmd(opts *root.Options) *cobra.Command {
Short: "Export automation rule as JSON",
Long: `Export the full automation rule definition as JSON.

This outputs the exact JSON returned by the API, suitable for editing
and re-importing via 'jtk auto update'. Output is always JSON.
This validates and formats the JSON returned by the API, suitable for
editing and re-importing via 'jtk auto update'. Output is always JSON.

RECOMMENDED WORKFLOW:
jtk auto export <rule-id> > rule.json
Expand Down Expand Up @@ -51,17 +51,14 @@ func runExport(ctx context.Context, opts *root.Options, ruleID string, compact b
return err
}

var buf bytes.Buffer
if compact {
_, err = fmt.Fprintln(opts.Stdout, string(raw))
return err
err = json.Compact(&buf, raw)
} else {
err = json.Indent(&buf, raw, "", " ")
}

// Pretty-print the JSON
var buf bytes.Buffer
if err := json.Indent(&buf, raw, "", " "); err != nil {
// If indenting fails, output raw
_, err = fmt.Fprintln(opts.Stdout, string(raw))
return err
if err != nil {
return fmt.Errorf("formatting automation rule JSON: %w", err)
}

_, err = fmt.Fprintln(opts.Stdout, buf.String())
Expand Down
78 changes: 78 additions & 0 deletions tools/jtk/internal/cmd/automation/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package automation

import (
"bytes"
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/open-cli-collective/atlassian-go/testutil"

"github.com/open-cli-collective/jira-ticket-cli/api"
"github.com/open-cli-collective/jira-ticket-cli/internal/cmd/root"
)

func TestRunExportFormatsJSONDeterministically(t *testing.T) {
t.Parallel()
tests := []struct {
name string
raw string
compact bool
want string
}{
{"pretty input defaults to pretty", "{\n \"name\": \"rule\",\n \"enabled\": true\n}", false, "{\n \"name\": \"rule\",\n \"enabled\": true\n}\n"},
{"compact input defaults to pretty", `{"name":"rule","enabled":true}`, false, "{\n \"name\": \"rule\",\n \"enabled\": true\n}\n"},
{"compact normalizes whitespace", " { \"name\" : \"rule\", \"enabled\" : true } ", true, `{"name":"rule","enabled":true}` + "\n"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := automationExportServer(tt.raw)
defer server.Close()

opts, stdout := exportTestOptions(t, server)
testutil.RequireNoError(t, runExport(context.Background(), opts, "rule-1", tt.compact))
if stdout.String() != tt.want {
t.Fatalf("output mismatch:\n got %q\nwant %q", stdout.String(), tt.want)
}
})
}
}

func TestRunExportMalformedJSONLeavesStdoutEmpty(t *testing.T) {
t.Parallel()
for _, compact := range []bool{false, true} {
server := automationExportServer(`{"broken":`)
opts, stdout := exportTestOptions(t, server)
err := runExport(context.Background(), opts, "rule-1", compact)
server.Close()

if err == nil {
t.Fatalf("compact=%v: expected malformed JSON error", compact)
}
if stdout.Len() != 0 {
t.Fatalf("compact=%v: stdout must stay empty, got %q", compact, stdout.String())
}
}
}

func automationExportServer(raw string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/_edge/tenant_info" {
_, _ = w.Write([]byte(`{"cloudId":"test-cloud"}`))
return
}
_, _ = w.Write([]byte(raw))
}))
}

func exportTestOptions(t *testing.T, server *httptest.Server) (*root.Options, *bytes.Buffer) {
t.Helper()
client, err := api.New(api.ClientConfig{URL: server.URL, Email: "t@x.com", APIToken: "tok"})
testutil.RequireNoError(t, err)
stdout := &bytes.Buffer{}
opts := &root.Options{Stdout: stdout, Stderr: &bytes.Buffer{}}
opts.SetAPIClient(client)
return opts, stdout
}
42 changes: 10 additions & 32 deletions tools/jtk/internal/cmd/comments/comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (

"github.com/spf13/cobra"

"github.com/open-cli-collective/atlassian-go/present"

"github.com/open-cli-collective/jira-ticket-cli/api"
"github.com/open-cli-collective/jira-ticket-cli/internal/cmd/root"
jtkpresent "github.com/open-cli-collective/jira-ticket-cli/internal/present"
Expand Down Expand Up @@ -42,7 +40,6 @@ func Register(parent *cobra.Command, opts *root.Options) {
func newListCmd(opts *root.Options) *cobra.Command {
var maxResults int
var nextPageToken string
var noTruncate bool
var fieldsFlag string

cmd := &cobra.Command{
Expand All @@ -61,14 +58,12 @@ func newListCmd(opts *root.Options) *cobra.Command {
return jtkpresent.ValidateMax(maxResults)
},
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), opts, args[0], maxResults, nextPageToken, noTruncate || opts.IsFullText(), fieldsFlag)
return runList(cmd.Context(), opts, args[0], maxResults, nextPageToken, opts.IsFullText(), fieldsFlag)
},
}

cmd.Flags().IntVarP(&maxResults, "max", "m", 50, "Page size")
cmd.Flags().StringVar(&nextPageToken, "next-page-token", "", "Decimal startAt for the next page")
cmd.Flags().BoolVar(&noTruncate, "no-truncate", false, "Show full comment bodies without truncation")
_ = cmd.Flags().MarkDeprecated("no-truncate", "use --fulltext instead")
cmd.Flags().StringVar(&fieldsFlag, "fields", "", "Comma-separated display fields (labels)")

return cmd
Expand All @@ -92,9 +87,6 @@ func runList(ctx context.Context, opts *root.Options, issueKey string, maxResult
var selected []projection.ColumnSpec
var projected bool
spec := jtkpresent.CommentListSpec
if noTruncate {
spec = jtkpresent.CommentDetailSpec
}
if !idOnly {
selected, projected, err = projection.Resolve(
ctx,
Expand Down Expand Up @@ -133,33 +125,19 @@ func runList(ctx context.Context, opts *root.Options, issueKey string, maxResult
return jtkpresent.Emit(opts, model)
}

optional := projection.HasOptionalFields(selected, spec)
var model *present.OutputModel
if noTruncate {
model = jtkpresent.CommentPresenter{}.PresentListFullWithPagination(result.Comments, optional, hasMore, nextToken)
if projected {
projectAllDetailSectionsInModel(model, selected)
}
} else {
model = jtkpresent.CommentPresenter{}.PresentListWithPagination(result.Comments, optional, hasMore, nextToken)
if projected {
projection.ApplyToTableInModel(model, selected)
}
model := jtkpresent.CommentPresenter{}.PresentListWithPagination(
result.Comments,
projection.HasOptionalFields(selected, spec),
noTruncate,
hasMore,
nextToken,
)
if projected {
projection.ApplyToTableInModel(model, selected)
}
return jtkpresent.Emit(opts, model)
}

// projectAllDetailSectionsInModel rewrites every DetailSection of model
// to the selected fields, leaving non-Detail sections (e.g. the
// pagination MessageSection) untouched.
func projectAllDetailSectionsInModel(model *present.OutputModel, selected []projection.ColumnSpec) {
for i, s := range model.Sections {
if ds, ok := s.(*present.DetailSection); ok {
model.Sections[i] = projection.ProjectDetail(ds, selected)
}
}
}

// commentsHasMore computes pagination using the authoritative API metadata,
// falling back to a full-page heuristic when Total is unavailable (Jira Cloud
// occasionally returns Total=0).
Expand Down
Loading
Loading