Skip to content

Eliminate all 11 source any with real Linear GraphQL and SDK context types - #48

Merged
unbraind merged 2 commits into
mainfrom
refactor/typed-linear-graphql-and-handler-contexts
Jul 27, 2026
Merged

unbraind merged 2 commits into
mainfrom
refactor/typed-linear-graphql-and-handler-contexts

Conversation

@unbraind

@unbraind unbraind commented Jul 27, 2026 •

Copy link
Copy Markdown
Owner

What

Removes the 11 remaining any annotations in index.ts, mirroring the proven pm-github PR #18 precedent. Typing refactor only — behaviour is unchanged.

A. Linear GraphQL responses (8 sites)

  • LinearResponse is now a generic envelope LinearResponse<TData> (data?: TData, errors?: LinearGraphQLError[]); linearRequest/linearRequestOnce take a type parameter so every call site gets a typed response with no cast.
  • New per-operation data types declare only the fields actually read:
    • LinearIssuesData — paginated issues query in fetchAllLinearIssues
    • LinearTeamData / LinearTeamNode — TEAM_QUERY team resolution (id, states, labels, cycles; everything optional since Linear may return partial data alongside errors)
    • LinearViewerData — viewer { id } credential probe in the preflight
  • The issueCreate/issueUpdate mutation sites consume only the error envelope, so they are typed linearRequest<unknown> — no invented schema mirror.
  • The JSON.parse wire boundary keeps its single deserialization cast, now as LinearResponse<TData>.

B. SDK handler/context types (3 sites)

  • isJsonMode and renderImportDryRun take CommandHandlerContext | ImportExportContext; the registerPreflight callback takes PreflightOverrideContext — all imported type-only from @unbrained/pm-cli/sdk/authoring (same subpath as the existing ExtensionApi import, so no runtime module edge for standalone installs).
  • --json continues to be read from ctx.global.json (the correct SDK contract); defensive ?. chains are retained.

Gates (all green)

  • npm run build ✅
  • npm test ✅ — 63 pass, 0 fail
  • npx tsc --noEmit ✅ clean
  • npm run changelog:check ✅
  • grep -nE ':\s*any\b|\bas any\b|<any>|any\[\]' index.ts ✅ prints nothing

Real activation proof (throwaway workspace, /tmp/pm-linear-proof)

$ pm linear --help        → exit=0, lists export/import/sync/validate subcommands (no arity error — nothing dropped)
$ pm linear sync --help   → exit=0, full flag list rendered
$ pm list --json          → exit=0, host commands intact
$ pm health               → exit=0
$ pm linear validate      → exit=0 (stderr report); --json → exit=0 (structured object)
$ pm linear sync --team ENG --dry-run --skip-preflight-network → exit=0, offline GraphQL plan printed

pm item

  • pm-linear-pfmo — Eliminate all source any with real Linear GraphQL and SDK handler types

Summary by Sourcery

Remove remaining untyped Linear GraphQL and SDK context usages by introducing precise TypeScript types while preserving existing behavior.

Enhancements:

  • Introduce a generic Linear GraphQL response envelope and per-operation data types to replace any-typed responses.
  • Type the Linear request helpers and all Linear GraphQL call sites using appropriate generics, avoiding casts to any.
  • Replace loosely typed SDK handler and preflight contexts with concrete CommandHandlerContext, ImportExportContext, and PreflightOverrideContext unions.
  • Document the new typing contracts around JSON mode handling, preflight behavior, and GraphQL error envelopes via clearer interfaces and comments.

Chores:

  • Add pm-linear-pfmo chore and history metadata files for tracking this typing cleanup.

Summary by cubic

