diff --git a/AGENTS.md b/AGENTS.md index 02462d4..2d54ed6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,10 @@ and best practices for agents to follow. - Phase 12 Plan: documents/phase-12-plan.adoc - Phase 13 Plan: documents/phase-13-plan.adoc - Phase 14 Plan: documents/phase-14-plan.adoc +- Phase 15 Plan: documents/phase-15-plan.adoc +- Phase 16 Plan: documents/phase-16-plan.adoc - Burrito distribution decision: documents/burrito-decision.adoc +- Development and CI quality gates decision: documents/quality-gates-decision.adoc ## Project Structure diff --git a/documents/phase-15-plan.adoc b/documents/phase-15-plan.adoc new file mode 100644 index 0000000..46c851d --- /dev/null +++ b/documents/phase-15-plan.adoc @@ -0,0 +1,172 @@ += {my-title} +Tj Vanderpoel (bougyman) +:revdate: Sep 04, 2026 +:my-title: Phase 15 plan: separate developer and CI quality gates +:icons: font +:env-github: +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:important-caption: :heavy_exclamation_mark: +:caution-caption: :fire: +:warning-caption: :warning: +endif::[] +:toc: + +== Goal + +Give the repository two purposeful validation entry points: + +* `mix precommit` is a quick, self-contained command developers can run + repeatedly while editing; and +* `mix ci` is the complete, potentially slower integration gate used by GitHub + Actions and by developers who want CI-equivalent assurance. + +The goal is not merely to rename commands. It is to prevent CI-only work from +silently accumulating in the everyday developer command. + +== Context and supersession + +Phase 14, decision 7, made `mix precommit` the comprehensive repository gate +and made `mix ci` a compatibility alias. The resulting implementation runs +dependency retrieval, security audits, repository-range validation, root and +app suites, and static analysis through `mix precommit`. + +That is the wrong boundary for routine local use. This plan supersedes only +Phase 14 decision 7 and its related task/workflow wording. Phase 14's goals +for Conventional Commit and pull-request-title enforcement remain in force; +they move to the CI gate where they can use the PR event data and a complete +Git history. EXT-32 and EXT-33 must be reconciled with this plan before their +remaining work merges. + +The enduring policy is recorded in +`documents/quality-gates-decision.adoc`. + +== Gate contracts + +=== `mix precommit`: frequent local feedback + +`mix precommit` must be usable from an ordinary checkout after dependencies +are already installed. It must not download packages, fetch Git refs, require +credentials, start a database or other service, invoke a container runtime, or +build/package release artifacts. + +It owns deterministic, source-local feedback: + +. root formatting and root unit tests; +. app formatting, compilation/static analysis, and only the app tests that are + explicitly classified as fast unit tests; and +. any similarly local check added later. + +The implementation target is a warm run in no more than five seconds on an ordinary +developer checkout. Measure it and record the result in the task documentation +or its test/issue evidence. A check that materially exceeds that budget belongs +in `mix ci` unless there is a deliberate revision of this plan. + +=== `mix ci`: complete integration assurance + +`mix ci` is the canonical pull-request and merge gate. It prepares the +environment, runs `mix precommit`, then runs the remaining CI-only work. +It may fetch dependencies and Git history, consume PR metadata, access +networked advisory services, provision databases or other test services, and +run integration, end-to-end, packaging, native-target, or release checks. + +The required relationship is: + +---- +mix precommit ⊂ mix ci +---- + +CI-only checks must be listed in `mix help ci` and remain independently +identifiable in workflow output. A green `mix precommit` means the local gate +passed; it must not be presented as a prediction that all CI checks will pass. + +== Implementation plan + +=== 1. Classify the current checks and tests + +Inventory every command currently run by `mix precommit`, `mix ci`, and the +GitHub workflows. For each, record its owner, dependencies, network/service +requirements, and warm-run cost. + +Split the app suite deliberately rather than relying on a naming convention or +a vague assumption that all `mix test` tests are local. Add an explicit test +classification mechanism for slow, integration, service-backed, native, or +end-to-end tests. The default app test command used by `precommit` must select +only the fast unit category; `ci` must run both that category and the excluded +CI category. SQLite files created privately by tests are acceptable only when +they remain self-contained and within the local time budget; an externally +managed database is CI-only. + +=== 2. Implement the local gate + +Replace the comprehensive `Mix.Tasks.Precommit` sequence with the classified +local checks. It must reject arguments consistently, preserve injectable task +execution for unit tests, and document that dependencies must already exist. + +Remove from it, at minimum, `deps.get`, Hex/dependency audits, pull-request +title validation, remote/range Git validation that can fetch, and every +CI-classified test. Add regression tests that assert both the included order +and the absence of CI-only commands. + +=== 3. Implement the CI gate + +Make `Mix.Tasks.Ci` an independent orchestrator, not an alias. It performs +environment/bootstrap work as needed, invokes the local gate, and then runs +the CI-only checks. It owns dependency and security audits, PR-title and +commit-range validation, and the classified integration/service suites. + +Design the task so local CI-equivalent runs can state their prerequisites and +fail clearly if a required service is unavailable. Do not conceal external +setup inside a nominally local task. Add unit tests for sequencing, error +propagation, and the strict-superset relationship. + +=== 4. Rewire workflows and guidance + +Update `.github/workflows/ci.yaml` to run `mix ci`, supplying the Git history +and pull-request-title environment only to the CI gate. Retain the Phase 14 +security rule: user-controlled titles are environment data, never shell source. +Ensure the root required workflow still exercises Conventional Commit and PR +title validation after it stops calling `mix precommit` directly. + +Update the README, task module documentation, agent prompts, and Phase 14 +references that currently call `mix precommit` the full gate. They must make +the distinction clear: run `mix precommit` frequently; run `mix ci` before a +PR when practical and rely on it as the required remote gate. + +=== 5. Verify the contracts + +Verify from a warm checkout that `mix precommit` neither uses the network nor +starts/depends on external services. Verify that a clean CI environment can +run `mix ci` end to end, including its bootstrap and service-backed checks. + +Test failures at each boundary: a formatting/unit-test failure must fail both +commands; a CI-only audit, metadata, or integration failure must fail only +`mix ci`. Confirm workflow runs report the CI gate and preserve Phase 14's +required protection behavior. + +== Linear issue decomposition + +1. *Classify test and check tiers.* Define the explicit fast-versus-CI-only + test selection, inventory current costs/dependencies, and add enforcement + tests for the classification. +2. *Restore `mix precommit` as a local gate.* Implement the lightweight task, + documentation, and task tests after classification is available. +3. *Make `mix ci` the full integration gate.* Implement CI orchestration, + bootstrap/audits/metadata/integration checks, workflow rewiring, and + guidance updates. This follows the first two issues and reconciles EXT-32 + and EXT-33 before their remaining changes land. + +== Acceptance criteria + +* `mix ci` is no longer an alias for `mix precommit`. +* A warm `mix precommit` completes in no more than five seconds on an ordinary + developer checkout. +* `mix precommit` needs no network, credentials, Git fetch, container runtime, + externally managed database, or other service after dependencies are present. +* `mix ci` runs every `mix precommit` check plus documented CI-only checks. +* Tests enforce both the command sequences and the excluded CI-only work. +* GitHub Actions calls `mix ci`; Conventional Commit and PR-title validation + remain required CI behavior. +* Developer documentation describes the two commands without calling the local + gate a complete CI prediction. diff --git a/documents/phase-16-plan.adoc b/documents/phase-16-plan.adoc new file mode 100644 index 0000000..73d1f4c --- /dev/null +++ b/documents/phase-16-plan.adoc @@ -0,0 +1,189 @@ += {my-title} +Tj Vanderpoel (bougyman) +:revdate: Sep 04, 2026 +:my-title: Phase 16 plan: issue relationship support +:icons: font +:env-github: +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:important-caption: :heavy_exclamation_mark: +:caution-caption: :fire: +:warning-caption: :warning: +endif::[] +:toc: + +== Goal + +Make issue relationships first-class in `lc` so users can inspect and manage +dependencies without leaving the terminal. In particular, a planner must be +able to express that one issue is blocked by another and verify the resulting +Linear dependency link. + +== Context + +`mix lc issue create` and `mix lc issue update` currently have no relationship +operation. The documented ordering of related work therefore remains prose in +issue descriptions rather than a real Linear dependency graph. + +Linear's checked-in GraphQL schema already provides the necessary API: + +* `Issue.relations` and `Issue.inverseRelations` for outgoing and incoming + links; +* `issueRelationCreate`, `issueRelationDelete`, and `issueRelationUpdate` + mutations; and +* the `blocks`, `related`, and `duplicate` relation types. + +The source endpoint of a `blocks` relation blocks the related endpoint. That +wire-level direction must not force users to mentally reverse a normal +dependency statement. + +== Command design + +Add an `issue relation` command group with three initial leaf commands: + +[source,sh] +---- +# Show both directions for EXT-38. +lc issue relation list EXT-38 + +# State the dependency in the natural direction: EXT-38 is blocked by EXT-37. +lc issue relation add EXT-38 EXT-37 --type blocked-by + +# One issue may be blocked by any number of issues in one command. +lc issue relation add EXT-39 EXT-37 EXT-38 --type blocked-by + +# Remove one or more relationships using the same natural statement. +lc issue relation remove EXT-39 EXT-37 EXT-38 --type blocked-by +---- + +`list` has the `ls` alias. The first positional argument is always the subject +of the statement and every following `RELATED_ISSUE` is an independent other +issue; command order is therefore never inferred from a relation ID or the +order returned by Linear. `add` and `remove` require one subject plus one or +more related issues. + +[cols="1,3", options="header"] +|=== +| Command +| Meaning + +| `lc issue relation add A B... --type blocks` +| A blocks every named B. + +| `lc issue relation add A B... --type blocked-by` +| A is blocked by every named B. The implementation reverses each endpoint + pair and sends a GraphQL `blocks` relation from each B to A. + +| `lc issue relation add A B... --type related` +| A is related to every named B. + +| `lc issue relation add A B... --type duplicate` +| A is a duplicate of every named B. +|=== + +`--type` is required and accepts exactly `blocks`, `blocked-by`, `related`, +and `duplicate`. `blocked-by` is a CLI convenience, not a GraphQL type. + +`list` presents outbound and inbound relations separately, with the related +issue identifier and title, relation type in the user's direction, and the +relation ID. Human output must say `Blocked by` for an incoming `blocks` +relation and `Blocks` for an outgoing one. JSON output must retain the API +relation ID, source issue, related issue, wire type, and direction so scripts +do not need to parse terminal prose. The human output must have distinct +`Blocks` and `Blocked by` sections; each line names the other issue, so a user +can see the direction without reconstructing it from argument order. + +`add` and `remove` are idempotent per target from the user's perspective: +adding an already-present identical relation succeeds with an informative +no-change result; removing an absent relation does the same. A multi-target +operation attempts every valid target and reports a result for each one; it +does not roll back successful independent mutations because Linear supplies no +transaction spanning relations. Its non-zero exit status and machine-readable +output must identify every failed target. Reject a self-link and ambiguous or +unknown relation type values before making that target's API request. Do not +add a generic relation-update command in this phase; a type change is an +explicit remove/add operation until its user-facing semantics are designed. + +== Domain and API design + +Add `LinearCli.Linear.IssueRelation` as an Ash resource registered in +`LinearCli.Linear`. It has manual read/create/destroy actions backed directly +by `LinearCli.Api`; no Ecto data layer and no Ash DSL relationship declaration +is introduced. + +Expose named domain code interfaces for listing an issue's relations, creating +one, and deleting one. The CLI layer resolves human issue identifiers through +the existing issue lookup helpers, normalizes the directional `--type` value, +and calls those interfaces rather than constructing GraphQL documents itself. + +The GraphQL selection must retrieve enough data to render and safely remove a +relation: relation ID and type plus both endpoint issues' IDs, identifiers, +titles, and URLs. Read both `relations` and `inverseRelations`, handle their +pagination, and preserve the API's error response rather than treating an +authorization or validation error as an empty list. + +For deletion, first resolve the exact relation from the two supplied issues and +normalized type. If Linear returns duplicate rows for an impossible/legacy +state, fail with a diagnostic that names every matching relation ID instead of +deleting arbitrarily. Treat Linear's normal duplicate/invalid mutation errors +as actionable CLI errors. Do not attempt to infer or alter parent/sub-issue +hierarchies; they are distinct from issue relations. + +== CLI integration and display + +Extend the Optimus command specification, dispatch, aliases, and help text for +the `issue relation` group, including `lc issue relation ls ISSUE` as the +documented alias for `list`. Follow existing `issue view` lookup behavior for +issue identifiers, including expansion of a bare numeric ID where that helper +supports it. The help synopsis must name the arguments `ISSUE` and +`RELATED_ISSUE...` and include the directional table above. + +Enhance full `issue view` output to show relationships through the same display +formatter used by `issue relation list`; compact issue lists remain unchanged. +The explicit `issue relation list` command is still required for discoverable, +scriptable relationship inspection. + +Successful mutations print the created or removed relationship in human and +JSON output. Do not use a generic success sentence that hides the direction; +the output must make it obvious whether the named issue now blocks or is +blocked by the other one. + +== Tests, documentation, and safety + +Add unit tests at each boundary: + +* resource/manual-action tests for GraphQL documents, variables, response + mapping, pagination, and API error propagation; +* domain code-interface tests; +* CLI/parser tests for every command, aliases, required `--type`, one-or-more + related issues, invalid types, self-links, reversed `blocked-by` variables, + per-target idempotency/partial failure, and ambiguous deletion; and +* display tests for both directions and JSON's stable relation fields. + +Update `documents/ash-domain-erd.adoc` in the same change to add the resource, +its public attributes, code interfaces, nested endpoint associations, and the +explicit statement that these remain GraphQL/nested associations rather than +Ash DSL relationships. Update the README's development/CLI usage material and +any command reference or completion tests. + +Use only `mix lc` for Linear interaction in repository documentation and agent +workflows. Verify against a disposable pair of test issues: create every +supported type, list from both endpoints, remove it, and confirm that repeated +add/remove operations have the documented idempotent result. + +== Acceptance criteria + +* `lc issue relation list` (and `ls`), `add`, and `remove` are discoverable in + `--help` and work with human and JSON output. +* `--type blocked-by` creates and removes the correctly reversed GraphQL + `blocks` relation. +* One `add` or `remove` command accepts an arbitrary non-empty list of related + issues and reports a separate outcome for every target. +* Both endpoints show the same relationship in their natural direction. +* Duplicate adds and absent removes are safe no-ops; self-links and ambiguity + fail before unintended mutation. +* The domain resource, code interfaces, CLI tests, display tests, README, and + Ash domain ERD remain in sync. +* No raw GraphQL or alternate Linear client is introduced outside + `LinearCli.Api`. diff --git a/documents/quality-gates-decision.adoc b/documents/quality-gates-decision.adoc new file mode 100644 index 0000000..4ad464c --- /dev/null +++ b/documents/quality-gates-decision.adoc @@ -0,0 +1,85 @@ += Development and CI Quality Gates +:toc: +:toc-placement: preamble + +== Context + +The repository needs two validation commands with different operating +constraints. Developers run `mix precommit` repeatedly while making a change, +often without a database, containers, credentials, or reliable network access. +Continuous integration can provision those dependencies and may spend more time +on broader checks. + +Giving both commands the same expanding list of checks makes the local command +slow and fragile. Developers then stop running it, defeating the feedback loop +that the name promises. Conversely, excluding integration coverage from CI +would leave changes unverified until after merge. + +== Decision + +`mix precommit` is the fast, self-contained developer gate. `mix ci` is the +complete integration gate. The commands have distinct contracts; `mix ci` is +not an alias for `mix precommit`. + +=== `mix precommit` + +Use `mix precommit` before committing, before pushing, and throughout normal +development. It must be practical to run repeatedly on an ordinary developer +checkout: on a warm checkout with dependencies already installed, it must +complete in no more than five seconds. + +It may include deterministic checks that need only the checked-out source, +the language toolchain, and already-installed project dependencies. Typical +examples are formatting, compilation, static analysis, unit tests, and local +repository metadata checks. + +It must not: + +* start or require a database, container runtime, queue, browser, or other + service; +* require credentials or access to an external service; +* download dependencies or otherwise require network access as part of its + normal run; +* run packaging, release, cross-platform, or end-to-end integration checks; +* grow beyond the five-second local-gate budget. + +If a proposed check needs one of those things, it belongs in `mix ci` or in a +separately named, opt-in command—not in `mix precommit`. + +=== `mix ci` + +Use `mix ci` as the pull-request and merge gate, and locally when a developer +wants confidence equivalent to CI. It runs everything in `mix precommit`, plus +checks that need additional setup, take materially longer, or verify +integration boundaries. + +`mix ci` may install dependencies, start databases or other services, provision +test fixtures, and run slower checks such as integration, end-to-end, +cross-platform, native-package, release, and security scans. CI configuration +is responsible for providing the prerequisites and making failures actionable. + +=== Relationship and maintenance rules + +The gate relationship is one-way: + +---- +mix precommit ⊂ mix ci +---- + +Every check in `mix precommit` must also run through `mix ci`, in the same +meaningful form. A check may move from `precommit` to `ci` when it no longer +meets the local-gate contract; it must not move in the other direction merely +to make CI faster. + +When adding or changing a check, document which gate owns it and why. Preserve +the `precommit` constraints above unless this decision is deliberately revised. +The task documentation and CI workflow must name the gate they invoke rather +than relying on an undocumented alias. + +== Current implementation + +At the time of this decision, `mix ci` delegates directly to `mix precommit`, +and `mix precommit` performs dependency retrieval, dependency audits, and the +full application suite. That arrangement does not satisfy this decision. +Implementation work must split the tasks so `mix precommit` has the local +contract above and `mix ci` composes it with the CI-only work.