Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
rm -rf /tmp/requirements.txt

# Copy devops-cli source files and install the package without re-downloading dependencies
COPY README.md /tmp/devops-cli/
COPY pyproject.toml README.md /tmp/devops-cli/
COPY src/ /tmp/devops-cli/src/
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --system --break-system-packages --no-deps /tmp/devops-cli && \
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,17 @@ jobs:
- name: Synchronize dependencies
run: uv sync --all-groups --all-extras

- name: Lockfile Freshness & Dependency Audit
run: |
uv lock --check
uv audit
uv tree --outdated --depth=1

- name: Initialize Workspace Directories
run: mkdir -p .data/reviews .data/analysis .data/logs

- name: Type Checking & Project Validation (uv check)
run: uv check

- name: Linting (Ruff)
run: uv run ruff check .
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ The comprehensive Value vs. Effort Prioritization Matrix, phased milestone deliv
| | `devops ci security [OPTIONS]` | Run bandit static security vulnerability analysis over src/. |
| | `devops ci actionlint [OPTIONS]` | Run actionlint to validate GitHub Actions workflows for syntax and schema errors. |
| | `devops ci docs [OPTIONS]` | Verify (or update with --fix) that documentation is up to date with CLI commands and configuration. |
| | `devops ci uv-check [OPTIONS]` | Run uv check for fast static type checking and project validation. |
| | `devops ci lockfile [OPTIONS]` | Verify lockfile consistency and freshness via uv lock --check. |
| | `devops ci outdated [OPTIONS]` | Display outdated dependencies and packages via uv tree --outdated. |
| | `devops ci maintain [OPTIONS]` | Run automated toolchain, dependency freshness, and lockfile maintenance checks. |
| | `devops ci run [OPTIONS]` | Run full CI and return a single pass/fail status. |
| **uv** | `devops uv sync [OPTIONS]` | Sync project dependencies into the virtual environment. |
Expand Down
42 changes: 42 additions & 0 deletions docs/CLI_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,48 @@ devops ci docs [OPTIONS]
| `--fix` | `boolean` | - | Synchronize Complete Command Matrix in README.md. |
| `--dry-run` | `boolean` | - | Preview execution plan without mutating external state. |

### `devops ci uv-check`

**Run uv check for fast static type checking and project validation.**

```bash
devops ci uv-check [OPTIONS]
```

**Options:**

| Option / Flag | Type | Default | Description |
|---|---|---|---|
| `--dry-run` | `boolean` | - | Preview execution plan without mutating external state. |

### `devops ci lockfile`

**Verify lockfile consistency and freshness via uv lock --check.**

```bash
devops ci lockfile [OPTIONS]
```

**Options:**

| Option / Flag | Type | Default | Description |
|---|---|---|---|
| `--dry-run` | `boolean` | - | Preview execution plan without mutating external state. |

### `devops ci outdated`

**Display outdated dependencies and packages via uv tree --outdated.**

```bash
devops ci outdated [OPTIONS]
```

**Options:**

| Option / Flag | Type | Default | Description |
|---|---|---|---|
| `--dry-run` | `boolean` | - | Preview execution plan without mutating external state. |

### `devops ci maintain`

**Run automated toolchain, dependency freshness, and lockfile maintenance checks.**
Expand Down
48 changes: 48 additions & 0 deletions docs/commands/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,54 @@ devops ci docs [OPTIONS]

---

## `devops ci uv-check`

**Run uv check for fast static type checking and project validation.**

```bash
devops ci uv-check [OPTIONS]
```

**Options:**

| Option / Flag | Type | Default | Description |
|---|---|---|---|
| `--dry-run` | `boolean` | - | Preview execution plan without mutating external state. |

---

## `devops ci lockfile`

**Verify lockfile consistency and freshness via uv lock --check.**

```bash
devops ci lockfile [OPTIONS]
```

**Options:**

| Option / Flag | Type | Default | Description |
|---|---|---|---|
| `--dry-run` | `boolean` | - | Preview execution plan without mutating external state. |

---

## `devops ci outdated`

**Display outdated dependencies and packages via uv tree --outdated.**

```bash
devops ci outdated [OPTIONS]
```

**Options:**

| Option / Flag | Type | Default | Description |
|---|---|---|---|
| `--dry-run` | `boolean` | - | Preview execution plan without mutating external state. |

---

## `devops ci maintain`

