Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-security/customizing-your-repository/about-code-owners
# Order matters — the last matching rule wins.

# Default reviewers.
* @netresearch/netresearch @netresearch/typo3

# CI / release / security automation.
/.github/ @netresearch/netresearch

# Python package + audit.
/cli_audit/ @netresearch/netresearch
/audit.py @netresearch/netresearch

# Installer / upgrade shell scripts.
/scripts/ @netresearch/netresearch
37 changes: 37 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!--
Thanks for the contribution! Keep this template terse — reviewers read it.
-->

## Summary

<!-- 1–3 bullets describing the change and why it's needed. -->

## Linked issues / PRs

<!-- Closes #123, Relates to #456 -->

## Type of change

- [ ] Bug fix (`fix:`)
- [ ] New feature (`feat:`)
- [ ] Refactor / internal change (`refactor:`)
- [ ] Docs only (`docs:`)
- [ ] Chore / CI / deps (`chore:`)
- [ ] Breaking change (describe migration below)

## Test plan

<!-- Commands you ran and their result. Prefer automated over manual. -->

- [ ] `uv run pytest`
- [ ] `uv run python -m flake8 cli_audit tests`
- [ ] `./scripts/test_smoke.sh`
- [ ] Manual scenario: …

## Checklist

- [ ] Conventional Commits (`type(scope): description`)
- [ ] Commits signed (`git commit -S --signoff`)
- [ ] `CHANGELOG.md` updated for user-visible changes
- [ ] Relevant `AGENTS.md` files updated if structure / commands changed
- [ ] No secrets, credentials, or PII committed
34 changes: 6 additions & 28 deletions .github/workflows/auto-merge-deps.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,13 @@
name: Auto-merge dependency PRs

on:
pull_request_target:
types: [opened, synchronize, reopened]
pull_request:

permissions:
contents: write
pull-requests: write
permissions: {}

jobs:
auto-merge:
runs-on: ubuntu-latest
if: >-
github.event.pull_request.user.login == 'dependabot[bot]' ||
github.event.pull_request.user.login == 'renovate[bot]'
steps:
- name: Approve PR
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr review --approve "$PR_URL"

