mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-09-20 12:13:38 +08:00
fix: build task dependencies in two phases
This commit is contained in:
@@ -73,6 +73,7 @@ def test_s10_keeps_the_s04_kernel_and_adds_task_tools() -> None:
|
||||
"edit_file",
|
||||
"glob",
|
||||
"create_task",
|
||||
"update_task",
|
||||
"list_tasks",
|
||||
"get_task",
|
||||
"claim_task",
|
||||
@@ -83,6 +84,13 @@ def test_s10_keeps_the_s04_kernel_and_adds_task_tools() -> None:
|
||||
assert not hasattr(lesson, "MEMORY_DIR")
|
||||
assert not (workdir / ".tasks").exists()
|
||||
|
||||
tools = {tool["name"]: tool for tool in lesson.TOOLS}
|
||||
create_schema = tools["create_task"]["input_schema"]
|
||||
update_schema = tools["update_task"]["input_schema"]
|
||||
assert "blockedBy" not in create_schema["properties"]
|
||||
assert create_schema["additionalProperties"] is False
|
||||
assert update_schema["required"] == ["task_id", "addBlockedBy"]
|
||||
|
||||
|
||||
def test_dependencies_gate_claim_and_completion_checks_owner() -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
@@ -90,7 +98,8 @@ def test_dependencies_gate_claim_and_completion_checks_owner() -> None:
|
||||
lesson = load_lesson(workdir)
|
||||
|
||||
schema = lesson.create_task("create schema")
|
||||
api = lesson.create_task("write API", blockedBy=[schema.id])
|
||||
api = lesson.create_task("write API")
|
||||
lesson.update_task(api.id, [schema.id])
|
||||
|
||||
assert lesson.claim_task(api.id) == f"Blocked by: ['{schema.id}']"
|
||||
assert "Claimed" in lesson.claim_task(schema.id)
|
||||
@@ -103,6 +112,41 @@ def test_dependencies_gate_claim_and_completion_checks_owner() -> None:
|
||||
assert lesson.load_task(api.id).status == "completed"
|
||||
|
||||
|
||||
def test_dependencies_are_added_after_create_returns_runtime_ids() -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp))
|
||||
|
||||
create_results = [
|
||||
lesson.execute_tool(tool_call("create_task", subject=subject))
|
||||
for subject in (
|
||||
"create schema",
|
||||
"write API",
|
||||
"write tests",
|
||||
"write docs",
|
||||
)
|
||||
]
|
||||
task_ids = [result.split()[1].rstrip(":") for result in create_results]
|
||||
schema_id, api_id, tests_id, docs_id = task_ids
|
||||
|
||||
update_results = [
|
||||
lesson.execute_tool(tool_call(
|
||||
"update_task", task_id=api_id, addBlockedBy=[schema_id]
|
||||
)),
|
||||
lesson.execute_tool(tool_call(
|
||||
"update_task", task_id=tests_id, addBlockedBy=[api_id]
|
||||
)),
|
||||
lesson.execute_tool(tool_call(
|
||||
"update_task", task_id=docs_id, addBlockedBy=[schema_id]
|
||||
)),
|
||||
]
|
||||
|
||||
assert all(not result.startswith("Error:") for result in update_results)
|
||||
assert lesson.load_task(schema_id).blockedBy == []
|
||||
assert lesson.load_task(api_id).blockedBy == [schema_id]
|
||||
assert lesson.load_task(tests_id).blockedBy == [api_id]
|
||||
assert lesson.load_task(docs_id).blockedBy == [schema_id]
|
||||
|
||||
|
||||
def test_invalid_and_missing_task_ids_become_tool_results() -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp))
|
||||
@@ -132,17 +176,49 @@ def test_create_retries_instead_of_overwriting_an_existing_id(
|
||||
assert [task.subject for task in lesson.list_tasks()] == ["second", "first"]
|
||||
|
||||
|
||||
def test_create_rejects_unknown_dependencies() -> None:
|
||||
def test_update_rejects_invalid_graph_changes_without_partial_mutation() -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp))
|
||||
dependency = lesson.create_task("create schema")
|
||||
target = lesson.create_task("write API")
|
||||
|
||||
output = lesson.execute_tool(tool_call(
|
||||
"create_task",
|
||||
subject="write API",
|
||||
blockedBy=["task_00000000"],
|
||||
missing = lesson.execute_tool(tool_call(
|
||||
"update_task",
|
||||
task_id=target.id,
|
||||
addBlockedBy=[dependency.id, "task_00000000"],
|
||||
))
|
||||
self_dependency = lesson.execute_tool(tool_call(
|
||||
"update_task", task_id=target.id, addBlockedBy=[target.id]
|
||||
))
|
||||
|
||||
assert output == "Error: Dependency not found: task_00000000"
|
||||
assert missing == "Error: Dependency not found: task_00000000"
|
||||
assert self_dependency == "Error: Task cannot depend on itself"
|
||||
assert lesson.load_task(target.id).blockedBy == []
|
||||
|
||||
|
||||
def test_update_is_idempotent_and_rejects_cycles_or_started_tasks() -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp))
|
||||
first = lesson.create_task("first")
|
||||
second = lesson.create_task("second")
|
||||
third = lesson.create_task("third")
|
||||
|
||||
lesson.update_task(second.id, [first.id, first.id])
|
||||
lesson.update_task(second.id, [first.id])
|
||||
lesson.update_task(third.id, [second.id])
|
||||
|
||||
cycle = lesson.execute_tool(tool_call(
|
||||
"update_task", task_id=first.id, addBlockedBy=[third.id]
|
||||
))
|
||||
assert cycle.startswith("Error: Dependency cycle detected")
|
||||
assert lesson.load_task(first.id).blockedBy == []
|
||||
assert lesson.load_task(second.id).blockedBy == [first.id]
|
||||
|
||||
assert "Claimed" in lesson.claim_task(first.id)
|
||||
started = lesson.execute_tool(tool_call(
|
||||
"update_task", task_id=first.id, addBlockedBy=[second.id]
|
||||
))
|
||||
assert "only be updated while pending and unowned" in started
|
||||
|
||||
|
||||
def test_task_store_rejects_a_symlink_outside_the_workspace() -> None:
|
||||
|
||||
Reference in New Issue
Block a user