docs: sync status with code, add AI-resistance research, restyle README

Documentation had drifted from the actual implementation: the noxis
(CLI) and noxis-verify crates were fully built and tested but every
doc called them "not started". Verified against the code (build +
test run) and corrected status across README.md, todo.md,
architecture.md, techniques.md and learnings/{lessons,priorities}.md.

Also:
- restyled README.md (badges, no em dashes, usage examples, dropped
  the redundant "Принципы" section already covered elsewhere)
- added docs/learnings/ai-resistance.md: a sourced writeup of what
  actually works against LLM-driven reverse engineering (Elastic
  Security Labs' Opus 4.6 vs Tigress study, the Acoda paper) and what
  of it applies to noxis, plus concrete todo.md §7 items
- noxis-structure-check: the 250-line/4-file gate was scanning
  docs/**, which fought against writing documentation freely; it now
  only scans crates/*/{src,tests,benches} and only counts .rs files
This commit is contained in:
Loki 2026-10-08 22:34:00 +02:00
parent 2674d7fc89
commit c3b8d4dd62
9 changed files with 349 additions and 162 deletions

View file

@ -76,4 +76,16 @@ mod tests {
let found = violations(&tmp.0);
assert!(found.is_empty(), "{found:?}");
}
#[test]
fn ignores_markdown_size_and_count() {
// Правила — только про Rust-код: `.md` не лимитирован ни по
// строкам, ни в счётчике файлов директории.
let tmp = TempDir::new("md");
for index in 0..super::MAX_DIR_FILES + 3 {
fs::write(tmp.0.join(format!("doc{index}.md")), "line\n".repeat(300)).expect("write");
}
let found = violations(&tmp.0);
assert!(found.is_empty(), "{found:?}");
}
}

View file

@ -1,31 +1,32 @@
//! Project structure rules, invoked from every crate's `build.rs`.
//!
//! Rules:
//! * every `.rs` and `.md` file is at most [`MAX_FILE_LINES`] lines long;
//! * every directory inside a crate `src/` or the workspace `docs/` holds at
//! most [`MAX_DIR_FILES`] files — subdirectories are unlimited.
//! Rules apply to Rust code only, not to documentation:
//! * every `.rs` file is at most [`MAX_FILE_LINES`] lines long;
//! * every directory inside a crate's `src/`, `tests/` or `benches/` holds
//! at most [`MAX_DIR_FILES`] files — subdirectories are unlimited.
//!
//! Deliberately excluded: `Cargo.lock`, `.gitignore`, the legacy `info/` tree,
//! and crate roots (they always own `Cargo.toml` plus `build.rs`).
//! Deliberately excluded: `Cargo.lock`, `.gitignore`, `docs/`, the legacy
//! `info/` tree, and crate roots (they always own `Cargo.toml` plus
//! `build.rs`).
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
/// Maximum number of lines in a source (`rs`) or documentation (`md`) file.
/// Maximum number of lines in a Rust source file.
pub const MAX_FILE_LINES: usize = 250;
/// Maximum number of files per directory; subdirectories are not counted.
pub const MAX_DIR_FILES: usize = 4;
/// Extensions subject to [`MAX_FILE_LINES`].
const LINE_LIMITED_EXTS: [&str; 2] = ["rs", "md"];
const LINE_LIMITED_EXTS: [&str; 1] = ["rs"];
/// Directory names the scan never descends into.
const SKIPPED_DIRS: [&str; 2] = ["target", ".git"];
/// Scans the calling crate's `src/`/`tests/`/`benches/` plus the workspace
/// `docs/` tree and panics with a report on the first violation.
/// Scans the calling crate's `src/`/`tests/`/`benches/` and panics with a
/// report on the first violation.
///
/// # Panics
///
@ -35,13 +36,13 @@ pub fn check() {
let mut violations = Vec::new();
// `src/` обязателен; `tests/` и `benches/` проверяются, если созданы —
// иначе правило «250 строк» обходится выносом кода в тесты.
let mut roots = vec![manifest.join("src"), manifest.join("tests")];
roots.push(manifest.join("benches"));
if let Some(root) = workspace_root(&manifest) {
roots.push(root.join("docs"));
}
// иначе правило «250 строк» обходится выносом кода в тесты. `docs/` вне
// гейта: правила — только про Rust-код.
let roots = [
manifest.join("src"),
manifest.join("tests"),
manifest.join("benches"),
];
for root in &roots {
watch(root);
@ -64,8 +65,9 @@ pub fn check() {
/// Re-runs the build script whenever a checked tree changes.
///
/// Without this cargo only invalidates `build.rs` on crate-local changes, so
/// editing `docs/` would never be re-validated.
/// Without an explicit `rerun-if-changed`, cargo falls back to its default
/// (rerun on any change anywhere in the package); naming the trees keeps the
/// gate from firing on unrelated edits.
fn watch(root: &Path) {
if root.is_dir() {
println!("cargo:rerun-if-changed={}", root.display());
@ -101,6 +103,10 @@ pub fn scan(root: &Path, violations: &mut Vec<String>) {
subdirs.push(path);
continue;
}
let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
if !LINE_LIMITED_EXTS.contains(&ext) {
continue;
}
files += 1;
check_line_limit(&path, violations);
}
@ -118,10 +124,6 @@ pub fn scan(root: &Path, violations: &mut Vec<String>) {
}
fn check_line_limit(path: &Path, violations: &mut Vec<String>) {
let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
if !LINE_LIMITED_EXTS.contains(&ext) {
return;
}
let lines = count_lines(path);
if lines > MAX_FILE_LINES {
violations.push(format!(
@ -149,17 +151,3 @@ fn manifest_dir() -> PathBuf {
None => panic!("CARGO_MANIFEST_DIR is unset; run this check from a build script"),
}
}
/// Walks up from `start` until a manifest declaring `[workspace]` is found.
fn workspace_root(start: &Path) -> Option<PathBuf> {
let mut current = Some(start);
while let Some(dir) = current {
if let Ok(text) = fs::read_to_string(dir.join("Cargo.toml"))
&& text.lines().any(|line| line.trim() == "[workspace]")
{
return Some(dir.to_path_buf());
}
current = dir.parent();
}
None
}