From 360602c0f0e7e9b3140ad9bba99ec612d46174fe Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 8 Oct 2026 17:46:12 -0400 Subject: [PATCH 1/4] chore: drop cosmetic commitlint rules that fail agent commits --- commitlint.config.ts | 167 ++++++++----------------------------------- 1 file changed, 28 insertions(+), 139 deletions(-) diff --git a/commitlint.config.ts b/commitlint.config.ts index 4d3c528..ade51d3 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -1,96 +1,14 @@ import type { UserConfig } from '@commitlint/types' -import { execFileSync } from 'node:child_process' /** - * Single-package repo: scopes are a static enum, not discovered from a - * workspace. `solutions` covers docs/solutions/ entries; `global` covers - * repo-wide docs and plans. + * systemfsoftware shared commitlint config. + * + * Design rule: a rule is `error` (2) ONLY if a wrong value has real downstream + * impact in THIS org. Releases run on changesets (.changeset/*.md), NOT commit + * messages, so scope / punctuation / type-vs-diff have ZERO release impact and + * must never fail a commit. The one message policy that matters is the + * AI-coauthor ban. */ -const SCOPES = ['repo', 'deps', 'release', 'ci', 'global', 'solutions'] as const - -const matchesAny = (...patterns: readonly RegExp[]) => (path: string) => patterns.some((p) => p.test(path)) - -const isDoc = matchesAny( - /\.mdx?$/, - /^docs\//, - /(^|\/)README\.md$/i, - /(^|\/)AGENTS\.md$/i, - /(^|\/)CLAUDE\.md$/i, - /(^|\/)CHANGELOG\.md$/i, -) - -const isTest = matchesAny( - /\.(test|spec|tst)\.(ts|tsx|js|jsx|mjs|cjs)$/, - /(^|\/)__tests__\//, - /(^|\/)__mocks__\//, - /(^|\/)tests\//, - /(^|\/)test-helpers\//, - /(^|\/)e2e\//, - /(^|\/)fixtures\//, -) - -const isCI = matchesAny( - /^\.github\/workflows\//, - /^\.github\/actions\//, - /^\.github\/dependabot\.ya?ml$/, -) - -const isLockfile = matchesAny( - /(^|\/)pnpm-lock\.yaml$/, - /(^|\/)package-lock\.json$/, - /(^|\/)bun\.lockb?$/, - /(^|\/)yarn\.lock$/, -) - -const isTooling = matchesAny( - /^\.claude\//, - /^\.husky\//, - /(^|\/)commitlint\.config\.[mc]?[jt]s$/, - /(^|\/)\.lintstagedrc(\..+)?$/, - /(^|\/)tsconfig.*\.json$/, - /(^|\/)vitest\.config\.[mc]?[jt]s$/, - /(^|\/)stryker\.conf(ig)?\.[mc]?[jt]s$/, - /(^|\/)stryker(\..+)?\.json$/, - /(^|\/)\.editorconfig$/, - /(^|\/)\.gitignore$/, - /(^|\/)biome\.json$/, - /(^|\/)oxlint\.config\.[mc]?[jt]s$/, - /(^|\/)\.dprint\.jsonc?$/, - /(^|\/)package\.json$/, - /(^|\/)pnpm-workspace\.yaml$/, - /(^|\/)\.npmrc$/, - /(^|\/)dprint\.json$/, - /(^|\/)\.envrc$/, - /^nix\//, - /^bin\//, - /^flake\.nix$/, - /^flake\.lock$/, -) - -const ALLOWED_BY_SHAPE: readonly { - readonly name: string - readonly match: (path: string) => boolean - readonly allowed: Readonly> -}[] = [ - { name: 'docs', match: isDoc, allowed: { docs: true, chore: true, ai: true } }, - { name: 'test', match: isTest, allowed: { test: true, chore: true } }, - { name: 'CI', match: isCI, allowed: { ci: true, chore: true } }, - { name: 'lockfile', match: isLockfile, allowed: { deps: true, chore: true } }, - { - name: 'tooling', - match: isTooling, - allowed: { chore: true, build: true, ci: true, deps: true, ai: true, security: true }, - }, -] - -const stagedFiles = (): readonly string[] => - execFileSync('git', ['diff', '--cached', '--name-only'], { - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'inherit'] as const, - }) - .split('\n') - .map((l: string) => l.trim()) - .filter((l: string) => l.length > 0) const configuration: UserConfig = { extends: ['@commitlint/config-conventional'], @@ -133,45 +51,17 @@ const configuration: UserConfig = { if (hasAICoauthor) return [false, message] return [true, 'OK'] }, - - 'type-matches-diff-shape': ({ type }) => { - const files = stagedFiles() - if (files.length === 0 || type == null || type === '') return [true, 'OK'] - - const allMatch = (m: (p: string) => boolean) => files.every(m) - - for (const shape of ALLOWED_BY_SHAPE) { - if (allMatch(shape.match) && !(type in shape.allowed)) { - const allowed = Object.keys(shape.allowed).sort().join(' / ') - return [false, `'${type}' with 100% ${shape.name} paths — REQUIRED type: ${allowed}`] - } - } - - if (type === 'feat' || type === 'fix') { - const hasProductionSource = files.some( - (p) => !isDoc(p) && !isTest(p) && !isCI(p) && !isLockfile(p) && !isTooling(p), - ) - if (!hasProductionSource) { - return [ - false, - `'${type}' MUST touch >=1 production source file (none of: docs, test, CI, lockfile, tooling)`, - ] - } - } - - return [true, 'OK'] - }, }, }, ], rules: { - // AI co-author prevention (enforced) + // The one policy that matters. 'no-ai-coauthors': [2, 'always'], - 'type-matches-diff-shape': [2, 'always'], - // Commit types — aligned with semantic-release changelog filtering - // feat/fix/perf/api/revert/improvement/deps/security bump a version; the rest are noise-filtered out of the changelog + // Type: tidy log/PR grouping only (releases run on changesets). + 'type-empty': [2, 'never'], + 'type-case': [2, 'always', 'lower-case'], 'type-enum': [ 2, 'always', @@ -195,32 +85,31 @@ const configuration: UserConfig = { ], ], - // Static scope enum — single-package repo, no workspace discovery - 'scope-enum': [2, 'always', [...SCOPES]], - 'scope-case': [2, 'always', 'kebab-case'], + // Subject: must exist; everything else about it is cosmetic. + 'subject-empty': [2, 'never'], + 'subject-case': [0], + 'subject-full-stop': [0], - // Type constraints - 'type-case': [2, 'always', 'lower-case'], - 'type-empty': [2, 'never'], + // Punctuation: zero impact. OFF. (This is the reported pain.) + 'header-full-stop': [0], + 'body-full-stop': [0], - // Subject — case disabled (agents capitalize; cosmetic, no release impact) - 'subject-case': [0], - 'subject-empty': [2, 'never'], - 'subject-full-stop': [2, 'never', '.'], + // Scope: zero release impact and agents can't guess it. OFF; nudge casing. + 'scope-enum': [0], + 'scope-case': [1, 'always', 'kebab-case'], - // Disabled — length / blank-line cosmetics that burn tokens on retries; semantic-release ignores them + // Length: burns retry tokens, no impact. OFF. 'header-max-length': [0], 'body-max-line-length': [0], 'footer-max-line-length': [0], - 'body-leading-blank': [0], - 'footer-leading-blank': [0], - // Structural constraints (kept — low friction, prevent trailing-period noise) - 'header-full-stop': [2, 'never', '.'], - 'body-full-stop': [2, 'never', '.'], - - // References encouraged but not required (warning, non-blocking) + // Readability nudges — warn, never block. + 'body-leading-blank': [1, 'always'], + 'footer-leading-blank': [1, 'always'], 'references-empty': [1, 'never'], + + // DELETED: 'type-matches-diff-shape' — heuristic that hard-failed agents + // and its plugin for no downstream benefit. Do not reintroduce. }, defaultIgnores: true, From c6832f271d35df6cde44f99e9216162a44ac65d8 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 8 Oct 2026 18:04:03 -0400 Subject: [PATCH 2/4] chore: format commitlint config --- commitlint.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/commitlint.config.ts b/commitlint.config.ts index ade51d3..e782640 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -107,7 +107,6 @@ const configuration: UserConfig = { 'body-leading-blank': [1, 'always'], 'footer-leading-blank': [1, 'always'], 'references-empty': [1, 'never'], - // DELETED: 'type-matches-diff-shape' — heuristic that hard-failed agents // and its plugin for no downstream benefit. Do not reintroduce. }, From 30dbc6a3198f44e9ffdca743dc57bea710a97c8f Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 8 Oct 2026 18:31:19 -0400 Subject: [PATCH 3/4] test: drop sandbox-proof assertions for the deleted diff-shape rule --- sandbox-proofs/git-hooks.test.ts | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/sandbox-proofs/git-hooks.test.ts b/sandbox-proofs/git-hooks.test.ts index dc3d792..ae253f9 100644 --- a/sandbox-proofs/git-hooks.test.ts +++ b/sandbox-proofs/git-hooks.test.ts @@ -80,11 +80,14 @@ Deno.test('git hooks run their dependency code inside the sandbox from a linked await Deno.writeTextFile(`${worktree}/${PROBE}`, '{"probe":1}\n') await must('git', ['add', PROBE], worktree) - await t.step('a feat commit of a tooling-only change is refused by commitlint', async () => { - const outcome = await run('git', ['commit', '-m', 'feat(repo): probe'], { cwd: worktree }) + await t.step('commitlint accepts a feat tooling-only message with a trailing period', async () => { + const outcome = await run('sandbox', ['--', 'pnpm', 'exec', 'commitlint'], { + cwd: worktree, + stdin: 'feat(repo): probe.\n', + }) expect( - outcome.code !== 0 && outcome.out.includes('100% tooling paths'), - 'the diff-shape rule did not refuse', + outcome.code === 0, + 'commitlint rejected a message it should now accept (type/diff-shape and full-stop rules are gone)', outcome, ) }) @@ -118,12 +121,6 @@ Deno.test('git hooks run their dependency code inside the sandbox from a linked await t.step("a commit of named paths is graded and formatted against git's temporary index", async () => { await Deno.writeTextFile(`${worktree}/${PROBE}`, '{"probe":2}\n') - const refused = await run('git', ['commit', '-m', 'feat(repo): probe', PROBE], { cwd: worktree }) - expect( - refused.code !== 0 && refused.out.includes('100% tooling paths'), - 'the hooks graded the wrong index', - refused, - ) const landed = await run('git', ['commit', '-m', 'build(repo): named path probe', PROBE], { cwd: worktree }) const subject = await must('git', ['log', '-1', '--format=%s'], worktree) const committed = await must('git', ['show', `HEAD:${PROBE}`], worktree) @@ -135,18 +132,6 @@ Deno.test('git hooks run their dependency code inside the sandbox from a linked ) }) - await t.step("commitlint fails with git's error when git cannot read the index", async () => { - const outcome = await run('sandbox', ['--', 'env', 'GIT_DIR=/nonexistent', 'pnpm', 'exec', 'commitlint'], { - cwd: worktree, - stdin: 'feat(repo): probe\n', - }) - expect( - outcome.code !== 0 && outcome.out.includes('Command failed: git diff --cached --name-only'), - 'git failed silently', - outcome, - ) - }) - await t.step('the git directory outside the bound paths is unreadable', async () => { await Deno.writeTextFile(commonCanary, 'secret') const outcome = await refusedInside(worktree, `cat '${commonCanary}'`) From 90473b52f956390a5d880d4caecdbbe3c9743ba5 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 8 Oct 2026 18:48:18 -0400 Subject: [PATCH 4/4] test: drop tautological commitlint-accepts step from sandbox proofs --- sandbox-proofs/git-hooks.test.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/sandbox-proofs/git-hooks.test.ts b/sandbox-proofs/git-hooks.test.ts index ae253f9..3361031 100644 --- a/sandbox-proofs/git-hooks.test.ts +++ b/sandbox-proofs/git-hooks.test.ts @@ -80,18 +80,6 @@ Deno.test('git hooks run their dependency code inside the sandbox from a linked await Deno.writeTextFile(`${worktree}/${PROBE}`, '{"probe":1}\n') await must('git', ['add', PROBE], worktree) - await t.step('commitlint accepts a feat tooling-only message with a trailing period', async () => { - const outcome = await run('sandbox', ['--', 'pnpm', 'exec', 'commitlint'], { - cwd: worktree, - stdin: 'feat(repo): probe.\n', - }) - expect( - outcome.code === 0, - 'commitlint rejected a message it should now accept (type/diff-shape and full-stop rules are gone)', - outcome, - ) - }) - await t.step('the hooks refuse to run when the sandbox is unavailable', async () => { const env = { PATH: await withoutSandboxOnPath() } const outcome = await run('git', ['commit', '-m', 'build(repo): probe'], { cwd: worktree, env })