aboutsummaryrefslogtreecommitdiff
path: root/scripts/tests/test_seed_pearl_workspace.py
Commit message (Collapse)AuthorAgeFilesLines
* chore(scripts): warn when apikey.txt is group/other-readableCraig Jennings2026-06-161-0/+22
| | | | The seed script reads the Linear key from the environment, but the README sources it from a gitignored `apikey.txt` that was landing at mode 644. `main` now warns on startup when that file is group- or other-accessible, so a loose key file gets flagged instead of sitting silently exposed. The warning points at `chmod 600`.
* fix(scripts): seed column types for board order, healing type driftCraig Jennings2026-06-071-52/+146
| | | | | | | | | | The seed put Triage on the unstarted type and Backlog on the backlog type. Linear's board orders columns by type category first (backlog before unstarted) and only then by position, so it grouped Icebox and Backlog together and pushed Triage to the right of Backlog. Position couldn't fix it: a state's type is immutable (workflowStateUpdate takes name, color, description, and position only), so the columns had to be retyped at the source. To make the board read Icebox, Triage, Backlog, two of the three pre-work columns have to be the backlog type and the third the unstarted one Linear renders last. So Icebox and Triage are now backlog and Backlog is the team's unstarted state, which also satisfies Linear's rule that a team keep one. Since type can't change in place, the reconcile gained a recreate path: a column found with the wrong type gets a fresh state of the right type, its issues moved over, and the old one archived. The phases are ordered so a new unstarted state exists before the old one is archived, and so a name is freed before another column reuses it. That lets a re-run heal a workspace seeded under the old layout, which is how I fixed the live one (Triage and Backlog recreated, 32 issues moved). Matching now prefers a type-correct state and only recreates when there's none, so the same targets build a fresh team and heal an existing one. pearl's own grouped view needed no change — it already orders sections the way Linear's board does, by type rank then position, so the migrated types sort it correctly.
* fix(scripts): make Triage the intake column and the default new-issue stateCraig Jennings2026-06-061-9/+19
| | | | | | | | Two problems showed up dogfooding the seeded board. New issues landed in Backlog, because Linear's teamCreate sets the team's default new-issue state to its own Backlog default and the seed never changed it. And the intake column read "Triage Queue" when "Triage" is the name we want. The script now points the team's defaultIssueStateId at the intake column after the columns reconcile, so a fresh issue opens in Triage instead of Backlog. It skips the update when the default is already correct, so a re-run stays a no-op. The intake column is renamed Triage throughout: the target state, the Pearl Inbox view filter, and INTAKE_STATE. find_team reads defaultIssueState so the idempotence check has something to compare. Both mutations were schema-introspected first: TeamUpdateInput.defaultIssueStateId and CustomViewUpdateInput.filterData. The live board was fixed the same way (rename, default-state, Inbox filter) and the dry-run against it comes back all-update, no-create. 13 tests cover the rename, the default-state set on a fresh seed, and the skip when it's already Triage.
* fix(scripts): reconcile the seed against Linear's default columnsCraig Jennings2026-06-061-136/+113
| | | | | | | | The first cut of the seed assumed an empty team and created all six columns. Run live, it died on "Cannot create a duplicate workflow state": teamCreate seeds Linear's own default columns and Linear dedups state names case-insensitively, so creating "backlog" collided with the default "Backlog". The additive model is wrong whenever the team already has states, which after teamCreate it always does. The rewrite reconciles instead. It renames Linear's defaults into the six Agile columns, keeping Backlog / In Progress / Done / Canceled and repurposing the unstarted Todo as Triage Queue, and creates only the genuinely-new Icebox. It never archives, which sidesteps two limits I hit live: a team must keep an unstarted state (so Todo can't be removed, only repurposed), and the Duplicate state is reserved. Positions are set in a second update pass with distinct nonzero values, because Linear ignores a 0.0 position and appends a new state at a high one. Leftover columns the script doesn't claim (Duplicate) are reported, not touched. The planner (reconcile_state_plan, leftover_state_names) is pure and unit-tested, including the case-insensitive match that recovers a half-seeded board. The client and seed() get fake-transport tests. 13 tests, and the dry-run ran live against the seeded workspace and came back all-update, no-create, so the model matches what Linear does. The personal key stays in the gitignored apikey.txt.
* feat(scripts): add a Linear workspace seed scriptCraig Jennings2026-06-061-0/+217
scripts/seed_pearl_workspace.py stands up the Pearl dogfooding conventions in a Linear workspace: a "Pearl" team whose columns are the six Agile states (icebox, triage queue, backlog, in-progress, done, cancelled), a "Pearl" project, and three Custom Views (Pearl Open Issues, Pearl Icebox, Pearl Inbox). It's idempotent: it snapshots the workspace, plans only what's missing by name, and re-running creates nothing. Reusable for us and for end-users standing up the same setup. The six columns are typed so pearl's own grouped view reproduces the board order. icebox, triage queue, and backlog are all the backlog Linear type with ascending positions, so pearl's (state-type, position) ordering lays them out in sequence. The rest take the started, completed, and canceled types. Two limits the Linear API forced, both verified by schema introspection first. CustomViewCreateInput has no grouping field, so Pearl Open Issues is created with its filter and grouped by category later, in pearl (pearl-set-grouping) or the Linear UI. And teamCreate seeds its own default columns, so the script adds the six and reports the leftovers to tidy in the UI rather than auto-archiving, since a team's default state can't always be archived safely. The logic splits into a pure planner and a thin GraphQL client. The planner is unit-tested without mocks, the client and orchestration through a fake transport. 14 tests cover the targets, planning, idempotence, and the leftover report, and the snapshot queries ran live against a real workspace. The personal API key stays in a gitignored apikey.txt, never in the repo.