feat(deploy): install update/remove scripts into .mam_deploy/ and refine markdown staging

This commit is contained in:
2026-08-04 22:17:59 +09:00
parent 2ff8b2c4a9
commit 68eff79810
7 changed files with 1092 additions and 157 deletions
+148
View File
@@ -0,0 +1,148 @@
# ==============================================================================
# test_deploy_layout.py — Deploy Script Layout, Refresh & Gitignore Test Suite
# ==============================================================================
import os
import shutil
import subprocess
import tempfile
import unittest
class TestDeployLayout(unittest.TestCase):
def setUp(self):
self.repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
self.temp_dir = tempfile.mkdtemp(prefix="mam_deploy_layout_test_")
self.test_dir = os.path.join(self.temp_dir, "workspace")
os.makedirs(self.test_dir, exist_ok=True)
self.env = os.environ.copy()
self.env["MAM_REPO_URL"] = self.repo_root
self.env["MAM_SKIP_VENV"] = "1"
def tearDown(self):
shutil.rmtree(self.temp_dir, ignore_errors=True)
def test_td1_td2_td3_essential_markdowns_only(self):
"""T-D1~D3: Verify essential markdowns are shipped and internal reports/references are excluded."""
res = subprocess.run(
["bash", os.path.join(self.repo_root, "deploy/install.sh"), self.test_dir],
env=self.env,
capture_output=True,
text=True
)
self.assertEqual(res.returncode, 0, f"install.sh failed: {res.stderr}")
# T-D1: No internal reports in .agents/reports
reports_dir = os.path.join(self.test_dir, ".agents", "reports")
if os.path.exists(reports_dir):
reports_files = [f for root, _, files in os.walk(reports_dir) for f in files if f.endswith(".md")]
self.assertEqual(len(reports_files), 0, f"Internal reports found: {reports_files}")
# T-D2: Non-essential root docs omitted by default
self.assertFalse(os.path.exists(os.path.join(self.test_dir, "MESSAGING.md")))
self.assertFalse(os.path.exists(os.path.join(self.test_dir, "BOOTSTRAP.md")))
# T-D3: Core essential docs present (B-3 gate)
self.assertTrue(os.path.exists(os.path.join(self.test_dir, "AGENTS.md")))
self.assertTrue(os.path.exists(os.path.join(self.test_dir, ".agents", "MULTI_AGENT_RULES.md")))
self.assertTrue(os.path.exists(os.path.join(self.test_dir, ".agents", "INSTALL.md")))
self.assertTrue(os.path.exists(os.path.join(self.test_dir, ".agents", "skills", "lib.sh")))
def test_td6_td7_td8_mam_deploy_layout_and_removal(self):
"""T-D6~D8: Verify remove.sh/update.sh installed in .mam_deploy and work properly."""
subprocess.run(
["bash", os.path.join(self.repo_root, "deploy/install.sh"), self.test_dir],
env=self.env,
check=True
)
# T-D6: Installed in .mam_deploy and executable
remover = os.path.join(self.test_dir, ".mam_deploy", "remove.sh")
updater = os.path.join(self.test_dir, ".mam_deploy", "update.sh")
self.assertTrue(os.path.exists(remover))
self.assertTrue(os.path.exists(updater))
self.assertTrue(os.access(remover, os.X_OK))
# T-D8: Execution from inside .mam_deploy directory targets parent workspace
res = subprocess.run(
["bash", "./remove.sh", "--force"],
cwd=os.path.join(self.test_dir, ".mam_deploy"),
capture_output=True,
text=True
)
self.assertEqual(res.returncode, 0, f"remove.sh failed: {res.stderr}")
self.assertFalse(os.path.exists(os.path.join(self.test_dir, ".agents", "skills")))
self.assertFalse(os.path.exists(os.path.join(self.test_dir, ".mam_deploy")))
def test_td12_td13_td14_gitignore_managed_block(self):
"""T-D12~D14: Verify .gitignore managed block injection and manifest exclusion (B-1 gate)."""
subprocess.run(
["bash", os.path.join(self.repo_root, "deploy/install.sh"), self.test_dir],
env=self.env,
check=True
)
gi_path = os.path.join(self.test_dir, ".gitignore")
self.assertTrue(os.path.exists(gi_path))
with open(gi_path) as f:
content = f.read()
self.assertIn("# >>> MAM managed block", content)
self.assertIn("/.mam.env", content)
self.assertIn("/.mam_deploy/", content)
# T-D14 [MERGE BLOCKER B-1]: .gitignore MUST NOT be in manifest
manifest_path = os.path.join(self.test_dir, ".mam", "install_manifest.txt")
with open(manifest_path) as f:
manifest = f.read()
self.assertNotIn(".gitignore", manifest)
def test_td21_td22_td23_safe_refresh_custom_skills(self):
"""T-D21~D23: Verify custom modifications to skills are preserved on refresh (B-4 gate)."""
subprocess.run(
["bash", os.path.join(self.repo_root, "deploy/install.sh"), self.test_dir],
env=self.env,
check=True
)
# Modify a framework skill file
custom_skill = os.path.join(self.test_dir, ".agents", "skills", "lib.sh")
with open(custom_skill, "a") as f:
f.write("\n# CUSTOM_USER_MODIFICATION\n")
# Run install.sh again (REFRESH=1)
res = subprocess.run(
["bash", os.path.join(self.repo_root, "deploy/install.sh"), self.test_dir],
env=self.env,
capture_output=True,
text=True
)
self.assertEqual(res.returncode, 0)
self.assertIn("Local modification detected", res.stderr)
# Verify modification preserved
with open(custom_skill) as f:
self.assertIn("CUSTOM_USER_MODIFICATION", f.read())
# Verify backup created in .mam/skill-backups
backups_dir = os.path.join(self.test_dir, ".mam", "skill-backups")
self.assertTrue(os.path.exists(backups_dir))
def test_td27_td28_update_preserves_mam_state(self):
"""T-D27~D28: Verify update.sh preserves .mam state files across cycles (B-5 gate)."""
subprocess.run(
["bash", os.path.join(self.repo_root, "deploy/install.sh"), self.test_dir],
env=self.env,
check=True
)
# Run update.sh
updater = os.path.join(self.test_dir, ".mam_deploy", "update.sh")
res = subprocess.run(["bash", updater, "--force"], cwd=self.test_dir, env=self.env, capture_output=True, text=True)
self.assertEqual(res.returncode, 0, f"update.sh failed: {res.stderr}")
# Verify asset_hashes.txt and version.txt still exist
self.assertTrue(os.path.exists(os.path.join(self.test_dir, ".mam", "asset_hashes.txt")))
self.assertTrue(os.path.exists(os.path.join(self.test_dir, ".mam", "version.txt")))
if __name__ == "__main__":
unittest.main()