Skip to content

Commit 1423c6c

Browse files
Merge pull request #540 from forecast-bio/release/v0.7.0
Release v0.7.0
2 parents 8c616a4 + 491c31f commit 1423c6c

File tree

187 files changed

+12933
-8919
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

187 files changed

+12933
-8919
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9+
## [0.7.0] - 2026-03-30
10+
11+
### Added
12+
- `crosslink init --update` with manifest-tracked safe upgrades — tracks installed resource versions and applies incremental updates without overwriting user customizations
13+
- First-class Shell/Bash support in language rules, detection, and hooks
14+
- QA architectural review skill (`/qa`) shipped with `crosslink init`
15+
- Team and solo configuration preset documentation
16+
17+
### Fixed
18+
- Full-codebase QA audit — 180+ fixes across security, correctness, and architecture: shell injection, fail-open hooks, CORS, transaction safety, hydration data loss, non-atomic writes, TOCTOU races, N+1 queries, and structural refactors (init.rs split, config registry extraction, `status.rs``lifecycle.rs`)
19+
- `swarm merge --base` flag for repos without a `develop` branch
20+
- `gh` added to allowed bash prefixes; session status caching in work-check hook
21+
- `.hub-write-lock` excluded from git tracking to prevent recovery commit loop
22+
- Consistent signing bypass for all hub-cache commits
23+
- Resolved clippy pedantic and nursery warnings across codebase
24+
25+
### Changed
26+
- `init.rs` split into `init/mod.rs`, `init/merge.rs`, `init/python.rs`, `init/signing.rs`, `init/walkthrough.rs` for maintainability
27+
- Config command logic extracted to `config_registry.rs`
28+
- `status.rs` renamed to `lifecycle.rs`
29+
- Shared error helpers module added to server (`server/errors.rs`)
30+
- TUI tabs refactored with shared helpers to reduce duplication
31+
932
## [0.6.0] - 2026-03-24
1033

1134
### Added

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,28 @@ Research done by one agent is available to all.
102102
- **Auto-injection** — Relevant knowledge pages injected into agent context via MCP server
103103
- **Conflict resolution** — Accept-both merge strategy for concurrent knowledge edits
104104

105+
### Configuration Presets
106+
107+
Get the right defaults for your workflow without reading docs.
108+
109+
- **Team mode** — Strict tracking, required comments, CI verification, enforced commit signing. For shared repos with multiple contributors or agents.
110+
- **Solo mode** — Relaxed tracking, encouraged comments, local-only verification, signing disabled. For personal projects and solo development.
111+
- **Custom** — Configure each setting individually via the interactive walkthrough.
112+
113+
```bash
114+
# Choose during first-time setup (interactive TUI)
115+
crosslink init
116+
117+
# Or apply a preset directly
118+
crosslink config --preset team
119+
crosslink config --preset solo
120+
121+
# Skip the TUI and use team defaults
122+
crosslink init --defaults
123+
```
124+
125+
The presets configure tracking strictness, comment discipline, lock stealing policy, kickoff verification level, and signing enforcement. Run `crosslink config show` to see current settings, or `crosslink config --reconfigure` to re-run the walkthrough.
126+
105127
### Behavioral Hooks & Rules
106128

107129
Your agents follow the rules without being told.

crosslink/Cargo.lock

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

crosslink/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "crosslink"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
edition = "2021"
55
rust-version = "1.87"
66
authors = ["dollspace-gay", "Maxine Levesque <hello@maxine.science>", "Forecast Analytical <analytical@forecast.bio>"]

crosslink/build.rs

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Build script to track include_str! dependencies, inject git metadata,
1+
//! Build script to track `include_str`! dependencies, inject git metadata,
22
//! and auto-generate rule file includes from resources/crosslink/rules/.
33
44
use std::fs;
@@ -25,7 +25,7 @@ fn main() {
2525
} else {
2626
format!("{}+{}", env!("CARGO_PKG_VERSION"), hash)
2727
};
28-
println!("cargo:rustc-env=CROSSLINK_VERSION={}", suffix);
28+
println!("cargo:rustc-env=CROSSLINK_VERSION={suffix}");
2929
}
3030
}
3131

