1"""ll-issues fingerprint: extract structured fingerprint from an issue file."""

2

3 from __future__ import annotations

4

5 import argparse

6 import json

7 import re

8 import sys

9 from pathlib import Path

10 from typing import TYPE_CHECKING

11

12 if TYPE_CHECKING:

13 from little_loops.config import BRConfig

14

15

16 def cmd_fingerprint(config: BRConfig, args: argparse.Namespace) -> int:

17 """Extract structured fingerprint (id, files_to_modify, key_terms) from an issue file.

18

19 Reads the issue file and outputs JSON with:

20 - id: issue ID from frontmatter (falls back to filename parsing)

21 - files_to_modify: sorted list of file paths from the Integration Map section

22 - key_terms: sorted list of significant words after stop-word filtering

23

24 Args:

25 config: Project configuration.

26 args: Parsed arguments with .issue_path.

27

28 Returns:

29 0 on success, 1 on error.

30 """

31 from little_loops.parallel.file_hints import extract_file_hints

32 from little_loops.text_utils import extract_words

33

34 issue_path = Path(args.issue_path)

35 if not issue_path.is_absolute():

36 issue_path = config.project_root / issue_path

37

38 if not issue_path.is_file():

39 print(f"Error: issue file not found: {issue_path}", file=sys.stderr)

40 return 1

41

42 content = issue_path.read_text()

43

44 # Extract issue ID from frontmatter

45 issue_id = ""

46 fm_match = re.search(r"^---\n(.*?)^---", content, re.MULTILINE | re.DOTALL)

47 if fm_match:

48 id_match = re.search(r"^id:\s*(\S+)", fm_match.group(1), re.MULTILINE)

49 if id_match:

50 issue_id = id_match.group(1)

51 if not issue_id:

52 # Fallback: parse from filename (P3-ENH-1801-title -> ENH-1801)

53 stem = issue_path.stem

54 parts = stem.split("-")

55 issue_id = "-".join(parts[1:3]) if len(parts) >= 3 else stem

56

57 hints = extract_file_hints(content, issue_id=issue_id)

58 key_terms = extract_words(content)

59

60 result = {

61 "id": issue_id,

62 "files_to_modify": sorted(hints.files),

63 "key_terms": sorted(key_terms),

64 }

65

66 print(json.dumps(result, indent=2))

67 return 0