ail: merge-prose subcommand + PROSE_ROUNDTRIP.md (iter 20d)
Closes family 20: prose-edit cycle is end-to-end. ail merge-prose <original.ail.json> <edited.prose.txt> composes a shaped LLM prompt to stdout. User pipes to their LLM (Claude/etc.), gets new .ail.json, runs ail check, saves. No built-in API client — AILang stays a compiler + tooling, the LLM mediator is supplied externally. PROSE_ROUNDTRIP.md documents the 6-step cycle, the prompt contract (preserve modes/effects/tail flags from original), failure modes, and the design choice to omit an API client. Family 20 now closes: 20a renderer + 20b polish + 20d mediator shipped; 20c rolled into 20a. Three deferred polishes (let-inlining, print sugar, doc-wrap widow control) wait for corpus signal.
This commit is contained in:
@@ -2372,3 +2372,51 @@ fn alloc_rc_partial_drop_at_app_let_close_does_not_leak_unmoved_fields() {
|
||||
`partial_drop_<m>_<T>(p, mask)`"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 20d: `ail merge-prose` reads two files and prints a prompt
|
||||
/// that frames the prose-round-trip task for an external LLM. Smoke
|
||||
/// test: writes two temp inputs, invokes the binary, asserts exit 0
|
||||
/// and that the captured stdout carries the role-statement landmark
|
||||
/// plus both inputs verbatim.
|
||||
#[test]
|
||||
fn merge_prose_prints_framed_prompt() {
|
||||
let tmp = std::env::temp_dir().join(format!(
|
||||
"ailang_e2e_merge_prose_{}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&tmp).unwrap();
|
||||
let orig_path = tmp.join("orig.ail.json");
|
||||
let prose_path = tmp.join("edited.prose.txt");
|
||||
let orig_bytes =
|
||||
b"{\"schema\":\"ailang/v0\",\"name\":\"foo\",\"imports\":[],\"defs\":[]}";
|
||||
let prose_bytes = b"// module foo\n\nfn bar() -> Int { 42 }\n";
|
||||
std::fs::write(&orig_path, orig_bytes).unwrap();
|
||||
std::fs::write(&prose_path, prose_bytes).unwrap();
|
||||
|
||||
let output = Command::new(ail_bin())
|
||||
.args(["merge-prose"])
|
||||
.arg(&orig_path)
|
||||
.arg(&prose_path)
|
||||
.output()
|
||||
.expect("ail merge-prose failed to run");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"ail merge-prose exited non-zero: stderr={}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
|
||||
assert!(!stdout.is_empty(), "empty stdout from merge-prose");
|
||||
assert!(
|
||||
stdout.contains("integrating prose edits"),
|
||||
"prompt missing role-statement landmark; got:\n{stdout}"
|
||||
);
|
||||
// Both inputs must round-trip into the prompt verbatim.
|
||||
assert!(
|
||||
stdout.contains("ailang/v0"),
|
||||
"prompt missing original .ail.json content"
|
||||
);
|
||||
assert!(
|
||||
stdout.contains("fn bar() -> Int { 42 }"),
|
||||
"prompt missing edited prose content"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user