moai-adk 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of moai-adk might be problematic. Click here for more details.

Files changed (94) hide show
  1. moai_adk/cli/commands/update.py +15 -4
  2. moai_adk/core/issue_creator.py +309 -0
  3. moai_adk/core/tags/__init__.py +87 -0
  4. moai_adk/core/tags/ci_validator.py +435 -0
  5. moai_adk/core/tags/cli.py +283 -0
  6. moai_adk/core/tags/generator.py +109 -0
  7. moai_adk/core/tags/inserter.py +99 -0
  8. moai_adk/core/tags/mapper.py +126 -0
  9. moai_adk/core/tags/parser.py +76 -0
  10. moai_adk/core/tags/pre_commit_validator.py +355 -0
  11. moai_adk/core/tags/reporter.py +959 -0
  12. moai_adk/core/tags/tags.py +149 -0
  13. moai_adk/core/tags/validator.py +897 -0
  14. moai_adk/core/template_engine.py +253 -0
  15. moai_adk/templates/.claude/agents/alfred/cc-manager.md +25 -2
  16. moai_adk/templates/.claude/agents/alfred/debug-helper.md +24 -12
  17. moai_adk/templates/.claude/agents/alfred/doc-syncer.md +19 -12
  18. moai_adk/templates/.claude/agents/alfred/git-manager.md +45 -14
  19. moai_adk/templates/.claude/agents/alfred/implementation-planner.md +19 -12
  20. moai_adk/templates/.claude/agents/alfred/project-manager.md +29 -2
  21. moai_adk/templates/.claude/agents/alfred/quality-gate.md +25 -2
  22. moai_adk/templates/.claude/agents/alfred/skill-factory.md +30 -2
  23. moai_adk/templates/.claude/agents/alfred/spec-builder.md +26 -11
  24. moai_adk/templates/.claude/agents/alfred/tag-agent.md +30 -8
  25. moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +27 -12
  26. moai_adk/templates/.claude/agents/alfred/trust-checker.md +25 -2
  27. moai_adk/templates/.claude/commands/alfred/0-project.md +5 -0
  28. moai_adk/templates/.claude/commands/alfred/1-plan.md +17 -4
  29. moai_adk/templates/.claude/commands/alfred/2-run.md +7 -0
  30. moai_adk/templates/.claude/commands/alfred/3-sync.md +6 -0
  31. moai_adk/templates/.claude/commands/alfred/9-feedback.md +149 -0
  32. moai_adk/templates/.claude/hooks/alfred/.moai/cache/version-check.json +9 -0
  33. moai_adk/templates/.claude/hooks/alfred/README.md +258 -145
  34. moai_adk/templates/.claude/hooks/alfred/TROUBLESHOOTING.md +471 -0
  35. moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +92 -57
  36. moai_adk/templates/.claude/hooks/alfred/core/version_cache.py +198 -0
  37. moai_adk/templates/.claude/hooks/alfred/notification__handle_events.py +102 -0
  38. moai_adk/templates/.claude/hooks/alfred/post_tool__log_changes.py +102 -0
  39. moai_adk/templates/.claude/hooks/alfred/pre_tool__auto_checkpoint.py +108 -0
  40. moai_adk/templates/.claude/hooks/alfred/session_end__cleanup.py +102 -0
  41. moai_adk/templates/.claude/hooks/alfred/session_start__show_project_info.py +102 -0
  42. moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/project.py +271 -15
  43. moai_adk/templates/.claude/hooks/alfred/shared/core/version_cache.py +198 -0
  44. moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/session.py +21 -7
  45. moai_adk/templates/.claude/hooks/alfred/stop__handle_interrupt.py +102 -0
  46. moai_adk/templates/.claude/hooks/alfred/subagent_stop__handle_subagent_end.py +102 -0
  47. moai_adk/templates/.claude/hooks/alfred/user_prompt__jit_load_docs.py +120 -0
  48. moai_adk/templates/.claude/settings.json +5 -5
  49. moai_adk/templates/.claude/skills/moai-foundation-ears/SKILL.md +9 -6
  50. moai_adk/templates/.claude/skills/moai-spec-authoring/README.md +56 -56
  51. moai_adk/templates/.claude/skills/moai-spec-authoring/SKILL.md +101 -100
  52. moai_adk/templates/.claude/skills/moai-spec-authoring/examples/validate-spec.sh +3 -3
  53. moai_adk/templates/.claude/skills/moai-spec-authoring/examples.md +219 -219
  54. moai_adk/templates/.claude/skills/moai-spec-authoring/reference.md +287 -287
  55. moai_adk/templates/.github/ISSUE_TEMPLATE/spec.yml +6 -6
  56. moai_adk/templates/.github/PULL_REQUEST_TEMPLATE.md +1 -1
  57. moai_adk/templates/.github/workflows/moai-gitflow.yml +22 -16
  58. moai_adk/templates/.github/workflows/moai-release-create.yml +100 -0
  59. moai_adk/templates/.github/workflows/moai-release-pipeline.yml +182 -0
  60. moai_adk/templates/.github/workflows/release.yml +49 -0
  61. moai_adk/templates/.github/workflows/spec-issue-sync.yml +10 -6
  62. moai_adk/templates/.github/workflows/tag-report.yml +261 -0
  63. moai_adk/templates/.github/workflows/tag-validation.yml +176 -0
  64. moai_adk/templates/.moai/config.json +18 -1
  65. moai_adk/templates/.moai/docs/quick-issue-creation-guide.md +219 -0
  66. moai_adk/templates/.moai/hooks/install.sh +79 -0
  67. moai_adk/templates/.moai/hooks/pre-commit.sh +66 -0
  68. moai_adk/templates/.moai/memory/ISSUE-LABEL-MAPPING.md +150 -0
  69. moai_adk/templates/CLAUDE.md +39 -40
  70. moai_adk/templates/src/moai_adk/core/__init__.py +5 -0
  71. moai_adk/templates/src/moai_adk/core/tags/__init__.py +87 -0
  72. moai_adk/templates/src/moai_adk/core/tags/ci_validator.py +435 -0
  73. moai_adk/templates/src/moai_adk/core/tags/cli.py +283 -0
  74. moai_adk/templates/src/moai_adk/core/tags/pre_commit_validator.py +355 -0
  75. moai_adk/templates/src/moai_adk/core/tags/reporter.py +959 -0
  76. moai_adk/templates/src/moai_adk/core/tags/validator.py +897 -0
  77. {moai_adk-0.8.0.dist-info → moai_adk-0.8.2.dist-info}/METADATA +348 -1
  78. {moai_adk-0.8.0.dist-info → moai_adk-0.8.2.dist-info}/RECORD +89 -52
  79. moai_adk/templates/.claude/hooks/alfred/HOOK_SCHEMA_VALIDATION.md +0 -313
  80. moai_adk/templates/.claude/hooks/alfred/test_hook_output.py +0 -175
  81. moai_adk/templates/.moai/memory/config-schema.md +0 -444
  82. moai_adk/templates/.moai/memory/gitflow-protection-policy.md +0 -220
  83. moai_adk/templates/.moai/memory/spec-metadata.md +0 -356
  84. /moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/__init__.py +0 -0
  85. /moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/checkpoint.py +0 -0
  86. /moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/context.py +0 -0
  87. /moai_adk/templates/.claude/hooks/alfred/{core → shared/core}/tags.py +0 -0
  88. /moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/__init__.py +0 -0
  89. /moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/notification.py +0 -0
  90. /moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/tool.py +0 -0
  91. /moai_adk/templates/.claude/hooks/alfred/{handlers → shared/handlers}/user.py +0 -0
  92. {moai_adk-0.8.0.dist-info → moai_adk-0.8.2.dist-info}/WHEEL +0 -0
  93. {moai_adk-0.8.0.dist-info → moai_adk-0.8.2.dist-info}/entry_points.txt +0 -0
  94. {moai_adk-0.8.0.dist-info → moai_adk-0.8.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,109 @@
1
+ # @CODE:GEN-001, @CODE:GEN-002
2
+ """TAG ID generation and duplicate detection.
3
+
4
+ Generates sequential @DOC:DOMAIN-NNN identifiers and detects duplicates
5
+ using ripgrep for performance.
6
+
7
+ @SPEC:DOC-TAG-001: @DOC tag automatic generation infrastructure
8
+ """
9
+
10
+ import re
11
+ import subprocess
12
+ from typing import List
13
+
14
+ # Domain validation: Must start with letter, alphanumeric + hyphens, end with alphanumeric
15
+ DOMAIN_PATTERN = re.compile(r"^[A-Z]([A-Z0-9-]*[A-Z0-9])?$")
16
+
17
+
18
+ def generate_doc_tag(domain: str, existing_ids: List[str]) -> str:
19
+ """Generate next available @DOC tag ID for domain.
20
+
21
+ Validates domain format (uppercase alphanumeric, hyphens allowed).
22
+ Finds highest current number and increments by 1.
23
+
24
+ Args:
25
+ domain: Domain name (e.g., "AUTH", "CLI-TOOL")
26
+ existing_ids: List of existing TAG IDs for this domain
27
+
28
+ Returns:
29
+ Next TAG ID (e.g., "@DOC:AUTH-003")
30
+
31
+ Raises:
32
+ ValueError: Invalid domain format
33
+
34
+ Examples:
35
+ >>> generate_doc_tag("AUTH", [])
36
+ '@DOC:AUTH-001'
37
+ >>> generate_doc_tag("AUTH", ["@DOC:AUTH-001", "@DOC:AUTH-002"])
38
+ '@DOC:AUTH-003'
39
+ """
40
+ # Validate domain format: uppercase alphanumeric and hyphens only
41
+ if not DOMAIN_PATTERN.match(domain):
42
+ raise ValueError(
43
+ f"Invalid domain format: {domain}. "
44
+ "Domain must be uppercase, start with a letter, "
45
+ "and contain only alphanumeric characters and hyphens."
46
+ )
47
+
48
+ # Find max number from existing IDs for this domain
49
+ max_num = 0
50
+ pattern = rf"@DOC:{re.escape(domain)}-(\d{{3}})$"
51
+
52
+ for tag_id in existing_ids:
53
+ match = re.search(pattern, tag_id)
54
+ if match:
55
+ num = int(match.group(1))
56
+ max_num = max(max_num, num)
57
+
58
+ # Generate next ID
59
+ next_num = max_num + 1
60
+ return f"@DOC:{domain}-{next_num:03d}"
61
+
62
+
63
+ def detect_duplicates(domain: str, search_path: str = "docs/") -> List[str]:
64
+ """Detect existing @DOC tags using ripgrep.
65
+
66
+ Performs efficient ripgrep search for all @DOC:DOMAIN-NNN tags.
67
+
68
+ Args:
69
+ domain: Domain to search for
70
+ search_path: Directory to search (default: docs/)
71
+
72
+ Returns:
73
+ List of existing TAG IDs
74
+
75
+ Raises:
76
+ RuntimeError: ripgrep not available or execution error
77
+
78
+ Examples:
79
+ >>> detect_duplicates("AUTH", "docs/")
80
+ ['@DOC:AUTH-001', '@DOC:AUTH-002']
81
+ """
82
+ try:
83
+ result = subprocess.run(
84
+ ["rg", rf"@DOC:{re.escape(domain)}-\d{{3}}", search_path, "-o", "--no-heading"],
85
+ capture_output=True,
86
+ text=True,
87
+ timeout=5,
88
+ )
89
+
90
+ if result.returncode == 0:
91
+ # Found matches
92
+ return [tag.strip() for tag in result.stdout.strip().split("\n") if tag.strip()]
93
+ elif result.returncode == 1:
94
+ # No matches found (normal case for new domains)
95
+ return []
96
+ else:
97
+ # ripgrep error
98
+ raise RuntimeError(f"ripgrep error: {result.stderr}")
99
+
100
+ except FileNotFoundError:
101
+ raise RuntimeError(
102
+ "ripgrep (rg) not found in PATH. Please install it: "
103
+ "brew install ripgrep (macOS) or apt install ripgrep (Linux)"
104
+ )
105
+ except subprocess.TimeoutExpired:
106
+ raise RuntimeError(
107
+ f"ripgrep timeout after 5 seconds searching {search_path}. "
108
+ "Consider narrowing the search path."
109
+ )
@@ -0,0 +1,99 @@
1
+ # @CODE:INS-001, @CODE:INS-002
2
+ """Markdown TAG insertion and file operations.
3
+
4
+ Inserts @DOC tags into markdown file headers with chain references
5
+ and provides backup/recovery functionality.
6
+
7
+ @SPEC:DOC-TAG-001: @DOC tag automatic generation infrastructure
8
+ """
9
+
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+
14
+ def format_tag_header(tag_id: str, chain_ref: Optional[str] = None) -> str:
15
+ """Format TAG header comment with chain reference.
16
+
17
+ Args:
18
+ tag_id: TAG ID (e.g., "@DOC:AUTH-001")
19
+ chain_ref: Chain reference (e.g., "@SPEC:AUTH-001")
20
+
21
+ Returns:
22
+ Formatted header comment
23
+
24
+ Examples:
25
+ >>> format_tag_header("@DOC:AUTH-001", "@SPEC:AUTH-001")
26
+ '# @DOC:AUTH-001 | Chain: @SPEC:AUTH-001 -> @DOC:AUTH-001'
27
+ """
28
+ if chain_ref:
29
+ return f"# {tag_id} | Chain: {chain_ref} -> {tag_id}"
30
+ return f"# {tag_id}"
31
+
32
+
33
+ def insert_tag_to_markdown(
34
+ file_path: Path, tag_id: str, chain_ref: Optional[str] = None
35
+ ) -> bool:
36
+ """Insert TAG comment into markdown file header.
37
+
38
+ Inserts TAG as first line before the document title.
39
+ Creates backup before modification.
40
+
41
+ Args:
42
+ file_path: Path to markdown file
43
+ tag_id: TAG ID to insert
44
+ chain_ref: Optional chain reference
45
+
46
+ Returns:
47
+ True if successful, False on error
48
+
49
+ Examples:
50
+ >>> insert_tag_to_markdown(Path("guide.md"), "@DOC:AUTH-001", "@SPEC:AUTH-001")
51
+ True
52
+ """
53
+ try:
54
+ # Read original content
55
+ content = file_path.read_text(encoding="utf-8")
56
+
57
+ # Format TAG header
58
+ tag_header = format_tag_header(tag_id, chain_ref)
59
+
60
+ # Insert at beginning
61
+ new_content = f"{tag_header}\n{content}"
62
+
63
+ # Write back
64
+ file_path.write_text(new_content, encoding="utf-8")
65
+
66
+ return True
67
+
68
+ except (FileNotFoundError, PermissionError, OSError) as e:
69
+ # Handle file operation errors gracefully
70
+ print(f"Error inserting TAG into {file_path}: {e}")
71
+ return False
72
+
73
+
74
+ def create_backup(file_path: Path, backup_dir: Path = Path(".moai/backups")) -> Optional[Path]:
75
+ """Create backup of file before modification.
76
+
77
+ Args:
78
+ file_path: Path to file to backup
79
+ backup_dir: Directory for backups
80
+
81
+ Returns:
82
+ Path to backup file or None on error
83
+
84
+ Examples:
85
+ >>> create_backup(Path("guide.md"))
86
+ Path('.moai/backups/guide.md.bak')
87
+ """
88
+ try:
89
+ backup_dir.mkdir(parents=True, exist_ok=True)
90
+ backup_path = backup_dir / f"{file_path.name}.bak"
91
+
92
+ content = file_path.read_text(encoding="utf-8")
93
+ backup_path.write_text(content, encoding="utf-8")
94
+
95
+ return backup_path
96
+
97
+ except (FileNotFoundError, PermissionError, OSError) as e:
98
+ print(f"Error creating backup for {file_path}: {e}")
99
+ return None
@@ -0,0 +1,126 @@
1
+ # @CODE:MAP-001, @CODE:MAP-002
2
+ """SPEC-DOC mapping and confidence scoring.
3
+
4
+ Maps documentation files to related SPEC IDs based on domain matching
5
+ and calculates confidence scores for chain references.
6
+
7
+ @SPEC:DOC-TAG-001: @DOC tag automatic generation infrastructure
8
+ """
9
+
10
+ import re
11
+ from pathlib import Path
12
+ from typing import Any, Optional
13
+
14
+ from moai_adk.core.tags.parser import extract_spec_id, parse_domain
15
+
16
+
17
+ def find_related_spec(doc_path: Path, specs_dir: Path = Path(".moai/specs")) -> Optional[str]:
18
+ """Find related SPEC ID by matching domain from document path.
19
+
20
+ Searches for SPEC directories matching the domain inferred from the
21
+ document's file path. Returns the most recent SPEC (highest number)
22
+ if multiple matches exist.
23
+
24
+ Args:
25
+ doc_path: Path to documentation file (e.g., docs/auth/setup.md)
26
+ specs_dir: Path to SPEC directory (default: .moai/specs)
27
+
28
+ Returns:
29
+ SPEC ID (e.g., "AUTH-001") or None if no match found
30
+
31
+ Examples:
32
+ >>> find_related_spec(Path("docs/auth/guide.md"))
33
+ 'AUTH-001'
34
+ >>> find_related_spec(Path("docs/api/endpoints.md"))
35
+ 'API-001'
36
+ """
37
+ # Extract potential domain from file path
38
+ # E.g., docs/auth/setup.md -> 'auth'
39
+ path_parts = doc_path.parts
40
+ if len(path_parts) < 2:
41
+ return None
42
+
43
+ # Get the first directory under 'docs/' as potential domain
44
+ potential_domain = path_parts[1] if path_parts[0] == "docs" else path_parts[0]
45
+ potential_domain = potential_domain.upper().replace("_", "-")
46
+
47
+ # Search for matching SPEC directories
48
+ if not specs_dir.exists():
49
+ return None
50
+
51
+ matching_specs = []
52
+ for spec_dir in specs_dir.glob("SPEC-*"):
53
+ spec_file = spec_dir / "spec.md"
54
+ if not spec_file.exists():
55
+ continue
56
+
57
+ try:
58
+ with open(spec_file, "r", encoding="utf-8") as f:
59
+ content = f.read()
60
+ spec_id = extract_spec_id(content)
61
+ spec_domain = parse_domain(spec_id)
62
+
63
+ # Case-insensitive domain match
64
+ if spec_domain.upper() == potential_domain:
65
+ matching_specs.append(spec_id)
66
+ except (ValueError, OSError):
67
+ # Skip invalid or unreadable SPEC files
68
+ continue
69
+
70
+ if not matching_specs:
71
+ return None
72
+
73
+ # Return most recent SPEC (highest number)
74
+ def extract_number(spec: str) -> int:
75
+ match: Any = re.search(r"-(\d{3})$", spec)
76
+ return int(match.group(1)) if match else 0
77
+
78
+ matching_specs.sort(key=extract_number, reverse=True)
79
+ return str(matching_specs[0])
80
+
81
+
82
+ def calculate_confidence(spec_id: str, doc_path: Path) -> float:
83
+ """Calculate confidence score for SPEC-DOC mapping.
84
+
85
+ Confidence levels:
86
+ - 0.95+: Exact SPEC ID in file path
87
+ - 0.80+: Domain match + relevant keywords in filename
88
+ - 0.50+: Domain match only
89
+ - <0.50: No domain match
90
+
91
+ Args:
92
+ spec_id: SPEC ID (e.g., "AUTH-001")
93
+ doc_path: Path to documentation file
94
+
95
+ Returns:
96
+ Confidence score (0.0 to 1.0)
97
+
98
+ Examples:
99
+ >>> calculate_confidence("AUTH-001", Path("docs/auth/AUTH-001-impl.md"))
100
+ 0.95
101
+ >>> calculate_confidence("AUTH-001", Path("docs/auth/authentication.md"))
102
+ 0.85
103
+ >>> calculate_confidence("AUTH-001", Path("docs/api/guide.md"))
104
+ 0.2
105
+ """
106
+ doc_str = str(doc_path).lower()
107
+ spec_id_lower = spec_id.lower()
108
+ domain = parse_domain(spec_id).lower()
109
+
110
+ # Exact SPEC ID match in path
111
+ if spec_id_lower in doc_str:
112
+ return 0.95
113
+
114
+ # Domain match + relevant keywords
115
+ if domain in doc_str:
116
+ # Keywords related to the domain
117
+ keywords = [domain, "guide", "setup", "implementation", "tutorial"]
118
+ keyword_matches = sum(1 for kw in keywords if kw in doc_str)
119
+
120
+ if keyword_matches >= 2:
121
+ return 0.80
122
+ else:
123
+ return 0.50
124
+
125
+ # No domain match
126
+ return 0.20
@@ -0,0 +1,76 @@
1
+ # @CODE:PAR-001 | Chain: @SPEC:DOC-TAG-001 -> @CODE:PAR-001
2
+ """SPEC parser utilities for TAG generation system.
3
+
4
+ This module extracts SPEC metadata (ID, domain, title) from SPEC documents
5
+ for use in TAG generation and SPEC-DOC mapping.
6
+
7
+ @SPEC:DOC-TAG-001: @DOC tag automatic generation infrastructure
8
+ """
9
+
10
+ import re
11
+ from typing import Any
12
+
13
+ import yaml
14
+
15
+
16
+ def extract_spec_id(spec_content: str) -> str:
17
+ """Extract SPEC ID from YAML frontmatter.
18
+
19
+ Args:
20
+ spec_content: Full content of SPEC markdown file
21
+
22
+ Returns:
23
+ SPEC ID (e.g., "AUTH-001")
24
+
25
+ Raises:
26
+ ValueError: If YAML frontmatter or ID not found
27
+
28
+ Examples:
29
+ >>> content = "---\\nid: AUTH-001\\n---\\n# SPEC"
30
+ >>> extract_spec_id(content)
31
+ 'AUTH-001'
32
+ """
33
+ # Find YAML frontmatter between --- markers
34
+ match = re.search(r"^---\s*\n(.*?)\n---\s*$", spec_content, re.MULTILINE | re.DOTALL)
35
+ if not match:
36
+ raise ValueError("YAML frontmatter not found in SPEC document")
37
+
38
+ # Parse YAML
39
+ yaml_content = match.group(1)
40
+ try:
41
+ metadata: Any = yaml.safe_load(yaml_content)
42
+ spec_id: Any = metadata.get("id")
43
+ if not spec_id:
44
+ raise ValueError("'id' field not found in YAML frontmatter")
45
+ return str(spec_id)
46
+ except yaml.YAMLError as e:
47
+ raise ValueError(f"Invalid YAML frontmatter: {e}")
48
+
49
+
50
+ def parse_domain(spec_id: str) -> str:
51
+ """Extract domain from SPEC ID.
52
+
53
+ The domain is everything before the final -NNN sequence.
54
+
55
+ Examples:
56
+ >>> parse_domain("AUTH-001")
57
+ 'AUTH'
58
+ >>> parse_domain("CLI-TOOL-001")
59
+ 'CLI-TOOL'
60
+ >>> parse_domain("DOC-TAG-001")
61
+ 'DOC-TAG'
62
+
63
+ Args:
64
+ spec_id: Full SPEC ID
65
+
66
+ Returns:
67
+ Domain part (everything before last "-NNN")
68
+
69
+ Raises:
70
+ ValueError: Invalid SPEC ID format
71
+ """
72
+ # Match pattern: any chars followed by hyphen and exactly 3 digits at end
73
+ match = re.match(r"^(.*?)-\d{3}$", spec_id)
74
+ if not match:
75
+ raise ValueError(f"Invalid SPEC ID format: {spec_id}")
76
+ return match.group(1)