Skip to content

Switch to uv and prek, and enforce the shared lint suite #100

Description

@rtibbles

❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Overview

Nothing in this repo enforces the JS or Python linting our other repos run: ESLint reports 13 errors and Prettier flags 9 files on main. This brings its tooling in line with Kolibri, Studio, and Morango.

Complexity: Low
Target branch: main

Context

  • .pre-commit-config.yaml runs pre-commit-hooks v4.1.0, yamlfmt v0.14.0, actionlint v1.7.7, and the local generate-automation drift check.
  • requirements-dev.txt lists pre-commit; pre-commit.yml installs prek with pipx install prek.
  • The repo has JS under scripts/ (ESLint and Prettier configured in package.json, .eslintrc.js, .prettierrc.js) and two Python scripts under scripts/.
  • .github/dependabot.yml covers npm, pip, and github-actions monthly with a 7-day cooldown.

The Change

  • The repo should be a uv project like Kolibri, Studio, and Morango, declaring requests for the Python scripts and prek in a dev group.
  • .pre-commit-config.yaml should run the suite our other repos run: pre-commit-hooks, ruff and ruff-format, yamlfmt, actionlint, zizmor, uv-lock, and ESLint and Prettier on the JS, alongside the existing generate-automation check.
  • Existing violations should be fixed, not excluded.
  • Dependabot should replace its pip entry with uv and add the pre-commit ecosystem, monthly with the 7-day cooldown, hook bumps grouped into one PR.

Out of Scope

  • Autofixing lint failures on PRs, including restoring the pre-commit.ci lite action.

Acceptance Criteria

  • On a clean checkout, uv sync --group dev then uv run prek run --all-files passes.
  • pre-commit.yml runs the same hooks through uv and passes on main.
  • requirements-dev.txt is removed and no workflow runs pip install.
  • Insights > Dependency graph > Dependabot lists uv, pre-commit, npm, and github-actions, and no pip.
  • A page in docs/ covers setting up a dev environment: installing the Python and JS dependencies and the prek git hook.

References

AI usage

I chose the scope: prek and uv as in our other repos, Dependabot over a scheduled autoupdate job, and autofix left out. Claude Code surveyed this repo's and Kolibri's configs and drafted the sections, which I reviewed one by one.

Activity

  1. added
    dependenciesPull requests that update a dependency file
    github_actionsPull requests that update GitHub Actions code
    on Sep 26, 2026
  2. added theissue type on Sep 26, 2026
  3. rtibblesbot commented on Oct 6, 2026

    @rtibblesbot
    Contributor

    The 13 ESLint errors on main are 12 no-console and one max-len, at 0ce46cf:

    • ...Object.entries(buildReusableWorkflows(automations, retiredSecrets)).map(([file, content]) => [
      path.join(WORKFLOWS_DIR, file),
      content,
      ]),
      [TEMPLATE_PATH, buildTemplateYml(automations, secrets,
      'Caller template: copy this file to .github/workflows/automation.yml in a consumer repo.\n' +
      '# No edits are needed - the on: block is the exhaustive union of every enabled automation.')],
      [DOGFOOD_PATH, buildTemplateYml(automations, secrets,
      'This repo\'s own automation caller - .github acts as consumer zero and stays in sync automatically.')],
      ];
      const targetPaths = new Set(targets.map(([target]) => target));
      const stale = fs
      .readdirSync(WORKFLOWS_DIR)
      .filter((f) => f.startsWith(GROUP_PREFIX))
      .map((f) => path.join(WORKFLOWS_DIR, f))
      .filter((f) => !targetPaths.has(f));
      if (check) {
      let drifted = false;
      for (const [target, content] of targets) {
      const current = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
      if (current !== content) {
      console.error(`Drift detected in ${path.relative(ROOT, target)} - run \`node scripts/generate-automation.js\``);
      drifted = true;
      }
      }
      for (const target of stale) {
      console.error(`Stale ${path.relative(ROOT, target)} - run \`node scripts/generate-automation.js\``);
      drifted = true;
      }
      process.exit(drifted ? 1 : 0);
      }
      for (const [target, content] of targets) {
      fs.mkdirSync(path.dirname(target), { recursive: true });
      fs.writeFileSync(target, content);
      console.log(`Wrote ${path.relative(ROOT, target)}`);
      }
      for (const target of stale) {
      fs.unlinkSync(target);
      console.log(`Removed ${path.relative(ROOT, target)}`);
    • console.log(`${r.repo.padEnd(26)} ${r.state.padEnd(20)} ${extra}`);
      }
      const problems = results.filter((r) => PROBLEM_STATES.includes(r.state));
      const drifted = results.filter((r) => r.state !== 'in-sync');
      console.log(`\n${results.length} consumers, ${drifted.length} not in sync, ${problems.length} needing attention.`);
      for (const p of problems) {
      console.log(`::error title=${p.repo}::${p.state}${p.detail ? `: ${p.detail}` : ''}`);
      }
      return problems.length;
      }
      // Both spellings, because an --only nobody parses is a full run against every
      // consumer, which is the opposite of what it asks for.
      function parseArgs(argv) {
      const i = argv.findIndex((a) => a === '--only' || a.startsWith('--only='));
      let only;
      if (i !== -1) {
      only = argv[i] === '--only' ? argv[i + 1] : argv[i].slice('--only='.length);
      if (!only || only.startsWith('--')) throw new Error('--only needs a repo name');
      }
      return { dryRun: argv.includes('--dry-run'), only };
      }
      async function main() {
      const token = process.env.GITHUB_TOKEN;
      if (!token) {
      console.error('GITHUB_TOKEN is not set.');
      process.exit(1);
      }
      const template = fs.readFileSync(TEMPLATE_PATH, 'utf8');
      const results = await run(httpApi(token), template, parseArgs(process.argv.slice(2)));
      process.exit(report(results) ? 1 : 0);
      }
      if (require.main === module) {
      main().catch((err) => {
      console.error(err.message);
    • const quiet = console.log;
      console.log = () => {};
      try {
      const problems = report([
      { repo: 'a', state: 'in-sync' },
      { repo: 'b', state: 'opened', pr: 1 },
      { repo: 'c', state: 'declined', pr: 2 },
      { repo: 'd', state: 'error', detail: 'no access' },
      { repo: 'e', state: 'toolchain-conflict', pr: 3 },
      ]);
      assert.equal(problems, 2, 'error and toolchain-conflict each need attention');
      } finally {
      console.log = quiet;

    Until they are fixed, yarn lint exits 1, so #109's "yarn lint passes" criterion can't be met.


    Written by rtibblesbot, an LLM-based coding agent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dependenciesPull requests that update a dependency filegithub_actionsPull requests that update GitHub Actions code

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions