Skip to content
Draft
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
83 changes: 83 additions & 0 deletions .dev/guard.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Fast, diff-only regression tripwires for this repository. Rules intentionally
# favor precise skipped-work and failure-suppression signals over broad keywords.
version = 1

[[rules]]
id = "SKIP-001"
severity = "deny"
pattern = '(?i)\b(TODO|FIXME)\b'
message = "New unfinished-work marker"
guidance = "Complete the work or omit the incomplete path before submission."
include = ["crates/*/src/**", "src/**", "scripts/**"]

[[rules]]
id = "SKIP-002"
severity = "deny"
pattern = '\b(todo!|unimplemented!)\s*\('
message = "New unimplemented Rust path"
guidance = "Implement the path or return an explicit supported error."
include = ["**/*.rs"]

[[rules]]
id = "SKIP-003"
severity = "deny"
pattern = '#\s*\[\s*ignore(?:\s*=|\s*\])|\b(?:describe|context|it|test)\.skip\s*\('
message = "New skipped or ignored test"
guidance = "Keep the test active or remove the unsupported behavior with its test."
include = ["**/*.rs", "**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]

[[rules]]
id = "SKIP-004"
severity = "deny"
pattern = '(?i)\b(?:PLACEHOLDER|CHANGEME|NOT_IMPLEMENTED)\b'
message = "New placeholder value in production code"
guidance = "Replace the placeholder with implemented behavior before submission."
include = ["crates/*/src/**", "src/**", "scripts/**"]

[[rules]]
id = "ERR-001"
severity = "warn"
pattern = 'let\s+_\s*=\s*[^;]*(?:insert|update|delete|save|store|grant|revoke|send|emit|write)'
message = "Possible discarded mutation or delivery failure"
guidance = "Propagate the error or retain explicit diagnostics for intentional best-effort work."
include = ["**/*.rs"]

[[rules]]
id = "ERR-002"
severity = "deny"
pattern = '(?:catch\s*\([^)]*\)\s*\{\s*\}|\.catch\s*\([^=]*=>\s*(?:\{\s*\}|undefined)\s*\))'
message = "New empty error handler"
guidance = "Return a typed failure or record useful diagnostics instead of swallowing it."
include = ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]

[[rules]]
id = "AUTH-001"
severity = "deny"
pattern = '(?:is_(?:blocked|banned|authorized|allowed)|has_(?:access|permission|grant))[^;]*\.unwrap_or\(false\)'
message = "Possible fail-open authorization lookup"
guidance = "Propagate lookup failure or deny access explicitly."
include = ["**/*.rs"]

[[rules]]
id = "CI-001"
severity = "warn"
pattern = '(?:continue-on-error:\s*true|(?:^|\s)\|\|\s*true\b|--no-verify\b)'
message = "New CI or verification bypass"
guidance = "Keep required checks gating; narrowly document any best-effort command."
include = [".github/**", "scripts/**", "**/*.yml", "**/*.yaml", "**/*.sh"]

[[rules]]
id = "SECRET-001"
severity = "deny"
pattern = '(?:^\s*set\s+-[^#]*x|^\s*#!\s*/bin/(?:ba)?sh\s+-[^\s]*x)'
message = "New shell tracing may expose secrets"
guidance = "Do not enable shell tracing in code that may handle credentials or key material."
include = ["**/*.sh", "**/*.yml", "**/*.yaml"]

[[rules]]
id = "DATA-001"
severity = "warn"
pattern = '(?i)\b(?:INSERT\s+OR\s+REPLACE|REPLACE\s+INTO)\b'
message = "Replacement SQL can weaken ownership or cascade integrity"
guidance = "Prefer an ownership-bound update/upsert with explicit conflict and revision checks."
include = ["**/*.rs", "**/*.sql"]
40 changes: 34 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,37 @@ permissions:
contents: read

jobs:
guard:
name: Recent-change guard
runs-on: self-hosted
if: >
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build guard
run: cargo build -p devkit-cli
- name: Check only recent changes
env:
PUSH_BASE: ${{ github.event.before }}
run: |
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
base="origin/$GITHUB_BASE_REF"
else
base="$PUSH_BASE"
if [[ -z "$base" || "$base" =~ ^0+$ ]]; then
base="HEAD^"
fi
fi
target/debug/dev guard --base "$base" --format github

fmt:
name: Format
runs-on: ubuntu-latest
runs-on: self-hosted
if: >
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
Expand All @@ -26,7 +54,7 @@ jobs:

clippy:
name: Clippy
runs-on: ubuntu-latest
runs-on: self-hosted
if: >
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
Expand All @@ -41,7 +69,7 @@ jobs:

test:
name: Test
runs-on: ubuntu-latest
runs-on: self-hosted
if: >
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
Expand All @@ -54,7 +82,7 @@ jobs:

release-artifacts:
name: Release artifact checks
runs-on: ubuntu-latest
runs-on: self-hosted
if: >
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
Expand All @@ -74,8 +102,8 @@ jobs:

build:
name: Build
runs-on: ubuntu-latest
needs: [fmt, clippy, test, release-artifacts]
runs-on: self-hosted
needs: [guard, fmt, clippy, test, release-artifacts]
if: >
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

## Unreleased

## 2026-07-13 - v0.5.0

### Added

- Added `dev guard`, a fast diff-only regression check with repository-owned regex and path rules, compact GitHub annotations, and separate blocking and advisory severities.
- Added a low-noise default guard policy for skipped work, swallowed failures, fail-open authorization, verification bypasses, unsafe shell tracing, and replacement SQL.
- Added a required CI guard job that compares only recent commits with their merge base.

### Changed

- Guard policies are read from the base revision by default so a proposed change cannot weaken its own check.
- Bumped `devkit` and `devkit-cli` to 0.5.0.

## 2026-06-12 - v0.4.0

### Added
Expand Down
19 changes: 17 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "devkit"
version = "0.4.0"
version = "0.5.0"
edition = "2024"
rust-version = "1.85"
license = "MIT"
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ It is a good fit for:
- teams that want `.env` profile management, validation, templates, diffs, and sync helpers;
- release flows that benefit from scripted branch, version, changelog, and release-PR commands;
- machines that need repeatable developer setup;
- LLM-assisted code review where `dev review` and `dev walk` generate bounded Markdown context.
- LLM-assisted development where `dev guard` catches new regression markers and `dev review`/`dev walk` generate bounded context.

For LLM tool loops that need compact summaries of noisy commands or detached agent runs, use [`agntctl`](https://crates.io/crates/agntctl) alongside `devkit`. `devkit` owns the project workflow surface; `agntctl` owns bounded command and agent reports.

Expand Down Expand Up @@ -94,6 +94,7 @@ dev update --yes

# Review and context reports
dev review --main --output review.md
dev guard --base origin/main
dev walk crates/dev -o manifest.md --extensions .rs .toml
dev walk --stdout
```
Expand All @@ -116,7 +117,7 @@ Tagged releases publish a multi-arch Docker image for `linux/amd64` and `linux/a

```bash
docker pull bakobiibizo/devkit-core:latest
docker pull bakobiibizo/devkit-core:v0.4.0
docker pull bakobiibizo/devkit-core:v0.5.0
```

The image is built from the NGC PyTorch base and includes the build toolchain, Git/Git LFS, `uv`, cache directories for Hugging Face/Torch/uv, `nvidia-ml-py` instead of the deprecated `pynvml` package, and patched `torchaudio`/`torchvision` installs for the CUDA PyTorch stack. It is intended for aarch64 inference hosts such as GB10 / DGX Spark class machines where the host GPU stack is already provisioned.
Expand Down
4 changes: 3 additions & 1 deletion crates/dev/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "devkit-cli"
version = "0.4.0"
version = "0.5.0"
edition = "2024"
rust-version = "1.85"
license = "MIT"
Expand All @@ -20,6 +20,8 @@ camino = "1.1"
chrono = { version = "0.4", default-features = false, features = ["std", "clock"] }
clap = { version = "4.5", features = ["derive"] }
rust-embed = "8.5"
globset = "0.4"
regex = "1.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
Expand Down
37 changes: 35 additions & 2 deletions crates/dev/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::cli_help::{dynamic_help, should_append_dynamic_help};
name = "dev",
version,
about = "Unified developer workflows",
long_about = "A single-binary developer workflow tool for configured tasks, language pipelines, git flows, setup, review reports, directory manifests, and environment management.",
long_about = "A single-binary developer workflow tool for configured tasks, language pipelines, git flows, setup, diff regression guards, review reports, directory manifests, and environment management.",
after_help = "Examples:\n dev config generate\n dev list\n dev lint\n dev run all_check\n dev git branch-create feature/docs\n dev setup status"
)]
pub struct Cli {
Expand Down Expand Up @@ -105,7 +105,7 @@ pub enum Command {
},
/// Check for and install newer dev releases.
#[command(
after_help = "Examples:\n dev update --check\n dev update --yes\n dev update --version v0.4.0 --install-dir ~/.local/bin"
after_help = "Examples:\n dev update --check\n dev update --yes\n dev update --version v0.5.0 --install-dir ~/.local/bin"
)]
Update(UpdateArgs),
/// Environment variable helper commands backed by a `.env` file.
Expand Down Expand Up @@ -150,6 +150,11 @@ pub enum Command {
#[arg(long = "main")]
main: bool,
},
/// Check newly added lines for configured failure-mode regressions.
#[command(
after_help = "Examples:\n dev guard\n dev guard --base origin/main\n dev guard --format github\n dev guard --format detailed"
)]
Guard(GuardArgs),
/// Generate a directory structure map with file contents (for LLM context).
#[command(
after_help = "Examples:\n dev walk\n dev walk crates/dev -o manifest.md --extensions .rs .toml\n dev walk . --no-content --max-depth 4\n dev walk --stdout"
Expand Down Expand Up @@ -197,6 +202,34 @@ pub enum Verb {
Ci,
}

