Files
familysync/.planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-01-PLAN.md
T
Lucas Berger e19d4736c7 docs(15): create phase plan — doc-only CI skip + markdown lint
3 plans across 3 waves:
- 15-01: markdownlint-cli2 config/script/step + fix 13 baseline violations (SC-4)
- 15-02: ci.yml changes job + conditional api/harness + always-running gate (SC-1/2, SC-3 YAML)
- 15-03: operator branch-protection checkpoint + publish.yml comment (SC-3)
2026-06-12 10:28:56 -04:00

13 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
15-ci-skip-api-harness-jobs-for-doc-only-prs 01 execute 1
.markdownlint-cli2.jsonc
package.json
pnpm-lock.yaml
.gitea/workflows/ci.yml
README.md
apps/api/README.md
apps/pwa/README.md
apps/pwa/e2e/README.md
docs/API.md
docs/ARCHITECTURE.md
docs/DEVELOPMENT.md
docs/GETTING-STARTED.md
true
truths artifacts key_links
pnpm md:lint exits 0 on the current repo (13 baseline violations fixed)
Introducing a bare fenced code block (MD040) makes pnpm md:lint exit non-zero
The fast-checks CI job runs a Markdown lint step between Format check and Typecheck
pnpm format:check still exits 0 after the MD031 blank-line fixes (no Prettier conflict)
markdownlint never scans .planning/** (excluded by config ignores)
path provides contains
.markdownlint-cli2.jsonc markdownlint-cli2 config (extends prettier preset; content rules; globs + ignores) markdownlint/style/prettier
path provides contains
package.json md:lint script + markdownlint-cli2 devDependency md:lint
path provides contains
.gitea/workflows/ci.yml Markdown lint step in fast-checks job pnpm md:lint
from to via pattern
.gitea/workflows/ci.yml package.json md:lint script fast-checks step runs pnpm md:lint pnpm md:lint
from to via pattern
package.json md:lint script .markdownlint-cli2.jsonc markdownlint-cli2 auto-discovers root config (globs + ignores) markdownlint-cli2
Add a real markdown lint gate to the existing `fast-checks` CI job: install `markdownlint-cli2`, create the root `.markdownlint-cli2.jsonc` config (Prettier-compatible preset + content rules + scoped globs that exclude `.planning/**`), add a root `md:lint` script, wire a "Markdown lint" step into `fast-checks`, and fix the 13 baseline violations so the gate starts GREEN.

This delivers Success Criterion 4: fast-checks runs markdownlint over docs; an introduced violation fails the gate; the existing baseline passes.

Purpose: Docs get a fast but real format+lint gate without a separate CI job, scoped so churny .planning/** bookkeeping is never linted. Output: .markdownlint-cli2.jsonc, updated package.json (script + devDep), one new fast-checks step, 7 doc files fixed (13 violations), green pnpm md:lint + pnpm format:check.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-RESEARCH.md @.planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-PATTERNS.md Task 1: Install markdownlint-cli2, add md:lint script, create .markdownlint-cli2.jsonc - package.json (root — current scripts block lines 6-16 and devDependencies lines 17-25; mirror the existing `format:check` script + `prettier` devDep placement) - .prettierrc (root JSON tool-config structural analog: printWidth 100 — the reason MD013 must stay disabled) - .planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-RESEARCH.md (Pattern 5 + "Code Examples" → the exact `.markdownlint-cli2.jsonc` body and rule-decision table; Pitfall 5 `#` vs `!` negation; Pitfall 6 .pnpm-store) - .planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-PATTERNS.md ("`.markdownlint-cli2.jsonc` (new file)" and "package.json (root)" sections — exact insertion points) Install `markdownlint-cli2` at version `0.22.1` as a root-workspace devDependency: run `pnpm add -D markdownlint-cli2@0.22.1 --workspace-root` (this updates root package.json devDependencies and pnpm-lock.yaml). Add a root script `"md:lint": "markdownlint-cli2"` immediately after the existing `"format:check"` script — NO glob args on the CLI; globs/ignores live in the config file so `pnpm md:lint` and a bare `npx markdownlint-cli2` resolve identically (RESEARCH Open Question 3 resolved).
Create `.markdownlint-cli2.jsonc` at the repo root using the exact body from RESEARCH "Code Examples → .markdownlint-cli2.jsonc" / PATTERNS "`.markdownlint-cli2.jsonc` (new file)". Required keys: `config.extends` = `"markdownlint/style/prettier"`; ENABLE `MD001`, `MD024`, `MD040`, `MD031`, `MD051`, `MD052` (all `true`); DISABLE `MD041`, `MD034`, `MD036` (all `false`) — rationale per the rule-decision table (MD041 because CLAUDE.md starts with `## Project`; MD034 because the version table uses `pkg@version` syntax; MD036 because docs/API.md uses bold response labels). Set `globs` to exactly `["docs/**/*.md", "*.md", "apps/**/*.md"]` and `ignores` to exactly `[".planning/**", "node_modules/**", "**/node_modules/**", ".pnpm-store/**"]`. Do NOT add MD013 (line-length) — it is disabled by the prettier preset and re-enabling it produces 700+ false violations against `.prettierrc` printWidth 100.

Do NOT run md:lint to green yet — the 13 baseline violations are fixed in Task 2; this task may leave `pnpm md:lint` red.
test -f .markdownlint-cli2.jsonc && node -e "const p=require('./package.json'); if(!p.scripts['md:lint']) process.exit(1); if(!p.devDependencies['markdownlint-cli2']) process.exit(1); console.log('script+dep present')" && grep -q 'markdownlint/style/prettier' .markdownlint-cli2.jsonc && grep -q '.planning/' .markdownlint-cli2.jsonc && echo OK - package.json `scripts` contains `"md:lint": "markdownlint-cli2"` (no glob args) - package.json `devDependencies` contains `markdownlint-cli2` at `0.22.1`; pnpm-lock.yaml updated - `.markdownlint-cli2.jsonc` exists at repo root and `config.extends` is `"markdownlint/style/prettier"` - `.markdownlint-cli2.jsonc` enables MD001/MD024/MD040/MD031/MD051/MD052 and disables MD041/MD034/MD036 - `.markdownlint-cli2.jsonc` `ignores` array includes `".planning/**"`, `"node_modules/**"`, `"**/node_modules/**"`, `".pnpm-store/**"` - `.markdownlint-cli2.jsonc` `globs` array is exactly `["docs/**/*.md", "*.md", "apps/**/*.md"]` markdownlint-cli2@0.22.1 is a root devDep, `pnpm md:lint` is wired to the root config, and the config scopes the lint to docs/repo-root/app markdown while excluding `.planning/**`. md:lint may still be red (fixed next task). Task 2: Fix the 13 baseline markdown violations; green md:lint + format:check; wire the fast-checks step - .planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-RESEARCH.md ("Baseline Violation Assessment" table — exact file/rule/count; Pitfall 4 — verify MD031 fix does not break Prettier) - .planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-PATTERNS.md ("Docs content fixes" table + MD040/MD031 fix patterns; "Markdown lint step in fast-checks" insertion slot) - .gitea/workflows/ci.yml (fast-checks job lines 8-38 — confirm the real step names: "Format check" line 31-32, "Typecheck" line 34; insert the new step between them) - README.md (lines ~78, ~119 — the 2 bare fences) - docs/API.md (lines ~517, ~544), docs/ARCHITECTURE.md (lines ~64, ~171), docs/DEVELOPMENT.md (lines ~9, ~22) — 2 bare fences each - docs/GETTING-STARTED.md (lines ~52, ~54 — the MD031 fence pair needing blank lines) - apps/api/README.md (line ~19), apps/pwa/README.md (line ~45), apps/pwa/e2e/README.md (line ~76) — 1 bare fence each Fix all 13 violations confirmed at these exact locations (run `pnpm md:lint` first to re-confirm line numbers, then fix): - MD040 (11 bare fences — add a language tag matching the fence content; use `bash` for shell, `text` for plain output, `json`/`yaml`/`ts` as appropriate): apps/api/README.md:19, apps/pwa/e2e/README.md:76, apps/pwa/README.md:45, docs/API.md:517, docs/API.md:544, docs/ARCHITECTURE.md:64, docs/ARCHITECTURE.md:171, docs/DEVELOPMENT.md:9, docs/DEVELOPMENT.md:22, README.md:78, README.md:119. - MD031 (1 fence pair — add a blank line before the opening ```` ```bash ```` and after the closing ```` ``` ````): docs/GETTING-STARTED.md:52 and :54.
Only change what each rule requires — add a language token after the opening backticks (MD040) or add surrounding blank lines (MD031); do NOT rewrite fence bodies or restructure docs. Do not "fix" any rule that the config disables (MD013/MD034/MD036/MD041) — those are intentionally off.

After fixes, add the new step to the `fast-checks` job in ci.yml between the "Format check" step and the "Typecheck" step — a named step `Markdown lint` running `pnpm md:lint` (mirror the one-line `Format check` step pattern exactly). This is the only ci.yml change in this plan; do not touch the api/harness/changes/gate jobs (Plan 02 owns those).

Then run BOTH `pnpm md:lint` (must exit 0) AND `pnpm format:check` (must exit 0) — per RESEARCH Pitfall 4, the MD031 blank-line additions must not introduce a Prettier conflict on docs/GETTING-STARTED.md. If format:check newly fails on a file you touched, run `pnpm format` on that file and re-confirm md:lint is still 0.
pnpm md:lint && pnpm format:check && grep -q 'pnpm md:lint' .gitea/workflows/ci.yml && echo OK - `pnpm md:lint` exits 0 (13 baseline violations resolved) - `pnpm format:check` exits 0 (no Prettier conflict introduced by the MD031 fix) - `.gitea/workflows/ci.yml` fast-checks job contains a step named `Markdown lint` running `pnpm md:lint`, positioned after the `Format check` step and before the `Typecheck` step - Re-running md:lint after temporarily inserting a bare ```` ``` ```` fence into any in-scope .md (then reverting) exits non-zero (gate can fail) — proven during execution, not left in the tree - No api/harness/changes/gate job was modified by this plan The markdown baseline is clean, `fast-checks` runs `pnpm md:lint`, the gate can fail on a real violation, and Prettier and markdownlint do not conflict. SC-4 satisfied.

<artifacts_produced> This phase introduces the following new symbols (Plan 01 portion). The plan-review source-grounding pass must treat these as newly-created, not drift:

  • .markdownlint-cli2.jsonc — new root config file
  • md:lint — new root package.json script
  • markdownlint-cli2 — new root devDependency (0.22.1)
  • Markdown lint — new step name in the fast-checks job </artifacts_produced>

<threat_model>

Trust Boundaries

Boundary Description
npm registry → repo devDependencies markdownlint-cli2 is fetched and runs in CI and on developer machines
markdown content → lint tool doc files are the input; markdownlint only reads, never executes content

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-15-01 Tampering (supply chain) new markdownlint-cli2 devDependency mitigate Pin to exact 0.22.1 (no ^/~); RESEARCH Package Legitimacy Audit verdict OK (~4 yrs, ~3M/wk, DavidAnson/markdownlint-cli2). Audit table present in RESEARCH; no [ASSUMED]/[SUS] packages so no blocking-human checkpoint required.
T-15-02 Denial of Service (gate noise) lint glob accidentally scanning .planning/** or node_modules accept→mitigate ignores excludes .planning/**, node_modules/**, **/node_modules/**, .pnpm-store/**; verified the glob lints exactly 12 in-scope files, not bookkeeping churn.
T-15-03 Tampering (false-green) a disabled content rule silently hides a real doc defect accept Rule-disable decisions (MD034/MD036/MD041) are scoped to non-author-controlled patterns documented in RESEARCH; MD040/MD031/MD001/MD024/MD051/MD052 stay enabled to catch broken fences/links.
</threat_model>
- `pnpm md:lint` exits 0 on the clean tree; exits non-zero when a bare fence is introduced. - `pnpm format:check` exits 0 (Prettier/markdownlint do not conflict). - `.gitea/workflows/ci.yml` fast-checks job runs `pnpm md:lint` between Format check and Typecheck. - markdownlint scans 12 in-scope files and never touches `.planning/**` (confirm "Finding:" line lists the negated ignores).

<success_criteria> Maps to Phase 15 Success Criterion 4: fast-checks runs markdownlint-cli2 over the docs glob; an introduced markdown-lint violation fails the gate; the existing markdown baseline passes (13 violations fixed, rules configured) so the gate starts green. </success_criteria>

Create `.planning/phases/15-ci-skip-api-harness-jobs-for-doc-only-prs/15-01-SUMMARY.md` when done.