Skip to content

Commit 41fe97b

Browse files
docs: plan quality gates and issue relationships (#230)
Co-authored-by: bougyman's bot <ruby-automation@users.noreply.github.com>
1 parent 5dc5ad8 commit 41fe97b

4 files changed

Lines changed: 449 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ and best practices for agents to follow.
1717
- Phase 12 Plan: documents/phase-12-plan.adoc
1818
- Phase 13 Plan: documents/phase-13-plan.adoc
1919
- Phase 14 Plan: documents/phase-14-plan.adoc
20+
- Phase 15 Plan: documents/phase-15-plan.adoc
21+
- Phase 16 Plan: documents/phase-16-plan.adoc
2022
- Burrito distribution decision: documents/burrito-decision.adoc
23+
- Development and CI quality gates decision: documents/quality-gates-decision.adoc
2124

2225
## Project Structure
2326

documents/phase-15-plan.adoc

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
= {my-title}
2+
Tj Vanderpoel (bougyman) <tj.vanderpoel@prizepicks.com>
3+
:revdate: Sep 04, 2026
4+
:my-title: Phase 15 plan: separate developer and CI quality gates
5+
:icons: font
6+
:env-github:
7+
ifdef::env-github[]
8+
:tip-caption: :bulb:
9+
:note-caption: :information_source:
10+
:important-caption: :heavy_exclamation_mark:
11+
:caution-caption: :fire:
12+
:warning-caption: :warning:
13+
endif::[]
14+
:toc:
15+
16+
== Goal
17+
18+
Give the repository two purposeful validation entry points:
19+
20+
* `mix precommit` is a quick, self-contained command developers can run
21+
repeatedly while editing; and
22+
* `mix ci` is the complete, potentially slower integration gate used by GitHub
23+
Actions and by developers who want CI-equivalent assurance.
24+
25+
The goal is not merely to rename commands. It is to prevent CI-only work from
26+
silently accumulating in the everyday developer command.
27+
28+
== Context and supersession
29+
30+
Phase 14, decision 7, made `mix precommit` the comprehensive repository gate
31+
and made `mix ci` a compatibility alias. The resulting implementation runs
32+
dependency retrieval, security audits, repository-range validation, root and
33+
app suites, and static analysis through `mix precommit`.
34+
35+
That is the wrong boundary for routine local use. This plan supersedes only
36+
Phase 14 decision 7 and its related task/workflow wording. Phase 14's goals
37+
for Conventional Commit and pull-request-title enforcement remain in force;
38+
they move to the CI gate where they can use the PR event data and a complete
39+
Git history. EXT-32 and EXT-33 must be reconciled with this plan before their
40+
remaining work merges.
41+
42+
The enduring policy is recorded in
43+
`documents/quality-gates-decision.adoc`.
44+
45+
== Gate contracts
46+
47+
=== `mix precommit`: frequent local feedback
48+
49+
`mix precommit` must be usable from an ordinary checkout after dependencies
50+
are already installed. It must not download packages, fetch Git refs, require
51+
credentials, start a database or other service, invoke a container runtime, or
52+
build/package release artifacts.
53+
54+
It owns deterministic, source-local feedback:
55+
56+
. root formatting and root unit tests;
57+
. app formatting, compilation/static analysis, and only the app tests that are
58+
explicitly classified as fast unit tests; and
59+
. any similarly local check added later.
60+
61+
The implementation target is a warm run in no more than five seconds on an ordinary
62+
developer checkout. Measure it and record the result in the task documentation
63+
or its test/issue evidence. A check that materially exceeds that budget belongs
64+
in `mix ci` unless there is a deliberate revision of this plan.
65+
66+
=== `mix ci`: complete integration assurance
67+
68+
`mix ci` is the canonical pull-request and merge gate. It prepares the
69+
environment, runs `mix precommit`, then runs the remaining CI-only work.
70+
It may fetch dependencies and Git history, consume PR metadata, access
71+
networked advisory services, provision databases or other test services, and
72+
run integration, end-to-end, packaging, native-target, or release checks.
73+
74+
The required relationship is:
75+
76+
----
77+
mix precommit ⊂ mix ci
78+
----
79+
80+
CI-only checks must be listed in `mix help ci` and remain independently
81+
identifiable in workflow output. A green `mix precommit` means the local gate
82+
passed; it must not be presented as a prediction that all CI checks will pass.
83+
84+
== Implementation plan
85+
86+
=== 1. Classify the current checks and tests
87+
88+
Inventory every command currently run by `mix precommit`, `mix ci`, and the
89+
GitHub workflows. For each, record its owner, dependencies, network/service
90+
requirements, and warm-run cost.
91+
92+
Split the app suite deliberately rather than relying on a naming convention or
93+
a vague assumption that all `mix test` tests are local. Add an explicit test
94+
classification mechanism for slow, integration, service-backed, native, or
95+
end-to-end tests. The default app test command used by `precommit` must select
96+
only the fast unit category; `ci` must run both that category and the excluded
97+
CI category. SQLite files created privately by tests are acceptable only when
98+
they remain self-contained and within the local time budget; an externally
99+
managed database is CI-only.
100+
101+
=== 2. Implement the local gate
102+
103+
Replace the comprehensive `Mix.Tasks.Precommit` sequence with the classified
104+
local checks. It must reject arguments consistently, preserve injectable task
105+
execution for unit tests, and document that dependencies must already exist.
106+
107+
Remove from it, at minimum, `deps.get`, Hex/dependency audits, pull-request
108+
title validation, remote/range Git validation that can fetch, and every
109+
CI-classified test. Add regression tests that assert both the included order
110+
and the absence of CI-only commands.
111+
112+
=== 3. Implement the CI gate
113+
114+
Make `Mix.Tasks.Ci` an independent orchestrator, not an alias. It performs
115+
environment/bootstrap work as needed, invokes the local gate, and then runs
116+
the CI-only checks. It owns dependency and security audits, PR-title and
117+
commit-range validation, and the classified integration/service suites.
118+
119+
Design the task so local CI-equivalent runs can state their prerequisites and
120+
fail clearly if a required service is unavailable. Do not conceal external
121+
setup inside a nominally local task. Add unit tests for sequencing, error
122+
propagation, and the strict-superset relationship.
123+
124+
=== 4. Rewire workflows and guidance
125+
126+
Update `.github/workflows/ci.yaml` to run `mix ci`, supplying the Git history
127+
and pull-request-title environment only to the CI gate. Retain the Phase 14
128+
security rule: user-controlled titles are environment data, never shell source.
129+
Ensure the root required workflow still exercises Conventional Commit and PR
130+
title validation after it stops calling `mix precommit` directly.
131+
132+
Update the README, task module documentation, agent prompts, and Phase 14
133+
references that currently call `mix precommit` the full gate. They must make
134+
the distinction clear: run `mix precommit` frequently; run `mix ci` before a
135+
PR when practical and rely on it as the required remote gate.
136+
137+
=== 5. Verify the contracts
138+
139+
Verify from a warm checkout that `mix precommit` neither uses the network nor
140+
starts/depends on external services. Verify that a clean CI environment can
141+
run `mix ci` end to end, including its bootstrap and service-backed checks.
142+
143+
Test failures at each boundary: a formatting/unit-test failure must fail both
144+
commands; a CI-only audit, metadata, or integration failure must fail only
145+
`mix ci`. Confirm workflow runs report the CI gate and preserve Phase 14's
146+
required protection behavior.
147+
148+
== Linear issue decomposition
149+
150+
1. *Classify test and check tiers.* Define the explicit fast-versus-CI-only
151+
test selection, inventory current costs/dependencies, and add enforcement
152+
tests for the classification.
153+
2. *Restore `mix precommit` as a local gate.* Implement the lightweight task,
154+
documentation, and task tests after classification is available.
155+
3. *Make `mix ci` the full integration gate.* Implement CI orchestration,
156+
bootstrap/audits/metadata/integration checks, workflow rewiring, and
157+
guidance updates. This follows the first two issues and reconciles EXT-32
158+
and EXT-33 before their remaining changes land.
159+
160+
== Acceptance criteria
161+
162+
* `mix ci` is no longer an alias for `mix precommit`.
163+
* A warm `mix precommit` completes in no more than five seconds on an ordinary
164+
developer checkout.
165+
* `mix precommit` needs no network, credentials, Git fetch, container runtime,
166+
externally managed database, or other service after dependencies are present.
167+
* `mix ci` runs every `mix precommit` check plus documented CI-only checks.
168+
* Tests enforce both the command sequences and the excluded CI-only work.
169+
* GitHub Actions calls `mix ci`; Conventional Commit and PR-title validation
170+
remain required CI behavior.
171+
* Developer documentation describes the two commands without calling the local
172+
gate a complete CI prediction.

documents/phase-16-plan.adoc

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
= {my-title}
2+
Tj Vanderpoel (bougyman) <tj.vanderpoel@prizepicks.com>
3+
:revdate: Sep 04, 2026
4+
:my-title: Phase 16 plan: issue relationship support
5+
:icons: font
6+
:env-github:
7+
ifdef::env-github[]
8+
:tip-caption: :bulb:
9+
:note-caption: :information_source:
10+
:important-caption: :heavy_exclamation_mark:
11+
:caution-caption: :fire:
12+
:warning-caption: :warning:
13+
endif::[]
14+
:toc:
15+
16+
== Goal
17+
18+
Make issue relationships first-class in `lc` so users can inspect and manage
19+
dependencies without leaving the terminal. In particular, a planner must be
20+
able to express that one issue is blocked by another and verify the resulting
21+
Linear dependency link.
22+
23+
== Context
24+
25+
`mix lc issue create` and `mix lc issue update` currently have no relationship
26+
operation. The documented ordering of related work therefore remains prose in
27+
issue descriptions rather than a real Linear dependency graph.
28+
29+
Linear's checked-in GraphQL schema already provides the necessary API:
30+
31+
* `Issue.relations` and `Issue.inverseRelations` for outgoing and incoming
32+
links;
33+
* `issueRelationCreate`, `issueRelationDelete`, and `issueRelationUpdate`
34+
mutations; and
35+
* the `blocks`, `related`, and `duplicate` relation types.
36+
37+
The source endpoint of a `blocks` relation blocks the related endpoint. That
38+
wire-level direction must not force users to mentally reverse a normal
39+
dependency statement.
40+
41+
== Command design
42+
43+
Add an `issue relation` command group with three initial leaf commands:
44+
45+
[source,sh]
46+
----
47+
# Show both directions for EXT-38.
48+
lc issue relation list EXT-38
49+
50+
# State the dependency in the natural direction: EXT-38 is blocked by EXT-37.
51+
lc issue relation add EXT-38 EXT-37 --type blocked-by
52+
53+
# One issue may be blocked by any number of issues in one command.
54+
lc issue relation add EXT-39 EXT-37 EXT-38 --type blocked-by
55+
56+
# Remove one or more relationships using the same natural statement.
57+
lc issue relation remove EXT-39 EXT-37 EXT-38 --type blocked-by
58+
----
59+
60+
`list` has the `ls` alias. The first positional argument is always the subject
61+
of the statement and every following `RELATED_ISSUE` is an independent other
62+
issue; command order is therefore never inferred from a relation ID or the
63+
order returned by Linear. `add` and `remove` require one subject plus one or
64+
more related issues.
65+
66+
[cols="1,3", options="header"]
67+
|===
68+
| Command
69+
| Meaning
70+
71+
| `lc issue relation add A B... --type blocks`
72+
| A blocks every named B.
73+
74+
| `lc issue relation add A B... --type blocked-by`
75+
| A is blocked by every named B. The implementation reverses each endpoint
76+
pair and sends a GraphQL `blocks` relation from each B to A.
77+
78+
| `lc issue relation add A B... --type related`
79+
| A is related to every named B.
80+
81+
| `lc issue relation add A B... --type duplicate`
82+
| A is a duplicate of every named B.
83+
|===
84+
85+
`--type` is required and accepts exactly `blocks`, `blocked-by`, `related`,
86+
and `duplicate`. `blocked-by` is a CLI convenience, not a GraphQL type.
87+
88+
`list` presents outbound and inbound relations separately, with the related
89+
issue identifier and title, relation type in the user's direction, and the
90+
relation ID. Human output must say `Blocked by` for an incoming `blocks`
91+
relation and `Blocks` for an outgoing one. JSON output must retain the API
92+
relation ID, source issue, related issue, wire type, and direction so scripts
93+
do not need to parse terminal prose. The human output must have distinct
94+
`Blocks` and `Blocked by` sections; each line names the other issue, so a user
95+
can see the direction without reconstructing it from argument order.
96+
97+
`add` and `remove` are idempotent per target from the user's perspective:
98+
adding an already-present identical relation succeeds with an informative
99+
no-change result; removing an absent relation does the same. A multi-target
100+
operation attempts every valid target and reports a result for each one; it
101+
does not roll back successful independent mutations because Linear supplies no
102+
transaction spanning relations. Its non-zero exit status and machine-readable
103+
output must identify every failed target. Reject a self-link and ambiguous or
104+
unknown relation type values before making that target's API request. Do not
105+
add a generic relation-update command in this phase; a type change is an
106+
explicit remove/add operation until its user-facing semantics are designed.
107+
108+
== Domain and API design
109+
110+
Add `LinearCli.Linear.IssueRelation` as an Ash resource registered in
111+
`LinearCli.Linear`. It has manual read/create/destroy actions backed directly
112+
by `LinearCli.Api`; no Ecto data layer and no Ash DSL relationship declaration
113+
is introduced.
114+
115+
Expose named domain code interfaces for listing an issue's relations, creating
116+
one, and deleting one. The CLI layer resolves human issue identifiers through
117+
the existing issue lookup helpers, normalizes the directional `--type` value,
118+
and calls those interfaces rather than constructing GraphQL documents itself.
119+
120+
The GraphQL selection must retrieve enough data to render and safely remove a
121+
relation: relation ID and type plus both endpoint issues' IDs, identifiers,
122+
titles, and URLs. Read both `relations` and `inverseRelations`, handle their
123+
pagination, and preserve the API's error response rather than treating an
124+
authorization or validation error as an empty list.
125+
126+
For deletion, first resolve the exact relation from the two supplied issues and
127+
normalized type. If Linear returns duplicate rows for an impossible/legacy
128+
state, fail with a diagnostic that names every matching relation ID instead of
129+
deleting arbitrarily. Treat Linear's normal duplicate/invalid mutation errors
130+
as actionable CLI errors. Do not attempt to infer or alter parent/sub-issue
131+
hierarchies; they are distinct from issue relations.
132+
133+
== CLI integration and display
134+
135+
Extend the Optimus command specification, dispatch, aliases, and help text for
136+
the `issue relation` group, including `lc issue relation ls ISSUE` as the
137+
documented alias for `list`. Follow existing `issue view` lookup behavior for
138+
issue identifiers, including expansion of a bare numeric ID where that helper
139+
supports it. The help synopsis must name the arguments `ISSUE` and
140+
`RELATED_ISSUE...` and include the directional table above.
141+
142+
Enhance full `issue view` output to show relationships through the same display
143+
formatter used by `issue relation list`; compact issue lists remain unchanged.
144+
The explicit `issue relation list` command is still required for discoverable,
145+
scriptable relationship inspection.
146+
147+
Successful mutations print the created or removed relationship in human and
148+
JSON output. Do not use a generic success sentence that hides the direction;
149+
the output must make it obvious whether the named issue now blocks or is
150+
blocked by the other one.
151+
152+
== Tests, documentation, and safety
153+
154+
Add unit tests at each boundary:
155+
156+
* resource/manual-action tests for GraphQL documents, variables, response
157+
mapping, pagination, and API error propagation;
158+
* domain code-interface tests;
159+
* CLI/parser tests for every command, aliases, required `--type`, one-or-more
160+
related issues, invalid types, self-links, reversed `blocked-by` variables,
161+
per-target idempotency/partial failure, and ambiguous deletion; and
162+
* display tests for both directions and JSON's stable relation fields.
163+
164+
Update `documents/ash-domain-erd.adoc` in the same change to add the resource,
165+
its public attributes, code interfaces, nested endpoint associations, and the
166+
explicit statement that these remain GraphQL/nested associations rather than
167+
Ash DSL relationships. Update the README's development/CLI usage material and
168+
any command reference or completion tests.
169+
170+
Use only `mix lc` for Linear interaction in repository documentation and agent
171+
workflows. Verify against a disposable pair of test issues: create every
172+
supported type, list from both endpoints, remove it, and confirm that repeated
173+
add/remove operations have the documented idempotent result.
174+
175+
== Acceptance criteria
176+
177+
* `lc issue relation list` (and `ls`), `add`, and `remove` are discoverable in
178+
`--help` and work with human and JSON output.
179+
* `--type blocked-by` creates and removes the correctly reversed GraphQL
180+
`blocks` relation.
181+
* One `add` or `remove` command accepts an arbitrary non-empty list of related
182+
issues and reports a separate outcome for every target.
183+
* Both endpoints show the same relationship in their natural direction.
184+
* Duplicate adds and absent removes are safe no-ops; self-links and ambiguity
185+
fail before unintended mutation.
186+
* The domain resource, code interfaces, CLI tests, display tests, README, and
187+
Ash domain ERD remain in sync.
188+
* No raw GraphQL or alternate Linear client is introduced outside
189+
`LinearCli.Api`.

0 commit comments

Comments
 (0)