#!/usr/bin/env python3 """ `issue_init.py` — the statement that makes a directory a project. python3 -m unittest discover -s tests -v The marker is the anchor every other script resolves from, so the command that creates it carries the whole contract: it is idempotent, it never picks a winner between two versions of one issue, and it migrates the old `tmp/` layout by MOVING — a store left behind at the old path is a store somebody edits by accident. Like the rest of the suite, these run the real script against throwaway directories: the script stays where it is installed, the project is somewhere else entirely. """ import os import subprocess import sys import tempfile import unittest REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ISSUE_SCRIPTS = os.path.join(REPO, "skills", "issue", "scripts") INIT = os.path.join(ISSUE_SCRIPTS, "issue_init.py") sys.path.insert(0, ISSUE_SCRIPTS) import issue # noqa: E402 def run(*args, **kw): env = dict(os.environ) env.pop("PYTHONPATH", None) env.pop("CLAUDE_PROJECT_DIR", None) p = subprocess.run([sys.executable, INIT] + list(args), cwd=kw.pop("cwd"), env=env, capture_output=True, text=True) return p.returncode, p.stdout, p.stderr def write(path, text): os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w") as f: f.write(text) class Dir(object): def __init__(self): self._tmp = tempfile.TemporaryDirectory() self.root = os.path.realpath(self._tmp.name) def cleanup(self): self._tmp.cleanup() def path(self, *parts): return os.path.join(self.root, *parts) class TestInit(unittest.TestCase): def setUp(self): self.d = Dir() self.addCleanup(self.d.cleanup) def test_it_creates_the_marker_and_both_directories(self): rc, out, err = run(cwd=self.d.root) self.assertEqual(rc, 0, err) self.assertTrue(os.path.isdir(self.d.path(issue.MARKER, "issues"))) self.assertTrue(os.path.isdir(self.d.path(issue.MARKER, "payload"))) self.assertEqual(issue.project_root(self.d.root), self.d.root) def test_the_store_resolves_from_a_subdirectory_afterwards(self): run(cwd=self.d.root) deep = self.d.path("a", "b", "c") os.makedirs(deep) self.assertEqual(issue.store_root(deep), self.d.path(*issue.STORE_PARTS)) def test_running_it_twice_changes_nothing(self): run(cwd=self.d.root) before = sorted(os.walk(self.d.root)) rc, out, err = run(cwd=self.d.root) self.assertEqual(rc, 0, err) self.assertIn("already initialized", out) self.assertEqual(sorted(os.walk(self.d.root)), before) def test_a_dry_run_touches_nothing(self): rc, out, err = run("--dry-run", cwd=self.d.root) self.assertEqual(rc, 0, err) self.assertIn("would:", out) self.assertFalse(os.path.exists(self.d.path(issue.MARKER))) def test_at_initializes_somewhere_else(self): other = Dir() self.addCleanup(other.cleanup) rc, out, err = run("--at", other.root, cwd=self.d.root) self.assertEqual(rc, 0, err) self.assertTrue(os.path.isdir(other.path(issue.MARKER))) self.assertFalse(os.path.exists(self.d.path(issue.MARKER))) class TestGitignore(unittest.TestCase): def setUp(self): self.d = Dir() self.addCleanup(self.d.cleanup) def test_the_marker_is_added(self): run(cwd=self.d.root) with open(self.d.path(".gitignore")) as f: self.assertIn(issue.MARKER + "/", f.read().split()) def test_an_existing_gitignore_keeps_its_contents(self): write(self.d.path(".gitignore"), "node_modules/\n*.log\n") run(cwd=self.d.root) with open(self.d.path(".gitignore")) as f: lines = f.read().split() self.assertIn("node_modules/", lines) self.assertIn("*.log", lines) self.assertIn(issue.MARKER + "/", lines) def test_it_is_not_added_twice(self): write(self.d.path(".gitignore"), issue.MARKER + "\n") run(cwd=self.d.root) with open(self.d.path(".gitignore")) as f: body = f.read() self.assertEqual(body.count(issue.MARKER), 1, body) class TestMigration(unittest.TestCase): """The old layout moves in. Moves, not copies: two stores is the state this whole change exists to prevent.""" def setUp(self): self.d = Dir() self.addCleanup(self.d.cleanup) def test_an_old_store_is_moved_in(self): write(self.d.path("tmp", "issues", "old-work.md"), "id: old-work\n") write(self.d.path("tmp", "payload", "request.json"), "{}") rc, out, err = run(cwd=self.d.root) self.assertEqual(rc, 0, err) self.assertTrue(os.path.isfile( self.d.path(issue.MARKER, "issues", "old-work.md"))) self.assertTrue(os.path.isfile( self.d.path(issue.MARKER, "payload", "request.json"))) self.assertFalse(os.path.exists(self.d.path("tmp", "issues")), "the old store was left behind for somebody to edit") self.assertIn("moved 1 file(s)", out) def test_a_clash_stops_everything_and_moves_nothing(self): write(self.d.path("tmp", "issues", "same.md"), "old version\n") write(self.d.path(issue.MARKER, "issues", "same.md"), "new version\n") rc, out, err = run(cwd=self.d.root) self.assertNotEqual(rc, 0) self.assertIn("same.md", out + err) with open(self.d.path("tmp", "issues", "same.md")) as f: self.assertEqual(f.read(), "old version\n") with open(self.d.path(issue.MARKER, "issues", "same.md")) as f: self.assertEqual(f.read(), "new version\n") def test_a_dry_run_reports_the_move_without_making_it(self): write(self.d.path("tmp", "issues", "old-work.md"), "id: old-work\n") rc, out, err = run("--dry-run", cwd=self.d.root) self.assertEqual(rc, 0, err) self.assertIn("moved 1 file(s)", out) self.assertTrue(os.path.isfile(self.d.path("tmp", "issues", "old-work.md"))) self.assertFalse(os.path.exists(self.d.path(issue.MARKER))) def test_no_old_layout_is_not_an_error(self): rc, out, err = run(cwd=self.d.root) self.assertEqual(rc, 0, err) self.assertNotIn("moved", out) class TestNesting(unittest.TestCase): def setUp(self): self.d = Dir() self.addCleanup(self.d.cleanup) run(cwd=self.d.root) def test_initializing_inside_a_project_warns(self): inner = self.d.path("packages", "api") os.makedirs(inner) rc, out, err = run(cwd=inner) self.assertEqual(rc, 0, err) self.assertIn("already sits inside the project", err) self.assertIn(self.d.root, err) def test_the_warning_does_not_stop_it(self): """A monorepo package that genuinely wants its own issues is allowed to say so. The warning is that the nearer marker wins from then on, which is a consequence worth reading, not an error.""" inner = self.d.path("packages", "api") os.makedirs(inner) run(cwd=inner) self.assertEqual(issue.project_root(inner), inner) self.assertEqual(issue.project_root(self.d.root), self.d.root) if __name__ == "__main__": unittest.main()