Replaced the remaining 11 any types with precise Linear GraphQL and SDK context types, and updated the GraphQL envelope to allow data: null per spec. Behavior is unchanged; type safety is improved across requests and handlers.

  • Refactors
    • GraphQL: Introduced LinearResponse<T> with data?: T | null and typed errors; made linearRequest/linearRequestOnce generic; added per-operation payload types (LinearIssuesData, LinearTeamData/LinearTeamNode, LinearViewerData); mutations use linearRequest<unknown>; single JSON.parse cast to LinearResponse<T>.
    • SDK contexts: Typed isJsonMode and renderImportDryRun as CommandHandlerContext | ImportExportContext, and registerPreflight as PreflightOverrideContext from @unbrained/pm-cli/sdk/authoring.
    • Cleanup: Removed 11 any; retained defensive ?.; build, tests, and typecheck pass.

Written for commit 5e3fef4. Summary will update on new commits.

Review in cubic

…K context types

Mirror the pm-github PR #18 precedent. Two clusters:

(A) Linear GraphQL responses: LinearResponse is now a generic envelope
LinearResponse<TData> carrying data?: TData and errors?: LinearGraphQLError[],
and linearRequest/linearRequestOnce take a type parameter so each call site
gets a typed response with no cast. Per-operation data types declare only the
fields actually read: LinearIssuesData (paginated issues query),
LinearTeamData/LinearTeamNode (TEAM_QUERY team resolution), LinearViewerData
(viewer{id} credential probe). The issueCreate/issueUpdate mutation sites
consume only the error envelope, so they are typed linearRequest<unknown>
rather than mirroring an unread schema. The JSON.parse wire boundary keeps
its single deserialization cast, now as LinearResponse<TData>.

(B) SDK handler/context types: isJsonMode and renderImportDryRun take
CommandHandlerContext | ImportExportContext, and the registerPreflight
callback takes PreflightOverrideContext, all imported type-only from
@unbrained/pm-cli/sdk/authoring (same subpath as the existing ExtensionApi
imports, so no runtime module edge). --json continues to be read from
ctx.global.json per the SDK contract; defensive ?. chains are retained.

Behaviour is unchanged. Gates: build, npm test (63 pass), tsc --noEmit,
changelog:check, and a zero-any grep all pass; real activation proof in a
throwaway workspace shows pm linear --help, sync/validate, JSON mode, and
the offline dry-run all working with host commands intact.

pm-item: pm-linear-pfmo
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This refactor adds generic Linear GraphQL response types, types operation-specific payloads, replaces remaining handler and context any annotations with SDK types, and records verification and chore lifecycle details.

Changes

Linear typing refactor

Layer / File(s) Summary
Generic GraphQL response contracts
index.ts
Adds LinearResponse<TData>, GraphQL error types, operation payload interfaces, and generic request parsing and retry handling.
Typed Linear operations
index.ts
Applies typed responses to issue pagination, team resolution, credential preflight, and exporter mutations.
SDK context annotations and task records
index.ts, .agents/pm/chores/pm-linear-pfmo.toon, .agents/pm/history/pm-linear-pfmo.jsonl
Replaces handler and preflight any parameters with SDK authoring types and records the completed typing task lifecycle.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main refactor: removing 11 any annotations by adding real Linear GraphQL and SDK context types.
Description check ✅ Passed The description matches the changeset and clearly explains the typing-only refactor, validation, and unchanged behavior.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/typed-linear-graphql-and-handler-contexts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors the Linear GraphQL client and SDK integration in index.ts to eliminate remaining any usages by introducing typed GraphQL response envelopes, per-operation data types, and concrete SDK context types, while keeping runtime behavior unchanged and recording the work in pm agent metadata files.

File-Level Changes

