diff --git a/.agent/skills/bootstrap-clever-work/SKILL.md b/.agent/skills/bootstrap-clever-work/SKILL.md index 163fa3a..74768cc 100644 --- a/.agent/skills/bootstrap-clever-work/SKILL.md +++ b/.agent/skills/bootstrap-clever-work/SKILL.md @@ -34,13 +34,12 @@ Use this local folder layout unless the user has explicitly provided another pat clever-agent-project/ clever-context-monorepo/ clever-change-control/ - projects/ - / + / ``` The three agent/control-plane repositories stay under `/clever-agent-workspace/`. Remote product/service repositories are cloned or pulled under -`/projects//`. Seed files are copied +`//`. Seed files are copied into that target repo root. - `AGENTS.md`: agent execution procedure, working order, issue/branch rules, verification, context update checks, and completion conditions @@ -71,7 +70,7 @@ Interpret the result like this: - Always read `workspace_check.session_open_check` before startup questions. The Python preflight must confirm the CLEVER_ROOT-based session layout first: three control-plane repos under `/clever-agent-workspace/`, target - repos under `/projects//`. + repos under `//`. - Always read `preflight_check.agent_response_contract` before replying to a CLEVER_ROOT-level prompt. It defines the response style: inherit the CLEVER agent workflow, complete initial setup, then continue with the provided prompt. @@ -389,7 +388,7 @@ Once the user approves: - 새 target repo는 public으로 생성한다. - Use `gh repo create / --public` for a newly created target repo. - GitHub Free organization rulesets are enforced on public repositories; private repository enforcement requires GitHub Team, GitHub Pro, or GitHub Enterprise Cloud. -5. Clone or pull the target repo under `/projects//`. +5. Clone or pull the target repo under `//`. 6. Copy the target repo seed files before handoff: - `docs/templates/target-repo-AGENTS.md` -> target repo `AGENTS.md` - `docs/templates/target-repo-project-brief.md` -> target repo `docs/project-brief.md` diff --git a/.agent/skills/bootstrap-clever-work/scripts/bootstrap_clever_work.py b/.agent/skills/bootstrap-clever-work/scripts/bootstrap_clever_work.py index a1c42ee..0bb4e29 100644 --- a/.agent/skills/bootstrap-clever-work/scripts/bootstrap_clever_work.py +++ b/.agent/skills/bootstrap-clever-work/scripts/bootstrap_clever_work.py @@ -16,7 +16,6 @@ CONTEXT_REPO_NAME = "clever-context-monorepo" CHANGE_REPO_NAME = "clever-change-control" AGENT_WORKSPACE_DIR_NAME = "clever-agent-workspace" -PROJECTS_DIR_NAME = "projects" START_REPO_NAME = "clever-agent-project" CONTROL_PLANE_REPOS = ( START_REPO_NAME, @@ -252,7 +251,7 @@ def infer_workspace_root(start: Path, git_root: Path | None = None) -> Path: Preferred layout: /clever-agent-workspace/{three control-plane repos} - /projects/ + / Legacy direct layout is still recognized so existing local checkouts can report a useful migration-oriented preflight instead of failing path discovery. @@ -297,15 +296,22 @@ def is_relative_to_path(path: Path, parent: Path) -> bool: def build_session_open_check(*, cwd: Path, control_plane_root: Path, current_repo: str | None) -> dict[str, Any]: clever_work_root = derive_clever_work_root(control_plane_root) expected_control_plane_root = clever_work_root / AGENT_WORKSPACE_DIR_NAME - projects_root = clever_work_root / PROJECTS_DIR_NAME + target_repo_parent_root = clever_work_root uses_expected_nested_layout = control_plane_root.resolve() == expected_control_plane_root.resolve() cwd_under_control_plane = is_relative_to_path(cwd, control_plane_root) - cwd_under_projects = is_relative_to_path(cwd, projects_root) + cwd_under_clever_root = is_relative_to_path(cwd, clever_work_root) + cwd_is_clever_root = cwd.resolve() == clever_work_root.resolve() + cwd_under_target_repo_area = ( + cwd_under_clever_root + and not cwd_under_control_plane + and not cwd_is_clever_root + and current_repo not in CONTROL_PLANE_REPOS + ) opened_from_start_repo = current_repo == START_REPO_NAME and cwd_under_control_plane opened_from_control_plane_repo = current_repo in CONTROL_PLANE_REPOS and cwd_under_control_plane if uses_expected_nested_layout: - status = "pass" if (opened_from_control_plane_repo or cwd_under_projects) else "fail" + status = "pass" if (opened_from_control_plane_repo or cwd_under_target_repo_area) else "fail" layout_mode = "clever-root-with-agent-workspace" else: status = "legacy-layout" if opened_from_control_plane_repo else "fail" @@ -317,9 +323,9 @@ def build_session_open_check(*, cwd: Path, control_plane_root: Path, current_rep "clever_root": str(clever_work_root), "control_plane_root": str(control_plane_root), "expected_control_plane_root": str(expected_control_plane_root), - "projects_root": str(projects_root), + "target_repo_parent_root": str(target_repo_parent_root), "cwd_under_control_plane_root": cwd_under_control_plane, - "cwd_under_projects_root": cwd_under_projects, + "cwd_under_target_repo_parent_root": cwd_under_target_repo_area, "opened_from_start_repo": opened_from_start_repo, "opened_from_control_plane_repo": opened_from_control_plane_repo, "message": ( @@ -327,7 +333,7 @@ def build_session_open_check(*, cwd: Path, control_plane_root: Path, current_rep if status == "pass" else "Session uses the legacy direct control-plane layout; prefer /clever-agent-workspace for the three agent repos." if status == "legacy-layout" - else "Session is not opened from the expected CLEVER_ROOT control-plane or projects layout." + else "Session is not opened from the expected CLEVER_ROOT control-plane or target repo layout." ), } @@ -427,7 +433,7 @@ def build_workspace_check(start: Path, *, current_repo_maintenance: bool = False "clever_root": str(clever_root), "clever_work_root": session_open_check["clever_root"], "control_plane_root": session_open_check["control_plane_root"], - "projects_root": session_open_check["projects_root"], + "target_repo_parent_root": session_open_check["target_repo_parent_root"], "session_open_check": session_open_check, "control_plane_complete": control_plane_complete, "current_repo_is_start": current_repo_is_start, @@ -747,7 +753,7 @@ def build_agent_response_contract(workspace_check: dict[str, Any]) -> dict[str, "confirm the CLEVER_ROOT session layout with workspace_check.session_open_check", "create or confirm the target repository only after the project-start gate", "apply or confirm repository rules before normal development", - "clone or pull the target repo under /projects/", + "clone or pull the target repo under /", "inject AGENTS.md, docs/project-brief.md, PR template, and ruleset script into the target repo root", "after initial setup succeeds, continue according to the user's provided prompt", ] @@ -1483,13 +1489,13 @@ def build_packet( "clever-context-monorepo", "clever-change-control", ], - "project_repositories_root": "/projects", - "target_repo_checkout": "/projects/", + "target_repo_parent_root": "", + "target_repo_checkout": "/", "seed_injection_root": "target repo root", }, "post_create_clone": [ "create-or-confirm public target repo after project-start approval", - "clone-or-pull the target repo under /projects/", + "clone-or-pull the target repo under /", "copy target repo seed files into the target repo root before handoff", "apply GitHub rulesets after dev exists", "verify local checkout is ready for follow-on work", @@ -1606,7 +1612,7 @@ def print_workspace_check(workspace_check: dict[str, Any]) -> None: print(f"clever-root: {workspace_check['clever_root']}") print(f"clever-work-root: {workspace_check['clever_work_root']}") print(f"control-plane-root: {workspace_check['control_plane_root']}") - print(f"projects-root: {workspace_check['projects_root']}") + print(f"target-repo-parent-root: {workspace_check['target_repo_parent_root']}") session_open_check = workspace_check.get("session_open_check", {}) print(f"session-open-check: {session_open_check.get('status')}") print(f"session-open-message: {session_open_check.get('message')}") diff --git a/AGENTS.md b/AGENTS.md index 3c97f5c..95a7a78 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,13 +23,12 @@ Recommended local layout: clever-agent-project/ clever-context-monorepo/ clever-change-control/ - projects/ - / + / ``` `` is the top-level CLEVER work root. Keep the three agent/control repositories inside `/clever-agent-workspace/`. Put real product/service target -repositories under `/projects//`. When +repositories under `//`. When bootstrapping a target repo, clone or pull the remote repo into that project folder, then inject the agent documents into the target repo root. @@ -85,9 +84,9 @@ Read these preflight output fields before asking anything: - `workspace_check.session_open_check`: the Python preflight's CLEVER_ROOT-based session-open validation. It must confirm the expected structure `/clever-agent-workspace/{three control-plane repos}` and - `/projects/` before startup work + `/` before startup work proceeds. If it reports `legacy-layout`, treat it as a migration warning and - keep the target project checkout under `/projects/`. If it + keep the target project checkout under `/`. If it reports `fail`, stop and fix the session location first. - `auto_skipped_questions`: questions already answered by tool evidence, such as GitHub login inference, startup location, or dirty-state inspection. @@ -777,7 +776,7 @@ The expected operating flow is: 5. Anchor the root line in `clever-change-control` 6. Fix scoped execution 7. Apply or confirm the repo branch operating contract -8. Handoff to the target repository under `/projects//` +8. Handoff to the target repository under `//` 9. Feed rollout / rollback / release evidence back into `clever-change-control` ## If The Workspace Is Incomplete diff --git a/README.md b/README.md index a91ea21..a2ce0e7 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ CLEVER는 한 가지 앱 전용 흐름이 아니다. 사용자가 익숙한 도 첫 화면에서는 직접 shell 명령보다 **어떤 환경에서 시작하는지**를 먼저 고른다. 공통 원칙은 **3개 레포를 같은 로컬 workspace에 두고, 일반 시작은 `clever-agent-project`에서 진행한다**는 점이다. -권장 폴더 구조는 아래처럼 나눈다. ``는 전체 CLEVER 작업 루트이고, 에이전트 제어 평면은 `/clever-agent-workspace/`이며, 실제 제품/서비스 코드는 `/projects//` 아래에 둔다. target repo를 만들거나 확인한 뒤에는 이 target repo 폴더에 원격 repo를 clone/pull 하고, 그 repo 루트에 `AGENTS.md`, `docs/project-brief.md`, `.github/PULL_REQUEST_TEMPLATE.md`, `scripts/apply-github-rulesets.sh`를 주입한다. +권장 폴더 구조는 아래처럼 나눈다. ``는 전체 CLEVER 작업 루트이고, 에이전트 제어 평면은 `/clever-agent-workspace/`이며, 실제 제품/서비스 코드는 `//` 아래에 둔다. target repo를 만들거나 확인한 뒤에는 이 target repo 폴더에 원격 repo를 clone/pull 하고, 그 repo 루트에 `AGENTS.md`, `docs/project-brief.md`, `.github/PULL_REQUEST_TEMPLATE.md`, `scripts/apply-github-rulesets.sh`를 주입한다. ```text / @@ -27,12 +27,11 @@ CLEVER는 한 가지 앱 전용 흐름이 아니다. 사용자가 익숙한 도 clever-agent-project/ clever-context-monorepo/ clever-change-control/ - projects/ - / - AGENTS.md - docs/project-brief.md - .github/PULL_REQUEST_TEMPLATE.md - scripts/apply-github-rulesets.sh + / + AGENTS.md + docs/project-brief.md + .github/PULL_REQUEST_TEMPLATE.md + scripts/apply-github-rulesets.sh ``` 브라우저에서 버튼과 텍스트 입력으로 한 번에 정리하려면 [GitHub Pages 시작 도우미](https://evnsolution.github.io/clever-agent-project/start/)를 사용한다. 버튼과 텍스트 입력으로 답한 뒤 최종 copy text를 만들고, 에이전트에 붙여 넣을 위치까지 안내한다. @@ -51,7 +50,7 @@ CLEVER는 한 가지 앱 전용 흐름이 아니다. 사용자가 익숙한 도 - https://github.com/EVNSolution/clever-change-control.git 3. 이후 작업 기준은 /clever-agent-workspace/clever-agent-project 로 잡아줘. 4. clever-agent-project의 README와 AGENTS.md 지침을 읽고, 그 지침에 따라 preflight와 작업 준비를 진행해줘. -5. 실제 제품/서비스 target repo는 /projects// 아래에 준비해줘. +5. 실제 제품/서비스 target repo는 // 아래에 준비해줘. 6. target repo를 만들거나 확인한 뒤에는 clever-agent-project 지침에 따라 agent 문서를 target repo에 주입해줘. 7. 초기 준비가 끝나기 전에는 구현, 커밋, PR 생성을 시작하지 마. 8. 초기 준비가 성공하면 아래 형식으로 답해줘. diff --git a/docs/setting.md b/docs/setting.md index aec60da..f19c092 100644 --- a/docs/setting.md +++ b/docs/setting.md @@ -149,15 +149,14 @@ CLEVER 관련 저장소는 하나의 workspace root 아래에 두는 것을 권 clever-agent-project/ clever-change-control/ clever-context-monorepo/ - projects/ - / + / ``` ``는 전체 CLEVER 작업 루트다. 에이전트 제어 평면은 `/clever-agent-workspace/`이고, 3대 레포는 항상 그 안의 sibling으로 둔다. -실제 제품/서비스 원격 repo는 `/projects//` +실제 제품/서비스 원격 repo는 `//` 아래에 clone 또는 pull 한다. 새 target repo seed 파일은 이 target repo 루트에 주입한다. -generic CLEVER startup 세션은 `/clever-agent-workspace/clever-agent-project`에서 시작한다. 승인 후 target GitHub repo를 생성하거나 확인한 다음, `/projects/` 아래에 로컬 clone 또는 pull 하고 그 target repo 루트에서 새 세션을 시작한다. +generic CLEVER startup 세션은 `/clever-agent-workspace/clever-agent-project`에서 시작한다. 승인 후 target GitHub repo를 생성하거나 확인한 다음, `/` 아래에 로컬 clone 또는 pull 하고 그 target repo 루트에서 새 세션을 시작한다. 예외는 sibling control-plane repo 자체를 직접 수정하는 경우다. @@ -353,7 +352,7 @@ PR 정보를 wiki에 올리는 것이 아니다. wiki에는 필요한 서비스/ ### 새 target repo 초기 seed 파일 -새 target repo를 만들거나 첫 target repo를 bootstrap할 때는 프로젝트 기획 초안과 agent 실행 절차서를 분리해서 넣는다. 주입 위치는 `/projects//`에 clone/pull된 target repo 루트다. +새 target repo를 만들거나 첫 target repo를 bootstrap할 때는 프로젝트 기획 초안과 agent 실행 절차서를 분리해서 넣는다. 주입 위치는 `//`에 clone/pull된 target repo 루트다. - [target repo AGENTS template](templates/target-repo-AGENTS.md) -> target repo `AGENTS.md` - [target repo project brief template](templates/target-repo-project-brief.md) -> target repo `docs/project-brief.md` @@ -394,9 +393,9 @@ pull/clone, target repo agent 문서 주입을 먼저 끝낸 뒤 "초기 작업 `workspace_check.session_open_check`는 Python preflight가 현재 세션이 `` 기준으로 열렸는지 확인한 결과다. `pass`이면 -`/clever-agent-workspace/`와 `/projects/` 구조가 맞다. +`/clever-agent-workspace/`와 `/` 구조가 맞다. `legacy-layout`이면 기존 direct control-plane layout에서 실행 중이라는 뜻이므로 -새 target repo는 반드시 `/projects//`에 +새 target repo는 반드시 `//`에 둔다. `fail`이면 작업 질문으로 내려가기 전에 세션 위치를 먼저 고친다. `true`이면 내부 `workspace_check.agent_action`이 아래 중 하나를 돌려준다. diff --git a/tests/test_bootstrap_clever_work.py b/tests/test_bootstrap_clever_work.py index abaeb55..29fe833 100644 --- a/tests/test_bootstrap_clever_work.py +++ b/tests/test_bootstrap_clever_work.py @@ -263,7 +263,7 @@ def test_build_workspace_check_reports_ready_from_agent_project(): assert report["missing_repositories"] == [] assert report["session_open_check"]["status"] in {"pass", "legacy-layout"} assert "control_plane_root" in report["session_open_check"] - assert "projects_root" in report["session_open_check"] + assert "target_repo_parent_root" in report["session_open_check"] def test_build_workspace_check_requires_switch_when_started_from_change_control(): @@ -312,14 +312,13 @@ def test_build_workspace_check_detects_clever_root_agent_workspace_layout(tmp_pa module = load_module() clever_root = tmp_path / "CLEVER_ROOT" agent_workspace = clever_root / "clever-agent-workspace" - projects_root = clever_root / "projects" + target_repo_parent_root = clever_root for repo_name in [ "clever-agent-project", "clever-context-monorepo", "clever-change-control", ]: (agent_workspace / repo_name).mkdir(parents=True) - projects_root.mkdir() monkeypatch_values = { agent_workspace / "clever-agent-project": "clever-agent-project", @@ -343,12 +342,57 @@ def fake_git_root(path: Path): assert report["clever_work_root"] == str(clever_root) assert report["control_plane_root"] == str(agent_workspace) - assert report["projects_root"] == str(projects_root) + assert report["target_repo_parent_root"] == str(target_repo_parent_root) assert report["session_open_check"]["status"] == "pass" assert report["session_open_check"]["layout_mode"] == "clever-root-with-agent-workspace" assert report["recommended_start_repo"] == str(agent_workspace / "clever-agent-project") +def test_build_workspace_check_detects_direct_target_repo_under_clever_root(tmp_path: Path): + module = load_module() + clever_root = tmp_path / "CLEVER_ROOT" + agent_workspace = clever_root / "clever-agent-workspace" + target_repo = clever_root / "thundercrew-domain" + for repo_name in [ + "clever-agent-project", + "clever-context-monorepo", + "clever-change-control", + ]: + (agent_workspace / repo_name).mkdir(parents=True) + target_repo.mkdir(parents=True) + + monkeypatch_values = { + agent_workspace / "clever-agent-project": "clever-agent-project", + agent_workspace / "clever-context-monorepo": "clever-context-monorepo", + agent_workspace / "clever-change-control": "clever-change-control", + target_repo: "thundercrew-domain", + } + control_plane_roots = { + agent_workspace / "clever-agent-project", + agent_workspace / "clever-context-monorepo", + agent_workspace / "clever-change-control", + } + + def fake_git_root(path: Path): + path = path.resolve() + for candidate in monkeypatch_values: + if path == candidate or candidate in path.parents: + return candidate + return None + + module.try_find_git_root = fake_git_root + module.try_repo_name = lambda path: monkeypatch_values.get(path) if path else None + module.current_branch = lambda _path: "main" + module.is_checkout_root = lambda path: path in control_plane_roots + + report = module.build_workspace_check(target_repo) + + assert report["clever_work_root"] == str(clever_root) + assert report["target_repo_parent_root"] == str(clever_root) + assert report["session_open_check"]["status"] == "pass" + assert report["session_open_check"]["cwd_under_target_repo_parent_root"] is True + + @pytest.mark.skipif( not CHANGE_WORKTREE.is_dir() or not CONTEXT_WORKTREE.is_dir() @@ -486,8 +530,8 @@ def test_build_packet_includes_target_repo_seed_files(): "clever-context-monorepo", "clever-change-control", ], - "project_repositories_root": "/projects", - "target_repo_checkout": "/projects/", + "target_repo_parent_root": "", + "target_repo_checkout": "/", "seed_injection_root": "target repo root", } assert ( @@ -495,7 +539,7 @@ def test_build_packet_includes_target_repo_seed_files(): in packet["repo_bootstrap"]["post_create_clone"] ) assert any( - "/projects/" in step + "/" in step for step in packet["repo_bootstrap"]["post_create_clone"] ) assert ( @@ -524,7 +568,7 @@ def test_target_repo_seed_templates_separate_execution_rules_from_project_brief( assert "agent 작업 절차" in project_brief -def test_control_plane_docs_define_projects_folder_layout(): +def test_control_plane_docs_define_single_target_repo_folder_layout(): docs = [ REPO_ROOT / "AGENTS.md", REPO_ROOT / "README.md", @@ -539,9 +583,9 @@ def test_control_plane_docs_define_projects_folder_layout(): assert "clever-agent-project/" in text assert "clever-context-monorepo/" in text assert "clever-change-control/" in text - assert "projects/" in text + assert "projects/" not in text assert "" not in text - assert "projects/" in text + assert "" in text assert "/" in text setting = (REPO_ROOT / "docs/setting.md").read_text(encoding="utf-8") @@ -818,13 +862,13 @@ def test_build_packet_includes_post_create_clone_and_handoff_plan(): assert repo_bootstrap["post_create_clone"] == [ "create-or-confirm public target repo after project-start approval", - "clone-or-pull the target repo under /projects/", + "clone-or-pull the target repo under /", "copy target repo seed files into the target repo root before handoff", "apply GitHub rulesets after dev exists", "verify local checkout is ready for follow-on work", ] assert repo_bootstrap["local_folder_layout"]["target_repo_checkout"] == ( - "/projects/" + "/" ) assert handoff["recommended_session"] == "new-target-repo-session" assert handoff["status"] == "recommended-after-clone" @@ -1451,7 +1495,7 @@ def test_readme_includes_short_new_root_user_prompt_for_agent_bootstrap(): assert "https://github.com/EVNSolution/clever-change-control.git" in root_prompt assert "clever-agent-project의 README와 AGENTS.md 지침" in root_prompt assert "preflight와 작업 준비" in root_prompt - assert "/projects/" in root_prompt + assert "/" in root_prompt assert "agent 문서를 target repo에 주입" in root_prompt assert "구현, 커밋, PR 생성을 시작하지 마" in root_prompt assert "초기 작업(작업 루트 생성, 3대 레포 준비, preflight, target repo 준비, 에이전트 문서 주입)이 완료됐습니다" in root_prompt