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
25 changes: 3 additions & 22 deletions .github/workflows/release-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,15 @@ jobs:
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
use-cross: false
archive: tar.gz
- target: aarch64-unknown-linux-gnu
runner: ubuntu-latest
use-cross: true
archive: tar.gz
- target: x86_64-apple-darwin
runner: macos-15-intel
use-cross: false
archive: tar.gz
- target: aarch64-apple-darwin
runner: macos-latest
use-cross: false
archive: tar.gz
- target: x86_64-pc-windows-msvc
runner: windows-latest
use-cross: false
archive: zip

steps:
- name: Checkout
Expand Down Expand Up @@ -78,28 +70,17 @@ jobs:
cargo build --release --target ${{ matrix.target }} --package trg
fi

- name: Package (unix)
if: matrix.archive == 'tar.gz'
- name: Package
shell: bash
run: |
staging="trg-${{ steps.version.outputs.version }}-${{ matrix.target }}"
mkdir -p "$staging"
cp "target/${{ matrix.target }}/release/trg" "$staging/"
cp LICENSE README.md "$staging/"
cp LICENSE "$staging/"
cp crates/trg/README.md "$staging/"
tar czf "$staging.tar.gz" "$staging"
echo "ASSET=$staging.tar.gz" >> "$GITHUB_ENV"

- name: Package (windows)
if: matrix.archive == 'zip'
shell: bash
run: |
staging="trg-${{ steps.version.outputs.version }}-${{ matrix.target }}"
mkdir -p "$staging"
cp "target/${{ matrix.target }}/release/trg.exe" "$staging/"
cp LICENSE README.md "$staging/"
7z a "$staging.zip" "$staging"
echo "ASSET=$staging.zip" >> "$GITHUB_ENV"

- name: Attest build provenance
uses: actions/attest-build-provenance@v4
with:
Expand Down
1 change: 1 addition & 0 deletions crates/trg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name = "trg"
version = "0.4.0"
edition = "2021"
publish = false
readme = "README.md"

[features]
default = ["schema-validation"]
Expand Down
3 changes: 3 additions & 0 deletions crates/trg/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# trg

> **Platform support:** Linux and macOS only. Windows is not supported — use [WSL](https://learn.microsoft.com/windows/wsl/) (clone and build inside the WSL filesystem for full functionality).
7 changes: 2 additions & 5 deletions crates/trg/src/agentskills/grading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1436,11 +1436,8 @@ echo '{"passed": true, "evidence": "script verified workspace contents", "ration
"#,
)
.unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&script, fs::Permissions::from_mode(0o755)).unwrap();
}
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&script, fs::Permissions::from_mode(0o755)).unwrap();

let ctx = ctx_with_outputs(tmp.path());
fs::write(ctx.workspace_dir.join("done.txt"), "ok").unwrap();
Expand Down
36 changes: 14 additions & 22 deletions crates/trg/src/agentskills/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
//!
//! Canonical run outputs stay under `runs/run-###/workspace`. Each report also
//! emits an `iteration-<N>/eval-<slug>/<scenario>/` tree that matches the
//! agentskills.io docs layout. Scenario directories are **symlinks** on Unix
//! pointing at the canonical workspace paths so outputs are not duplicated.
//! When symlinks are unavailable, a thin `.workspace-ref` JSON file records
//! the relative path to the canonical workspace instead.
//! agentskills.io docs layout. Scenario directories are symlinks pointing at
//! the canonical workspace paths so outputs are not duplicated. When the
//! symlink call fails (e.g. read-only filesystem), a thin `.workspace-ref`
//! JSON file records the relative path to the canonical workspace instead.

use std::collections::HashMap;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -327,9 +327,10 @@ fn write_scenario_mirror(
Ok(())
}

