Skip to content

feat: add projects command and issues --project filter - #39

Closed
seraph-pixelperfect wants to merge 1 commit into
feat/multi-label-paginationfrom
feat/projects-command
Closed

seraph-pixelperfect wants to merge 1 commit into
feat/multi-label-paginationfrom
feat/projects-command

Conversation

@seraph-pixelperfect

Copy link
Copy Markdown
Collaborator

Closes #20
Depends on #38 (this branch is stacked on feat/multi-label-pagination, itself stacked on #37)

What

  • linear-axi projects — lists workspace projects via the root projects connection: name, state, progress, lead, target (target date), most recently updated first, auto-paginated in 50-size batches exactly like fetchIssues (same cursor guards).
  • linear-axi issues --project <name> — composes project: { name: { eq: ... } } into the existing IssueFilter builder in fetchIssues, so it works on both the plain issues query and the searchIssues path (composes with --team, --state, --label, --search). A missing/blank --project value is rejected loudly (same guard class as --search), never silently dropped.
  • resolveProjectId(name) helper in src/linear.ts, mirroring resolveTeamId (projects(filter: { name: { eq: $name } })). Not wired into any command yet — reserved for v0.3 write-side project assignment per the issue; covered by tests now.

Schema verification (source: generated types in @linear/sdk 90.0.0)

Verified by npm pack @linear/sdk and inspecting the generated documents (dist/index-zxW4m1xd.d.mts, dist/index.mjs):

  • Root projects query exists with the same connection args as issues: filter: ProjectFilter, first: Int, after: String, orderBy: PaginationOrderBy (ProjectsDocument in dist/index.mjs; ProjectConnection { nodes, pageInfo }).
  • IssueFilter.project?: NullableProjectFilter ("Filters that the issues project must satisfy"), and NullableProjectFilter.name?: StringComparator with eq — this is the comparator behind issues --project. Same for ProjectFilter.name (used by resolveProjectId).
  • Project.progress: Float — "(completed estimate points + 0.25 * in progress estimate points) / total estimate points", i.e. a 0–1 float.
  • Project.lead?: Maybe<User> — "Null if no lead is assigned"; User.name is what we select.
  • Project.targetDate?: Maybe<TimelessDate> — null when unset; Linear serializes TimelessDate as a YYYY-MM-DD date string (rendered raw).

Ambiguities / decisions flagged

  1. state vs status: the issue says "state is an enum (ProjectState)". In the current schema Project.state: String is deprecated ("Use project.status instead"), and there is no ProjectState type — the values live on ProjectStatus.type: ProjectStatusType (backlog, planned, started, paused, completed, canceled). We query status { type } and render it as state with the raw lowercase value the issue asked for — same rendered output, no deprecated field. Renaming the output column to status later would be a one-liner.
  2. Progress rendering: chosen as an integer percent 0–100 (e.g. 0.42 → 42), documented in projects --help ("percent complete, 0-100"). Raw float was the alternative; percent reads better in TOON and is documented.
  3. Nulls: lead and targetDate are nullable; both render as none (matching the repo's existing empty-render convention from joinArray).

Demo (before/after)

pnpm exec tsx bin/linear-axi.ts ... --key lin_api_fake — the fake key is rejected by the real API, which proves the commands reach the API layer; LINEAR_AXI_DEBUG=1 dumps the emitted document.

Before (base 230c540):

$ pnpm exec tsx bin/linear-axi.ts projects --key lin_api_fake
error: "Unknown command: projects"
code: VALIDATION_ERROR

$ pnpm exec tsx bin/linear-axi.ts issues --project "Mobile app" --key lin_api_fake
error: unknown flag --project
code: VALIDATION_ERROR
help[1]: "valid flags: --team, --state, --assignee, --label, --search, --limit (--help always allowed)"

After:

$ pnpm exec tsx bin/linear-axi.ts projects --key lin_api_fake   # LINEAR_AXI_DEBUG=1
[linear-axi] query: query Projects($first: Int!, $after: String) {
    projects(first: $first, after: $after, orderBy: updatedAt) {
      nodes { id name status { type } progress lead { name } targetDate }
      pageInfo { hasNextPage endCursor }
    }
  }
[linear-axi] variables: {"first":50}
error: Linear auth required — set LINEAR_API_KEY or pass --key <key>   # fake key rejected by api.linear.app
code: AUTH_REQUIRED

$ pnpm exec tsx bin/linear-axi.ts issues --project "Mobile app" --team LIN --state started --key lin_api_fake
issues(filter: { team: { key: { eq: "LIN" } }, state: { type: { eq: "started" } }, project: { name: { eq: "Mobile app" } } }, first: $first, after: $after, orderBy: updatedAt) { ... }
error: Linear auth required ...   # reached the API layer

$ pnpm exec tsx bin/linear-axi.ts issues --search crash --project "Mobile app" --key lin_api_fake
searchIssues(term: $term, filter: { project: { name: { eq: "Mobile app" } } }, first: $first, after: $after) { ... }
# composes on the search path too

$ pnpm exec tsx bin/linear-axi.ts projects --help
usage: linear-axi projects
	List the projects in your Linear workspace, most recently updated first.

columns:
  name     project name
  state    lifecycle state: backlog, planned, started, paused, completed, canceled
  progress percent complete, 0-100
  lead     project lead (none if unassigned)
  target   target date as YYYY-MM-DD (none if unset)

Local gates (all run on the pushed commit c8a0fbf)

  • pnpm build — clean (tsc, exit 0)
  • pnpm lint — clean (eslint ., exit 0)
  • pnpm run build:skill -- --check — skills/linear-axi/SKILL.md is up to date. (TOP_HELP command list + skill description/workflow updated, regenerated)
  • pnpm test:
 Test Files  9 passed (9)
      Tests  78 passed (78)

(63 → 78; +15 in the new test/projects.test.ts)

  • pnpm run format:check — RED pre-existing on the base branch (18 files) and unchanged by this PR (still exactly the same 18; the two new files pass pnpm exec prettier --check). Not enforced in CI.

Stacked-PR / CI note

Stacked on #38 (which is stacked on #37). No CI checks will appear on this PR — ci.yml only triggers on PRs targeting main. CI runs when retargeted to main after #38 and #37 merge; local gates pasted above are the evidence. Not retargeting.

CI quota-block caveat: org billing/spending-limit rejections can look like check failures in gh pr checks; if a check is red, check gh run view <run-id> for a billing annotation or the jobs API for empty steps + ~3-4s duration before assuming breakage. Jobs are not retried.

Identity

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

List workspace projects via the root `projects` connection (name,
lifecycle state, progress, lead, target date), auto-paginated in 50-size
batches like fetchIssues. State is selected as `status { type }` —
Project.state is deprecated in favor of Project.status per @linear/sdk,
and status.type yields the same raw lowercase lifecycle values
(backlog, planned, started, paused, completed, canceled). Progress is
rendered as an integer percent (0-100).

Add `issues --project <name>`, composing project: { name: { eq: ... } }
into the shared IssueFilter builder so it works on both the issues and
searchIssues query paths; a missing/blank value is rejected loudly like
--search. Add resolveProjectId mirroring resolveTeamId (reserved for
v0.3 write-side project assignment).

Closes #20
@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