037c45d8 Andy McCall 2 days ago ↶
1
2
3
4
5
6
use crate :: event:: Event;
7
use anyhow:: { bail, Result } ;
8
use std:: path:: Path;
9
10
11
pub const MAX_RULES : usize = 64 * 1024 ;
12
13
14
# [ derive ( Clone, Debug, PartialEq, Eq ) ]
15
pub struct Rules {
16
pub text : String,
17
18
pub version : String,
19
}
20
21
22
pub fn compile ( data : & Path) -> Result< Rules> {
23
let mut text = String :: new( ) ;
24
let site = data.join ( " rules" ) .join ( " site.md" ) ;
25
if let Ok ( body) = std:: fs:: read_to_string( & site) {
26
if body.len ( ) > MAX_RULES {
27
bail! ( " {} is over 64 KiB: the rules must stay readable in one call" , site.display ( ) ) ;
28
}
29
text.push_str ( " # House rules: site (rules/site.md)\n \n " ) ;
30
text.push_str ( body.trim_end ( ) ) ;
31
text.push ( ' \n ' ) ;
32
}
33
let version = blake3:: hash( text.as_bytes ( ) ) .to_hex ( ) [ .. 12 ] .to_string ( ) ;
34
Ok ( Rules { text, version } )
35
}
36
37
38
pub async fn last_read ( pool : & sqlx:: SqlitePool, agent : & str ) -> sqlx:: Result< Option< String> > {
39
sqlx:: query_scalar( " SELECT version FROM rules_read WHERE agent = ?" ) .bind ( agent) .fetch_optional ( pool) .await
40
}
41
42
43
pub fn changed_line ( read : & str , now : & str ) -> String {
44
format! ( " House rules changed ({read} → {now} ): call rules before continuing." )
45
}
46
47
48
pub async fn project ( conn : & mut sqlx:: SqliteConnection, e : & Event) -> sqlx:: Result< ( ) > {
49
let p = e.json ( ) ;
50
let agent = e.actor.strip_prefix ( " agent:" ) ;
51
match e.kind.as_str ( ) {
52
" rules.read" => {
53
if let ( Some ( a) , Some ( v) ) = ( agent, p.get ( " version" ) .and_then ( | v | v.as_str ( ) ) ) {
54
sqlx:: query( " INSERT INTO rules_read (agent, version, at_ms) VALUES (?, ?, ?) ON CONFLICT(agent) DO UPDATE SET version = excluded.version, at_ms = excluded.at_ms" )
55
.bind ( a)
56
.bind ( v)
57
.bind ( e.at_ms as i64 )
58
.execute ( & mut * conn)
59
.await? ;
60
}
61
}
62
" tool.called" => {
63
let s = | k : & str | p.get ( k) .and_then ( | v | v.as_str ( ) ) .map ( String :: from) ;
64
if let ( Some ( a) , Some ( tool) ) = ( agent, s ( " tool" ) ) {
65
sqlx:: query( " INSERT OR IGNORE INTO tool_calls (seq, agent, tool, args, result, ms, is_error, read, session, at_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" )
66
.bind ( e.seq as i64 )
67
.bind ( a)
68
.bind ( tool)
69
.bind ( s ( " args" ) .unwrap_or_default ( ) )
70
.bind ( s ( " result" ) .unwrap_or_default ( ) )
71
.bind ( p.get ( " ms" ) .and_then ( | v | v.as_i64 ( ) ) .unwrap_or ( 0 ) )
72
.bind ( p.get ( " is_error" ) .and_then ( | v | v.as_bool ( ) ) .unwrap_or ( false ) )
73
.bind ( p.get ( " read" ) .and_then ( | v | v.as_bool ( ) ) .unwrap_or ( false ) )
74
.bind ( & e.session)
75
.bind ( e.at_ms as i64 )
76
.execute ( & mut * conn)
77
.await? ;
78
}
79
}
80
_ => { }
81
}
82
Ok ( ( ) )
83
}
84
85
# [ cfg ( test ) ]
86
mod tests {
87
use super :: * ;
88
89
# [ test ]
90
fn the_version_follows_the_text_exactly ( ) {
91
let dir = tempfile:: tempdir( ) .unwrap ( ) ;
92
let empty = compile ( dir.path ( ) ) .unwrap ( ) ;
93
assert_eq! ( empty.text, " " ) ;
94
std:: fs:: create_dir_all( dir.path ( ) .join ( " rules" ) ) .unwrap ( ) ;
95
std:: fs:: write( dir.path ( ) .join ( " rules/site.md" ) , " Commit with oxy.\n " ) .unwrap ( ) ;
96
let a = compile ( dir.path ( ) ) .unwrap ( ) ;
97
assert! ( a.text.contains ( " Commit with oxy." ) & & a.text.starts_with ( " # House rules: site" ) ) ;
98
std:: fs:: write( dir.path ( ) .join ( " rules/site.md" ) , " Commit with oxy!\n " ) .unwrap ( ) ;
99
let b = compile ( dir.path ( ) ) .unwrap ( ) ;
100
assert_ne! ( a.version, b.version) ;
101
assert_eq! ( a.version.len ( ) , 12 ) ;
102
std:: fs:: write( dir.path ( ) .join ( " rules/site.md" ) , " x" .repeat ( MAX_RULES + 1 ) ) .unwrap ( ) ;
103
assert! ( compile ( dir.path ( ) ) .is_err ( ) ) ;
104
}
105
}