- name: Enable auto-merge
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
STRATEGY=$(gh api "repos/$REPO" --jq '
if .allow_squash_merge then "--squash"
elif .allow_merge_commit then "--merge"
elif .allow_rebase_merge then "--rebase"
else "--merge" end')
gh pr merge --auto "$STRATEGY" "$PR_URL"
uses: netresearch/.github/.github/workflows/auto-merge-deps.yml@main
permissions:
contents: write
pull-requests: write
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ jobs:
- name: Upload coverage to Codecov
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
file: ./coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: unittests
name: codecov-${{ matrix.os }}-py${{ matrix.python-version }}
fail_ci_if_error: false
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Dependency Review

on:
pull_request:
branches: [main]

permissions: {}

jobs:
dependency-review:
uses: netresearch/.github/.github/workflows/dependency-review.yml@main
permissions:
contents: read
pull-requests: write
with:
fail-on-severity: moderate
23 changes: 23 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Security

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:

permissions: {}

jobs:
audit:
# Pinned to feat/python-audit-workflow until netresearch/.github#19 merges.
# Switch to @main after merge.
uses: netresearch/.github/.github/workflows/python-audit.yml@feat/python-audit-workflow
permissions:
contents: read
with:
python-version-file: pyproject.toml
package-manager: uv
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ tools_snapshot.json
# Node.js
node_modules/

# Vendored dependencies (this project uses uv; vendor/ is reserved for ad-hoc checkouts)
vendor/

# AI agent session context (not committed)
claudedocs/
.serena/
Expand Down
47 changes: 47 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# See https://pre-commit.com for usage.
#
# Install: uv run pre-commit install
# Run all: uv run pre-commit run --all-files

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files
- id: check-merge-conflict
- id: mixed-line-ending
args: ['--fix=lf']

- repo: https://github.com/PyCQA/flake8
rev: 7.3.0
hooks:
- id: flake8
files: ^(cli_audit/|tests/|audit\.py$)

- repo: https://github.com/PyCQA/isort
rev: 8.0.1
hooks:
- id: isort
files: ^(cli_audit/|tests/|audit\.py$)
args: ['--profile=black']

- repo: https://github.com/psf/black
rev: 26.3.1
hooks:
- id: black
files: ^(cli_audit/|tests/|audit\.py$)

- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.11.0
hooks:
- id: shellcheck
files: ^scripts/.*\.sh$
args: ['--severity=warning']

ci:
autofix_commit_msg: 'chore: pre-commit autofixes'
autoupdate_commit_msg: 'chore(deps): pre-commit autoupdate'
46 changes: 41 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- Ask before: heavy deps, full rewrites, breaking changes
- Never commit secrets, PII, or credentials

## Package management (uv)
## Setup

**This project uses [uv](https://docs.astral.sh/uv/) for package management.** Always use `uv run` to execute Python commands.

Expand All @@ -29,7 +29,11 @@ uv run python -m flake8 # Run linter

**Why uv?** Fast dependency resolution, proper lockfile support, and isolated environments without manual venv activation.

## Minimal pre-commit checks
**Prerequisites:** Python ≥ 3.14, `uv`, and a POSIX shell (most installer scripts are Bash).

## Development

Minimal pre-commit checks (also enforced by `.pre-commit-config.yaml`):

```bash
uv run python -m pytest # All tests (required)
Expand All @@ -39,6 +43,21 @@ uv run python -m mypy cli_audit # mypy (optional)
uv run python audit.py --help # Verify CLI works
```

Install the git hooks once per checkout: `uv run pre-commit install`.
New features and bug fixes go on a feature branch (`fix/…`, `feat/…`, `chore/…`) → PR against `main` → signed commits (`git commit -S --signoff`).
See [`SECURITY.md`](./SECURITY.md) for vulnerability reporting and [`CHANGELOG.md`](./CHANGELOG.md) for release history.

## Testing

```bash
uv run pytest # Full suite (546 tests)
uv run pytest -x -q # Fail-fast, quiet
uv run pytest tests/test_upgrade.py -k multi_version # Focused run
uv run pytest --cov=cli_audit --cov-report=term # With coverage
```

Test layout and conventions: see [`tests/AGENTS.md`](./tests/AGENTS.md). Integration suite under `tests/integration/` is collected separately — CI runs it as its own step.

## Project maintenance vs. tool feature

This repo **is** a CLI tool manager, so the word "upgrade" is overloaded:
Expand All @@ -57,7 +76,7 @@ This repo **is** a CLI tool manager, so the word "upgrade" is overloaded:
- [scripts/AGENTS.md](./scripts/AGENTS.md) — Installation scripts (Bash, 33 scripts)
- [tests/AGENTS.md](./tests/AGENTS.md) — Test suite (pytest, 14 test files)

## Quick reference
## Commands (verified 2026-04-16)

| Command | Purpose |
|---------|---------|
Expand Down Expand Up @@ -99,15 +118,28 @@ This repo **is** a CLI tool manager, so the word "upgrade" is overloaded:
**Caches:**
- `~/.cache/cli-audit/endoflife.json` — endoflife.date response cache, used as fallback on transient HTTP failures (override path via `CLI_AUDIT_ENDOFLIFE_CACHE`).

## Project overview
## Architecture

**AI CLI Preparation v2.0** — Tool version auditing and installation management for AI coding agents.

- **Architecture:** 21 Python modules, 89 JSON tool catalogs
- **Modules:** 21 Python modules under `cli_audit/` (see [`cli_audit/AGENTS.md`](./cli_audit/AGENTS.md))
- **Catalog:** 89 JSON tool definitions under `catalog/`
- **Installers:** 33 Bash scripts under `scripts/` (see [`scripts/AGENTS.md`](./scripts/AGENTS.md))
- **Phase 1:** Detection & auditing (complete)
- **Phase 2:** Installation & upgrade management (complete)
- **Entry point:** `audit.py` → `cli_audit` package

### Project structure

```
audit.py # CLI dispatcher (cmd_audit, cmd_update, cmd_install, …)
cli_audit/ # Core library (detection, collectors, snapshot, installer, …)
catalog/ # Per-tool JSON definitions (binary_name, version_command, multi_version)
scripts/ # Installer / upgrade / reconcile shell scripts + scripts/lib helpers
tests/ # pytest suite (unit + integration)
Makefile / Makefile.d/ # Task runner: `make audit`, `make upgrade`, `make upgrade-all`
```

## Multi-version runtimes

Runtimes supporting multiple concurrent versions (PHP, Python, Node.js, Ruby, Go) use dynamic detection from [endoflife.date](https://endoflife.date/) API.
Expand All @@ -125,3 +157,7 @@ CLI_AUDIT_JSON=1 uv run python audit.py --versions
## When instructions conflict

Nearest AGENTS.md wins. User prompts override all files.

---

*Last verified: 2026-04-16 (against `main` at HEAD).*
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Changelog

All notable changes to this project are documented in this file.

The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Governance files: PR template, `CHANGELOG.md`. Security reporting is covered by the [org-level SECURITY.md](https://github.com/netresearch/.github/blob/main/SECURITY.md).
- `.github/workflows/dependency-review.yml` and a dedicated `security.yml` (pip-audit + bandit + CycloneDX SBOM). CodeQL continues to run via GitHub's default code-scanning setup.
- `.pre-commit-config.yaml` for local hook enforcement (flake8, black, isort, shellcheck).
- README badges: CI, License.
- AGENTS.md structural sections (Commands, Setup, Testing, Architecture, Development).
- Per-cycle `auto_update` storage for multi-version tools (`python@3.13` vs `python@3.14`).
- Persistent endoflife.date cache at `~/.cache/cli-audit/endoflife.json` with fallback on HTTP failure.
- Binary-probe fallback in `guide.sh` when the post-install snapshot refresh is stale.

### Fixed
- `cmd_update_local` in MERGE mode now refreshes multi-version cycle entries (`python@3.14`, …) instead of only the base-tool entry. Resolved false-negative "Upgrade did not succeed" messages after successful uv installs.

### Changed
- Upgraded 23 locked Python dev-dependencies to latest compatible versions (bandit 1.9.4, mypy 1.20.1, isort 8.0.1, rich 15.0, coverage 7.13.5, …).

## Prior history

See [git log](https://github.com/netresearch/coding_agent_cli_toolset/commits/main) for commits prior to this changelog. Tagged releases: [Releases page](https://github.com/netresearch/coding_agent_cli_toolset/releases).
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# AI CLI Preparation

[![CI](https://github.com/netresearch/coding_agent_cli_toolset/actions/workflows/ci.yml/badge.svg)](https://github.com/netresearch/coding_agent_cli_toolset/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/netresearch/coding_agent_cli_toolset/branch/main/graph/badge.svg)](https://codecov.io/gh/netresearch/coding_agent_cli_toolset)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

<!-- CodeQL runs via GitHub's default code-scanning setup; results are visible in the Security tab. -->


A minimal utility to verify that tools used by AI coding agents are installed and up to date on your system. It audits versions of common agent toolchain CLIs against the latest upstream releases and prints a pipe-delimited report suitable for quick human scan or downstream tooling.

## Quick Start
Expand Down
4 changes: 3 additions & 1 deletion cli_audit/bulk.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

from __future__ import annotations

import os
import shutil
import subprocess
import tempfile
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
Expand Down Expand Up @@ -350,7 +352,7 @@ def generate_rollback_script(results: Sequence[InstallResult], verbose: bool = F
Path to generated rollback script
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
script_path = f"/tmp/rollback_{timestamp}.sh"
script_path = os.path.join(tempfile.gettempdir(), f"rollback_{timestamp}.sh")

with open(script_path, "w") as f:
f.write("#!/bin/bash\n")
Expand Down
9 changes: 6 additions & 3 deletions cli_audit/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,15 @@ def get_version_line(path: str, tool_name: str, version_flag: str | None = None,
Returns:
Version line string or empty string
"""
# Priority 1: If catalog specifies custom shell command, run it directly
# Priority 1: If catalog specifies custom shell command, run it directly.
# version_command comes from trusted catalog JSON (committed in-repo), never
# from user input — e.g. `uv python list --only-installed | grep … | sed …`.
# shell=True is required for the pipelines used in the catalog.
if version_command:
try:
proc = subprocess.run(
proc = subprocess.run( # nosec B602
version_command,
shell=True,
shell=True, # nosec B602
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
stdin=subprocess.DEVNULL,
Expand Down
3 changes: 2 additions & 1 deletion cli_audit/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,8 @@ def cleanup_old_backups(retention_days: int = 7, verbose: bool = False):
"""
import glob

backup_dirs = glob.glob("/tmp/upgrade_backup_*")
# Mirror the location used by create_upgrade_backup() (tempfile.mkdtemp).
backup_dirs = glob.glob(os.path.join(tempfile.gettempdir(), "upgrade_backup_*"))
cutoff = time.time() - (retention_days * 86400)

for backup_dir in backup_dirs:
Expand Down
Loading
Loading