agent: Update specs downloader and document commit style

- Make downloader.py dynamic to find latest spec versions automatically
- Remove leading whitespaces from empty lines in downloader.py
- Add commit message guidelines to GEMINI.md

Test: Verified downloader script execution and file formatting
Change-Id: I687ca9fb55bebae97b0a679e1dbc1101a4bc6a43
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/3241
diff --git a/.agents/skills/le_profiles_specifications/scripts/downloader.py b/.agents/skills/le_profiles_specifications/scripts/downloader.py
index a50acec..d9ed4ef 100644
--- a/.agents/skills/le_profiles_specifications/scripts/downloader.py
+++ b/.agents/skills/le_profiles_specifications/scripts/downloader.py
@@ -1,4 +1,5 @@
 import os
+import re
 from urllib.request import urlopen, Request
 from urllib.error import URLError
 from bs4 import BeautifulSoup
@@ -8,71 +9,66 @@
     "specifications",
 )
 
-# List of specifications to download
-SPECS = [
-    {
-        "name": "BAP_v1.0.2",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/BAP_v1.0.2/out/en/index-en.html",
-    },
-    {
-        "name": "CAP_v1.0.1",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/CAP_v1.0.1/out/en/index-en.html",
-    },
-    {
-        "name": "TMAP_v1.0.1",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/TMAP_v1.0.1/out/en/index-en.html",
-    },
-    {
-        "name": "HAP_v1.0.1",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/HAP_v1.0.1/out/en/index-en.html",
-    },
-    {
-        "name": "PBP_v1.0.1",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/PBP_v1.0.1/out/en/index-en.html",
-    },
-    {
-        "name": "MCP_v1.0",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/28982-MCP-html5/out/en/index-en.html",
-    },
-    {
-        "name": "CCP_v1.0",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/26587-CCP-html5/out/en/index-en.html",
-    },
-    {
-        "name": "MICP_v1.0",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/21264-MICP-html5/out/en/index-en.html",
-    },
-    {
-        "name": "VCP_v1.0",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/76878-VCP-html5/out/en/index-en.html",
-    },
-    {
-        "name": "ASCS_v1.0.1",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/ASCS_v1.0.1/out/en/index-en.html",
-    },
-    {
-        "name": "PACS_v1.0.2",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/PACS_v1.0.2/out/en/index-en.html",
-    },
-    {
-        "name": "CSIS_v1.0",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/28085-CSIS-html5/out/en/index-en.html",
-    },
-    {
-        "name": "CSIP_v1.0",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/27407-CSIP-html5/out/en/index-en.html",
-    },
-    {
-        "name": "BASS_v1.0.1",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/BASS_v1.0.1/out/en/index-en.html",
-    },
-    {
-        "name": "AICS_v1.0.1",
-        "url": "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/AICS_v1.0.1/out/en/index-en.html",
-    },
-]
+ROOT_URL = "https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/"
 
-def download_spec(name, url):
+# Targets to download
+TARGETS = ['BAP', 'CAP', 'TMAP', 'HAP', 'PBP', 'MCP', 'CCP', 'MICP', 'VCP', 'ASCS', 'PACS', 'CSIS', 'CSIP', 'BASS', 'AICS']
+
+def get_all_index_links():
+    print(f"Fetching directory listing from {ROOT_URL}...")
+    headers = {'User-Agent': 'Mozilla/5.0'}
+    try:
+        req = Request(ROOT_URL, headers=headers)
+        with urlopen(req, timeout=30) as response:
+            html_content = response.read()
+            soup = BeautifulSoup(html_content, 'html.parser')
+            links = []
+            for a in soup.find_all('a'):
+                href = a.get('href', '')
+                if "index-en.html" in href:
+                    links.append(href)
+            return links
+    except URLError as e:
+        print(f"Error fetching root directory: {e}")
+        return []
+
+def match_target(path, acronym):
+    pattern = re.compile(rf"(?:^|/|_|-)({acronym})(?:[._-]|html5)", re.IGNORECASE)
+    return bool(pattern.search(path))
+
+def parse_version(path, acronym):
+    pattern = re.compile(rf"{acronym}_v(\d+(?:[.-]\d+)*)", re.IGNORECASE)
+    match = pattern.search(path)
+    if match:
+        return match.group(1).replace('-', '.')
+    return "0.0"
+
+def get_latest_link(links, acronym):
+    target_links = [l for l in links if match_target(l, acronym)]
+    if not target_links:
+        return None
+
+    timestamp_pattern = re.compile(r"_(\d{10})/")
+
+    def sort_key(path):
+        version_str = parse_version(path, acronym)
+        version_parts = []
+        for x in version_str.split('.'):
+            try:
+                version_parts.append(int(x))
+            except ValueError:
+                version_parts.append(0)
+
+        timestamp_match = timestamp_pattern.search(path)
+        timestamp = int(timestamp_match.group(1)) if timestamp_match else 0
+
+        return (version_parts, timestamp)
+
+    sorted_links = sorted(target_links, key=sort_key, reverse=True)
+    return sorted_links[0]
+
+def download_spec(name, relative_url):
+    url = ROOT_URL + relative_url
     print(f"Downloading {name} from {url}...")
     headers = {'User-Agent': 'Mozilla/5.0'}
     try:
@@ -104,8 +100,24 @@
         print(f"An error occurred: {e}")
 
 def main():
-    for spec in SPECS:
-        download_spec(spec["name"], spec["url"])
+    links = get_all_index_links()
+    if not links:
+        print("No links found.")
+        return
+
+    for target in TARGETS:
+        latest_link = get_latest_link(links, target)
+        if latest_link:
+            version = parse_version(latest_link, target)
+            name = f"{target}_v{version}" if version != "0.0" else target
+            if version == "0.0":
+                match = re.search(rf"(\d+)-{target}-html5", latest_link, re.IGNORECASE)
+                if match:
+                    name = f"{target}_ID_{match.group(1)}"
+
+            download_spec(name, latest_link)
+        else:
+            print(f"No link found for {target}")
 
 if __name__ == "__main__":
     main()
diff --git a/GEMINI.md b/GEMINI.md
index 3b84746..6628610 100644
--- a/GEMINI.md
+++ b/GEMINI.md
@@ -1,3 +1,8 @@
 # Gemini Assistant Guidelines - Bluetooth Workspace
 
-See [README.md](./README.md) for project structure and description, and general guidelines for development.
\ No newline at end of file
+See [README.md](./README.md) for project structure and description, and general guidelines for development.
+
+## Guidelines
+
+### Commit Messages
+When asked to write or suggest a commit message, strictly follow the rules in [Commit message style](./docs/commit_messages.md).
\ No newline at end of file