Skip to content

fix(install): dogfood Prettier on the shipped surface — GH #531 - #540

Merged
artyhoo merged 2 commits into
stagingfrom
fix/gh-531-dogfood-prettier
Jun 15, 2026
Merged

fix(install): dogfood Prettier on the shipped surface — GH #531#540
artyhoo merged 2 commits into
stagingfrom
fix/gh-531-dogfood-prettier

Conversation

@artyhoo

@artyhoo artyhoo commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Fixes #531

Problem

Right after install.sh, a consumer's first npm run validate fails on format:check (prettier --check .) on ~50 files — every one installer-shipped, none consumer-original. The framework had no format script and never ran its own authored skill docs / agent prompts / ESLint-rule sources / config templates through Prettier, so it delivered unformatted artefacts. The headline "one button → green validate" was red out-of-box through no fault of the consumer's code.

Fix — dogfood Prettier on the shipped surface

Scope: the shipped artefacts only — NOT the framework's own README / CLAUDE / .claude/rules/ / docs/meta-factory (authority-owned, out of the consumer surface; reformatting them would be a huge unrelated diff touching frozen docs).

  • prettier --write over the shipped sources (skills, agents, ESLint-rule sources, config templates), in the framework's existing style.
  • Ship .prettierrc.json ({ singleQuote: true }) so the consumer's prettier --check uses the same style the artefacts are written in — prettier's double-quote default would otherwise re-flag every shipped .ts/.mjs/.cjs. copy_safe never clobbers a consumer's own config.
  • Exclude only the GENERATED install artefacts (.claude/settings.json, the eslint-rules-local/index.ts barrel) in the shipped .prettierignore — they have no stable authored source (same class as the already-ignored rendered RULES.md). Authored docs are formatted, not hidden (a regression test asserts skill docs are not blanket-ignored).
  • Fix the stryker packageManager patch to swap the value in place instead of a full JSON.stringify re-serialize (which re-expanded prettier-collapsed arrays and re-broke the consumer).
  • hooks/package.json template → multi-line (Prettier formats package.json with the json-stringify parser, which forces multi-line).