/// Symlink the scenario leaf to the canonical workspace when supported; otherwise no-op.
/// Symlink the scenario leaf to the canonical workspace.
///
/// Failures are logged to stderr and ignored so exotic filesystems still get `alias-index.json`.
/// Failures are logged to stderr and a `.workspace-ref` fallback is written so exotic
/// filesystems (e.g. read-only or non-symlink-capable) still get `alias-index.json`.
fn link_to_workspace(link_path: &Path, report_dir: &Path, workspace: &Path) {
let relative = match workspace.strip_prefix(report_dir) {
Ok(path) => path.to_path_buf(),
Expand All @@ -343,21 +344,13 @@ fn link_to_workspace(link_path: &Path, report_dir: &Path, workspace: &Path) {
}
};

#[cfg(unix)]
{
let relative_link = relative_path_from(link_path.parent().unwrap(), report_dir, &relative);
if let Err(error) = std::os::unix::fs::symlink(&relative_link, link_path) {
eprintln!(
"docs mirror: symlink {} -> {} failed ({error}); writing .workspace-ref fallback",
link_path.display(),
relative_link.display(),
);
write_workspace_ref(link_path, &relative);
}
}

#[cfg(not(unix))]
{
let relative_link = relative_path_from(link_path.parent().unwrap(), report_dir, &relative);
if let Err(error) = std::os::unix::fs::symlink(&relative_link, link_path) {
eprintln!(
"docs mirror: symlink {} -> {} failed ({error}); writing .workspace-ref fallback",
link_path.display(),
relative_link.display(),
);
write_workspace_ref(link_path, &relative);
}
}
Expand Down Expand Up @@ -386,7 +379,6 @@ fn write_workspace_ref(link_path: &Path, workspace_relative: &Path) {
}
}

#[cfg(unix)]
fn relative_path_from(from_dir: &Path, report_dir: &Path, to_relative: &Path) -> PathBuf {
let depth = from_dir
.strip_prefix(report_dir)
Expand Down
17 changes: 5 additions & 12 deletions crates/trg/src/agentskills/runner/availability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,20 +137,13 @@ fn is_executable(path: &Path) -> bool {
if !path.is_file() {
return false;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path)
.map(|meta| meta.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(not(unix))]
{
true
}
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path)
.map(|meta| meta.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}

#[cfg(all(test, unix))]
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
Expand Down
17 changes: 3 additions & 14 deletions crates/trg/src/agentskills/runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,19 +400,12 @@ fn remove_staged_skill(path: &Path) -> std::io::Result<()> {
/// Dot-prefixed so the skill is hidden from default `ls`/glob and won't collide with
/// staged fixture paths or with a `skill/` directory the agent might create itself —
/// the workspace is the agent's task space; the skill is sidecar reference material.
#[cfg(unix)]
fn symlink_skill_into_workspace(skill_path: &Path, workspace_dir: &Path, link_name: &str) -> std::io::Result<()> {
let link = workspace_dir.join(link_name.trim_end_matches('/'));
let absolute = std::fs::canonicalize(skill_path)?;
std::os::unix::fs::symlink(absolute, link)
}

#[cfg(not(unix))]
fn symlink_skill_into_workspace(skill_path: &Path, workspace_dir: &Path, link_name: &str) -> std::io::Result<()> {
let dest = workspace_dir.join(link_name.trim_end_matches('/'));
copy_skill_tree(skill_path, &dest)
}

fn copy_skill_into_workspace(skill_path: &Path, dest: &Path) -> std::io::Result<()> {
copy_skill_tree(skill_path, dest)
}
Expand Down Expand Up @@ -654,7 +647,6 @@ mod workspace_tests {
assert!(workspace.join("outputs").is_dir());

let link = workspace.join(".skill");
#[cfg(unix)]
assert!(link.symlink_metadata().unwrap().file_type().is_symlink());
assert!(link.join("SKILL.md").is_file());
assert!(workspace.join("evals/files/input.txt").is_file());
Expand Down Expand Up @@ -739,12 +731,9 @@ mod workspace_tests {
assert!(!prepared.prompt.contains("# Current"));

let link = workspace.join(".old-skill");
#[cfg(unix)]
{
assert!(link.symlink_metadata().unwrap().file_type().is_symlink());
let target = std::fs::read_link(&link).unwrap();
assert_eq!(target, std::fs::canonicalize(&old_skill).unwrap());
}
assert!(link.symlink_metadata().unwrap().file_type().is_symlink());
let target = std::fs::read_link(&link).unwrap();
assert_eq!(target, std::fs::canonicalize(&old_skill).unwrap());
assert_eq!(
std::fs::read_to_string(link.join("SKILL.md")).unwrap(),
"---\nname: old-skill\ndescription: Old skill\n---\n# Old\n"
Expand Down
7 changes: 2 additions & 5 deletions crates/trg/src/commands/ai/skills/eval/grade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,8 @@ echo '{"passed": true, "evidence": "script confirmed custom check", "rationale":
"#,
)
.unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&script, fs::Permissions::from_mode(0o755)).unwrap();
}
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(&script, fs::Permissions::from_mode(0o755)).unwrap();

let fs = MemFS::new();
fs.insert(
Expand Down
3 changes: 3 additions & 0 deletions crates/trg/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#[cfg(not(unix))]
compile_error!("trg only supports Unix targets (Linux, macOS). On Windows, build and run inside WSL.");

pub mod agentskills;
pub mod commands;
pub mod config;
Expand Down
Loading