Repository navigation
feat: support project and cycle assignment on issue create/update - #45
Closed
seraph-pixelperfect wants to merge 1 commit into
Closed
seraph-pixelperfect wants to merge 1 commit into
seraph-pixelperfect wants to merge 1 commit into
Conversation
This was referenced Aug 19, 2026
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #25. Depends on #44.
What
--project <name>and--cycle <current|number>on bothissue createandissue update(parity), resolved to ids before any mutation via the v0.2 helpers:--projectresolves throughresolveProjectId(Addprojectscommand andissues --projectfilter #20) wrapped in a new loudrequireProjectId(see decision 1 below); the id flows into the mutation through the existing omit-null builders asprojectId.--cycle currentresolves the active cycle to an id via the newfetchActiveCycles—cycles(filter: { isActive: { eq: true } }), optionally scopedteam: { key: { eq: $teamKey } }— the sameisActivecomparator Add cycle support (issues --cycle current,cycles) #21 put intoIssueFilter.cycle, now on the write path.--cycle <number>resolves via the newresolveCycleIdByNumber—cycles(filter: { number: { eq: $number }, team: { key: { eq: $teamKey } } })— and mirrors Add cycle support (issues --cycle current,cycles) #21's read-path rule: a bare number fails loud (--cycle <number> requires --team (cycle numbers restart per team)), before any network request.ISSUE_DETAIL_FIELDSnow selectsproject { id name } cycle { id number }; update compares the issue's current ids against the resolved ones, skips matching fields (the feat: support assignee and label changes inissue update#44 assignee/label convention), and reportsalready up to date (no-op)when nothing would be sent.Schema verification (source:
npm pack @linear/sdk→dist/index-zxW4m1xd.d.mts, v90.0.0)IssueCreateInput.cycleId?: InputMaybe<Scalars["String"]>— "The cycle associated with the issue."IssueCreateInput.projectId?: InputMaybe<Scalars["String"]>— "The project associated with the issue."IssueUpdateInputcarries the same two optional Strings with the same doc comments — hence$projectId: String/$cycleId: Stringin the omit-null builders, exactly likestateId/assigneeId.QueryCyclesArgs.filter?: InputMaybe<CycleFilter>;CycleFilter.isActive?: BooleanComparator(eq),CycleFilter.number?: NumberComparator(eq, Float),CycleFilter.team?: TeamFilter(whosekeyStringComparatorresolveTeamIdalready relies on) — both cycle documents are schema-valid.Cycle.teamis non-nullable ("The team that the cycle belongs to. Each cycle is scoped to exactly one team"), so active cycles fetched workspace-wide can always be attributed to their team.Before / after (acceptance cases, stubbed fetch — no live network)
Before (base d4312bd):
After (this PR):
Local gates (real output)
Tests
test/project-cycle-assignment.test.ts(24 cases, stubbedfetch, no network): create with--project/--cycle current/--cycle 3 --team LIN/ both flags together; unknown project and no-active-cycle loud failures (no mutation); update--project(move, no-op when already set, set when previously unset, loud miss); update--cycle current(single active cycle success, multi-active-cycle ambiguity error listingcycle N (TEAM)candidates, foreign-team rejection,--teamscoping,--teammismatch rejection, numbered resolution, requires---teambefore any network request, unknown cycle number, no-op when already in the cycle); parity/guards (--teamalone on update, blank/invalid values, the extended nothing-to-update guard, help text). All 149 pre-existing tests are untouched and pass.Design decisions / ambiguities flagged
resolveProjectIdis silent on no-match; writes are loud. The read path keepsresolveProjectId's silent-undefined semantics (Addprojectscommand andissues --projectfilter #20 behavior, exercised by its tests); mutations use the newrequireProjectIdwrapper that throwsProject "X" not found in your workspace— silently creating an issue with no project would surprise, so this followsresolveStateIdByName/resolveUserId's loud convention for mutations.--cycle currentwithout--teamon update: error-if-ambiguous (recommended option). Mirroring Add cycle support (issues --cycle current,cycles) #21, "current" may match any team's active cycle; for a write, more than one active cycle fails loud listing the candidates (Active cycles: cycle 12 (LIN), cycle 7 (ENG)) rather than first-matching — a silent wrong-team assignment is worse. As an extra guard, when the issue's team is known and the single resolved active cycle belongs to a different team, the command rejects it too. On create this cannot arise (--teamis required, so the lookup is always team-scoped).already up to date (no-op). (The pre-existing--stateshort-circuit, which returns early even when other flags are pending, was left untouched — out of scope here.)--teamon update exists only to scope--cycle(create already requires it); passed without--cycleit fails loud, and it must match the issue's own team — moving issues between teams is not supported and a foreign team's cycle would be a wrong-team assignment.Stacked PR note
Stacked on #44 (stack: #37 → #38 → #39 → #40 → #41 → #42 → #43 → #44 → this PR). No CI checks will appear on this PR: ci.yml only triggers on PRs targeting main. CI runs when retargeted to main after the stack merges; local gates below are the evidence. Do not retarget.
CI quota-block caveat: if checks do appear after retargeting and show failures, check whether they are GitHub Actions billing/spending-limit rejections rather than real failures — inspect
gh run view <run-id>(or the jobs API): a billing annotation, an emptystepslist, and a ~3-4s total duration indicate quota-blocked runs. If quota-blocked, treat the local gate output above as the evidence and do not retry.Automated agent dispatch authenticated as
seraph-pixelperfect, submitted for human review — not self-approved. Merge is a human decision.