Skip to content

feat: add comment list to read issue comments - #42

Closed
seraph-pixelperfect wants to merge 1 commit into
feat/fields-wiringfrom
feat/comment-list
Closed

seraph-pixelperfect wants to merge 1 commit into
feat/fields-wiringfrom
feat/comment-list

Conversation

@seraph-pixelperfect

Copy link
Copy Markdown
Collaborator

Closes #19. Depends on #41.

Stacked on #41 (stack: #37 → #38 → #39 → #40 → #41). Review this PR with feat/fields-wiring as the base; do not retarget — CI runs when retargeted to main after the stack merges; local gates below are the evidence.

What

Comments were write-only (comment <ref> --body). This adds a read path:

  • linear-axi comment list <IDENTIFIER|UUID> — renders the issue's comment thread, newest first, as a TOON list (author, body, relative creation time).
  • --full disables body truncation.
  • Bare comment <ref> --body "..." (no subcommand) keeps dispatching to create exactly as before — the create path stays the default, only the literal first positional list selects the reader (no Linear ref — TEAM-NUMBER or UUID — can collide with that word).

Schema evidence (source: @linear/sdk v90.0.0 via npm pack @linear/sdk, dist/index-zxW4m1xd.d.mts)

  • Connection shape: QueryCommentsArgs (line ~20044) = after / before / filter?: CommentFilter / first / includeArchived / last / orderBy?: PaginationOrderBy. There is no issueId argument on the root comments connection in the current schema (there was in older Linear API versions). The thread is scoped via filter: { issue: { id: { eq: $issueId } } }: CommentFilter.issue is a NullableIssueFilter → IssueFilter.id is an IssueIdComparator with eq?: ID.
  • Comment fields (Comment type): body: String ("The comment content in markdown format"), createdAt: DateTime, user?: Maybe<User> ("The user who wrote the comment. Null for comments created by integrations or bots without a user association"), externalUser?: Maybe<ExternalUser> (integration author, displayName: String). We select id, body, user { name }, externalUser { displayName }, createdAt.
  • Ordering: PaginationOrderBy (lines ~16597–16600) offers only createdAt | updatedAt. A PaginationSortOrder enum (Ascending/Descending) exists in the SDK types but is not an argument on this connection (only on new-style sort inputs like ApproximateNeedCountSort), so there is no server-side direction flag. Linear's connections sort descending (newest first) — per Linear's pagination docs (https://linear.app/developers/pagination) and the Apollo Studio Linear schema reference ("Available options are createdAt (default) and updatedAt", direction fixed), matching this repo's live-verified precedent (fetchIssues/fetchProjects/fetchCycles all document newest/most-recent-first with orderBy:). Therefore orderBy: createdAt returns the newest comment first with no client-side reversing.

Chosen over updatedAt: a thread's stable order is creation order — updatedAt would make an edited old comment jump to the top.

Demo (real render via stubbed fetch, same stub shape as the tests)

$ linear-axi comment list AXI-19 (before: unknown-subcommand error path would not exist — this command simply did not work; after:)

count: 3
comments[3]{author,body,created}:
  Ada,"Investigated the ordering question. Linear connections expose only orderBy: createdAt|updatedAt (PaginationOrderBy) and the direction is fixed descending, so the newest comment comes back first with n... (528 chars total)",1d ago
  Grace,Ordering looks right - newest first reads like the Linear UI.,3d ago
  Slack Sam,Took a first pass at fetchComments.,10d ago
help[2]:
  Run `linear-axi comment list AXI-19 --full` for untruncated bodies
  Run `linear-axi comment AXI-19 --body "..."` to reply

$ linear-axi comment list AXI-19 --full:

count: 3
comments[3]{author,body,created}:
  Ada,"Investigated the ordering question. Linear connections expose only orderBy: createdAt|updatedAt (PaginationOrderBy) and the direction is fixed descending, so the newest comment comes back first with no client-side reversing; updatedAt ordering would reshuffle threads whenever an old comment is edited, so createdAt is the stable thread order. Also verified the connection has no issueId argument in the current schema - the thread is scoped via filter: { issue: { id: { eq: $id } } } (CommentFilter.issue -> IssueFilter.id eq).",1d ago
  Grace,Ordering looks right - newest first reads like the Linear UI.,3d ago
  Slack Sam,Took a first pass at fetchComments.,10d ago
help[1]:
  Run `linear-axi comment AXI-19 --body "..."` to reply

$ linear-axi comment AXI-19 --body "LGTM" (bare create, unchanged dispatch):

comment: added to AXI-19
help[2]:
  Run `linear-axi issue view AXI-19` to see it in context
  Run `linear-axi comment list AXI-19` to read the thread

(The third thread row shows the externalUser.displayName fallback for integration comments; the empty state renders count: 0 + comments: 0 found + a start-the-thread hint.)

Local gates (real output)

  • pnpm build — OK
  • pnpm lint — OK
  • pnpm run build:skill -- --check — skills/linear-axi/SKILL.md is up to date.
  • pnpm test — Test Files 12 passed (12) / Tests 124 passed (124) (base: 110 in 11 files; +14 in new test/comment-list.test.ts)
  • pnpm run format:check — red pre-existing on the same 18 files as the base branch (CI does not run it); no newly flagged files — pnpm exec prettier --check test/comment-list.test.ts passes and all new code in touched files is prettier-clean (remaining deviations are pre-existing lines untouched by this PR).

Test coverage: subcommand dispatch (list vs bare create — create still hits mutation Comment with the same variables), document shape (filter: { issue: { id: { eq: $issueId } } }, orderBy: createdAt, requested fields, {issueId, first} variables), newest-first rendering order, truncation by default + --full, empty state + count line, NOT_FOUND on an unresolvable ref, unknown flags rejected on both paths, fetchComments cursor pagination.

Ambiguities / judgment calls (flagging for review)

  1. Truncation length: 200-char preview per comment (issue view's description uses 1000). Rationale: a list renders one row per comment (author + body + time), so 1000 would make long threads unreadable; 200 keeps ~5 rows of real content per screen with a (N chars total) note and a --full hint. Easy one-line constant to change.
  2. Ordering mechanism: no direction flag exists on the connection; orderBy: createdAt relies on Linear's fixed descending order (same assumption the repo already ships for issues/projects/cycles). If live behavior ever differed, a one-line .reverse() would fix it — tests pin the render-order contract, not the server's.
  3. Dispatch edge: the tiny arg parser cannot distinguish a flag value from a positional, so comment --body "list" LIN-1 would dispatch to list (first non--- token wins). This quirk is shared with issueCommand's positional switch (issue --title create behaves the same way); kept consistent rather than special-cased. Documented forms (comment <ref> --body, comment list <ref>) are unaffected.
  4. Schema drift: the issue text sketched comments(issueId: $id); the current schema has no such argument, so the filter form above is used instead (cited). Functionally equivalent, one extra field in the document.
  5. Create-path hint: the create success output gained one hint line (Run \linear-axi comment list ` to read the thread`) for discoverability. Flags/dispatch/queries on the create path are byte-identical to before; only this hint is new.

CI caveat

No CI checks will appear on this PR — ci.yml only triggers on PRs targeting main. When the stack merges and this is retargeted, CI runs then; local gates above are the evidence in the meantime. Separately: if checks do run and look failed, verify quota — Linear-adjacent GHA billing/spending-limit rejections present like check failures; check gh run view <run-id> or the jobs API for a billing annotation or empty steps with ~3-4s duration. If quota-blocked, this PR relies on the local output above rather than retries.

Identity

Automated agent dispatch authenticated as seraph-pixelperfect, submitted for human review — not self-approved. Merge is a human decision.

`comment list <ref>` reads an issue's comment thread through the root
`comments` connection, scoped via `filter: { issue: { id: { eq:  } } }`
(the connection has no issueId argument in current @linear/sdk), ordered
`orderBy: createdAt` (Linear connections sort descending, so newest-first
with no client-side reversing). TOON list rows render author (user, falling
back to externalUser for integration comments), body truncated to a
200-char preview (`--full` disables), and relative creation time, with a
count line, definitive empty state, and reply hints. Bare `comment <ref>`
--body` keeps dispatching to create exactly as before.

Closes #19
@ghostinprod-pixelperfect

Copy link
Copy Markdown
Collaborator

Superseded by merged rc/0.1 PR #51 (commit 694a564), which consolidated this read/discovery stack with validation and review fixes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants