Files
2026-06-18 22:21:38 -04:00

418 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 15: Doc-Only CI Skip + Markdown Lint - Pattern Map
**Mapped:** 2026-06-12
**Files analyzed:** 4 (2 modified, 1 created, N docs fixed)
**Analogs found:** 3 / 4 (`.markdownlint-cli2.jsonc` has no role-match analog — closest is `.prettierrc`)
## File Classification
| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
|---|---|---|---|---|
| `.gitea/workflows/ci.yml` | CI workflow | event-driven | itself (existing job blocks) | exact — new jobs modeled on existing jobs in same file |
| `.markdownlint-cli2.jsonc` | tool config | — | `.prettierrc` (root JSON tool config) | structural-match (same layer, no content analog) |
| `package.json` (root) | package manifest | — | itself (existing `format:check` script + `prettier` devDep) | exact — same script/dep pattern |
| `docs/**/*.md`, `README.md`, `apps/*/README.md` | docs content | — | the violating files themselves | exact — in-place fixes |
---
## Pattern Assignments
### `.gitea/workflows/ci.yml` — `changes` job (new)
**Analog:** The existing `fast-checks` job in the same file, lines 838.
**Job skeleton pattern** (ci.yml lines 811 — `fast-checks` header as template):
```yaml
fast-checks:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
```
**New `changes` job — copy this structure, replacing steps with the paths-filter action:**
```yaml
changes:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
pull-requests: read
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
code:
- '**/*.ts'
- '**/*.tsx'
- '**/*.js'
- '**/*.json'
- '**/*.yaml'
- '**/*.yml'
- 'apps/**'
- 'packages/**'
- 'pnpm-lock.yaml'
- 'Dockerfile'
- 'docker-compose*.yml'
```
**Key design notes:**
- No `actions/checkout` step — `dorny/paths-filter@v4` uses the Gitea/GitHub REST API on PR events (no fetch-depth needed).
- `permissions: pull-requests: read` is required by v4; declare it at job scope (not workflow scope).
- Define the positive `code` filter (not a `docs` filter) — `code == 'false'` means "only docs changed", and ambiguous new file types safely default to the full test run.
- Insert this job **before** `fast-checks` in the file so the job ordering in the UI is logical (changes → fast-checks / api / harness → gate).
---
### `.gitea/workflows/ci.yml` — `api` job modification
**Analog:** Existing `api` job, ci.yml lines 40125.
**Current header** (lines 4044):
```yaml
api:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
# Runs in PARALLEL with fast-checks (D-03) — no needs: dependency.
services:
```
**Modified header — add `needs` and extend the `if` condition:**
```yaml
api:
runs-on: ubuntu-latest
needs: [changes]
if: github.event_name == 'pull_request' && needs.changes.outputs.code == 'true'
# Runs in PARALLEL with fast-checks (D-03) — skipped for doc-only PRs.
services:
```
**Nothing else in the `api` job body changes.** The existing comment at line 43 should be updated to reflect conditional skip behavior.
---
### `.gitea/workflows/ci.yml` — `harness` job modification
**Analog:** Existing `harness` job, ci.yml lines 126314.
**Current header** (lines 126129):
```yaml
harness:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
# Runs in PARALLEL with fast-checks + api (D-03) — no needs: dependency.
services:
```
**Modified header — same pattern as `api`:**
```yaml
harness:
runs-on: ubuntu-latest
needs: [changes]
if: github.event_name == 'pull_request' && needs.changes.outputs.code == 'true'
# Runs in PARALLEL with fast-checks (D-03) — skipped for doc-only PRs.
services:
```
**Nothing else in the `harness` job body changes.**
---
### `.gitea/workflows/ci.yml` — `gate` job (new)
**Analog:** `publish.yml` line 94 — `if: always()` pattern on the `Docker logout` step; same construct used at job scope here.
**Insert after `harness` job, at end of file:**
```yaml
gate:
runs-on: ubuntu-latest
needs: [fast-checks, changes, api, harness]
if: always()
steps:
- name: Check all required jobs passed or were skipped
run: |
# fast-checks always runs — must be success
if [ "${{ needs.fast-checks.result }}" != "success" ]; then
echo "fast-checks: ${{ needs.fast-checks.result }}"
exit 1
fi
# api and harness are conditionally skipped — success OR skipped are both acceptable
for result in "${{ needs.api.result }}" "${{ needs.harness.result }}"; do
if [ "$result" != "success" ] && [ "$result" != "skipped" ]; then
echo "Heavy job failed or was cancelled: $result"
exit 1
fi
done
echo "Gate passed."
```
**Critical Gitea 1.26.2 constraint:** Do NOT use `contains(needs.*.result, 'success')` — Gitea issue #31007 confirms this returns `false` even when jobs succeed. Individual `needs.X.result` string comparisons are the only reliable approach on this instance.
**Why `if: always()`:** Without it, a skipped upstream job causes `gate` to also skip — branch protection waiting for `CI / gate` would deadlock. The `if: always()` deadlock bug (Gitea #27906) was fixed in 1.21.8; this instance runs 1.26.2.
---
### `.gitea/workflows/ci.yml` — Markdown lint step in `fast-checks`
**Analog:** Existing `Format check` step, ci.yml lines 3132:
```yaml
- name: Format check
run: pnpm format:check
```
**New step — insert after `Format check`, before `Typecheck`:**
```yaml
- name: Markdown lint
run: pnpm md:lint
```
**Slot:** After line 32 (`run: pnpm format:check`), before line 34 (`- name: Typecheck`). One line of YAML mirrors the `format:check` step pattern exactly — a named step calling a root pnpm script.
---
### `package.json` (root) — `md:lint` script + devDependency
**Analog:** Existing `format:check` script and `prettier` devDependency, package.json lines 15 and 21.
**Current scripts block** (lines 616):
```json
"scripts": {
"dev:api": "pnpm --filter @familysync/api dev",
"dev:pwa": "pnpm --filter @familysync/pwa dev",
"build": "pnpm --filter @familysync/api build && pnpm --filter @familysync/pwa build",
"test": "pnpm --filter @familysync/api test",
"test:e2e": "pnpm --filter @familysync/pwa test:e2e",
"lint": "pnpm -r --if-present lint",
"typecheck": "pnpm -r typecheck",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
```
**Add `md:lint` after `format:check` (alphabetical/logical grouping matches existing order):**
```json
"format:check": "prettier --check .",
"md:lint": "markdownlint-cli2"
```
**Note:** No glob args on the CLI — globs and ignores live entirely in `.markdownlint-cli2.jsonc` (see Open Question #3 in RESEARCH.md resolved: config-file globs make `pnpm md:lint` and direct `npx markdownlint-cli2` behave identically).
**Current devDependencies block** (lines 1725):
```json
"devDependencies": {
"@eslint/js": "9.39.4",
"eslint": "9.39.4",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.1.1",
"prettier": "3.8.4",
"typescript-eslint": "8.61.0"
}
```
**Add `markdownlint-cli2` (alphabetical, between `eslint-plugin-react-hooks` and `prettier`):**
```json
"markdownlint-cli2": "0.22.1",
"prettier": "3.8.4",
```
**Install command (must run at workspace root):**
```bash
pnpm add -D markdownlint-cli2@0.22.1 --workspace-root
```
---
### `.markdownlint-cli2.jsonc` (new file)
**Analog:** `.prettierrc` (root JSON tool config, lines 17) — same layer (repo root), same purpose (configure a formatting/lint tool), same JSON format. No `.markdownlint-cli2.jsonc` exists yet.
**`.prettierrc` structural pattern** (lines 17):
```json
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100
}
```
**New file — use JSONC format (`.jsonc` extension supports comments), place at repo root:**
```jsonc
// .markdownlint-cli2.jsonc
{
"config": {
// Disable all rules that conflict with Prettier (23 rules — line-length, list-indent,
// blanks-around-fences, emphasis-style, etc.)
"extends": "markdownlint/style/prettier",
// Content rules to KEEP:
"MD001": true, // heading-increment: no skipping h1→h3
"MD024": true, // no-duplicate-heading
"MD040": true, // fenced-code-language: all fences must declare a language
"MD031": true, // blanks-around-fences (re-enabled — see Pitfall 4 in RESEARCH.md)
"MD051": true, // link-fragments: broken anchor links
"MD052": true, // reference-links-images: undefined link references
// Rules DISABLED (Prettier owns these OR they fire on non-author-controlled files):
"MD041": false, // first-line-h1: CLAUDE.md legitimately starts with ## Project
"MD034": false, // no-bare-urls: CLAUDE.md version table uses pkg@version syntax
"MD036": false // no-emphasis-as-heading: docs/API.md uses **Response 200** as label
},
"globs": [
"docs/**/*.md",
"*.md",
"apps/**/*.md"
],
"ignores": [
".planning/**",
"node_modules/**",
"**/node_modules/**",
".pnpm-store/**"
]
}
```
**Why `extends` resolves:** `markdownlint-cli2` depends on `markdownlint`, which ships `style/prettier.json`. After `pnpm add -D markdownlint-cli2 --workspace-root`, the file exists at `node_modules/markdownlint/style/prettier.json`.
**Glob placement in config, not CLI:** Putting `globs` and `ignores` in the config file means `pnpm md:lint` (just `markdownlint-cli2`, no args) and a developer running `npx markdownlint-cli2` directly both use the same scope automatically.
---
### Docs content fixes — 13 baseline violations (MD040 / MD031)
**Analog:** The violating files themselves. All fixes are mechanical one-liners.
**Files and fix type:**
| File | Rule | Fix |
|------|------|-----|
| `apps/api/README.md` | MD040 (1) | Add language tag to bare ` ``` ` |
| `apps/pwa/e2e/README.md` | MD040 (1) | Add language tag to bare ` ``` ` |
| `apps/pwa/README.md` | MD040 (1) | Add language tag to bare ` ``` ` |
| `docs/API.md` | MD040 (2) | Add language tags to 2 bare ` ``` ` blocks |
| `docs/ARCHITECTURE.md` | MD040 (2) | Add language tags to 2 bare ` ``` ` blocks |
| `docs/DEVELOPMENT.md` | MD040 (2) | Add language tags to 2 bare ` ``` ` blocks |
| `docs/GETTING-STARTED.md` | MD031 (2) | Add blank lines around 1 fence pair |
| `README.md` | MD040 (2) | Add language tags to 2 bare ` ``` ` blocks |
**MD040 fix pattern** — change:
````
```
some content
```
````
to (pick the language that matches: `bash`, `text`, `json`, `yaml`, etc.):
````
```bash
some content
```
````
**MD031 fix pattern** — change:
````
- list item
```bash
code
```
next paragraph
````
to:
````
- list item
```bash
code
```
next paragraph
````
**Verification command (run locally after fixes, before pushing):**
```bash
pnpm md:lint
pnpm format:check
```
Run both — see RESEARCH.md Pitfall 4: MD031 fixes and Prettier must not conflict.
---
## Shared Patterns
### `runs-on` label
**Source:** ci.yml line 9, 41, 127 — `runs-on: ubuntu-latest`
**Apply to:** All new jobs (`changes`, `gate`)
```yaml
runs-on: ubuntu-latest
```
The runner advertises `ubuntu-latest`. Never use `self-hosted`.
### `if: github.event_name == 'pull_request'` guard
**Source:** ci.yml lines 10, 41, 128 — existing jobs all carry this guard
**Apply to:** `changes` job (same guard); `api` and `harness` extend it with `&&`
```yaml
if: github.event_name == 'pull_request'
```
### Step ordering in `fast-checks`
**Source:** ci.yml lines 1138 — current step sequence:
1. `actions/checkout@v4`
2. `actions/setup-node@v4` (node 22)
3. `Enable pnpm` (corepack)
4. `Install dependencies` (pnpm install --frozen-lockfile)
5. `Lint`
6. `Format check`
7. **← insert `Markdown lint` here**
8. `Typecheck`
9. `PWA unit tests`
The new `Markdown lint` step slots between `Format check` and `Typecheck` — logically grouped with format/style checks, before compilation checks.
### `if: always()` usage
**Source:** publish.yml line 94 — `if: always()` on the `Docker logout` step
**Apply to:** `gate` job (job-level `if: always()`, not step-level)
```yaml
if: always()
```
This is the only safe way to ensure `gate` always reports a commit-status regardless of upstream job outcomes.
### No `actions/cache@v4`
**Source:** ci.yml lines 2223, 7576, 161162 — comment in every job:
```yaml
# actions/cache@v4 is intentionally omitted — probe (D-PROBE-04) showed it
# times out on this runner (socket hang-up between runner container and job
# container cache server). pnpm install without cache takes ~30s; acceptable.
```
**Apply to:** `changes` job has no install step so this is not relevant. `gate` job has no install step. No new jobs require caching.
---
## No Analog Found
| File | Role | Data Flow | Reason |
|------|------|-----------|--------|
| `.markdownlint-cli2.jsonc` | tool config | — | No existing markdownlint config in repo; `.prettierrc` used as structural analog only (JSON root tool config pattern) |
---
## publish.yml Comment Update (advisory)
**Source:** publish.yml lines 1617 — the file's safety-gate comment names `CI / api` and `CI / harness` as the required checks:
```yaml
# The PR test jobs (fast-checks, api, harness in ci.yml) run on pull_request — they never
# run in the same workflow invocation as publish.yml. Tests gate the PR; main is trusted to
# be green because direct push and force push are blocked and the three required checks
# (CI / fast-checks, CI / api, CI / harness) must pass before merge.
```
After this phase, the required checks change to `CI / fast-checks` + `CI / gate`. The planner should include a step to update this comment to reflect the new required-check names.
---
## Metadata
**Analog search scope:** `.gitea/workflows/`, repo root (config files), `package.json`
**Files scanned:** 4 (ci.yml, publish.yml, package.json, .prettierrc)
**Pattern extraction date:** 2026-06-12