aiox-core 5.0.7 → 5.0.8

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.
Files changed (191) hide show
  1. package/.aiox-core/cli/commands/pro/buyer.js +379 -0
  2. package/.aiox-core/cli/commands/pro/index.js +191 -52
  3. package/.aiox-core/cli/commands/validate/index.js +2 -0
  4. package/.aiox-core/core/code-intel/helpers/dev-helper.js +1 -1
  5. package/.aiox-core/core/code-intel/helpers/devops-helper.js +0 -1
  6. package/.aiox-core/core/code-intel/helpers/planning-helper.js +1 -1
  7. package/.aiox-core/core/code-intel/helpers/qa-helper.js +2 -2
  8. package/.aiox-core/core/config/schemas/framework-config.schema.json +1 -0
  9. package/.aiox-core/core/config/template-overrides.js +1 -1
  10. package/.aiox-core/core/doctor/checks/ide-sync.js +81 -25
  11. package/.aiox-core/core/doctor/checks/rules-files.js +0 -1
  12. package/.aiox-core/core/doctor/checks/skills-count.js +83 -15
  13. package/.aiox-core/core/graph-dashboard/cli.js +1 -2
  14. package/.aiox-core/core/graph-dashboard/data-sources/code-intel-source.js +1 -1
  15. package/.aiox-core/core/ids/layer-classifier.js +1 -1
  16. package/.aiox-core/core/pro/pro-updater.js +578 -0
  17. package/.aiox-core/core/synapse/context/context-tracker.js +107 -9
  18. package/.aiox-core/core/synapse/layers/layer-processor.js +1 -1
  19. package/.aiox-core/core-config.yaml +15 -1
  20. package/.aiox-core/data/capability-detection.js +15 -15
  21. package/.aiox-core/data/entity-registry.yaml +18 -2
  22. package/.aiox-core/data/registry-update-log.jsonl +5 -0
  23. package/.aiox-core/data/tok3-token-comparison.js +0 -4
  24. package/.aiox-core/data/tool-search-validation.js +1 -1
  25. package/.aiox-core/development/agents/aiox-master.md +44 -6
  26. package/.aiox-core/development/agents/data-engineer.md +4 -4
  27. package/.aiox-core/development/agents/devops.md +52 -2
  28. package/.aiox-core/development/agents/po.md +1 -1
  29. package/.aiox-core/development/agents/qa.md +5 -11
  30. package/.aiox-core/development/agents/sm.md +3 -3
  31. package/.aiox-core/development/agents/ux-design-expert.md +1 -1
  32. package/.aiox-core/development/scripts/unified-activation-pipeline.js +29 -3
  33. package/.aiox-core/development/tasks/dev-develop-story.md +46 -7
  34. package/.aiox-core/development/tasks/devops-pro-access-grant.md +93 -0
  35. package/.aiox-core/development/tasks/devops-pro-activate.md +42 -0
  36. package/.aiox-core/development/tasks/devops-pro-check-access.md +34 -0
  37. package/.aiox-core/development/tasks/devops-pro-request-reset.md +34 -0
  38. package/.aiox-core/development/tasks/devops-pro-resend-verification.md +32 -0
  39. package/.aiox-core/development/tasks/devops-pro-reset-password.md +36 -0
  40. package/.aiox-core/development/tasks/devops-pro-validate-login.md +36 -0
  41. package/.aiox-core/development/tasks/devops-pro-verify-status.md +33 -0
  42. package/.aiox-core/development/tasks/qa-gate.md +54 -4
  43. package/.aiox-core/development/tasks/validate-next-story.md +39 -2
  44. package/.aiox-core/framework-config.yaml +1 -0
  45. package/.aiox-core/infrastructure/scripts/codex-skills-sync/README.md +69 -0
  46. package/.aiox-core/infrastructure/scripts/codex-skills-sync/bootstrap.js +727 -0
  47. package/.aiox-core/infrastructure/scripts/codex-skills-sync/index.js +10 -0
  48. package/.aiox-core/infrastructure/scripts/codex-skills-sync/validate.js +65 -4
  49. package/.aiox-core/infrastructure/scripts/generate-settings-json.js +29 -4
  50. package/.aiox-core/infrastructure/scripts/ide-sync/agent-parser.js +4 -0
  51. package/.aiox-core/infrastructure/scripts/ide-sync/index.js +67 -7
  52. package/.aiox-core/infrastructure/scripts/ide-sync/transformers/claude-code.js +145 -3
  53. package/.aiox-core/infrastructure/scripts/repair-agent-references.js +263 -0
  54. package/.aiox-core/infrastructure/scripts/validate-claude-integration.js +60 -8
  55. package/.aiox-core/infrastructure/scripts/validate-paths.js +13 -0
  56. package/.aiox-core/install-manifest.yaml +134 -82
  57. package/.aiox-core/utils/filters/index.js +2 -1
  58. package/.claude/commands/AIOX/agents/aiox-master.md +21 -0
  59. package/.claude/commands/AIOX/agents/analyst.md +21 -0
  60. package/.claude/commands/AIOX/agents/architect.md +21 -0
  61. package/.claude/commands/AIOX/agents/data-engineer.md +21 -0
  62. package/.claude/commands/AIOX/agents/dev.md +21 -0
  63. package/.claude/commands/AIOX/agents/devops.md +21 -0
  64. package/.claude/commands/AIOX/agents/pm.md +21 -0
  65. package/.claude/commands/AIOX/agents/po.md +21 -0
  66. package/.claude/commands/AIOX/agents/qa.md +21 -0
  67. package/.claude/commands/AIOX/agents/sm.md +21 -0
  68. package/.claude/commands/AIOX/agents/squad-creator.md +21 -0
  69. package/.claude/commands/AIOX/agents/ux-design-expert.md +21 -0
  70. package/.claude/commands/AIOX/scripts/agent-config-loader.js +624 -0
  71. package/.claude/commands/AIOX/scripts/generate-greeting.js +160 -0
  72. package/.claude/commands/AIOX/scripts/greeting-builder.js +866 -0
  73. package/.claude/commands/AIOX/scripts/session-context-loader.js +286 -0
  74. package/.claude/commands/AIOX/stories/story-6.1.4.md +1404 -0
  75. package/.claude/commands/cohort-squad/agents/cohort-manager.md +156 -0
  76. package/.claude/commands/design-system/agents/brad-frost.md +1097 -0
  77. package/.claude/commands/design-system/agents/dan-mall.md +857 -0
  78. package/.claude/commands/design-system/agents/dave-malouf.md +2272 -0
  79. package/.claude/commands/design-system/agents/design-chief.md +102 -0
  80. package/.claude/commands/design-system/agents/nano-banana-generator.md +162 -0
  81. package/.claude/commands/greet.md +101 -0
  82. package/.claude/commands/synapse/manager.md +75 -0
  83. package/.claude/commands/synapse/tasks/add-rule.md +94 -0
  84. package/.claude/commands/synapse/tasks/create-command.md +109 -0
  85. package/.claude/commands/synapse/tasks/create-domain.md +127 -0
  86. package/.claude/commands/synapse/tasks/diagnose-synapse.md +245 -0
  87. package/.claude/commands/synapse/tasks/edit-rule.md +109 -0
  88. package/.claude/commands/synapse/tasks/suggest-domain.md +116 -0
  89. package/.claude/commands/synapse/tasks/toggle-domain.md +83 -0
  90. package/.claude/commands/synapse/templates/domain-template +8 -0
  91. package/.claude/commands/synapse/templates/manifest-entry-template +4 -0
  92. package/.claude/commands/synapse/utils/manifest-parser-reference.md +134 -0
  93. package/.claude/hooks/precompact-session-digest.cjs +2 -2
  94. package/.claude/skills/AIOX/agents/aiox-master/SKILL.md +511 -0
  95. package/.claude/skills/AIOX/agents/analyst/SKILL.md +281 -0
  96. package/.claude/skills/AIOX/agents/architect/SKILL.md +482 -0
  97. package/.claude/skills/AIOX/agents/data-engineer/SKILL.md +503 -0
  98. package/.claude/skills/AIOX/agents/dev/SKILL.md +568 -0
  99. package/.claude/skills/AIOX/agents/devops/SKILL.md +597 -0
  100. package/.claude/skills/AIOX/agents/pm/SKILL.md +385 -0
  101. package/.claude/skills/AIOX/agents/po/SKILL.md +343 -0
  102. package/.claude/skills/AIOX/agents/qa/SKILL.md +451 -0
  103. package/.claude/skills/AIOX/agents/sm/SKILL.md +295 -0
  104. package/.claude/skills/AIOX/agents/squad-creator/SKILL.md +352 -0
  105. package/.claude/skills/AIOX/agents/ux-design-expert/SKILL.md +503 -0
  106. package/.claude/skills/architect-first/SKILL.md +275 -0
  107. package/.claude/skills/architect-first/assets/architecture-template.md +505 -0
  108. package/.claude/skills/architect-first/assets/config-template.yaml +351 -0
  109. package/.claude/skills/architect-first/references/architecture-checklist.md +216 -0
  110. package/.claude/skills/architect-first/references/pre-implementation-checklist.md +119 -0
  111. package/.claude/skills/architect-first/references/stop-rules-guide.md +291 -0
  112. package/.claude/skills/architect-first/references/testing-strategy-guide.md +477 -0
  113. package/.claude/skills/architect-first/scripts/architecture_validator.py +490 -0
  114. package/.claude/skills/architect-first/scripts/check_coupling.py +306 -0
  115. package/.claude/skills/architect-first/scripts/validate_risk_mitigation.py +382 -0
  116. package/.claude/skills/checklist-runner/SKILL.md +113 -0
  117. package/.claude/skills/clone-mind.md +329 -0
  118. package/.claude/skills/coderabbit-review/SKILL.md +106 -0
  119. package/.claude/skills/course-generation-workflow.md +76 -0
  120. package/.claude/skills/enhance-workflow.md +466 -0
  121. package/.claude/skills/mcp-builder/LICENSE.txt +202 -0
  122. package/.claude/skills/mcp-builder/SKILL.md +328 -0
  123. package/.claude/skills/mcp-builder/reference/evaluation.md +602 -0
  124. package/.claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
  125. package/.claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
  126. package/.claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
  127. package/.claude/skills/mcp-builder/scripts/connections.py +151 -0
  128. package/.claude/skills/mcp-builder/scripts/evaluation.py +373 -0
  129. package/.claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  130. package/.claude/skills/mcp-builder/scripts/requirements.txt +2 -0
  131. package/.claude/skills/ralph.md +181 -0
  132. package/.claude/skills/skill-creator/LICENSE.txt +202 -0
  133. package/.claude/skills/skill-creator/SKILL.md +209 -0
  134. package/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
  135. package/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
  136. package/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  137. package/.claude/skills/squad.md +301 -0
  138. package/.claude/skills/synapse/SKILL.md +132 -0
  139. package/.claude/skills/synapse/assets/README.md +50 -0
  140. package/.claude/skills/synapse/references/brackets.md +100 -0
  141. package/.claude/skills/synapse/references/commands.md +118 -0
  142. package/.claude/skills/synapse/references/domains.md +126 -0
  143. package/.claude/skills/synapse/references/layers.md +186 -0
  144. package/.claude/skills/synapse/references/manifest.md +142 -0
  145. package/.claude/skills/tech-search/SKILL.md +431 -0
  146. package/.claude/skills/tech-search/prompts/page-extract.md +133 -0
  147. package/README.en.md +2 -2
  148. package/README.md +8 -2
  149. package/bin/aiox.js +55 -4
  150. package/bin/utils/framework-guard.js +4 -2
  151. package/bin/utils/pro-detector.js +119 -28
  152. package/bin/utils/validate-publish.js +6 -6
  153. package/docs/aiox-agent-flows/devops-system.md +18 -0
  154. package/docs/aiox-workflows/README.md +1 -0
  155. package/docs/aiox-workflows/pro-access-grant-workflow.md +218 -0
  156. package/docs/guides/pro/access-grant-ops-playbook.md +370 -0
  157. package/docs/guides/pro/install-gate-setup.md +12 -6
  158. package/docs/guides/pro/squad-creator-handoff-pro-access-ops.md +134 -0
  159. package/docs/guides/supabase-ops-handoff.md +768 -0
  160. package/package.json +12 -1
  161. package/packages/aiox-pro-cli/bin/aiox-pro.js +33 -12
  162. package/packages/installer/src/config/configure-environment.js +118 -50
  163. package/packages/installer/src/installer/aiox-core-installer.js +124 -27
  164. package/packages/installer/src/installer/brownfield-upgrader.js +66 -9
  165. package/packages/installer/src/installer/dependency-installer.js +4 -0
  166. package/packages/installer/src/pro/pro-scaffolder.js +5 -5
  167. package/packages/installer/src/updater/index.js +151 -10
  168. package/packages/installer/src/wizard/ide-config-generator.js +73 -7
  169. package/packages/installer/src/wizard/index.js +119 -31
  170. package/packages/installer/src/wizard/pro-setup.js +118 -47
  171. package/packages/installer/src/wizard/validation/validators/dependency-validator.js +32 -25
  172. package/packages/installer/src/wizard/validation/validators/file-structure-validator.js +26 -0
  173. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +84 -1
  174. package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +1 -1
  175. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +85 -19
  176. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +4 -4
  177. package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +5 -5
  178. package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +4 -4
  179. package/packages/installer/tests/unit/merger/yaml-merger.test.js +11 -11
  180. package/pro/README.md +12 -1
  181. package/pro/license/index.js +3 -11
  182. package/pro/license/license-api.js +25 -0
  183. package/pro/license/license-cache.js +135 -31
  184. package/pro/license/license-crypto.js +59 -3
  185. package/pro/package.json +5 -4
  186. package/pro/squads/README.md +16 -16
  187. package/pro/squads/index.js +1 -1
  188. package/scripts/e2e/installed-skills-smoke.js +264 -0
  189. package/scripts/package-synapse.js +3 -3
  190. package/scripts/validate-package-completeness.js +8 -11
  191. package/.aiox-core/lib/build.json +0 -1
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Zero-Coupling Validation Script
4
+
5
+ Validates that modules/expansion-packs maintain zero-coupling principle:
6
+ - No hardcoded cross-module imports
7
+ - No hardcoded file paths to other modules
8
+ - Configuration-based integration only
9
+
10
+ Usage:
11
+ python check_coupling.py [--path PROJECT_PATH] [--config CONFIG_FILE]
12
+ """
13
+
14
+ import os
15
+ import re
16
+ import sys
17
+ import argparse
18
+ from pathlib import Path
19
+ from typing import List, Dict, Set, Tuple
20
+ import yaml
21
+
22
+
23
+ class CouplingViolation:
24
+ """Represents a coupling violation found in code"""
25
+
26
+ def __init__(
27
+ self,
28
+ file_path: str,
29
+ line_number: int,
30
+ line_content: str,
31
+ violation_type: str,
32
+ description: str,
33
+ ):
34
+ self.file_path = file_path
35
+ self.line_number = line_number
36
+ self.line_content = line_content.strip()
37
+ self.violation_type = violation_type
38
+ self.description = description
39
+
40
+ def __str__(self):
41
+ return (
42
+ f"\n {self.violation_type}: {self.file_path}:{self.line_number}\n"
43
+ f" {self.description}\n"
44
+ f" > {self.line_content}"
45
+ )
46
+
47
+
48
+ class CouplingChecker:
49
+ """Checks for coupling violations in codebase"""
50
+
51
+ def __init__(self, project_path: Path, config_path: Path = None):
52
+ self.project_path = project_path
53
+ self.violations: List[CouplingViolation] = []
54
+ self.config = self._load_config(config_path)
55
+
56
+ # Modules to check for coupling (from config or defaults)
57
+ self.modules = self.config.get("modules", [])
58
+ if not self.modules:
59
+ # Auto-detect expansion packs
60
+ expansion_pack_dir = project_path / "expansion-packs"
61
+ if expansion_pack_dir.exists():
62
+ self.modules = [
63
+ d.name for d in expansion_pack_dir.iterdir() if d.is_dir()
64
+ ]
65
+
66
+ # File patterns to scan
67
+ self.file_patterns = self.config.get(
68
+ "file_patterns", ["*.py", "*.js", "*.ts", "*.yaml", "*.yml"]
69
+ )
70
+
71
+ # Exclude patterns
72
+ self.exclude_patterns = self.config.get(
73
+ "exclude_patterns",
74
+ [
75
+ "**/node_modules/**",
76
+ "**/__pycache__/**",
77
+ "**/venv/**",
78
+ "**/.venv/**",
79
+ "**/dist/**",
80
+ "**/build/**",
81
+ ],
82
+ )
83
+
84
+ def _load_config(self, config_path: Path = None) -> Dict:
85
+ """Load configuration file if exists"""
86
+ if config_path and config_path.exists():
87
+ with open(config_path, "r") as f:
88
+ return yaml.safe_load(f) or {}
89
+
90
+ # Try default config location
91
+ default_config = self.project_path / ".coupling-check.yaml"
92
+ if default_config.exists():
93
+ with open(default_config, "r") as f:
94
+ return yaml.safe_load(f) or {}
95
+
96
+ return {}
97
+
98
+ def _should_exclude(self, file_path: Path) -> bool:
99
+ """Check if file should be excluded from scanning"""
100
+ path_str = str(file_path)
101
+ for pattern in self.exclude_patterns:
102
+ if Path(path_str).match(pattern):
103
+ return True
104
+ return False
105
+
106
+ def _find_files_to_scan(self) -> List[Path]:
107
+ """Find all files to scan for coupling violations"""
108
+ files = []
109
+ for pattern in self.file_patterns:
110
+ for file_path in self.project_path.rglob(pattern):
111
+ if file_path.is_file() and not self._should_exclude(file_path):
112
+ files.append(file_path)
113
+ return files
114
+
115
+ def _check_hardcoded_imports(self, file_path: Path, content: str):
116
+ """Check for hardcoded imports to other modules"""
117
+ lines = content.split("\n")
118
+
119
+ for line_num, line in enumerate(lines, 1):
120
+ # Python imports
121
+ if re.match(r"^\s*(from|import)\s+", line):
122
+ for module in self.modules:
123
+ # Check if importing from another module directly
124
+ if re.search(rf"\bfrom\s+{module}\b", line) or re.search(
125
+ rf"\bimport\s+{module}\b", line
126
+ ):
127
+ self.violations.append(
128
+ CouplingViolation(
129
+ str(file_path),
130
+ line_num,
131
+ line,
132
+ "HARDCODED_IMPORT",
133
+ f"Direct import of module '{module}'. "
134
+ f"Use plugin/config-based loading instead.",
135
+ )
136
+ )
137
+
138
+ # JavaScript/TypeScript imports
139
+ if re.search(r"(import|require)\s*\(?\s*['\"]", line):
140
+ for module in self.modules:
141
+ if module in line:
142
+ self.violations.append(
143
+ CouplingViolation(
144
+ str(file_path),
145
+ line_num,
146
+ line,
147
+ "HARDCODED_IMPORT",
148
+ f"Direct import of module '{module}'. "
149
+ f"Use plugin/config-based loading instead.",
150
+ )
151
+ )
152
+
153
+ def _check_hardcoded_paths(self, file_path: Path, content: str):
154
+ """Check for hardcoded file paths to other modules"""
155
+ lines = content.split("\n")
156
+
157
+ for line_num, line in enumerate(lines, 1):
158
+ # Look for file path patterns
159
+ path_patterns = [
160
+ r'["\']([^"\']*(?:expansion-packs|modules)/([^"\']+))["\']',
161
+ r"Path\(['\"]([^'\"]*(?:expansion-packs|modules)/[^'\"]+)['\"]\)",
162
+ ]
163
+
164
+ for pattern in path_patterns:
165
+ matches = re.finditer(pattern, line)
166
+ for match in matches:
167
+ path_str = match.group(1)
168
+ # Check if path references another module
169
+ for module in self.modules:
170
+ if module in path_str:
171
+ self.violations.append(
172
+ CouplingViolation(
173
+ str(file_path),
174
+ line_num,
175
+ line,
176
+ "HARDCODED_PATH",
177
+ f"Hardcoded path to module '{module}': {path_str}. "
178
+ f"Use configuration-based path resolution.",
179
+ )
180
+ )
181
+
182
+ def _check_shared_state(self, file_path: Path, content: str):
183
+ """Check for shared global state between modules"""
184
+ lines = content.split("\n")
185
+
186
+ # Patterns that suggest shared state
187
+ shared_state_patterns = [
188
+ (r"\bglobal\s+\w+", "Global variable usage"),
189
+ (r"^\s*[A-Z_]+\s*=\s*", "Module-level constant that might be shared"),
190
+ ]
191
+
192
+ for line_num, line in enumerate(lines, 1):
193
+ for pattern, description in shared_state_patterns:
194
+ if re.search(pattern, line):
195
+ # Check if it references other modules
196
+ for module in self.modules:
197
+ if module.lower() in line.lower():
198
+ self.violations.append(
199
+ CouplingViolation(
200
+ str(file_path),
201
+ line_num,
202
+ line,
203
+ "SHARED_STATE",
204
+ f"{description} referencing '{module}'.",
205
+ )
206
+ )
207
+
208
+ def check_file(self, file_path: Path):
209
+ """Check a single file for coupling violations"""
210
+ try:
211
+ with open(file_path, "r", encoding="utf-8") as f:
212
+ content = f.read()
213
+
214
+ self._check_hardcoded_imports(file_path, content)
215
+ self._check_hardcoded_paths(file_path, content)
216
+ self._check_shared_state(file_path, content)
217
+
218
+ except Exception as e:
219
+ print(f"Warning: Could not scan {file_path}: {e}", file=sys.stderr)
220
+
221
+ def run(self) -> int:
222
+ """Run coupling check on entire project"""
223
+ print("🔍 Checking for coupling violations...")
224
+ print(f" Project: {self.project_path}")
225
+ print(f" Modules: {', '.join(self.modules)}")
226
+ print()
227
+
228
+ files = self._find_files_to_scan()
229
+ print(f"📁 Scanning {len(files)} files...")
230
+ print()
231
+
232
+ for file_path in files:
233
+ self.check_file(file_path)
234
+
235
+ return self._report_results()
236
+
237
+ def _report_results(self) -> int:
238
+ """Report results and return exit code"""
239
+ if not self.violations:
240
+ print("✅ No coupling violations found!")
241
+ print(" Zero-coupling principle maintained.")
242
+ return 0
243
+
244
+ print(f"❌ Found {len(self.violations)} coupling violation(s):")
245
+
246
+ # Group violations by type
247
+ by_type: Dict[str, List[CouplingViolation]] = {}
248
+ for violation in self.violations:
249
+ if violation.violation_type not in by_type:
250
+ by_type[violation.violation_type] = []
251
+ by_type[violation.violation_type].append(violation)
252
+
253
+ # Report by type
254
+ for violation_type, violations in by_type.items():
255
+ print(f"\n{violation_type} ({len(violations)}):")
256
+ for violation in violations:
257
+ print(violation)
258
+
259
+ print("\n" + "=" * 80)
260
+ print("REMEDIATION STEPS:")
261
+ print("=" * 80)
262
+ print()
263
+ print("1. Remove hardcoded imports:")
264
+ print(" - Use plugin/adapter pattern")
265
+ print(" - Load modules via configuration")
266
+ print(" - Implement dependency injection")
267
+ print()
268
+ print("2. Externalize paths to YAML configuration:")
269
+ print(" - Define module paths in config file")
270
+ print(" - Use configuration loader to resolve paths")
271
+ print(" - Never hardcode cross-module paths")
272
+ print()
273
+ print("3. Eliminate shared state:")
274
+ print(" - Use message passing between modules")
275
+ print(" - Implement clean interfaces")
276
+ print(" - Each module maintains its own state")
277
+ print()
278
+ print("See: references/stop-rules-guide.md (Stop Rule 3)")
279
+ print()
280
+
281
+ return 1 # Exit code 1 indicates violations found
282
+
283
+
284
+ def main():
285
+ parser = argparse.ArgumentParser(
286
+ description="Check for coupling violations in codebase"
287
+ )
288
+ parser.add_argument(
289
+ "--path",
290
+ type=Path,
291
+ default=Path.cwd(),
292
+ help="Project path to scan (default: current directory)",
293
+ )
294
+ parser.add_argument(
295
+ "--config", type=Path, help="Configuration file path (YAML)"
296
+ )
297
+
298
+ args = parser.parse_args()
299
+
300
+ checker = CouplingChecker(args.path, args.config)
301
+ exit_code = checker.run()
302
+ sys.exit(exit_code)
303
+
304
+
305
+ if __name__ == "__main__":
306
+ main()
@@ -0,0 +1,382 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Risk Mitigation Validation Script
4
+
5
+ Validates that identified risks have appropriate mitigation strategies.
6
+ Checks project documentation for risk/mitigation coverage.
7
+
8
+ Usage:
9
+ python validate_risk_mitigation.py [--path PROJECT_PATH] [--risks RISKS_FILE]
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import argparse
15
+ import re
16
+ from pathlib import Path
17
+ from typing import List, Dict, Tuple
18
+ import yaml
19
+
20
+
21
+ class Risk:
22
+ """Represents an identified risk"""
23
+
24
+ def __init__(self, name: str, description: str, severity: str, source: str):
25
+ self.name = name
26
+ self.description = description
27
+ self.severity = severity # high, medium, low
28
+ self.source = source # file where risk identified
29
+ self.mitigation = None
30
+
31
+ def __str__(self):
32
+ status = "✅ MITIGATED" if self.mitigation else "❌ NO MITIGATION"
33
+ return (
34
+ f"\n [{self.severity.upper()}] {self.name}\n"
35
+ f" {self.description}\n"
36
+ f" Source: {self.source}\n"
37
+ f" Status: {status}"
38
+ )
39
+
40
+
41
+ class RiskMitigationValidator:
42
+ """Validates risk mitigation coverage"""
43
+
44
+ def __init__(self, project_path: Path, risks_file: Path = None):
45
+ self.project_path = project_path
46
+ self.risks_file = risks_file
47
+ self.risks: List[Risk] = []
48
+ self.mitigation_docs: Dict[str, str] = {}
49
+
50
+ def _find_risk_documents(self) -> List[Path]:
51
+ """Find all documents that might contain risks"""
52
+ risk_doc_patterns = [
53
+ "**/architecture/*.md",
54
+ "**/design/*.md",
55
+ "**/docs/**/*risk*.md",
56
+ "**/docs/**/*adr*.md", # Architecture Decision Records
57
+ "**/*RISK*.md",
58
+ ]
59
+
60
+ docs = []
61
+ for pattern in risk_doc_patterns:
62
+ docs.extend(self.project_path.glob(pattern))
63
+
64
+ return list(set(docs)) # Remove duplicates
65
+
66
+ def _extract_risks_from_doc(self, doc_path: Path):
67
+ """Extract risks from a document"""
68
+ try:
69
+ with open(doc_path, "r", encoding="utf-8") as f:
70
+ content = f.read()
71
+
72
+ # Look for risk sections
73
+ risk_patterns = [
74
+ # Markdown headers with "risk"
75
+ r"#{1,6}\s+.*[Rr]isks?\s*.*\n(.*?)(?=\n#{1,6}|\Z)",
76
+ # Table format
77
+ r"\|\s*[Rr]isk\s*\|.*\n\|[-\s|]+\n((?:\|.*\n)*)",
78
+ # Bullet points
79
+ r"[-*]\s+\*\*[Rr]isk:?\*\*\s+(.*?)(?=\n[-*]|\n\n|\Z)",
80
+ ]
81
+
82
+ for pattern in risk_patterns:
83
+ matches = re.finditer(pattern, content, re.MULTILINE | re.DOTALL)
84
+ for match in matches:
85
+ risk_text = match.group(1)
86
+ self._parse_risks_from_text(risk_text, str(doc_path))
87
+
88
+ except Exception as e:
89
+ print(f"Warning: Could not parse {doc_path}: {e}", file=sys.stderr)
90
+
91
+ def _parse_risks_from_text(self, text: str, source: str):
92
+ """Parse individual risks from text block"""
93
+ # Simple heuristic: each line or paragraph is a risk
94
+ lines = [l.strip() for l in text.split("\n") if l.strip()]
95
+
96
+ for line in lines:
97
+ # Skip table headers
98
+ if re.match(r"^\|?[-\s|]+\|?$", line):
99
+ continue
100
+
101
+ # Extract from table row: | risk | description | severity |
102
+ table_match = re.match(r"\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|", line)
103
+ if table_match:
104
+ name = table_match.group(1).strip()
105
+ description = table_match.group(2).strip()
106
+ severity = self._infer_severity(name, description)
107
+ self.risks.append(Risk(name, description, severity, source))
108
+ continue
109
+
110
+ # Extract from bullet: - **Risk:** description
111
+ bullet_match = re.match(
112
+ r"[-*]\s+\*\*([^:*]+):?\*\*\s+(.*)", line, re.IGNORECASE
113
+ )
114
+ if bullet_match:
115
+ name = bullet_match.group(1).strip()
116
+ description = bullet_match.group(2).strip()
117
+ severity = self._infer_severity(name, description)
118
+ self.risks.append(Risk(name, description, severity, source))
119
+ continue
120
+
121
+ # Generic line as risk
122
+ if len(line) > 10: # Minimum length to be meaningful
123
+ severity = self._infer_severity(line, line)
124
+ self.risks.append(Risk(line[:50], line, severity, source))
125
+
126
+ def _infer_severity(self, name: str, description: str) -> str:
127
+ """Infer severity from risk name/description"""
128
+ text = (name + " " + description).lower()
129
+
130
+ high_keywords = ["critical", "severe", "major", "high", "blocker"]
131
+ low_keywords = ["minor", "low", "trivial", "cosmetic"]
132
+
133
+ if any(kw in text for kw in high_keywords):
134
+ return "high"
135
+ elif any(kw in text for kw in low_keywords):
136
+ return "low"
137
+ else:
138
+ return "medium"
139
+
140
+ def _find_mitigation_documents(self) -> List[Path]:
141
+ """Find documents that might contain mitigations"""
142
+ mitigation_patterns = [
143
+ "**/architecture/*.md",
144
+ "**/design/*.md",
145
+ "**/docs/**/*mitigation*.md",
146
+ "**/docs/**/*strategy*.md",
147
+ "**/*MITIGATION*.md",
148
+ ]
149
+
150
+ docs = []
151
+ for pattern in mitigation_patterns:
152
+ docs.extend(self.project_path.glob(pattern))
153
+
154
+ return list(set(docs))
155
+
156
+ def _extract_mitigations_from_doc(self, doc_path: Path):
157
+ """Extract mitigations from document"""
158
+ try:
159
+ with open(doc_path, "r", encoding="utf-8") as f:
160
+ content = f.read()
161
+
162
+ # Store entire content as potential mitigation source
163
+ self.mitigation_docs[str(doc_path)] = content
164
+
165
+ except Exception as e:
166
+ print(
167
+ f"Warning: Could not parse {doc_path}: {e}", file=sys.stderr
168
+ )
169
+
170
+ def _match_risks_to_mitigations(self):
171
+ """Match identified risks to mitigations"""
172
+ for risk in self.risks:
173
+ # Search for risk name/keywords in mitigation docs
174
+ risk_keywords = set(
175
+ re.findall(r"\b\w{4,}\b", risk.name.lower())
176
+ ) # Words 4+ chars
177
+
178
+ for doc_path, content in self.mitigation_docs.items():
179
+ content_lower = content.lower()
180
+
181
+ # Check if risk keywords appear in mitigation section
182
+ if any(keyword in content_lower for keyword in risk_keywords):
183
+ # Look for mitigation keywords nearby
184
+ mitigation_keywords = [
185
+ "mitigation",
186
+ "mitigate",
187
+ "solution",
188
+ "strategy",
189
+ "address",
190
+ "resolve",
191
+ ]
192
+
193
+ if any(kw in content_lower for kw in mitigation_keywords):
194
+ risk.mitigation = doc_path
195
+ break
196
+
197
+ def _load_risks_from_yaml(self):
198
+ """Load risks from YAML file if provided"""
199
+ if not self.risks_file or not self.risks_file.exists():
200
+ return
201
+
202
+ try:
203
+ with open(self.risks_file, "r") as f:
204
+ data = yaml.safe_load(f)
205
+
206
+ if "risks" in data:
207
+ for risk_data in data["risks"]:
208
+ risk = Risk(
209
+ name=risk_data.get("name", "Unnamed"),
210
+ description=risk_data.get("description", ""),
211
+ severity=risk_data.get("severity", "medium"),
212
+ source=str(self.risks_file),
213
+ )
214
+
215
+ # Check if mitigation provided in YAML
216
+ if "mitigation" in risk_data:
217
+ risk.mitigation = "YAML: " + risk_data["mitigation"]
218
+
219
+ self.risks.append(risk)
220
+
221
+ except Exception as e:
222
+ print(
223
+ f"Warning: Could not load risks from {self.risks_file}: {e}",
224
+ file=sys.stderr,
225
+ )
226
+
227
+ def run(self) -> int:
228
+ """Run risk mitigation validation"""
229
+ print("🔍 Validating risk mitigation coverage...")
230
+ print(f" Project: {self.project_path}")
231
+ print()
232
+
233
+ # Load risks from YAML if provided
234
+ if self.risks_file:
235
+ print(f"📄 Loading risks from: {self.risks_file}")
236
+ self._load_risks_from_yaml()
237
+
238
+ # Find and parse risk documents
239
+ print("📁 Scanning project documentation for risks...")
240
+ risk_docs = self._find_risk_documents()
241
+ print(f" Found {len(risk_docs)} potential risk documents")
242
+
243
+ for doc in risk_docs:
244
+ self._extract_risks_from_doc(doc)
245
+
246
+ print(f" Identified {len(self.risks)} risks")
247
+ print()
248
+
249
+ # Find mitigation documents
250
+ print("📁 Scanning for mitigation documentation...")
251
+ mitigation_docs = self._find_mitigation_documents()
252
+ print(f" Found {len(mitigation_docs)} potential mitigation documents")
253
+
254
+ for doc in mitigation_docs:
255
+ self._extract_mitigations_from_doc(doc)
256
+
257
+ print()
258
+
259
+ # Match risks to mitigations
260
+ print("🔗 Matching risks to mitigations...")
261
+ self._match_risks_to_mitigations()
262
+ print()
263
+
264
+ return self._report_results()
265
+
266
+ def _report_results(self) -> int:
267
+ """Report results and return exit code"""
268
+ if not self.risks:
269
+ print("⚠️ No risks identified in project documentation.")
270
+ print(
271
+ " This might mean:"
272
+ )
273
+ print(" - Project has no documented risks")
274
+ print(" - Risk documentation not in expected locations")
275
+ print(" - Risk documentation format not recognized")
276
+ print()
277
+ print(" Recommended: Create risk documentation in:")
278
+ print(" - docs/architecture/risks.md")
279
+ print(" - Design documents with ## Risks section")
280
+ print(" - Architecture Decision Records (ADRs)")
281
+ return 0
282
+
283
+ # Categorize risks
284
+ mitigated = [r for r in self.risks if r.mitigation]
285
+ unmitigated = [r for r in self.risks if not r.mitigation]
286
+
287
+ high_unmitigated = [
288
+ r for r in unmitigated if r.severity == "high"
289
+ ]
290
+
291
+ print("=" * 80)
292
+ print("RISK MITIGATION REPORT")
293
+ print("=" * 80)
294
+ print()
295
+ print(f"Total Risks: {len(self.risks)}")
296
+ print(f"Mitigated: {len(mitigated)}")
297
+ print(f"Unmitigated: {len(unmitigated)}")
298
+ print()
299
+
300
+ if high_unmitigated:
301
+ print(f"⚠️ HIGH PRIORITY: {len(high_unmitigated)} high-severity risks without mitigation")
302
+ print()
303
+
304
+ # Report unmitigated risks first
305
+ if unmitigated:
306
+ print("❌ UNMITIGATED RISKS:")
307
+ for risk in sorted(
308
+ unmitigated, key=lambda r: {"high": 0, "medium": 1, "low": 2}[r.severity]
309
+ ):
310
+ print(risk)
311
+ print()
312
+
313
+ # Report mitigated risks
314
+ if mitigated:
315
+ print("✅ MITIGATED RISKS:")
316
+ for risk in mitigated:
317
+ print(risk)
318
+ print(f" Mitigation: {risk.mitigation}")
319
+ print()
320
+
321
+ # Overall assessment
322
+ coverage = len(mitigated) / len(self.risks) * 100 if self.risks else 0
323
+
324
+ print("=" * 80)
325
+ print(f"COVERAGE: {coverage:.1f}%")
326
+ print("=" * 80)
327
+ print()
328
+
329
+ if coverage == 100:
330
+ print("✅ All identified risks have mitigation strategies!")
331
+ return 0
332
+ elif coverage >= 80:
333
+ print("⚠️ Good coverage, but some risks lack mitigation.")
334
+ if not high_unmitigated:
335
+ print(" No high-severity risks unmitigated. Consider acceptable.")
336
+ return 0
337
+ else:
338
+ print("❌ Low mitigation coverage. Address unmitigated risks.")
339
+
340
+ if unmitigated:
341
+ print()
342
+ print("REMEDIATION STEPS:")
343
+ print("-" * 80)
344
+ print("1. Document mitigation strategy for each unmitigated risk")
345
+ print("2. Create mitigation documentation in:")
346
+ print(" - Architecture Decision Records (ADRs)")
347
+ print(" - Design documents (## Risk Mitigation section)")
348
+ print(" - Dedicated mitigation strategy documents")
349
+ print("3. For each risk, specify:")
350
+ print(" - Mitigation approach (avoid, reduce, transfer, accept)")
351
+ print(" - Concrete steps to implement mitigation")
352
+ print(" - Contingency plan if mitigation fails")
353
+ print(" - Responsible party/team")
354
+ print()
355
+
356
+ # Exit code based on high-severity unmitigated risks
357
+ return 1 if high_unmitigated else 0
358
+
359
+
360
+ def main():
361
+ parser = argparse.ArgumentParser(
362
+ description="Validate risk mitigation coverage"
363
+ )
364
+ parser.add_argument(
365
+ "--path",
366
+ type=Path,
367
+ default=Path.cwd(),
368
+ help="Project path to scan (default: current directory)",
369
+ )
370
+ parser.add_argument(
371
+ "--risks", type=Path, help="YAML file with explicit risk definitions"
372
+ )
373
+
374
+ args = parser.parse_args()
375
+
376
+ validator = RiskMitigationValidator(args.path, args.risks)
377
+ exit_code = validator.run()
378
+ sys.exit(exit_code)
379
+
380
+
381
+ if __name__ == "__main__":
382
+ main()