@@ -48,7 +48,7 @@ fn main() {
4848
let rules_dir = Path::new("resources/crosslink/rules");
4949
if rules_dir.is_dir() {
5050
if let Err(e) = generate_rules_file(rules_dir) {
51-
eprintln!("cargo:warning=Failed to generate rules_gen.rs: {}", e);
51+
eprintln!("cargo:warning=Failed to generate rules_gen.rs: {e}");
5252
}
5353
}
5454

@@ -57,7 +57,7 @@ fn main() {
5757
let commands_dir = Path::new("resources/claude/commands");
5858
if commands_dir.is_dir() {
5959
if let Err(e) = generate_commands_file(commands_dir) {
60-
eprintln!("cargo:warning=Failed to generate commands_gen.rs: {}", e);
60+
eprintln!("cargo:warning=Failed to generate commands_gen.rs: {e}");
6161
}
6262
}
6363
}
@@ -66,22 +66,22 @@ fn generate_commands_file(commands_dir: &Path) -> Result<(), Box<dyn std::error:
6666
let mut cmd_entries: Vec<(String, String)> = Vec::new();
6767

6868
let mut entries: Vec<_> = fs::read_dir(commands_dir)?
69-
.filter_map(|e| e.ok())
69+
.filter_map(std::result::Result::ok)
7070
.filter(|e| e.file_name().to_string_lossy().ends_with(".md"))
7171
.collect();
72-
entries.sort_by_key(|e| e.file_name());
72+
entries.sort_by_key(std::fs::DirEntry::file_name);
7373

7474
for entry in entries {
7575
let filename = entry.file_name().to_string_lossy().to_string();
76-
let rel_path = format!("resources/claude/commands/{}", filename);
77-
println!("cargo:rerun-if-changed={}", rel_path);
76+
let rel_path = format!("resources/claude/commands/{filename}");
77+
println!("cargo:rerun-if-changed={rel_path}");
7878

7979
// Generate a const name: crosslink-guide.md -> CMD_CROSSLINK_GUIDE
8080
let const_name = filename
8181
.trim_end_matches(".md")
8282
.to_uppercase()
8383
.replace('-', "_");
84-
let const_name = format!("CMD_{}", const_name);
84+
let const_name = format!("CMD_{const_name}");
8585

8686
cmd_entries.push((filename, const_name));
8787
}
@@ -107,8 +107,7 @@ fn generate_commands_file(commands_dir: &Path) -> Result<(), Box<dyn std::error:
107107
let abs_path_str = abs_path.to_string_lossy().replace('\\', "/");
108108
writeln!(
109109
gen_file,
110-
"pub(crate) const {}: &str = include_str!(\"{}\");",
111-
const_name, abs_path_str
110+
"pub(crate) const {const_name}: &str = include_str!(\"{abs_path_str}\");"
112111
)?;
113112
}
114113

@@ -118,7 +117,7 @@ fn generate_commands_file(commands_dir: &Path) -> Result<(), Box<dyn std::error:
118117
"pub(crate) const COMMAND_FILES: &[(&str, &str)] = &["
119118
)?;
120119
for (filename, const_name) in &cmd_entries {
121-
writeln!(gen_file, " (\"{}\", {}),", filename, const_name)?;
120+
writeln!(gen_file, " (\"{filename}\", {const_name}),")?;
122121
}
123122
writeln!(gen_file, "];")?;
124123

@@ -129,26 +128,31 @@ fn generate_rules_file(rules_dir: &Path) -> Result<(), Box<dyn std::error::Error
129128
let mut rule_entries: Vec<(String, String)> = Vec::new();
130129

131130
let mut entries: Vec<_> = fs::read_dir(rules_dir)?
132-
.filter_map(|e| e.ok())
131+
.filter_map(std::result::Result::ok)
133132
.filter(|e| {
134133
let name = e.file_name().to_string_lossy().to_string();
135-
name.ends_with(".md") || name.ends_with(".txt")
134+
std::path::Path::new(&name)
135+
.extension()
136+
.is_some_and(|ext| ext.eq_ignore_ascii_case("md"))
137+
|| std::path::Path::new(&name)
138+
.extension()
139+
.is_some_and(|ext| ext.eq_ignore_ascii_case("txt"))
136140
})
137141
.collect();
138-
entries.sort_by_key(|e| e.file_name());
142+
entries.sort_by_key(std::fs::DirEntry::file_name);
139143

140144
for entry in entries {
141145
let filename = entry.file_name().to_string_lossy().to_string();
142-
let rel_path = format!("resources/crosslink/rules/{}", filename);
143-
println!("cargo:rerun-if-changed={}", rel_path);
146+
let rel_path = format!("resources/crosslink/rules/{filename}");
147+
println!("cargo:rerun-if-changed={rel_path}");
144148

145149
// Generate a const name from filename: quality.md -> RULE_QUALITY
146150
let const_name = filename
147151
.trim_end_matches(".md")
148152
.trim_end_matches(".txt")
149153
.to_uppercase()
150154
.replace('-', "_");
151-
let const_name = format!("RULE_{}", const_name);
155+
let const_name = format!("RULE_{const_name}");
152156

153157
rule_entries.push((filename, const_name));
154158
}
@@ -177,8 +181,7 @@ fn generate_rules_file(rules_dir: &Path) -> Result<(), Box<dyn std::error::Error
177181
let abs_path_str = abs_path.to_string_lossy().replace('\\', "/");
178182
writeln!(
179183
gen_file,
180-
"pub(crate) const {}: &str = include_str!(\"{}\");",
181-
const_name, abs_path_str
184+
"pub(crate) const {const_name}: &str = include_str!(\"{abs_path_str}\");"
182185
)?;
183186
}
184187

@@ -190,7 +193,7 @@ fn generate_rules_file(rules_dir: &Path) -> Result<(), Box<dyn std::error::Error
190193
"pub(crate) const RULE_FILES: &[(&str, &str)] = &["
191194
)?;
192195
for (filename, const_name) in &rule_entries {
193-
writeln!(gen_file, " (\"{}\", {}),", filename, const_name)?;
196+
writeln!(gen_file, " (\"{filename}\", {const_name}),")?;
194197
}
195198
writeln!(gen_file, "];")?;
196199

0 commit comments

Comments
 (0)