Skip to content

Commit 4976d14

Browse files
committed
Add package semantic lock output
1 parent 29d3558 commit 4976d14

7 files changed

Lines changed: 452 additions & 24 deletions

File tree

Cargo.lock

Lines changed: 84 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ path = "src/main.rs"
1515
[dependencies]
1616
serde = { version = "1", features = ["derive"] }
1717
serde_json = "1"
18+
sha2 = "0.10"
1819
toml = "0.8"
1920

2021
[dev-dependencies]

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Reviewable by design.
99
Compiled through Rust.
1010
```
1111

12-
I like Rust. But AI-generated Rust is hard to review at speed: lifetimes, ownership shapes, trait-heavy APIs, mutation boundaries, retention — all technically correct, all expensive to audit when a single PR drops hundreds of new lines on you.
12+
I like Rust, and that is where this project starts. AI-generated Rust can be hard to review at speed: lifetimes, ownership shapes, trait-heavy APIs, mutation boundaries, retention — all technically correct, all expensive to audit when a single PR drops hundreds of new lines on you.
1313

1414
RSScript is an experiment in putting a smaller, review-first semantic layer in front of Rust. It keeps rustc, Cargo, and the crate ecosystem; it just makes mutation, retention, resources, native boundaries, and managed/local transitions explicit *before* the Rust gets generated.
1515

@@ -23,7 +23,7 @@ One question drives the design:
2323

2424
Before AI, writing code was expensive and reviewing was manageable. That ratio has flipped — generating code is now cheap, reviewing generated code is the bottleneck.
2525

26-
Existing languages were designed for humans writing code by hand. RSScript is designed for a workflow where AI writes, the compiler checks semantic boundaries, and humans review the *risk*, not every line. So the things that actually matter to a reviewer — what mutates, what gets retained, who owns a resource, where you cross into native or unsafe, what changed in a public API — are pushed into source and machine-readable diagnostics instead of being inferred from context.
26+
Existing languages were designed for humans writing code by hand. RSScript is designed for a workflow where AI writes, the compiler checks semantic boundaries, and humans review the *risk profile first*. So the things that actually matter to a reviewer — what mutates, what gets retained, who owns a resource, where you cross into native or unsafe, what changed in a public API — are pushed into source and machine-readable diagnostics instead of being inferred from context.
2727

2828
---
2929

@@ -38,11 +38,11 @@ let user = User.load(id: read user_id)
3838
let response = Response.ok(body: read user)
3939
```
4040

41-
Managed values are easy to share, store, and drop into long-lived graphs. This is the default for business logic, agent memory, configuration, caches, ASTs, request/response objects — anything that isn't a hot path.
41+
Managed values are easy to share, store, and drop into long-lived graphs. This is the default for business logic, agent memory, configuration, caches, ASTs, request/response objects — the broad layer outside hot paths.
4242

4343
### Local when it matters
4444

45-
Hot paths can opt in to local exclusive values, which can't be silently retained by managed objects or captured by managed closures:
45+
Hot paths can opt in to local exclusive values, and the checker protects those values from silent retention by managed objects or managed closures:
4646

4747
```rust
4848
features: local
@@ -64,7 +64,7 @@ Files are managed-only unless they declare otherwise:
6464
features: local
6565
```
6666

67-
Only `local` is implemented today. Names like `native`, `unsafe`, `async`, `device`, `ffi`, and `reflection` are reserved for capabilities that genuinely change review risk. Ordinary libraries (JSON, File, Image, HTTP, Map, Regex) are *not* features. Repeated or unknown names are diagnostics capability boundaries shouldn't be silently normalized away.
67+
Only `local` is implemented today. Names like `native`, `unsafe`, `async`, `device`, `ffi`, and `reflection` are reserved for capabilities that genuinely change review risk. Ordinary libraries (JSON, File, Image, HTTP, Map, Regex) stay as libraries. Repeated or unknown names are diagnostics so capability boundaries stay explicit.
6868

6969
---
7070

