diff --git a/libs/beacon/src/beacon/data/skills/contribute-warehouse/scripts/draft_commit_message.py b/libs/beacon/src/beacon/data/skills/contribute-warehouse/scripts/draft_commit_message.py index ab69122..3f62a96 100644 --- a/libs/beacon/src/beacon/data/skills/contribute-warehouse/scripts/draft_commit_message.py +++ b/libs/beacon/src/beacon/data/skills/contribute-warehouse/scripts/draft_commit_message.py @@ -8,8 +8,10 @@ Mapping table (documented inline): Type prefix rules: - - All paths under skills/ AND at least one new file (status A) → feat - - All paths under skills/ AND only modifications (status M) → fix + - All paths under skills/ AND at least one new file (status + first or second column is A, ?, R, or C) → feat + - All paths under skills/ AND only modifications (status M, D, + or space in both columns) → fix - All paths under contexts/ or knowledge/ → docs - All paths under agents/ AND new file → feat - All paths under agents/ AND modification → fix @@ -118,8 +120,9 @@ def derive_type(paths: list[str], git_statuses: list[str] | None = None) -> str: Args: paths: List of warehouse-relative path strings. - git_statuses: Optional list of single-char git status codes per path. - Use 'A' for new/added files, 'M' for modifications, '?' for untracked. + git_statuses: Optional list of two-character git porcelain status + codes per path (index column + worktree column). A file is + treated as "new" when either column is A, ?, R, or C. Returns: One of: 'feat', 'fix', 'docs', 'chore', 'refactor', 'test'. @@ -143,10 +146,13 @@ def derive_type(paths: list[str], git_statuses: list[str] | None = None) -> str: return "docs" if top in ("skills", "agents"): - # Check statuses to distinguish feat vs fix + # Check statuses to distinguish feat vs fix. + # Porcelain is two chars: index column + worktree column. + # Treat as "new" when either column is A, ?, R, or C. if git_statuses: - statuses_clean = [s.strip() for s in git_statuses] - has_new = any(s in ("A", "A ", " A", "??") for s in statuses_clean) + has_new = any( + any(ch in "A?RC" for ch in status[:2]) for status in git_statuses + ) else: # Without status information, assume new files (feat) has_new = True diff --git a/libs/beacon/tests/unit/data/skills/contribute_warehouse/test_draft_commit_message.py b/libs/beacon/tests/unit/data/skills/contribute_warehouse/test_draft_commit_message.py index ed98ae4..e4c649a 100644 --- a/libs/beacon/tests/unit/data/skills/contribute_warehouse/test_draft_commit_message.py +++ b/libs/beacon/tests/unit/data/skills/contribute_warehouse/test_draft_commit_message.py @@ -322,6 +322,64 @@ def test_tc8_empty_paths_raises(self): # ───────────────────────────────────────────────────────────────────────────── +class TestCompoundPorcelainStatusCodes: + """PER-185: compound two-char git status codes (AM, AD, MM, RM, etc.).""" + + def test_am_added_then_modified_is_feat(self): + """AM (added in index, modified in worktree) → feat.""" + mod = _load_script() + result = mod.derive_type( + ["skills/foo/SKILL.md"], + git_statuses=["AM"], + ) + assert result == "feat" + + def test_ad_added_then_deleted_is_feat(self): + """AD (added in index, deleted in worktree) → feat.""" + mod = _load_script() + result = mod.derive_type( + ["skills/foo/SKILL.md"], + git_statuses=["AD"], + ) + assert result == "feat" + + def test_mm_modified_both_columns_is_fix(self): + """MM (modified in index and worktree) → fix.""" + mod = _load_script() + result = mod.derive_type( + ["skills/foo/SKILL.md"], + git_statuses=["MM"], + ) + assert result == "fix" + + def test_rm_renamed_then_modified_is_feat(self): + """RM (renamed in index, modified in worktree) → feat.""" + mod = _load_script() + result = mod.derive_type( + ["skills/foo/SKILL.md"], + git_statuses=["RM"], + ) + assert result == "feat" + + def test_worktree_deleted_is_fix(self): + """' D' (deleted in worktree) → fix.""" + mod = _load_script() + result = mod.derive_type( + ["skills/foo/SKILL.md"], + git_statuses=[" D"], + ) + assert result == "fix" + + def test_index_deleted_is_fix(self): + """'D ' (deleted in index) → fix.""" + mod = _load_script() + result = mod.derive_type( + ["skills/foo/SKILL.md"], + git_statuses=["D "], + ) + assert result == "fix" + + class TestDocumentedInvocationWithGitStatuses: """Tests for Finding 3 fix: SKILL.md documented invocation passes --git-statuses."""