Skip to content

feat: add comment update and comment delete - #48

Closed
seraph-pixelperfect wants to merge 1 commit into
feat/description-filefrom
feat/comment-update-delete
Closed

seraph-pixelperfect wants to merge 1 commit into
feat/description-filefrom
feat/comment-update-delete

Conversation

@seraph-pixelperfect

Copy link
Copy Markdown
Collaborator

Closes #27
Depends on #47

What

  • comment update <COMMENT-ID> --body "..."|--body-file <path> — edits a comment's body via the commentUpdate mutation. Body guards (mutual exclusion, readBodyFile, required-field errors) mirror the create path exactly. A missing comment fails loud with NOT_FOUND (update is not idempotent-by-convention — only delete is).
  • comment delete <COMMENT-ID> — removes a comment via commentDelete. Idempotent no-op when the comment is already gone, mirroring deleteIssueCmd's pre-fetch pattern: comment(id:) returns null → comment: "<id>" not found (no-op), no mutation issued. Like issue delete, any missing id no-ops.
  • comment list surfaces comment ids — every row now leads with the comment's full UUID (first column, comments[n]{id,author,body,created}), the handle update/delete target. The query already selected id (COMMENT_LIST_FIELDS); only the render changed. A hint advertises both new subcommands.
  • COMMENT_HELP documents the new subcommands and the now-reserved words (list, update, delete); a bare ref still routes to create (backward compat, tested). Skill prose updated; SKILL.md regenerated via pnpm run build:skill.

Schema source (verified against @linear/sdk v90.0.0 generated documents, npm pack @linear/sdk → dist/index-zxW4m1xd.d.mts)

  • UpdateCommentMutationVariables (line 61303): commentUpdate(id: String!, input: CommentUpdateInput, skipEditedAt?) → CommentPayload { comment, lastSyncId, success }.
  • CommentUpdateInput (line 3220): exactly one non-internal field — body: String ("The comment content"); everything else is [Internal]. Hence no omit-null input builder: body is the single required value.
  • DeleteCommentMutationVariables (line 61264): commentDelete(id: String!) → DeletePayload { entityId, lastSyncId, success } (line 5164) — success is all the CLI needs, mirroring issueDelete.
  • Root comment(id:) query exists (QueryCommentArgs line 20047: { hash?, id? }, returns Maybe<Comment>; missing id → null, not an error) — used by the pre-fetch. It also carries issue { identifier } (Comment.issue, nullable: "Null if the comment belongs to a different parent entity type") for the confirmations.

Demo (real code paths, stubbed network — captured via a fetch-stub harness)

=== 1. comment list LIN-1 (ids now surfaced, first column) ===        [BEFORE: rows were {author,body,created}, no id]
count: 1
comments[1]{id,author,body,created}:
  6f1b2f3a-1111-4bbb-9ccc-000000000001,Ada,Stale body text,1d ago
help[2]:
  Run `linear-axi comment LIN-1 --body "..."` to reply
  Run `linear-axi comment update <id> --body "..."` to edit, or `comment delete <id>` to remove
  doc 1: query Issue($id: String!) { issue(id: $id) { ... } }
         vars: {"id":"LIN-1"}
  doc 2: query Comments($issueId: ID!, $first: Int!, ...) { comments(filter: { issue: { id: { eq: $issueId } } }, ...)
         vars: {"issueId":"ir-1","first":50}

=== 2. comment update <id> --body "..." ===
comment: updated on LIN-1
help[2]:
  Run `linear-axi comment list LIN-1` to read the thread
  Run `linear-axi comment delete 6f1b2f3a-1111-4bbb-9ccc-000000000001` to remove it
  doc 1: query Comment($id: String!) { comment(id: $id) { id issue { identifier } } }
         vars: {"id":"6f1b2f3a-1111-4bbb-9ccc-000000000001"}
  doc 2: mutation UpdateComment($id: String!, $body: String!) { commentUpdate(id: $id, input: { body: $body }) { success } }
         vars: {"id":"6f1b2f3a-1111-4bbb-9ccc-000000000001","body":"Edited: fixed the typo, see notes"}

=== 3. comment delete <id> ===
deleted: comment on LIN-1
  doc 1: query Comment($id: String!) { comment(id: $id) { id issue { identifier } } }
  doc 2: mutation DeleteComment($id: String!) { commentDelete(id: $id) { success } }
         vars: {"id":"6f1b2f3a-1111-4bbb-9ccc-000000000001"}

=== 4. comment delete <same id> again (idempotent no-op) ===
comment: "6f1b2f3a-1111-4bbb-9ccc-000000000001" not found (no-op)
  doc 1: query Comment($id: String!) { comment(id: $id) { id issue { identifier } } }   <- pre-fetch null, no mutation issued

Local gates (real output)

$ pnpm build            → tsc, clean
$ pnpm lint             → eslint ., clean
$ pnpm run build:skill -- --check
skills/linear-axi/SKILL.md is up to date.
$ pnpm test
 Test Files  18 passed (18)
      Tests  214 passed (214)          <- was 200 in 17 files on the base; +14 in test/comment-update-delete.test.ts, +1 header assertion updated in comment-list.test.ts
$ pnpm run format:check → RED, pre-existing: the same 18 files as the base branch (verified set-identical via stash/diff); the new test file passes `prettier --check`.

New tests cover: update document + variables; --body-file content as body; mutual exclusion (zero network); update not-found loud NOT_FOUND; no-issue fallback render; delete document; delete idempotent no-op (pre-fetch null → no mutation); missing id / unknown-flag rejection on both paths; list document + render carrying ids; bare-ref dispatch to create; help contents; and the full list → update → delete round trip asserting all six documents in sequence.

Stacked PR note

Stacked on #47 (stack: #37→#38→#39→#40→#41→#42→#43→#44→#45→#46→#47). No CI checks appear here — ci.yml only triggers on PRs targeting main. CI runs when retargeted to main after the stack merges; local gates above are the evidence. Not retargeting.

CI quota-block caveat: if checks do appear and look failed, billing/spending-limit rejections resemble check failures — check gh run view <run-id> / 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; no retries.

Judgment calls / ambiguities flagged

  • Delete no-op via pre-fetch vs error-mapping: chose the pre-fetch (comment(id:) → null ⇒ no-op, no mutation), for consistency with deleteIssueCmd's established pattern; it also yields the issue identifier for the confirmation. Same asymmetry as issues: any missing id no-ops on delete, while update is loud.
  • Id rendering in list: rendered complete, never truncated — a truncated UUID could not be copy-pasted into comment update/delete. Cost: +36 chars/row.
  • Update confirmation shape: comment: updated on LIN-1 (mirrors comment: added to LIN-1), falling back to comment: updated <id> when the comment has no parent issue (Comment.issue is nullable). Kept minimal rather than echoing the new body.

Identity

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

- comment update <COMMENT-ID> --body|--body-file via commentUpdate (pre-fetch
  for a loud NOT_FOUND; body guards mirror the create path)
- comment delete <COMMENT-ID> via commentDelete, idempotent no-op when the
  comment is already gone (pre-fetch pattern, mirrors deleteIssueCmd)
- comment list rows now lead with the full comment id (the handle
  update/delete target); list hint advertises both subcommands
- COMMENT_HELP documents the new subcommands and the reserved words
- skill prose regenerated via pnpm run build:skill

Closes #27
@ghostinprod-pixelperfect

Copy link
Copy Markdown
Collaborator

Superseded by merged rc/0.2 PR #52 (commit ad6b141), which consolidated this write/transport 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