Skip to content

feat: add sub-issue support (--parent, children in issue view) - #46

Closed
seraph-pixelperfect wants to merge 1 commit into
feat/project-cycle-assignmentfrom
feat/sub-issues
Closed

seraph-pixelperfect wants to merge 1 commit into
feat/project-cycle-assignmentfrom
feat/sub-issues

Conversation

@seraph-pixelperfect

Copy link
Copy Markdown
Collaborator

Closes #26
Depends on #45

What

  • issue create --parent <IDENTIFIER|UUID> — creates the new issue as a sub-issue of the referenced parent. The parent ref is resolved to its id (via fetchIssue, which accepts identifier or UUID) before the mutation, failing loud with Parent issue "X" not found (NOT_FOUND) on a miss — the same resolve-loud-before-write convention as --project/--cycle from Support project and cycle assignment on issue create/update #25. parentId rides through createIssue's omit-null input builder (parentId: $parentId, $parentId: String), so the default create document is unchanged without the flag. A blank --parent fails loud before any network request.
  • issue view --full — additionally selects the issue's children connection and renders a sub-issues[N]: block after the description, one identifier | title | state line per child in server order. Without --full, neither the selection nor the block appears (the default detail document stays byte-identical — the same opt-in principle as --fields). An issue with no children renders a bare sub-issues[0]: header under --full ("no sub-issues" is information, not noise).
  • Help (ISSUE_HELP) and the generated skills/linear-axi/SKILL.md document the new flag and listing (pnpm run build:skill, --check clean).

Schema verification (mandatory per the issue)

Inspected the generated documents in @linear/sdk@90.0.0 (npm pack, then dist/index-zxW4m1xd.d.mts for types and dist/index.mjs / dist/chunk-DPPnyiuk.mjs for the runtime documents):

Question Finding Evidence
IssueCreateInput.parentId? Yes — nullable String input: "The identifier of the parent issue. Can be a UUID or issue identifier (e.g., 'LIN-123')." index-zxW4m1xd.d.mts:9698-9699 (inside type IssueCreateInput starting at line 9668)
subIssueCreate mutation? Does not exist — zero matches for subIssueCreate/SubIssueCreate across the type declarations and the runtime documents. Sub-issues are created through issueCreate + parentId; the related IssueCreateInput.subIssueSortOrder ("The position of the issue in parent's sub-issue list", line 9730) corroborates that issueCreate is the sub-issue path. grep over index-zxW4m1xd.d.mts, index.mjs, chunk-DPPnyiuk.mjs — 0 hits
Children connection field name? children (not subIssues): children: IssueConnection — "Children of the issue." IssueConnection = { nodes: Array<Issue>, ... }, so children { nodes { ... } } is the selection. index-zxW4m1xd.d.mts:9061 (on type Issue, line 9021, whose docstring reads "Issues support sub-issues (parent-child hierarchy up to 10 levels deep)")

Mechanism choice: parentId on issueCreate — it is the only mechanism the schema exposes, and it is also the simpler route (one input field vs a separate mutation). Bonus finding for future work: IssueUpdateInput.parentId also exists (line 11578), so re-parenting via issue update --parent is schema-supported but intentionally out of scope here (the issue only asks for create + view).

Before / after (stubbed endpoint, real CLI code)

issue create --title "Review the copy" --team LIN --parent LIN-42 — captured mutation document and variables:

mutation CreateIssue($teamId: String!, $title: String!, $parentId: String) {
      issueCreate(input: { teamId: $teamId, title: $title, parentId: $parentId }) {
        success
        issue { id identifier title state { name type } team { key } url }
      }
    }
--- variables ---
{
  "teamId": "team-1",
  "title": "Review the copy",
  "parentId": "ir-42"        // resolved from LIN-42 before the mutation
}

issue view LIN-42 --full — new sub-issues block after the description (absent without --full):

description:
Parent body text
sub-issues[2]:
  LIN-43 | Wire the toggle | In Progress
  LIN-44 | Write tests | Todo

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  16 passed (16)
      Tests  183 passed (183)      # was 173 in 15 files on the base branch

New test/sub-issues.test.ts (10 tests, network-free vi.stubGlobal('fetch', ...) per the established pattern) covers: parent resolution round trip (identifier and UUID) with parentId in the mutation variables; parent not found (loud error, no mutation); blank --parent rejected before any request; cross-team parent NOT pre-rejected (mutation still sent — Linear decides); create without --parent sends no parentId; --full selects children { nodes { identifier title state { name } } } and renders ordered child lines; default view document/output untouched without --full; empty-children header; help documentation.

pnpm run format:check remains RED on the same pre-existing 18-file set as the base branch (CI does not run it); the new/edited files pass pnpm exec prettier --check.

Stacked PR note

Stacked on #45 (stack: #37→#38→#39→#40→#41→#42→#43→#44→#45→this). No CI checks appear on this PR — 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 caveat: if checks do appear and fail suspiciously fast (~3-4s, empty steps), check gh run view <run-id> / the jobs API for a billing/spending-limit annotation — quota-block rejections look like check failures. If quota-blocked, this PR relies on the local output above; no retry.

Decisions & ambiguities flagged

  1. Mechanism — parentId on issueCreate (not subIssueCreate, which does not exist in the schema). Not actually ambiguous after verification; documented with evidence above.
  2. Cross-team parents — NOT pre-validated client-side. Linear's sub-issue hierarchy spans teams (a child may live in a different team than its parent — the Issue docstring describes a hierarchy up to 10 levels with no team constraint, and IssueCreateInput.parentId accepts any issue ref), so rejecting client-side would break legitimate use; any workspace-level rejection surfaces through the normal API error mapping. A test pins this behavior.
  3. Render conditionality — children are selected and rendered only under --full (conditional selection). Alternative considered: always select, render only with --full. Conditional won because ISSUE_DETAIL_FIELDS is shared by every fetchIssue caller (update no-op detection, delete, state resolution) which would otherwise fetch a children page it never reads, and because the repo's stated opt-in principle is that default documents stay byte-identical without the flag.
  4. Rendering shape — a manual sub-issues[N]: block with one identifier | title | state line per child (echoing the help[N]: bracket convention) rather than renderList TOON objects: one line per child vs three, carrying exactly the three fields the acceptance criteria name. Empty children still render the bare header under --full.
  5. parentId accepted raw — the API would accept LIN-42 directly as parentId, but the CLI pre-resolves to the UUID id for a clean loud NOT_FOUND instead of an opaque mutation error (and the resolved id is what the schema documents as canonical).

Identity note

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

@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