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
9 changes: 8 additions & 1 deletion src/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
update_manifest_run_status,
)
from .operator import ClaudeOperator
from .platform.foundry import generate_paper_package
from .platform.foundry import generate_paper_package, generate_release_package
from .writing_manifest import build_writing_manifest, format_manifest_for_prompt
from .utils import (
STAGES,
Expand Down Expand Up @@ -425,6 +425,13 @@ def _run_stage(self, paths: RunPaths, stage: StageSpec) -> bool:
f"{stage.slug} paper_package",
package.summary,
)
elif stage.slug == "08_dissemination":
package = generate_release_package(paths.run_root)
append_log_entry(
paths.logs,
f"{stage.slug} release_package",
package.summary,
)
write_stage_handoff(paths, stage, stage_markdown)
append_log_entry(
paths.logs,
Expand Down
102 changes: 102 additions & 0 deletions src/platform/foundry.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ class PaperPackageResult:
summary: str


@dataclass(frozen=True)
class ReleasePackageResult:
root_dir: Path
artifact_paths: list[Path]
summary: str


def generate_paper_package(run_root: Path) -> PaperPackageResult:
paths = build_run_paths(run_root)
package_dir = paths.writing_dir / "paper_package"
Expand Down Expand Up @@ -148,6 +155,101 @@ def generate_paper_package(run_root: Path) -> PaperPackageResult:
return PaperPackageResult(root_dir=package_dir, artifact_paths=artifact_paths, summary=summary)


def generate_release_package(run_root: Path) -> ReleasePackageResult:
paths = build_run_paths(run_root)
review_dir = paths.reviews_dir / "release_package"
artifact_dir = paths.artifacts_dir / "release_package"
writing_dir = paths.writing_dir / "release_package"
review_dir.mkdir(parents=True, exist_ok=True)
artifact_dir.mkdir(parents=True, exist_ok=True)
writing_dir.mkdir(parents=True, exist_ok=True)

readiness_path = review_dir / "readiness_checklist.md"
threats_path = review_dir / "threats_to_validity.md"
bundle_manifest_path = artifact_dir / "artifact_bundle_manifest.json"
release_notes_path = artifact_dir / "release_notes.md"
poster_path = artifact_dir / "poster.md"
slides_path = artifact_dir / "slides.md"
social_path = artifact_dir / "social.md"
external_summary_path = writing_dir / "external_summary.md"

write_text(
readiness_path,
(
"# Readiness Checklist\n\n"
"- [x] Approved manuscript package exists\n"
"- [x] Results and figures are bundled\n"
"- [x] Review materials are packaged\n"
"- [ ] Final communication review completed\n"
),
)
write_text(
threats_path,
(
"# Threats to Validity\n\n"
"- External validity depends on the representativeness of the selected literature and experiments.\n"
"- Implementation and analysis packages should be re-checked after upstream changes.\n"
"- Dissemination materials summarize the current approved state and must be regenerated after paper updates.\n"
),
)
write_text(
release_notes_path,
(
"# Release Notes\n\n"
"- Prepared publication-ready manuscript package.\n"
"- Generated outward-facing communication materials.\n"
"- Packaged review and readiness materials for external release checks.\n"
),
)
write_text(poster_path, "# Poster Outline\n\n- Title\n- Core figure\n- Main takeaway\n")
write_text(slides_path, "# Slide Deck Outline\n\n1. Problem\n2. Method\n3. Results\n4. Limitations\n")
write_text(social_path, "# Social Summary\n\nA concise public-facing summary of the approved research package.\n")
write_text(
external_summary_path,
(
"# External Summary\n\n"
"This release bundle contains the manuscript package, outward-facing collateral, and review artifacts "
"needed to communicate the approved state of the research.\n"
),
)

bundle_manifest = {
"artifacts": [
str(path.relative_to(run_root))
for path in [
readiness_path,
threats_path,
bundle_manifest_path,
release_notes_path,
poster_path,
slides_path,
social_path,
external_summary_path,
]
]
}
bundle_manifest_path.write_text(
__import__("json").dumps(bundle_manifest, indent=2, ensure_ascii=True) + "\n",
encoding="utf-8",
)

artifact_paths = [
readiness_path,
threats_path,
bundle_manifest_path,
release_notes_path,
poster_path,
slides_path,
social_path,
external_summary_path,
]
summary = (
f"Generated a review/dissemination package with {len(artifact_paths)} artifacts, "
"including readiness checklist, threats-to-validity notes, release notes, and outward-facing materials."
)
return ReleasePackageResult(root_dir=artifact_dir, artifact_paths=artifact_paths, summary=summary)


def _derive_title(paths) -> str:
text = read_text(paths.user_input).strip()
first_line = next((line.strip() for line in text.splitlines() if line.strip()), "AutoR Research Package")
Expand Down
36 changes: 36 additions & 0 deletions tests/test_release_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import annotations

import tempfile
import unittest
from pathlib import Path

from src.platform.foundry import generate_release_package
from src.utils import build_run_paths, ensure_run_config, ensure_run_layout, initialize_memory, write_text


class ReleasePackageTests(unittest.TestCase):
def test_generate_release_package_writes_expected_artifacts(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
run_root = Path(tmp_dir) / "run"
paths = build_run_paths(run_root)
ensure_run_layout(paths)
write_text(paths.user_input, "Release package title")
initialize_memory(paths, "Release package title")
ensure_run_config(paths, model="sonnet", venue="neurips_2025")

package = generate_release_package(run_root)

names = {path.name for path in package.artifact_paths}
self.assertIn("readiness_checklist.md", names)
self.assertIn("threats_to_validity.md", names)
self.assertIn("artifact_bundle_manifest.json", names)
self.assertIn("release_notes.md", names)
self.assertIn("poster.md", names)
self.assertIn("slides.md", names)
self.assertIn("social.md", names)
self.assertIn("external_summary.md", names)
self.assertTrue(all(path.exists() for path in package.artifact_paths))


if __name__ == "__main__":
unittest.main()