Change Details Files
Introduce typed Linear GraphQL response envelope and per-operation data payload types, and thread generics through the Linear request helpers and call sites.
  • Replace the untyped LinearResponse shape with a generic LinearResponse plus a dedicated LinearGraphQLError type.
  • Add LinearIssuesData, LinearViewerData, LinearTeamNode, and LinearTeamData interfaces that only model the fields actually read at each call site.
  • Make linearRequestOnce and linearRequest generic over TData, returning LinearResponse and casting the JSON.parse result accordingly at the wire boundary.
  • Update fetchAllLinearIssues, resolveTeamContext, preflightLinear, and issueCreate/issueUpdate call sites to use linearRequest with appropriate payload types, using unknown when only errors are consumed.
  • Remove any casts from GraphQL response handling and rely on typed errors arrays (map((e) => e.message)) and optional chaining on typed data fields.
index.ts
Strengthen SDK handler context typing for JSON mode detection, dry-run rendering, and preflight registration by using concrete SDK context types instead of any.
  • Import CommandHandlerContext, ImportExportContext, and PreflightOverrideContext as type-only additions from @unbrained/pm-cli/sdk/authoring alongside existing ExtensionApi and ExtensionModule.
  • Change isJsonMode and renderImportDryRun to accept CommandHandlerContext
ImportExportContext while preserving the ctx.global.json contract and defensive optional chaining.
  • Type the registerPreflight callback parameter as PreflightOverrideContext instead of any to better match the SDK contract.
  • Record the typing refactor as a pm chore/history item.
    • Add pm-linear-pfmo chore definition under .agents/pm/chores to describe the elimination of source any usages.
    • Append corresponding pm-linear-pfmo history record under .agents/pm/history to track execution of this maintenance work.
    .agents/pm/chores/pm-linear-pfmo.toon
    .agents/pm/history/pm-linear-pfmo.jsonl

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @greptile-apps

    greptile-apps Bot commented Jul 27, 2026 •

    Copy link
    Copy Markdown

    Greptile Summary

    This PR removes the remaining explicit any annotations without changing runtime behavior.

    • Adds generic Linear GraphQL response envelopes and operation-specific payload types.
    • Applies SDK authoring context types to command, import/export, and preflight helpers.
    • Adds project-management metadata recording completion of the typing cleanup.

    Confidence Score: 5/5

    The PR appears safe to merge.

    No blocking failure remains.

    Important Files Changed

    Filename Overview
    index.ts Replaces untyped GraphQL responses and SDK callback contexts with compile-time types while preserving the existing request and handler logic.
    .agents/pm/chores/pm-linear-pfmo.toon Records the completed typing-cleanup chore and its validation results.
    .agents/pm/history/pm-linear-pfmo.jsonl Adds the corresponding chore lifecycle history.

    Reviews (4): Last reviewed commit: "fix: model an explicit GraphQL null data..." | Re-trigger Greptile

    @unbraind

    Copy link
    Copy Markdown
    Owner Author

    @greptileai @coderabbitai full review

    Typing-only refactor eliminating the last 11 source any in index.ts. Independently verified by the maintainer before requesting review:

    • npm run build clean, npx tsc --noEmit clean, 63/63 tests pass
    • grep -nE ":\s*any\b|\bas any\b|<any>|any\[\]" index.ts → no matches
    • Real activation proof against the live CLI in a throwaway workspace (not just unit tests): pm install reports blocking_failure_count: 0 / failed_count: 0, and pm linear --help lists all four subcommands (export, import, sync, validate). Host commands stay intact (pm list --json still parses) and pm health reports zero errors.

    That last check matters on this fleet: under pm-cli 2026.7.27 a rejected command registration aborts at the offending command and silently drops every later sibling, while --help still exits 0 with a misleading Commander arity error (upstream pm-cli#772). So "tests pass" is not evidence an extension still registers — the subcommand listing is.

    Two review notes worth your attention specifically:

    1. The JSON.parse wire boundary keeps a single deserialization cast (as LinearResponse<TData>). That is deliberate — it is the one place where untyped bytes enter — but if you see a safer shape, say so.
    2. The mutation call sites use linearRequest<unknown> because only the errors envelope is read. I preferred that to inventing a schema mirror that nothing consumes; push back if you disagree.

    @coderabbitai coderabbitai Bot left a comment

    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 1

    🤖 Prompt for all review comments with AI agents
    Verify each finding against current code. Fix only still-valid issues, skip the
    rest with a brief reason, keep changes minimal, and validate.
    
    Inline comments:
    In `@index.ts`:
    - Around line 95-98: Update the shared LinearResponse interface’s data property
    to explicitly allow null alongside omitted and typed values, using the existing
    TData generic without changing the errors field or runtime handling.
    
    🪄 Autofix (Beta)

    Fix all unresolved CodeRabbit comments on this PR:

    • Push a commit to this branch (recommended)
    • Create a new PR with the fixes

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: Organization UI

    Review profile: ASSERTIVE

    Plan: Pro Plus

    Run ID: 22dd8d20-2f32-4d8e-9144-5cd22c95d8d0

    📥 Commits

    Reviewing files that changed from the base of the PR and between ac9dfb8 and aa590f2.

    📒 Files selected for processing (3)
    • .agents/pm/chores/pm-linear-pfmo.toon
    • .agents/pm/history/pm-linear-pfmo.jsonl
    • index.ts

    Comment thread index.ts
    @coderabbitai

    coderabbitai Bot commented Jul 27, 2026 •

    Copy link
    Copy Markdown
    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    set -euo pipefail
    
    echo '--- changed files ---'
    git diff --name-status HEAD^ HEAD
    
    echo '--- diff (tracked PR change) ---'
    git diff --unified=80 HEAD^ HEAD -- index.ts
    
    echo '--- index.ts outline ---'
    ast-grep outline index.ts --items all --view expanded || true
    
    echo '--- relevant type declarations and Linear request call sites ---'
    rg -n -C 12 \
      'interface Linear(Response|GraphQLError|IssuesData|TeamData|TeamNode|ViewerData)|type Linear(Response|GraphQLError|IssuesData|TeamData|TeamNode|ViewerData)|function linearRequest|const linearRequest|function linearRequestOnce|const linearRequestOnce|linearRequest<|linearRequestOnce<|JSON\.parse|isJsonMode|renderImportDryRun|registerPreflight|CommandHandlerContext|ImportExportContext|PreflightOverrideContext' \
      index.ts
    
    echo '--- package and compiler context ---'
    for f in package.json tsconfig.json; do
      if [ -f "$f" ]; then
        echo "--- $f ---"
        cat "$f"
      fi
    done
    
    echo '--- authored test changes and coverage references ---'
    git diff --name-status HEAD^ HEAD -- '*test*' '*spec*' || true
    rg -n -C 4 \
      'linearRequest|linearRequestOnce|LinearResponse|isJsonMode|renderImportDryRun|registerPreflight' \
      -g '*.{test,spec}.{ts,tsx,js,jsx}' . || true

    Length of output: 96163


    🏁 Script executed:

    #!/bin/bash
    set -euo pipefail
    
    echo '--- locate SDK authoring/type declaration files ---'
    fd -a -t f -e d.ts . node_modules 2>/dev/null | rg '/@unbrained/pm-cli/.*/?(sdk/)?authoring|/@unbrained/pm-cli/.*\.d\.ts$' || true
    
    echo '--- authoring symbols and registration signatures ---'
    rg -n -C 10 \
      'export (interface|type).*(CommandHandlerContext|ImportExportContext|PreflightOverrideContext|ExtensionApi)|interface (CommandHandlerContext|ImportExportContext|PreflightOverrideContext|ExtensionApi)|type (CommandHandlerContext|ImportExportContext|PreflightOverrideContext)|registerPreflight|registerCommand|registerImporter|global|pm_root' \
      node_modules/@unbrained/pm-cli 2>/dev/null || true
    
    echo '--- all Linear request uses, including inferred calls ---'
    rg -n -C 3 '\blinearRequest(?:Once)?\s*(?:<[^>]+>)?\s*\(' index.ts
    
    echo '--- residual any annotations/assertions in production entrypoint ---'
    rg -n ':\s*any\b|\bas\s+any\b|<any>|any\[\]' index.ts || true
    
    echo '--- complete compact diff statistics and non-index artifacts ---'
    git diff --check HEAD^ HEAD
    git diff --stat HEAD^ HEAD
    for f in .agents/pm/chores/pm-linear-pfmo.toon .agents/pm/history/pm-linear-pfmo.jsonl; do
      if [ -f "$f" ]; then
        echo "--- $f ---"
        cat "$f"
      fi
    done

    Length of output: 12460


    @unbraind Full review complete — no findings.

    The generic LinearResponse<TData> design correctly confines the deserialization assertion to the JSON.parse wire boundary, while each reader receives a minimal operation-specific payload type. I would keep that cast as-is for this typing-only PR; runtime schema validation would be a separate behavioral hardening change.

    Using linearRequest<unknown> for issueCreate and issueUpdate is also the right choice: those paths inspect only the top-level GraphQL error envelope, so modeling an unused mutation payload would add misleading surface area rather than safety.

    I also confirmed all request call sites are explicitly typed, the source any grep is clean, and the diff has no whitespace errors. Direct inspection of installed pm-cli declaration files was unavailable in the sandbox, but the reported strict typecheck is the relevant compatibility evidence for the explicit SDK context annotations.

    ✅ Action performed

    Full review finished.


    Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 56 minutes.

    The GraphQL spec permits a response carrying an explicit 'data: null' root
    payload alongside errors, not only an omitted data field. Typing it as
    'data?: TData' modelled the omitted case alone, which is unsound for any future
    consumer reading data without optional chaining -- the runtime already handled
    null correctly, so this closes a type-level gap rather than a behavioural one.
    
    Raised by CodeRabbit on review.
    @unbraind

    Copy link
    Copy Markdown
    Owner Author

    @greptileai @coderabbitai review

    5e3fef4 pushed — LinearResponse.data is now TData | null, per CodeRabbit's GraphQL-spec finding. Re-verified after the change: npm run build clean, npx tsc --noEmit clean, 63/63 tests, changelog:check up to date.

    @coderabbitai

    coderabbitai Bot commented Jul 27, 2026

    Copy link
    Copy Markdown

    Tip

    For best results, initiate chat on the files or code changes.

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    set -euo pipefail
    
    echo '--- follow-up commit ---'
    git show --no-ext-diff --format=fuller --stat 5e3fef4
    
    echo '--- follow-up diff ---'
    git diff --no-ext-diff --unified=60 5e3fef4^ 5e3fef4 -- index.ts
    
    echo '--- index structure ---'
    ast-grep outline index.ts --items all --view expanded
    
    echo '--- response types, request boundaries, and data consumers ---'
    rg -n -C 10 \
      'interface LinearResponse|type LinearResponse|LinearResponse<|function linearRequest(?:Once)?|const linearRequest(?:Once)?|JSON\.parse|\.data\b|data\?' \
      index.ts
    
    echo '--- whitespace and full changed-file list ---'
    git diff --check 5e3fef4^ 5e3fef4
    git diff --name-status 5e3fef4^ 5e3fef4

    Length of output: 23310


    @unbraind Follow-up review of 5e3fef4 complete — no findings.

    data?: TData | null correctly models both omitted and explicit-null GraphQL root payloads. All current consumers safely narrow it through optional chaining, while the single generic JSON.parse boundary assertion remains appropriately contained. The commit is clean (git diff --check) and affects only index.ts.

    @unbraind
    unbraind merged commit 0820724 into main Jul 27, 2026
    7 checks passed
    @unbraind
    unbraind deleted the refactor/typed-linear-graphql-and-handler-contexts branch July 27, 2026 22:05
    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.

    1 participant