@@ -139,13 +139,13 @@ User-facing APIs stay simple:
139139
let json = Json.parse(text: read body)?
140140
```
141141

142-
Internals can use local scratch and `*_into` forms for low-allocation paths, but that complexity doesn't leak out.
142+
Internals can use local scratch and `*_into` forms for low-allocation paths, while the public surface stays managed and reviewable.
143143

144144
---
145145

146146
## Semantic review
147147

148-
Review is meant to be semantic, not just textual diff.
148+
Review is meant to be semantic and stronger than textual diff.
149149

150150
```sh
151151
rss review --diff base/ changed/
@@ -175,7 +175,7 @@ FOLDABLE
175175
private pure helpers, no retention, no resources
176176
```
177177

178-
RSScript doesn't eliminate human review. It moves human review up a level.
178+
RSScript moves human review up a level.
179179

180180
---
181181

@@ -199,7 +199,7 @@ This is an architectural choice. The value is in the front end; the back end is
199199

200200
## Status
201201

202-
Experimental. The current implementation is a Rust-based front-end prototype: lexer, parser, semantic checker with the review-oriented rules, structured diagnostics, review-map metadata, package review/diff metadata, Rust source lowering with source maps, rustc diagnostic remapping, a small runtime crate, and core `.rssi` interface signatures (parsed as ordinary files, not a hand-written builtin table). CI gates formatting, lint, tests, and generated-Rust fixtures; golden tests cover lowering and source-map shape.
202+
Experimental. The current implementation is a Rust-based front-end prototype: lexer, parser, semantic checker with the review-oriented rules, structured diagnostics, review-map metadata, package review/diff/lock metadata, Rust source lowering with source maps, rustc diagnostic remapping, a small runtime crate, and core `.rssi` interface signatures parsed through the ordinary interface path. CI gates formatting, lint, tests, and generated-Rust fixtures; golden tests cover lowering and source-map shape.
203203

204204
The first milestone is a checker strong enough to validate the language model against real examples.
205205

@@ -213,6 +213,7 @@ rss fmt <file.rss>
213213
rss review [--json] --diff <old.rss> <new.rss>
214214
rss review [--json] --map <file-or-directory>
215215
rss package review [--json] <package-directory>
216+
rss package lock [--json] <package-directory>
216217
rss package diff [--json] <old-package-directory> <new-package-directory>
217218
rss lower --rust <file.rss> [--out-dir <directory>]
218219
rss run [--json] <file.rss> [--out-dir <directory>]
@@ -226,6 +227,7 @@ A few details worth knowing:
226227
- `rss lint` reuses the frontend checks and emits warnings. The first lint is `RSL001` — public signatures over the review budget for parameter count, generics, effects, or nested-type depth.
227228
- `rss review --map` validates inputs first, so files with frontend errors get diagnostics instead of misleading classifications.
228229
- `rss package review` reads `rsspkg.toml`, treats `.rssi` files as the public semantic contract, and raises risk for native Rust wrappers, build scripts, proc macros, unsafe policy, external links, frontend diagnostics, and unknown review-map regions.
230+
- `rss package lock` emits root package lock metadata with SHA-256 hashes for the public `.rssi` contract, review metadata, package contents, and native Rust wrapper contents when enabled.
229231
- `rss package diff` compares two local package directories and reports package version changes, RSScript dependency changes, package feature changes, native Rust wrapper metadata changes, and public `.rssi` semantic contract changes.
230232
- `rss run` lowers to a temporary Rust package and delegates to `cargo run`; `--out-dir` keeps the generated package around for inspection. Diagnostics support `--json`; program stdout stays the program's own.
231233
- `rss verify-rust --out-dir` keeps the generated package and source map, so unmappable rustc diagnostics can be inspected against the actual generated Rust.

src/lib.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ pub use diagnostic::{
1919
};
2020
pub use lint::lint_source;
2121
pub use package::{
22-
PackageDiff, PackageIdentity, PackageInterfaceChange, PackageInterfaceChangeKind,
23-
PackageManifestChange, PackageNativeRustReview, PackageReview, PackageReviewFile,
24-
PackageReviewFileKind, PackageReviewSummary, PackageRisk, diff_package_dirs,
25-
format_package_diff_human, format_package_diff_json, format_package_review_human,
26-
format_package_review_json, review_package_dir,
22+
PackageDiff, PackageIdentity, PackageInterfaceChange, PackageInterfaceChangeKind, PackageLock,
23+
PackageLockMetadata, PackageLockPackage, PackageManifestChange, PackageNativeRustReview,
24+
PackageReview, PackageReviewFile, PackageReviewFileKind, PackageReviewSummary, PackageRisk,
25+
diff_package_dirs, format_package_diff_human, format_package_diff_json,
26+
format_package_lock_json, format_package_lock_toml, format_package_review_human,
27+
format_package_review_json, lock_package_dir, review_package_dir,
2728
};
2829
pub use review::{
2930
ReviewFinding, ReviewFix, ReviewMap, ReviewMapCategorySummary, ReviewMapClassification,

src/main.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ use rsscript::{
88
Diagnostic, analyze_source, analyze_source_with_interfaces, check_generated_rust_package,
99
core_interfaces, diff_package_dirs, explain_diagnostic_code, format_diagnostic_explanation,
1010
format_diagnostics_human, format_diagnostics_json, format_package_diff_human,
11-
format_package_diff_json, format_package_review_human, format_package_review_json,
12-
format_review_human, format_review_json, format_review_map_human, format_review_map_json,
13-
lint_source, lower_source_to_rust, lower_source_to_rust_package, parse_runtime_diagnostics,
14-
parse_source_map_json, remap_rustc_diagnostic_json_lines, review_map_sources,
15-
review_package_dir, review_sources, write_generated_rust_package,
11+
format_package_diff_json, format_package_lock_json, format_package_lock_toml,
12+
format_package_review_human, format_package_review_json, format_review_human,
13+
format_review_json, format_review_map_human, format_review_map_json, lint_source,
14+
lock_package_dir, lower_source_to_rust, lower_source_to_rust_package,
15+
parse_runtime_diagnostics, parse_source_map_json, remap_rustc_diagnostic_json_lines,
16+
review_map_sources, review_package_dir, review_sources, write_generated_rust_package,
1617
};
1718

1819
fn main() -> ExitCode {
@@ -42,6 +43,7 @@ fn main() -> ExitCode {
4243
fn run_package(args: &[String]) -> ExitCode {
4344
match parse_package_args(args) {
4445
PackageCommand::Review { json, path } => run_package_review(json, path),
46+
PackageCommand::Lock { json, path } => run_package_lock(json, path),
4547
PackageCommand::Diff {
4648
json,
4749
old_path,
@@ -788,6 +790,10 @@ enum PackageCommand<'a> {
788790
json: bool,
789791
path: &'a str,
790792
},
793+
Lock {
794+
json: bool,
795+
path: &'a str,
796+
},
791797
Diff {
792798
json: bool,
793799
old_path: &'a str,
@@ -804,7 +810,7 @@ fn parse_package_args(args: &[String]) -> PackageCommand<'_> {
804810
for arg in args {
805811
if arg == "--json" {
806812
json = true;
807-
} else if arg == "review" || arg == "diff" {
813+
} else if arg == "review" || arg == "lock" || arg == "diff" {
808814
command = Some(arg.as_str());
809815
} else {
810816
paths.push(arg.as_str());
@@ -813,6 +819,7 @@ fn parse_package_args(args: &[String]) -> PackageCommand<'_> {
813819

814820
match (command, paths.as_slice()) {
815821
(Some("review"), [path]) => PackageCommand::Review { json, path },
822+
(Some("lock"), [path]) => PackageCommand::Lock { json, path },
816823
(Some("diff"), [old_path, new_path]) => PackageCommand::Diff {
817824
json,
818825
old_path,
@@ -953,6 +960,23 @@ fn run_package_review(json: bool, path: &str) -> ExitCode {
953960
}
954961
}
955962

963+
fn run_package_lock(json: bool, path: &str) -> ExitCode {
964+
let lock = match lock_package_dir(Path::new(path)) {
965+
Ok(lock) => lock,
966+
Err(error) => {
967+
eprintln!("{error}");
968+
return ExitCode::from(2);
969+
}
970+
};
971+
972+
if json {
973+
println!("{}", format_package_lock_json(&lock));
974+
} else {
975+
print!("{}", format_package_lock_toml(&lock));
976+
}
977+
ExitCode::SUCCESS
978+
}
979+
956980
fn run_package_diff(json: bool, old_path: &str, new_path: &str) -> ExitCode {
957981
let diff = match diff_package_dirs(Path::new(old_path), Path::new(new_path)) {
958982
Ok(diff) => diff,
@@ -1047,5 +1071,6 @@ fn print_usage() {
10471071
eprintln!(" rsscript review [--json] --diff <old.rss> <new.rss>");
10481072
eprintln!(" rsscript review [--json] --map <file-or-directory>");
10491073
eprintln!(" rsscript package review [--json] <package-directory>");
1074+
eprintln!(" rsscript package lock [--json] <package-directory>");
10501075
eprintln!(" rsscript package diff [--json] <old-package-directory> <new-package-directory>");
10511076
}

0 commit comments

Comments
 (0)