#[derive(Args, Debug)]
pub struct GuardArgs {
/// Git revision to compare with HEAD. The merge base is used.
#[arg(long = "base", default_value = "origin/main")]
pub base: String,
/// Git revision containing the proposed changes.
#[arg(long = "head", default_value = "HEAD")]
pub head: String,
/// Rule configuration path, relative to the repository root by default.
#[arg(long = "config", default_value = ".dev/guard.toml")]
pub config: PathBuf,
/// Output style. Summary and GitHub output stay deliberately compact.
#[arg(long = "format", value_enum, default_value_t = GuardFormat::Summary)]
pub format: GuardFormat,
/// Load policy from the proposed worktree instead of the base revision.
/// Intended for developing a new policy before it has landed.
#[arg(long = "rules-from-worktree", default_value_t = false)]
pub rules_from_worktree: bool,
}

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
pub enum GuardFormat {
#[default]
Summary,
Github,
Detailed,
}

impl Verb {
pub fn as_str(&self) -> &'static str {
match self {
Expand Down
25 changes: 25 additions & 0 deletions crates/dev/src/commands/guard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use anyhow::Result;

use crate::cli::GuardArgs;
use crate::dispatch::CliContext;
use crate::guard::{GuardOptions, run_guard};

pub(crate) fn handle(ctx: &CliContext, args: GuardArgs) -> Result<()> {
if ctx.dry_run {
println!(
"[dry-run] Check added lines in {}...{} with {}",
args.base,
args.head,
args.config.display()
);
return Ok(());
}

run_guard(GuardOptions {
base: args.base,
head: args.head,
config: args.config,
format: args.format,
rules_from_worktree: args.rules_from_worktree,
})
}
1 change: 1 addition & 0 deletions crates/dev/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub(crate) mod config;
pub(crate) mod env;
pub(crate) mod git;
pub(crate) mod guard;
pub(crate) mod language;
pub(crate) mod review;
pub(crate) mod setup;
Expand Down
2 changes: 2 additions & 0 deletions crates/dev/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub fn run(cli: Cli) -> Result<()> {
include_working,
main,
} => commands::review::handle(&ctx, output, include_working, main),
Command::Guard(args) => commands::guard::handle(&ctx, args),
Command::Walk {
directory,
output,
Expand Down Expand Up @@ -171,6 +172,7 @@ fn handle_with_state(state: &AppState, command: Command) -> Result<()> {
Command::Config { .. } => unreachable!("config commands handled earlier"),
Command::Setup { .. } => unreachable!("setup commands handled earlier"),
Command::Review { .. } => unreachable!("review commands handled earlier"),
Command::Guard(_) => unreachable!("guard command handled earlier"),
Command::Walk { .. } => unreachable!("walk commands handled earlier"),
Command::External(extra) => {
bail!("unknown command: {}", extra.join(" "))
Expand Down
Loading
Loading