Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 38 additions & 19 deletions src/deepscientist/artifact/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9283,6 +9283,16 @@ def record(
closing_artifact_id=artifact_id,
)

if not (record["kind"] == "report" and record.get("report_type") == "summary_refresh"):
try:
self.refresh_summary(
quest_root,
reason=f"auto after {record['kind']} {artifact_id}",
record_artifact=False,
)
except Exception:
pass

return {
"ok": True,
"artifact_id": artifact_id,
Expand Down Expand Up @@ -14202,7 +14212,13 @@ def waive_baseline(
"legacy_guidance": "Baseline gate waived. Continue carefully and keep the waiver rationale explicit downstream.",
}

def refresh_summary(self, quest_root: Path, *, reason: str | None = None) -> dict:
def refresh_summary(
self,
quest_root: Path,
*,
reason: str | None = None,
record_artifact: bool = True,
) -> dict:
workspace_root = self._workspace_root_for(quest_root)
recent = self.recent(quest_root, limit=20)
latest_runs = [item for item in recent if item.get("kind") == "runs"][-5:]
Expand All @@ -14229,27 +14245,30 @@ def refresh_summary(self, quest_root: Path, *, reason: str | None = None) -> dic
lines.append(f"- `{payload.get('run_id') or payload.get('artifact_id')}`: {summary}")
summary_body = "\n".join(lines).rstrip() + "\n"
summary_path = workspace_root / "SUMMARY.md"
write_text(summary_path, summary_body)
quest_root_summary_path = quest_root / "SUMMARY.md"
if quest_root_summary_path.resolve() != summary_path.resolve():
if record_artifact:
write_text(summary_path, summary_body)
if quest_root_summary_path.resolve() != summary_path.resolve() or not record_artifact:
write_text(quest_root_summary_path, summary_body)
artifact = self.record(
quest_root,
{
"kind": "report",
"status": "completed",
"report_type": "summary_refresh",
"report_id": generate_id("report"),
"summary": "Quest summary refreshed from recent artifacts.",
"reason": reason or "Summary refreshed after artifact updates.",
"paths": {
"summary_md": str(summary_path),
"quest_root_summary_md": str(quest_root_summary_path),
artifact: dict | None = None
if record_artifact:
artifact = self.record(
quest_root,
{
"kind": "report",
"status": "completed",
"report_type": "summary_refresh",
"report_id": generate_id("report"),
"summary": "Quest summary refreshed from recent artifacts.",
"reason": reason or "Summary refreshed after artifact updates.",
"paths": {
"summary_md": str(summary_path),
"quest_root_summary_md": str(quest_root_summary_path),
},
"source": {"kind": "system", "role": "artifact"},
},
"source": {"kind": "system", "role": "artifact"},
},
workspace_root=workspace_root,
)
workspace_root=workspace_root,
)
return {
"ok": True,
"summary_path": str(summary_path),
Expand Down
64 changes: 64 additions & 0 deletions tests/test_memory_and_artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,70 @@ def test_refresh_summary_writes_once_when_workspace_equals_quest_root(temp_home:
assert (quest_root / "SUMMARY.md").exists()


def test_record_auto_refreshes_quest_root_summary_without_extra_artifact(temp_home: Path) -> None:
ensure_home_layout(temp_home)
ConfigManager(temp_home).ensure_files()
quest_service = QuestService(temp_home, skill_installer=SkillInstaller(repo_root(), temp_home))
quest = quest_service.create("auto refresh on record")
quest_root = Path(quest["quest_root"])
artifact = ArtifactService(temp_home)

summary_path = quest_root / "SUMMARY.md"
assert summary_path.read_text(encoding="utf-8").strip() != "" or not summary_path.exists()
pre_summary = summary_path.read_text(encoding="utf-8") if summary_path.exists() else ""

result = artifact.record(
quest_root,
{
"kind": "report",
"status": "completed",
"report_id": "report-auto-1",
"summary": "first user-driven artifact",
"reason": "exercise auto-refresh hook",
"source": {"kind": "agent"},
},
)
assert result["ok"] is True

post_summary = summary_path.read_text(encoding="utf-8")
assert post_summary != pre_summary
assert "auto after report" in post_summary
assert result["artifact_id"] in post_summary

summary_refresh_artifacts = [
item for item in artifact.recent(quest_root, limit=20)
if item.get("kind") == "reports"
]
summary_refresh_payloads = []
for item in summary_refresh_artifacts:
payload = read_json(Path(item["path"]), {})
if payload.get("report_type") == "summary_refresh":
summary_refresh_payloads.append(payload)
assert summary_refresh_payloads == []


def test_record_skips_auto_refresh_when_record_is_summary_refresh(temp_home: Path) -> None:
ensure_home_layout(temp_home)
ConfigManager(temp_home).ensure_files()
quest_service = QuestService(temp_home, skill_installer=SkillInstaller(repo_root(), temp_home))
quest = quest_service.create("no recursion")
quest_root = Path(quest["quest_root"])
artifact = ArtifactService(temp_home)

explicit = artifact.refresh_summary(quest_root, reason="explicit user-driven refresh")
assert explicit["ok"] is True
assert explicit["artifact"] is not None

summary_refresh_payloads = []
for item in artifact.recent(quest_root, limit=20):
if item.get("kind") != "reports":
continue
payload = read_json(Path(item["path"]), {})
if payload.get("report_type") == "summary_refresh":
summary_refresh_payloads.append(payload)
assert len(summary_refresh_payloads) == 1


def test_artifact_interact_and_prepare_branch(temp_home: Path) -> None:
ensure_home_layout(temp_home)
ConfigManager(temp_home).ensure_files()
Expand Down