Regression guards (dogfood — so it can't return)

  • scripts/format-shipped.sh (--check/--write) over the shipped surface, wired as npm run format / format:check + a shipped-prettier audit-self CI job. Deterministic (Prettier, not an LLM) — complies with no-paid-llm-in-ci.
  • tests/install-sh/gh-531-shipped-prettier.test.sh (deterministic core + an end-to-end arm).

Verification

Fresh ts-server and react-next consumers are prettier --check . clean (0 issues); framework format:check green; install-sh suite 32/32; principles 188/188.

Note on the second commit

packages/core/principles/19 had two §2.5-table assertions that matched exact column padding, which Prettier re-aligns. Made them whitespace-tolerant (same content checked) — a separate atomic commit per the Artifact Ownership Contract (principles are meta-tests-CI-owned).

§1.7 Forward-check applied

Complies with active disciplines, verified against source: no-paid-llm-in-ci — the gate is deterministic Prettier via npx, no API-billed LLM (scripts/format-shipped.sh:58; CI step at .github/workflows/audit-self.yml:116); build-first-reuse-default — REUSE of Prettier (already a shipped consumer dev-dep), zero new framework dependency, the only new code is a bash glob-list helper under scripts/ (not packages/), so not a capability commit (escape-hatch trailer); doc-authority — new .sh/.json are code/config (no prose header); dual-implementation — single deterministic gate, no dual-channel. The consumer config is shipped non-destructively at install.sh:525 (copy_safe, skip-if-exists).

§1.7 Backward-check applied

Swept all surfaces under the new gate's scope (the shipped artefact set): formatted every shipped skill/agent/template/rule-source, verified both consumer stacks green end-to-end, and confirmed the GENERATED-vs-AUTHORED split is principled — only generated artefacts are excluded (packages/core/templates/shared/.prettierignore:12), authored docs are formatted (paired-negative in the test asserts skill docs are not blanket-ignored). The stryker mutation surface that also writes a shipped config was fixed to preserve formatting (install.sh:274). The one meta-test that asserted exact table padding was made format-tolerant (packages/core/principles/19-meta-orchestrator-alias-routing-consistency.test.ts:84, separate commit). Verified no existing test regressed (install-sh 32/32, principles 188/188) and the framework's authority docs were deliberately left out of scope (no README/CLAUDE/rules churn).

artyhoo added 2 commits June 16, 2026 00:28
…ant — GH #531

Prettier (adopted for the shipped surface in the companion #531 commit) re-aligns
markdown table columns — padding each cell to its column width. Principle 19's two
table assertions matched exact single-space padding ('| ALIAS | DISPATCH ...' and
'| DIRECT |'), so a prettier-formatted pipeline/SKILL.md broke them.

Collapse intra-line whitespace runs before matching: column padding is cosmetic and
NOT part of the invariant. The principle still verifies the SAME thing — the header
columns + all 6 ALIAS rows are present. This changes HOW it matches (whitespace-
tolerant), not WHAT it validates; intent preserved.

Separate atomic commit per the Artifact Ownership Contract (packages/core/principles/
is meta-tests-CI-owned; cross-owner edits land as their own commit with rationale).
Right after install, a consumer's first `npm run validate` failed on `format:check`
(`prettier --check .`) on ~50 files — every one installer-shipped, none consumer-
original. The framework had no `format` script and never ran its own authored skill
docs / agent prompts / ESLint-rule sources / config templates through Prettier, so it
shipped unformatted artefacts. "One button -> green validate" was red out-of-box.

Fix (scope: the SHIPPED surface only — NOT the framework's own README/CLAUDE/
.claude/rules/docs, which are authority-owned and out of the consumer surface):

- Run prettier --write over the shipped sources (skills, agents, ESLint-rule
  sources, config templates) — formatted with the framework's existing style.
- Ship .prettierrc.json ({ singleQuote: true }) so the consumer's prettier --check
  uses the same style the artefacts are written in (default double-quote would re-flag
  every .ts/.mjs/.cjs). copy_safe never clobbers a consumer's own config.
- Exclude GENERATED install artefacts (.claude/settings.json, the eslint-rules-local
  index.ts barrel) in the shipped .prettierignore — they have no stable authored
  source, same class as the already-ignored rendered RULES.md. Authored docs are
  formatted, not hidden.
- Fix the stryker packageManager patch to swap the VALUE in place instead of a full
  JSON.stringify re-serialize (which re-expanded prettier-collapsed arrays and re-broke
  the consumer).
- hooks/package.json template -> multi-line (Prettier formats package.json with the
  json-stringify parser, which forces multi-line).
- Add a language to 4 pre-existing bare code fences in the ARCHITECTURE templates
  (MD040, surfaced by the pre-commit markdownlint gate when re-committing them).

Dogfood gate so it can't regress: scripts/format-shipped.sh (--check/--write) over the
shipped surface, wired as npm run format / format:check and a shipped-prettier
audit-self CI job. Deterministic (Prettier, not an LLM) — complies with no-paid-llm-in-ci.

Verified: fresh ts-server AND react-next consumers are prettier --check . clean (0
issues); framework format:check green; install-sh suite 32/32; principles 188/188.
Regression: tests/install-sh/gh-531-shipped-prettier.test.sh.

Prior-art: skipped — bug fix + dogfood gate, no new capability (no new dependency; the gate is bash + npx prettier, no code module >=80 LOC under packages/).
@artyhoo
artyhoo merged commit 0a666fc into staging Jun 15, 2026
27 checks passed
@artyhoo
artyhoo deleted the fix/gh-531-dogfood-prettier branch June 15, 2026 21:36
artyhoo added a commit that referenced this pull request Jun 15, 2026
…merge — GH #531 (reopen) (#543)

* fix(install): pin prettier@3.8.3 exact on both shipped + dogfood sides — GH #531 (reopen)

RC#1: prettier floated on both surfaces — install.sh CORE_DEVDEPS listed bare
`prettier`, format-shipped.sh used `npx --yes prettier` (latest). Prettier ships
formatting changes in minor/patch, so a consumer's format:check was non-deterministic
across re-installs. Pin BOTH to prettier@3.8.3 exact (the version the shipped surface
is already clean under, post-#540). The format-shipped npx-with-pinned-version is the
deterministic path for the shipped-prettier CI job (no npm install there).

Prior-art: skipped — bug fix to existing #531 prettier surface, no new capability

* fix(install): non-destructive .prettierignore merge + drift-guard + CI wiring — GH #531 (reopen)

RC#2: install.sh used copy_safe (skip-if-exists) for .prettierignore, so a BROWNFIELD
consumer with a pre-existing .prettierignore never got the AIF exclusions → generated
.ai-factory/RULES.md (+ RULES.react-next.md, .claude/settings.json, eslint-rules-local/
index.ts) stayed un-ignored → `prettier --check .` re-broke on the non-format-stable
RULES.md table. Fix: merge_prettierignore appends a marker-delimited block of missing
AIF entries (dedup, idempotent on re-install); greenfield path stays byte-identical;
--force overwrites wholesale as before. Plain bash, no yq, no new dependency.

Also: drift-guard asserting the two pin sites agree on the exact version (project
invariants 2+4), paired-negative tests that genuinely flip, and wires gh-531 into
audit-self.yml (it was never in CI — closes the latent gap that #540's test never ran).

Prior-art: skipped — bug fix to existing #531 prettier surface, no new capability
artyhoo added a commit that referenced this pull request Jun 16, 2026
…te — GH #531 audit follow-up (#545)

The #531 audit found 4 install-sh test files that exist but were never invoked by
CI (armed-but-not-fired): gh-534-arch-boundaries, gh-535-rule-enforced, c1-wiring,
ship-orchestration-skills. A test wired at NO channel is a false-green — it fails
at none, contradicting the project's "fail at the earliest reachable channel"
thesis. Same class as #540 shipping gh-531's test unwired (fixed in #543).

- audit-self.yml: wire the 4 tests as deterministic bash steps (all pass locally;
  no paid-LLM — compliant with no-paid-llm-in-ci.md).
- tests/install-sh/meta-all-wired.test.sh (new): a meta-gate asserting EVERY
  tests/install-sh/*.test.sh is wired in audit-self.yml, so this omission cannot
  reland silently. Non-vacuous NEG (removing a wiring from a workflow copy flips
  the gate). Wired itself (33/33 test files now invoked in CI).

Prior-art: skipped — CI wiring + a deterministic meta-gate test, no new capability.
artyhoo added a commit that referenced this pull request Aug 10, 2026
… cannot drift again (#1362)

`.github/workflows/audit-self.yml` defines 31 top-level jobs; `ci-success` needed 28.
Excluding itself, exactly two were missing — `shipped-prettier` (:99) and
`framework-fresh-install-validate-multistack` (:1094). Both trigger on `pull_request`
and neither carries an `if:` guard, so both run on every PR. Branch protection on
staging requires only `ci-success`, `fidelity-verdict-in-pr-body` and
`stale-revert-in-pr-diff`, so neither was separately required: either could go RED
while `ci-success` was green and the PR merged.

`git log -S` on each needs-entry returns empty — neither was EVER wired, from the day
the jobs shipped (#540, #815). Both were green on the latest staging run
(31371037396), re-confirmed before pushing, so wiring them in does not deadlock merges.

The two lines are not the deliverable. `scripts/ci-success-gate.sh:6-7` asserted in its
own header that audit-self.yml «`needs:` every audit-self PR job» — false, and detected
by nothing: the script only judges the RESULTS of jobs already listed (it consumes
`join(needs.*.result, ' ')`). The gaps are the predicted output of a check resting on
someone reading the workflow, which attention-is-not-a-mechanism.md §1 rejects as a
detection layer. `launch-preannounce-track/kickoff.md:46` had already ordered this exact
fix once — «immediately, one line, before anything else» — and it never happened, then
drifted further. A fix without a mechanism regressed once already.

So the mechanism ships with it: `packages/core/principles/36-ci-needs-completeness.test.ts`
asserts every job defined in audit-self.yml appears in `ci-success.needs`, with an
explicit allowlist carrying a per-entry justification. The allowlist has exactly one
member — `ci-success` itself, which cannot depend on itself. No job is push-only: the
sole `if:`-guarded job (`pr-commit-trailers`) is wired in, and `skipped` already counts
as passing at ci-success-gate.sh:30, so an `if:` guard is not grounds for exclusion.

Channel (rule-enforcement-channel-selection.md §3): mechanically detectable → gate, not
reminder. A principle test is the earliest channel that actually fires — the principles
suite runs at pre-push (pre-push.ts:1267) AND in CI (audit-self.yml:210), so CI stays
the backstop rather than the only line. Deliberately NOT placed inside ci-success-gate.sh:
that script is the body of the very job under test, would run in CI only, and would
conflate judging job results with asserting the job graph.

Paired-negative, both directions, exercised against the real file — not a synthetic
fixture (destination-environment-verification.md §4 `#contract-that-cannot-fail`):
arm (c) deletes a real `needs:` entry from the real text and expects RED; arm (d) seeds
a new job definition and expects RED; arm (f) renames the aggregate and proves the
parser returns null instead of passing vacuously on an empty list. Confirmed live by
seeding each mutation on disk and running the gate: RED naming the exact job both times,
GREEN after restore. Suite 7/7; full principles suite 38 files / 360 tests green.

The ci-success-gate.sh header now states its real scope — it judges results, never the
wiring — and names principle 36 as what keeps the aggregate claim true.

§1.7: Forward-check — deterministic node/vitest, zero API-billed calls
(no-paid-llm-in-ci.md §1); BFR consult recorded as SSOT #244 with a BUILD verdict;
capability commit carries this trailer plus `Prior-art:` below; touches no
maintainer-owned artefact (.claude/rules/** and .husky/** unedited per the CLAUDE.md
Artifact Ownership Contract). Backward-check — delegated cold to
agents/backward-sweep-auditor.md on the change's class only (registry completeness
assumed rather than asserted), never the diff; two sibling surfaces verified by hand and
judged SEPARATE, with evidence, in the PR body: packages/core/hooks/pre-push.ts has zero
consumer-matrix sections against the claim at
.claude/orchestrator-prompts/launch-preannounce-track/kickoff.md:46, and
tests/consumer-matrix/python-unfamiliar-stack-cell.sh:39 claims it runs under
`make consumer-matrix` while Makefile:34-38 invokes only pnpm-monorepo-cell.sh and
npm-tarball-cell.sh.

Prior-art: prior-art-evaluations.md#244 (actionlint RuleJobNeeds / zizmor / marketplace aggregate actions, verdict BUILD — upstream validates the `needs:` edges that ARE written, or aggregates results at runtime; none asserts statically that the list mirrors the job population. DeepWiki on rhysd/actionlint: «no built-in rule to enforce that every job defined in the workflow must be depended on by another job»).

Co-authored-by: Test <test@example.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

format:check fails out-of-the-box on ~50 installer-shipped files (npm run validate) — framework doesn't dogfood Prettier

1 participant