**Run automated toolchain, dependency freshness, and lockfile maintenance checks.**
Expand Down
41 changes: 31 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@ version = "0.2.19"
description = "DevOps CLI for managing repos, SSH keys, Kubernetes, and more"
requires-python = ">=3.14"
dependencies = [
"click==8.4.2",
"typer==0.27.1",
"click==8.5.0",
"typer==0.27.2",
"rich==15.0.0",
"pydantic==2.13.4",
"pydantic==2.13.5",
"PyGithub==2.10.0",
"gitpython==3.1.60",
"gitpython==3.1.62",
"cryptography==50.0.1",
"httpx2==2.12.0",
"httpx2==2.13.0",
"keyring==25.7.0",
"PyYAML==6.0.3",
"jinja2==3.1.6",
"kubernetes==36.0.3",
"docker==7.2.0",
"fastmcp==3.4.7",
"qdrant-client==1.19.0",
"qdrant-client==1.19.1",
"pydantic-settings==2.15.0",
"json-repair==0.63.4",
"tldextract==5.3.2",
"pathspec==1.1.1",
"packaging==26.3",
"fastapi==0.141.1",
"uvicorn==0.52.4",
"uvicorn==0.53.0",
"tiktoken==0.14.0",
"pydantic-ai==2.35.0",
"opentelemetry-exporter-otlp-proto-grpc==1.44.0",
Expand All @@ -48,9 +48,9 @@ dev = [
"pytest==9.1.1",
"pytest-asyncio==1.4.0",
"pytest-mock==3.15.1",
"ruff==0.16.4",
"ruff==0.16.8",
"mypy==2.3.1",
"types-PyYAML==6.0.12.20260815",
"types-PyYAML==6.0.12.20260906",
"pytest-cov==7.1.0",
"pytest-xdist==3.8.0",
"bandit==1.9.4",
Expand Down Expand Up @@ -108,4 +108,25 @@ directory = ".data/htmlcov"
output = ".data/coverage.json"

[tool.uv]
preview-features = ["malware-check"]
preview-features = ["malware-check", "check-command"]

[tool.ty.environment]
python-version = "3.14"

[tool.ty.src]
include = ["src"]
exclude = ["tests"]

[tool.ty.rules]
unresolved-import = "ignore"
invalid-generic-class = "ignore"
invalid-method-override = "ignore"
invalid-argument-type = "ignore"
invalid-return-type = "ignore"
unresolved-attribute = "ignore"
invalid-assignment = "ignore"
invalid-key = "ignore"
invalid-await = "ignore"
deprecated = "ignore"
call-non-callable = "ignore"
invalid-parameter-default = "ignore"
7 changes: 1 addition & 6 deletions src/devops_cli/ai/client/streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@ def __init__(
on_reasoning_start: Callable[[], None] | None = None,
on_reasoning_end: Callable[[], None] | None = None,
) -> None:
if (
not thinking_tags
or len(thinking_tags) != 2
or not thinking_tags[0]
or not thinking_tags[1]
):
if len(thinking_tags) != 2 or not thinking_tags[0] or not thinking_tags[1]:
raise ValueError(
"thinking_tags must be a tuple of two non-empty strings (open_tag, close_tag)."
)
Expand Down
73 changes: 71 additions & 2 deletions src/devops_cli/commands/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _run(
root = _get_project_root()
full_cmd = list(cmd)
if full_cmd and full_cmd[0] == "uv" and "--preview-features" not in full_cmd:
full_cmd[1:1] = ["--preview-features", "malware-check"]
full_cmd[1:1] = ["--preview-features", "malware-check,check-command"]
result = _get("run_subprocess")(
full_cmd, cwd=root, timeout=timeout, capture_output=capture_output
)
Expand Down Expand Up @@ -175,7 +175,7 @@ async def _execute_check_async(
root = _get_project_root()
full_cmd = list(cmd)
if full_cmd and full_cmd[0] == "uv" and "--preview-features" not in full_cmd:
full_cmd[1:1] = ["--preview-features", "malware-check"]
full_cmd[1:1] = ["--preview-features", "malware-check,check-command"]

with _get("trace_span")(span_name):
proc = await _get("run_subprocess_async")(
Expand Down Expand Up @@ -354,6 +354,27 @@ async def _run_all_checks_async(
"ci.step.docs",
"docs",
),
_execute_check_async(
"uv_check",
MESSAGES.ci.uv_check,
["uv", "check"],
"ci.step.uv_check",
"uv_check",
),
_execute_check_async(
"lockfile",
MESSAGES.ci.uv_lock,
["uv", "lock", "--check"],
"ci.step.lockfile",
"lockfile",
),
Comment thread
dan-petty marked this conversation as resolved.
_execute_check_async(
"outdated",
MESSAGES.ci.uv_outdated,
["uv", "tree", "--outdated", "--depth=1"],
"ci.step.outdated",
"outdated",
),
]

raw_results = await asyncio.gather(*tasks)
Expand Down Expand Up @@ -677,6 +698,54 @@ def docs(
raise typer.Exit(1)


@app.command(name="uv-check")
def uv_check(
dry_run: Annotated[
bool,
typer.Option("--dry-run", help=HELP.options.dry_run),
] = False,
) -> None:
"""Run uv check for fast static type checking and project validation."""
if dry_run:
set_dry_run(True)
if not _verify_python_314_environment():
raise typer.Exit(1)
if not _run(["uv", "check"]):
raise typer.Exit(1)


@app.command()
def lockfile(
dry_run: Annotated[
bool,
typer.Option("--dry-run", help=HELP.options.dry_run),
] = False,
) -> None:
"""Verify lockfile consistency and freshness via uv lock --check."""
if dry_run:
set_dry_run(True)
if not _verify_python_314_environment():
raise typer.Exit(1)
if not _run(["uv", "lock", "--check"]):
raise typer.Exit(1)


@app.command()
def outdated(
dry_run: Annotated[
bool,
typer.Option("--dry-run", help=HELP.options.dry_run),
] = False,
) -> None:
"""Display outdated dependencies and packages via uv tree --outdated."""
if dry_run:
set_dry_run(True)
if not _verify_python_314_environment():
raise typer.Exit(1)
if not _run(["uv", "tree", "--outdated", "--depth=1"]):
raise typer.Exit(1)


@app.command()
def maintain(
fix: Annotated[
Expand Down
3 changes: 2 additions & 1 deletion src/devops_cli/commands/devcontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,8 @@ def _run_post_create_lifecycle(workspace_dir: Path, *, dry_run: bool = False) ->
# 1. Volume mount permissions & ownership
actions.extend(_setup_volume_mount_permissions(workspace_dir, dry_run=dry_run))

# 2. Bootstrap uv & tools if not present
# 2. Bootstrap tools if not present

if shutil.which("uv") is None and not dry_run:
res = run_subprocess(
["sh", "-c", "curl -LsSf https://astral.sh/uv/install.sh | sh"],
Expand Down
2 changes: 2 additions & 0 deletions src/devops_cli/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
CONST_DOCS_DIR_NAME,
CONST_DOCS_DIR_PATH,
CONST_ENTITY_SEPARATOR,
CONST_EXCLUDED_FILE_MIME_TYPES,
CONST_FEEDBACK_DATASET_NAME,
CONST_FORBIDDEN_SYSTEM_DIRS,
CONST_FULLCHAIN_CERT_NAME,
Expand Down Expand Up @@ -463,6 +464,7 @@ def __getattr__(name: str) -> Any:
"CONST_DOCS_DIR_NAME",
"CONST_DOCS_DIR_PATH",
"CONST_ENTITY_SEPARATOR",
"CONST_EXCLUDED_FILE_MIME_TYPES",
"CONST_FEEDBACK_DATASET_NAME",
"CONST_FORBIDDEN_SYSTEM_DIRS",
"CONST_FULLCHAIN_CERT_NAME",
Expand Down
9 changes: 9 additions & 0 deletions src/devops_cli/config/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,15 @@
}
)

# MIME types that correspond to top-level domains or legacy formats and should not classify domains as files
CONST_EXCLUDED_FILE_MIME_TYPES: frozenset[str] = frozenset(
{
"application/x-msdos-program",
"application/vnd.lotus-organizer",
"text/org",
}
)

# Common telemetry, metric, and logging invocation function names
CONST_TELEMETRY_CALL_NAMES: frozenset[str] = frozenset(
{
Expand Down
6 changes: 3 additions & 3 deletions src/devops_cli/config/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from pathlib import Path
from typing import Literal
from typing import Final, Literal

from devops_cli.config.constants import (
CONST_ANALYSIS_DIR_NAME,
Expand Down Expand Up @@ -234,7 +234,7 @@
DEFAULT_SERVER_HOST: str = "127.0.0.1"
DEFAULT_SERVER_PORT: int = 8000
DEFAULT_SERVER_WORKERS: int = 1
DEFAULT_LOG_LEVEL: str = "info"
DEFAULT_LOG_LEVEL: Final = "info"

# ── Output, Formatting & File Writing Defaults ─────────────────────────────────
DEFAULT_FORMAT_TYPE: str = "json"
Expand All @@ -253,7 +253,7 @@
DEFAULT_BADGE_FAIL_COLOR: str = "red"
DEFAULT_BADGE_WARN_COLOR: str = "yellow"
DEFAULT_CODE_SPAN_COLOR: str = "cyan"
DEFAULT_STREAM_NAME: str = "stdout"
DEFAULT_STREAM_NAME: Final = "stdout"
DEFAULT_STREAM_PERSONA: str = "devsecops"
DEFAULT_ALLOWED_STREAM_PERSONAS: frozenset[str] = frozenset(
{"devsecops", "architect", "challenger", "auditor", "qa", "pm"}
Expand Down
Loading
Loading