feat: add content targets and loop expansion for target templates
Content targets write literal text to files via 'content:' field, without requiring an AI provider or API keys. They are not archived when overwritten. Loop expansion allows defining 'loops:' at the top level with named lists of values. Targets with [var] in their name are expanded via cartesian product. Variables are substituted in all string fields. Explicit targets override expanded ones. Escaping: \[var] -> [var]. Expansion happens at config load time so the rest of the system (builder, graph, state) sees only expanded targets.
This commit is contained in:
parent
bb03975ece
commit
7503672942
7 changed files with 581 additions and 2 deletions
|
|
@ -709,6 +709,68 @@ class TestContentTarget:
|
|||
assert result.failed == {}
|
||||
|
||||
|
||||
class TestLoopExpansion:
|
||||
"""End-to-end tests for loop-expanded targets in builds."""
|
||||
|
||||
async def test_loop_content_targets_build(
|
||||
self, project_dir: Path, write_config: WriteConfig
|
||||
) -> None:
|
||||
config = write_config(
|
||||
{
|
||||
"loops": {"n": ["1", "2", "3"]},
|
||||
"targets": {"file-[n].txt": {"content": "Value [n]"}},
|
||||
}
|
||||
)
|
||||
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
|
||||
result = await run_build(config, project_dir, _PROJECT)
|
||||
|
||||
assert set(result.built) == {"file-1.txt", "file-2.txt", "file-3.txt"}
|
||||
assert (project_dir / "file-1.txt").read_text() == "Value 1"
|
||||
assert (project_dir / "file-2.txt").read_text() == "Value 2"
|
||||
assert (project_dir / "file-3.txt").read_text() == "Value 3"
|
||||
|
||||
async def test_loop_incremental_skip(
|
||||
self, project_dir: Path, write_config: WriteConfig
|
||||
) -> None:
|
||||
config = write_config(
|
||||
{
|
||||
"loops": {"n": ["1", "2"]},
|
||||
"targets": {"file-[n].txt": {"content": "Value [n]"}},
|
||||
}
|
||||
)
|
||||
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
|
||||
r1 = await run_build(config, project_dir, _PROJECT)
|
||||
assert len(r1.built) == 2
|
||||
|
||||
r2 = await run_build(config, project_dir, _PROJECT)
|
||||
assert r2.built == []
|
||||
assert set(r2.skipped) == {"file-1.txt", "file-2.txt"}
|
||||
|
||||
async def test_loop_with_dependency_chain(
|
||||
self, project_dir: Path, write_config: WriteConfig
|
||||
) -> None:
|
||||
config = write_config(
|
||||
{
|
||||
"loops": {"id": ["a", "b"]},
|
||||
"targets": {
|
||||
"data-[id].txt": {"content": "Data for [id]"},
|
||||
"summary-[id].txt": {
|
||||
"prompt": "Summarize [id]",
|
||||
"inputs": ["data-[id].txt"],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
with patch("hokusai.builder._create_providers", return_value=_fake_providers()):
|
||||
result = await run_build(config, project_dir, _PROJECT)
|
||||
|
||||
assert "data-a.txt" in result.built
|
||||
assert "data-b.txt" in result.built
|
||||
assert "summary-a.txt" in result.built
|
||||
assert "summary-b.txt" in result.built
|
||||
assert result.failed == {}
|
||||
|
||||
|
||||
class TestPlaceholderPrompts:
|
||||
"""Tests for prompt placeholder substitution in builds."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue