pumuki-ast-hooks 5.5.50 → 5.5.53
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.
- package/README.md +9 -0
- package/package.json +1 -1
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +56 -0
- package/scripts/hooks-system/application/services/AutonomousOrchestrator.js +37 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +84 -18
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +407 -5
- package/scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh +21 -67
package/README.md
CHANGED
|
@@ -866,6 +866,15 @@ Automates the complete Git Flow cycle: commit → push → PR → merge, plus co
|
|
|
866
866
|
|
|
867
867
|
For more details, see [MCP_SERVERS.md](./docs/MCP_SERVERS.md).
|
|
868
868
|
|
|
869
|
+
#### Troubleshooting
|
|
870
|
+
|
|
871
|
+
If `ai_gate_check` behaves inconsistently (stale branch name, missing rules, or intermittent transport errors), verify you are not running multiple `ast-intelligence-automation` servers across different repositories.
|
|
872
|
+
|
|
873
|
+
- Prefer enabling a single MCP server for the repository you are working on.
|
|
874
|
+
- Verify the active process points to this repository path:
|
|
875
|
+
- `.../ast-intelligence-hooks/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js`
|
|
876
|
+
- If you detect multiple processes, stop the duplicates and restart your IDE/MCP servers.
|
|
877
|
+
|
|
869
878
|
---
|
|
870
879
|
|
|
871
880
|
## API Reference
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki-ast-hooks",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.53",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -110,3 +110,59 @@
|
|
|
110
110
|
{"timestamp":1767772971939,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
111
111
|
{"timestamp":1767772971939,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
112
112
|
{"timestamp":1767772971939,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
113
|
+
{"timestamp":1767775802167,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
114
|
+
{"timestamp":1767775802167,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
115
|
+
{"timestamp":1767775802167,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
116
|
+
{"timestamp":1767775802167,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
117
|
+
{"timestamp":1767775833943,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
118
|
+
{"timestamp":1767775833943,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
119
|
+
{"timestamp":1767775833943,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
120
|
+
{"timestamp":1767775833943,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
121
|
+
{"timestamp":1767776135844,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
122
|
+
{"timestamp":1767776135844,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
123
|
+
{"timestamp":1767776135844,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
124
|
+
{"timestamp":1767776135844,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
125
|
+
{"timestamp":1767776384649,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
126
|
+
{"timestamp":1767776384649,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
127
|
+
{"timestamp":1767776384649,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
128
|
+
{"timestamp":1767776384649,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
129
|
+
{"timestamp":1767777653343,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
130
|
+
{"timestamp":1767777653343,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
131
|
+
{"timestamp":1767777653343,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
132
|
+
{"timestamp":1767777653344,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
133
|
+
{"timestamp":1767778142566,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
134
|
+
{"timestamp":1767778142567,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
135
|
+
{"timestamp":1767778142567,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
136
|
+
{"timestamp":1767778142567,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
137
|
+
{"timestamp":1767778800954,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
138
|
+
{"timestamp":1767778800955,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
139
|
+
{"timestamp":1767778800955,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
140
|
+
{"timestamp":1767778800955,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
141
|
+
{"timestamp":1767779528552,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
142
|
+
{"timestamp":1767779528552,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
143
|
+
{"timestamp":1767779528552,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
144
|
+
{"timestamp":1767779528552,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
145
|
+
{"timestamp":1767779903813,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
146
|
+
{"timestamp":1767779903813,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
147
|
+
{"timestamp":1767779903813,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
148
|
+
{"timestamp":1767779903813,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
149
|
+
{"timestamp":1767780497468,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
150
|
+
{"timestamp":1767780497468,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
151
|
+
{"timestamp":1767780497468,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
152
|
+
{"timestamp":1767780497468,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
153
|
+
{"timestamp":1767780655724,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
154
|
+
{"timestamp":1767780655725,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
155
|
+
{"timestamp":1767780655725,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
156
|
+
{"timestamp":1767780655725,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
157
|
+
{"timestamp":1767782291627,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
158
|
+
{"timestamp":1767782291627,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
159
|
+
{"timestamp":1767782291627,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
160
|
+
{"timestamp":1767782291627,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
161
|
+
{"timestamp":1767784424360,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
162
|
+
{"timestamp":1767784424360,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
163
|
+
{"timestamp":1767784424360,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
164
|
+
{"timestamp":1767784424360,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
165
|
+
{"timestamp":1767784524038,"hook":"audit_logger","operation":"constructor","status":"started","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
166
|
+
{"timestamp":1767784524038,"hook":"audit_logger","operation":"ensure_dir","status":"started"}
|
|
167
|
+
{"timestamp":1767784524038,"hook":"audit_logger","operation":"ensure_dir","status":"success"}
|
|
168
|
+
{"timestamp":1767784524038,"hook":"audit_logger","operation":"constructor","status":"success","repoRoot":"/Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/scripts/hooks-system"}
|
|
@@ -31,10 +31,47 @@ class AutonomousOrchestrator {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
detectFromBranchKeywords(branchName) {
|
|
35
|
+
try {
|
|
36
|
+
const PlatformHeuristics = require('./platform/PlatformHeuristics');
|
|
37
|
+
const heuristics = new PlatformHeuristics(this.platformDetector);
|
|
38
|
+
return heuristics.detectFromBranchKeywords(branchName);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const msg = error && error.message ? error.message : String(error);
|
|
41
|
+
this.logger?.debug?.('ORCHESTRATOR_BRANCH_KEYWORDS_DETECTION_ERROR', { error: msg });
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
detectFromEvidenceFile() {
|
|
47
|
+
try {
|
|
48
|
+
const PlatformHeuristics = require('./platform/PlatformHeuristics');
|
|
49
|
+
const heuristics = new PlatformHeuristics(this.platformDetector);
|
|
50
|
+
return heuristics.detectFromEvidenceFile();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const msg = error && error.message ? error.message : String(error);
|
|
53
|
+
this.logger?.debug?.('ORCHESTRATOR_EVIDENCE_FILE_DETECTION_ERROR', { error: msg });
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
34
58
|
detectFromASTSystemFilesLegacy(files) {
|
|
35
59
|
return this.detectFromASTSystemFiles(files);
|
|
36
60
|
}
|
|
37
61
|
|
|
62
|
+
async scoreConfidence(platforms) {
|
|
63
|
+
try {
|
|
64
|
+
const PlatformAnalysisService = require('./PlatformAnalysisService');
|
|
65
|
+
const analysisService = new PlatformAnalysisService(this.platformDetector);
|
|
66
|
+
const context = await this.contextEngine.detectContext();
|
|
67
|
+
return analysisService.analyzeConfidence(platforms || [], context || {});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const msg = error && error.message ? error.message : String(error);
|
|
70
|
+
this.logger?.debug?.('ORCHESTRATOR_SCORE_CONFIDENCE_ERROR', { error: msg });
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
38
75
|
async analyzeContext() {
|
|
39
76
|
const platforms = await this.detectActivePlatforms();
|
|
40
77
|
const scores = await this.scoreConfidence(platforms);
|
|
@@ -130,33 +130,99 @@ class McpConfigurator {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
this.configureProjectScoped(mcpConfig, serverId);
|
|
134
|
+
this.cleanupGlobalConfig(serverId);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
configureProjectScoped(mcpConfig, serverId) {
|
|
138
|
+
const windsurfProjectDir = path.join(this.targetRoot, '.windsurf');
|
|
139
|
+
const windsurfProjectPath = path.join(windsurfProjectDir, 'mcp.json');
|
|
138
140
|
|
|
139
141
|
try {
|
|
140
|
-
if (!fs.existsSync(
|
|
141
|
-
fs.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
if (!fs.existsSync(windsurfProjectDir)) {
|
|
143
|
+
fs.mkdirSync(windsurfProjectDir, { recursive: true });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let finalConfig = mcpConfig;
|
|
147
|
+
if (fs.existsSync(windsurfProjectPath)) {
|
|
148
|
+
const existing = JSON.parse(fs.readFileSync(windsurfProjectPath, 'utf8'));
|
|
146
149
|
if (!existing.mcpServers) existing.mcpServers = {};
|
|
150
|
+
Object.keys(existing.mcpServers).forEach(id => {
|
|
151
|
+
if (id.startsWith('ast-intelligence-automation-') && id !== serverId) {
|
|
152
|
+
delete existing.mcpServers[id];
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
existing.mcpServers[serverId] = mcpConfig.mcpServers[serverId];
|
|
156
|
+
finalConfig = existing;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fs.writeFileSync(windsurfProjectPath, JSON.stringify(finalConfig, null, 2));
|
|
160
|
+
this.logSuccess(`Configured project-scoped Windsurf MCP at ${windsurfProjectPath}`);
|
|
161
|
+
if (this.logger) this.logger.info('MCP_PROJECT_CONFIGURED', { path: windsurfProjectPath, serverId });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
this.logWarning(`Failed to configure project-scoped MCP: ${error.message}`);
|
|
164
|
+
if (this.logger) this.logger.warn('MCP_PROJECT_CONFIGURE_FAILED', { error: error.message });
|
|
165
|
+
}
|
|
147
166
|
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
const cursorProjectDir = path.join(this.targetRoot, '.cursor');
|
|
168
|
+
const cursorProjectPath = path.join(cursorProjectDir, 'mcp.json');
|
|
150
169
|
|
|
170
|
+
try {
|
|
171
|
+
if (!fs.existsSync(cursorProjectDir)) {
|
|
172
|
+
fs.mkdirSync(cursorProjectDir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let finalConfig = mcpConfig;
|
|
176
|
+
if (fs.existsSync(cursorProjectPath)) {
|
|
177
|
+
const existing = JSON.parse(fs.readFileSync(cursorProjectPath, 'utf8'));
|
|
178
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
179
|
+
Object.keys(existing.mcpServers).forEach(id => {
|
|
180
|
+
if (id.startsWith('ast-intelligence-automation-') && id !== serverId) {
|
|
181
|
+
delete existing.mcpServers[id];
|
|
182
|
+
}
|
|
183
|
+
});
|
|
151
184
|
existing.mcpServers[serverId] = mcpConfig.mcpServers[serverId];
|
|
185
|
+
finalConfig = existing;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fs.writeFileSync(cursorProjectPath, JSON.stringify(finalConfig, null, 2));
|
|
189
|
+
this.logSuccess(`Configured project-scoped Cursor MCP at ${cursorProjectPath}`);
|
|
190
|
+
if (this.logger) this.logger.info('MCP_CURSOR_CONFIGURED', { path: cursorProjectPath, serverId });
|
|
191
|
+
} catch (error) {
|
|
192
|
+
this.logWarning(`Failed to configure Cursor MCP: ${error.message}`);
|
|
193
|
+
if (this.logger) this.logger.warn('MCP_CURSOR_CONFIGURE_FAILED', { error: error.message });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
cleanupGlobalConfig(currentServerId) {
|
|
198
|
+
const globalConfigPath = this.getGlobalWindsurfConfigPath();
|
|
152
199
|
|
|
200
|
+
try {
|
|
201
|
+
if (!fs.existsSync(globalConfigPath)) return;
|
|
202
|
+
|
|
203
|
+
const existing = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
|
|
204
|
+
if (!existing.mcpServers) return;
|
|
205
|
+
|
|
206
|
+
let modified = false;
|
|
207
|
+
Object.keys(existing.mcpServers).forEach(id => {
|
|
208
|
+
const server = existing.mcpServers[id];
|
|
209
|
+
if (!server || !server.env) return;
|
|
210
|
+
|
|
211
|
+
if (server.env.REPO_ROOT === this.targetRoot) {
|
|
212
|
+
delete existing.mcpServers[id];
|
|
213
|
+
modified = true;
|
|
214
|
+
this.logInfo(`Removed ${id} from global config (now project-scoped)`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (modified) {
|
|
153
219
|
fs.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2));
|
|
154
|
-
this.
|
|
155
|
-
|
|
220
|
+
if (this.logger) this.logger.info('MCP_GLOBAL_CLEANUP', { removedForRepo: this.targetRoot });
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (process.env.DEBUG) {
|
|
224
|
+
process.stderr.write(`[MCP] cleanupGlobalConfig failed: ${error.message}\n`);
|
|
156
225
|
}
|
|
157
|
-
} catch (mergeError) {
|
|
158
|
-
this.logWarning(`${globalConfigPath} exists but couldn't be merged, skipping`);
|
|
159
|
-
if (this.logger) this.logger.warn('MCP_GLOBAL_MERGE_FAILED', { error: mergeError.message });
|
|
160
226
|
}
|
|
161
227
|
}
|
|
162
228
|
|
|
@@ -576,9 +576,293 @@ function checkBranchChangesCoherence(branchName, uncommittedChanges) {
|
|
|
576
576
|
return { isCoherent: true, expectedScope, detectedScope: dominantScope, reason: 'Changes match branch scope' };
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Load platform rules from .cursor/rules or .windsurf/rules
|
|
581
|
+
* Returns the content of the rules file for the detected platform
|
|
582
|
+
*/
|
|
583
|
+
async function loadPlatformRules(platforms) {
|
|
584
|
+
const DynamicRulesLoader = require('../../application/services/DynamicRulesLoader');
|
|
585
|
+
const loader = new DynamicRulesLoader();
|
|
586
|
+
const rules = {};
|
|
587
|
+
const criticalRules = [];
|
|
588
|
+
|
|
589
|
+
for (const platform of platforms) {
|
|
590
|
+
try {
|
|
591
|
+
const content = await loader.loadRule(`rules${platform}.mdc`);
|
|
592
|
+
if (content) {
|
|
593
|
+
rules[platform] = content;
|
|
594
|
+
const criticalPatterns = extractCriticalPatterns(content, platform);
|
|
595
|
+
criticalRules.push(...criticalPatterns);
|
|
596
|
+
}
|
|
597
|
+
} catch (error) {
|
|
598
|
+
if (process.env.DEBUG) {
|
|
599
|
+
process.stderr.write(`[MCP] Failed to load rules for ${platform}: ${error.message}\n`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const goldContent = await loader.loadRule('rulesgold.mdc');
|
|
606
|
+
if (goldContent) {
|
|
607
|
+
rules.gold = goldContent;
|
|
608
|
+
const goldPatterns = extractCriticalPatterns(goldContent, 'gold');
|
|
609
|
+
criticalRules.push(...goldPatterns);
|
|
610
|
+
}
|
|
611
|
+
} catch (error) {
|
|
612
|
+
if (process.env.DEBUG) {
|
|
613
|
+
process.stderr.write(`[MCP] Failed to load gold rules: ${error.message}\n`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return { rules, criticalRules };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Extract critical patterns from rules content that AI MUST follow
|
|
622
|
+
*/
|
|
623
|
+
function extractCriticalPatterns(content, platform) {
|
|
624
|
+
const patterns = [];
|
|
625
|
+
if (!content) return patterns;
|
|
626
|
+
|
|
627
|
+
const lines = content.split('\n');
|
|
628
|
+
for (const line of lines) {
|
|
629
|
+
if (line.includes('❌') || line.includes('NUNCA') || line.includes('PROHIBIDO') || line.includes('NO ')) {
|
|
630
|
+
patterns.push({ platform, rule: line.trim(), severity: 'CRITICAL' });
|
|
631
|
+
}
|
|
632
|
+
if (line.includes('✅') && (line.includes('OBLIGATORIO') || line.includes('SIEMPRE'))) {
|
|
633
|
+
patterns.push({ platform, rule: line.trim(), severity: 'MANDATORY' });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (platform === 'ios') {
|
|
638
|
+
// PROHIBICIONES CRÍTICAS iOS
|
|
639
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar GCD (DispatchQueue.main.async, DispatchQueue.global) - usar Swift Concurrency (async/await, Task, actor, @MainActor)', severity: 'CRITICAL' });
|
|
640
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar completion handlers/@escaping callbacks - usar async/await', severity: 'CRITICAL' });
|
|
641
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA dejar catch vacíos - siempre gestionar errores (log/rethrow/wrap)', severity: 'CRITICAL' });
|
|
642
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA mezclar operaciones de escritura (commands) con lecturas (queries) en el mismo flujo/servicio - aplicar CQRS', severity: 'CRITICAL' });
|
|
643
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar Singleton - usar Inyección de Dependencias (DI por init/protocols)', severity: 'CRITICAL' });
|
|
644
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar force unwrap (!) - usar guard let/if let/?? (excepción: IBOutlets)', severity: 'CRITICAL' });
|
|
645
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar librerías de terceros (Alamofire, Swinject, Quick/Nimble, etc.) - usar APIs nativas (URLSession, DI manual, Swift Testing)', severity: 'CRITICAL' });
|
|
646
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA añadir comentarios en el código - nombres autodescriptivos', severity: 'CRITICAL' });
|
|
647
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar prints/logs ad-hoc - usar Logger del framework os', severity: 'CRITICAL' });
|
|
648
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar AnyView - afecta performance, usar @ViewBuilder o generics', severity: 'CRITICAL' });
|
|
649
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar ObservableObject - usar @Observable (iOS 17+)', severity: 'CRITICAL' });
|
|
650
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar Storyboards/XIBs - usar SwiftUI o Programmatic UI', severity: 'CRITICAL' });
|
|
651
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar CocoaPods/Carthage - usar Swift Package Manager', severity: 'CRITICAL' });
|
|
652
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar JSONSerialization - usar Codable', severity: 'CRITICAL' });
|
|
653
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar OperationQueue para async - usar Task/TaskGroup', severity: 'CRITICAL' });
|
|
654
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar any (type erasure) - usar generics con protocolos de frontera', severity: 'CRITICAL' });
|
|
655
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA usar Localizable.strings - usar String Catalogs (.xcstrings)', severity: 'CRITICAL' });
|
|
656
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA ignorar warnings - warnings = errores futuros', severity: 'CRITICAL' });
|
|
657
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA crear Massive View Controllers (>300 líneas) - separar lógica en ViewModels', severity: 'CRITICAL' });
|
|
658
|
+
patterns.push({ platform: 'ios', rule: '❌ NUNCA crear retain cycles - usar [weak self] en closures', severity: 'CRITICAL' });
|
|
659
|
+
|
|
660
|
+
// OBLIGATORIOS iOS
|
|
661
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar Swift 6.2 con Strict Concurrency Checking en Complete', severity: 'MANDATORY' });
|
|
662
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar async/await para TODAS las operaciones asíncronas', severity: 'MANDATORY' });
|
|
663
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar SwiftUI (UIKit solo si estrictamente necesario)', severity: 'MANDATORY' });
|
|
664
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar @Observable (iOS 17+) con @Bindable para bindings', severity: 'MANDATORY' });
|
|
665
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar NavigationStack + NavigationPath para navegación', severity: 'MANDATORY' });
|
|
666
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar guard/early returns - evitar pyramid of doom', severity: 'MANDATORY' });
|
|
667
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO seguir Clean Architecture (Domain → Application → Infrastructure → Presentation)', severity: 'MANDATORY' });
|
|
668
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO aplicar CQRS (Command Query Responsibility Segregation): separar comandos (write) de consultas (read)', severity: 'MANDATORY' });
|
|
669
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO verificar SOLID (SRP, OCP, LSP, ISP, DIP)', severity: 'MANDATORY' });
|
|
670
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO nombres autodescriptivos en inglés', severity: 'MANDATORY' });
|
|
671
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO comprobar que compila (Xcode build) ANTES de sugerir', severity: 'MANDATORY' });
|
|
672
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar Sendable conformance para tipos thread-safe', severity: 'MANDATORY' });
|
|
673
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar actor para estado compartido thread-safe', severity: 'MANDATORY' });
|
|
674
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar @MainActor para código UI', severity: 'MANDATORY' });
|
|
675
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar Task {} para lanzar contextos asíncronos desde código síncrono', severity: 'MANDATORY' });
|
|
676
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar Keychain nativo para datos sensibles (NO UserDefaults)', severity: 'MANDATORY' });
|
|
677
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar SwiftData (iOS 17+) para persistencia (CoreData solo legacy)', severity: 'MANDATORY' });
|
|
678
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar Swift Testing framework para tests (XCTest solo legacy)', severity: 'MANDATORY' });
|
|
679
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar makeSUT pattern en tests', severity: 'MANDATORY' });
|
|
680
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar trackForMemoryLeaks helper en tests', severity: 'MANDATORY' });
|
|
681
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar spies en vez de mocks/stubs', severity: 'MANDATORY' });
|
|
682
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar protocols para testability (inyectar protocols, no tipos concretos)', severity: 'MANDATORY' });
|
|
683
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar struct por defecto, class solo cuando necesites identity/herencia', severity: 'MANDATORY' });
|
|
684
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO usar let > var (inmutabilidad por defecto)', severity: 'MANDATORY' });
|
|
685
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO cero warnings en el proyecto', severity: 'MANDATORY' });
|
|
686
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO seguir flujo BDD → TDD (Feature files → Specs → Implementación)', severity: 'MANDATORY' });
|
|
687
|
+
patterns.push({ platform: 'ios', rule: '✅ OBLIGATORIO en producción todo real contra APIs/persistencia (cero mocks/spies)', severity: 'MANDATORY' });
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (platform === 'android') {
|
|
691
|
+
// PROHIBICIONES CRÍTICAS Android
|
|
692
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar Singleton - usar Hilt para Inyección de Dependencias', severity: 'CRITICAL' });
|
|
693
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar XML layouts - usar Jetpack Compose', severity: 'CRITICAL' });
|
|
694
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar Java en código nuevo - usar Kotlin 100%', severity: 'CRITICAL' });
|
|
695
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar LiveData - usar StateFlow/SharedFlow', severity: 'CRITICAL' });
|
|
696
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA dejar catch vacíos - siempre gestionar errores', severity: 'CRITICAL' });
|
|
697
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA mezclar operaciones de escritura (commands) con lecturas (queries) en el mismo caso de uso/repository - aplicar CQRS', severity: 'CRITICAL' });
|
|
698
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA añadir comentarios en el código', severity: 'CRITICAL' });
|
|
699
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar force unwrap (!!) - usar ?, ?:, let, requireNotNull', severity: 'CRITICAL' });
|
|
700
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar AsyncTask (deprecated) - usar Coroutines', severity: 'CRITICAL' });
|
|
701
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar RxJava en código nuevo - usar Flow', severity: 'CRITICAL' });
|
|
702
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar findViewById - usar Compose o View Binding', severity: 'CRITICAL' });
|
|
703
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar Context leaks - no referencias a Activity en objetos long-lived', severity: 'CRITICAL' });
|
|
704
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar God Activities - Single Activity + Composables', severity: 'CRITICAL' });
|
|
705
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA hardcodear strings - usar strings.xml', severity: 'CRITICAL' });
|
|
706
|
+
patterns.push({ platform: 'android', rule: '❌ NUNCA usar callbacks para async - usar suspend functions', severity: 'CRITICAL' });
|
|
707
|
+
|
|
708
|
+
// OBLIGATORIOS Android
|
|
709
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Jetpack Compose + Material 3', severity: 'MANDATORY' });
|
|
710
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Hilt para DI (@HiltAndroidApp, @AndroidEntryPoint, @Inject)', severity: 'MANDATORY' });
|
|
711
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Kotlin Coroutines + Flow para async', severity: 'MANDATORY' });
|
|
712
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar StateFlow para exponer estado del ViewModel', severity: 'MANDATORY' });
|
|
713
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar sealed classes para estados (Success, Error, Loading)', severity: 'MANDATORY' });
|
|
714
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO seguir Clean Architecture (Domain → Data → Presentation)', severity: 'MANDATORY' });
|
|
715
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO aplicar CQRS (Command Query Responsibility Segregation): separar comandos (write) de consultas (read)', severity: 'MANDATORY' });
|
|
716
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO verificar SOLID', severity: 'MANDATORY' });
|
|
717
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar MVVM + Single Activity', severity: 'MANDATORY' });
|
|
718
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Navigation Compose para navegación', severity: 'MANDATORY' });
|
|
719
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Room para persistencia local', severity: 'MANDATORY' });
|
|
720
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Retrofit + Moshi para networking', severity: 'MANDATORY' });
|
|
721
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Coil para carga de imágenes', severity: 'MANDATORY' });
|
|
722
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Kotlin DSL (build.gradle.kts)', severity: 'MANDATORY' });
|
|
723
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Version Catalogs (libs.versions.toml)', severity: 'MANDATORY' });
|
|
724
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar JUnit5 + MockK para testing', severity: 'MANDATORY' });
|
|
725
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar Timber para logging (solo en DEBUG)', severity: 'MANDATORY' });
|
|
726
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO usar EncryptedSharedPreferences para datos sensibles', severity: 'MANDATORY' });
|
|
727
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO nombres autodescriptivos en inglés', severity: 'MANDATORY' });
|
|
728
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO comprobar que compila (Gradle build) ANTES de sugerir', severity: 'MANDATORY' });
|
|
729
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO seguir flujo BDD → TDD', severity: 'MANDATORY' });
|
|
730
|
+
patterns.push({ platform: 'android', rule: '✅ OBLIGATORIO en producción todo real contra APIs/persistencia', severity: 'MANDATORY' });
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (platform === 'backend') {
|
|
734
|
+
// PROHIBICIONES CRÍTICAS Backend
|
|
735
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA usar Singleton - usar NestJS DI (@Injectable)', severity: 'CRITICAL' });
|
|
736
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA usar console.log/console.error - usar Logger de NestJS', severity: 'CRITICAL' });
|
|
737
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA usar any/unknown en TypeScript - usar tipos explícitos o generics', severity: 'CRITICAL' });
|
|
738
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA dejar catch vacíos - siempre loggear o propagar (common.error.empty_catch)', severity: 'CRITICAL' });
|
|
739
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA mezclar Command y Query en el mismo handler/service - aplicar CQRS', severity: 'CRITICAL' });
|
|
740
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA hardcodear secretos - usar variables de entorno (AWS Secrets Manager, Vault)', severity: 'CRITICAL' });
|
|
741
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA añadir comentarios en el código', severity: 'CRITICAL' });
|
|
742
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA exponer datos sin validar - usar class-validator + DTOs', severity: 'CRITICAL' });
|
|
743
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA exponer stack traces en producción', severity: 'CRITICAL' });
|
|
744
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA usar mocks en producción - solo datos reales', severity: 'CRITICAL' });
|
|
745
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA loggear datos sensibles (passwords, tokens, PII)', severity: 'CRITICAL' });
|
|
746
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA usar God classes (servicios >500 líneas)', severity: 'CRITICAL' });
|
|
747
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA usar callback hell - usar async/await', severity: 'CRITICAL' });
|
|
748
|
+
patterns.push({ platform: 'backend', rule: '❌ NUNCA poner lógica en controllers - mover a servicios/use cases', severity: 'CRITICAL' });
|
|
749
|
+
|
|
750
|
+
// OBLIGATORIOS Backend
|
|
751
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar Repository Pattern (interfaces en domain, impl en infrastructure)', severity: 'MANDATORY' });
|
|
752
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar DTOs + class-validator para validación de entrada/salida', severity: 'MANDATORY' });
|
|
753
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar Guards para autenticación/autorización (@UseGuards)', severity: 'MANDATORY' });
|
|
754
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO paginación en TODOS los endpoints de listado', severity: 'MANDATORY' });
|
|
755
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO seguir Clean Architecture (Domain → Application → Infrastructure → Presentation)', severity: 'MANDATORY' });
|
|
756
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO aplicar CQRS (Command Query Responsibility Segregation): Commands (write) separados de Queries (read)', severity: 'MANDATORY' });
|
|
757
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO verificar SOLID', severity: 'MANDATORY' });
|
|
758
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO TypeScript strict mode', severity: 'MANDATORY' });
|
|
759
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar Use Cases explícitos (CreateOrderUseCase, etc.)', severity: 'MANDATORY' });
|
|
760
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar Exception Filters para manejo global de errores', severity: 'MANDATORY' });
|
|
761
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar Swagger/OpenAPI para documentación (@nestjs/swagger)', severity: 'MANDATORY' });
|
|
762
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar queries parametrizadas (prevenir SQL injection)', severity: 'MANDATORY' });
|
|
763
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar índices en columnas frecuentes en WHERE/JOIN', severity: 'MANDATORY' });
|
|
764
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar transacciones para operaciones críticas', severity: 'MANDATORY' });
|
|
765
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar soft deletes (deleted_at) por defecto', severity: 'MANDATORY' });
|
|
766
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar JWT + refresh tokens para autenticación', severity: 'MANDATORY' });
|
|
767
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar rate limiting (@nestjs/throttler)', severity: 'MANDATORY' });
|
|
768
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar Helmet para security headers', severity: 'MANDATORY' });
|
|
769
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar health checks (/health endpoint)', severity: 'MANDATORY' });
|
|
770
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO usar correlation IDs para tracing distribuido', severity: 'MANDATORY' });
|
|
771
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO nombres autodescriptivos en inglés', severity: 'MANDATORY' });
|
|
772
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO comprobar que compila (npm run build) ANTES de sugerir', severity: 'MANDATORY' });
|
|
773
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO seguir flujo BDD → TDD', severity: 'MANDATORY' });
|
|
774
|
+
patterns.push({ platform: 'backend', rule: '✅ OBLIGATORIO coverage >95% en lógica crítica', severity: 'MANDATORY' });
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (platform === 'frontend') {
|
|
778
|
+
// PROHIBICIONES CRÍTICAS Frontend
|
|
779
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA usar any/unknown en TypeScript - usar tipos explícitos o generics', severity: 'CRITICAL' });
|
|
780
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA usar class components - usar functional components + hooks', severity: 'CRITICAL' });
|
|
781
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA hardcodear strings - usar i18n (useTranslation)', severity: 'CRITICAL' });
|
|
782
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA dejar catch vacíos - siempre gestionar errores (common.error.empty_catch)', severity: 'CRITICAL' });
|
|
783
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA mezclar operaciones de escritura (commands/mutations) con lecturas (queries) en el mismo hook/service - aplicar CQRS', severity: 'CRITICAL' });
|
|
784
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA añadir comentarios en el código', severity: 'CRITICAL' });
|
|
785
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA usar console.log en producción', severity: 'CRITICAL' });
|
|
786
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA usar Singleton - usar providers/context para DI', severity: 'CRITICAL' });
|
|
787
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA usar índice como key en listas si el orden puede cambiar', severity: 'CRITICAL' });
|
|
788
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA usar prop drilling excesivo - usar Context/Zustand', severity: 'CRITICAL' });
|
|
789
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA renderizar HTML de usuario sin sanitizar (DOMPurify)', severity: 'CRITICAL' });
|
|
790
|
+
patterns.push({ platform: 'frontend', rule: '❌ NUNCA poner tokens en URLs - usar Authorization headers', severity: 'CRITICAL' });
|
|
791
|
+
|
|
792
|
+
// OBLIGATORIOS Frontend
|
|
793
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar TypeScript strict mode', severity: 'MANDATORY' });
|
|
794
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar React functional components + hooks', severity: 'MANDATORY' });
|
|
795
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar TailwindCSS para estilos', severity: 'MANDATORY' });
|
|
796
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar Next.js 15 App Router (app/ directory)', severity: 'MANDATORY' });
|
|
797
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar Server Components por defecto ("use client" solo cuando necesario)', severity: 'MANDATORY' });
|
|
798
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar React Query para server state', severity: 'MANDATORY' });
|
|
799
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar Zustand para estado global', severity: 'MANDATORY' });
|
|
800
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar React Hook Form + Zod para forms', severity: 'MANDATORY' });
|
|
801
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO API client en capa infrastructure (abstraer axios/fetch)', severity: 'MANDATORY' });
|
|
802
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO tests con React Testing Library + Playwright', severity: 'MANDATORY' });
|
|
803
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO seguir Clean Architecture', severity: 'MANDATORY' });
|
|
804
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO aplicar CQRS (Command Query Responsibility Segregation): separar writes (mutations) de reads (queries)', severity: 'MANDATORY' });
|
|
805
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO verificar SOLID', severity: 'MANDATORY' });
|
|
806
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar Next/Image para imágenes', severity: 'MANDATORY' });
|
|
807
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar loading.tsx/error.tsx en cada ruta', severity: 'MANDATORY' });
|
|
808
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar semantic HTML + ARIA labels', severity: 'MANDATORY' });
|
|
809
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO usar keyboard navigation en todos los interactivos', severity: 'MANDATORY' });
|
|
810
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO WCAG AA mínimo (contraste 4.5:1)', severity: 'MANDATORY' });
|
|
811
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO nombres autodescriptivos en inglés', severity: 'MANDATORY' });
|
|
812
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO comprobar que compila y pasa tests ANTES de sugerir', severity: 'MANDATORY' });
|
|
813
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO seguir flujo BDD → TDD', severity: 'MANDATORY' });
|
|
814
|
+
patterns.push({ platform: 'frontend', rule: '✅ OBLIGATORIO en producción todo real contra APIs', severity: 'MANDATORY' });
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (platform === 'gold') {
|
|
818
|
+
// PROHIBICIONES CRÍTICAS Gold (comunes a todas las plataformas)
|
|
819
|
+
patterns.push({ platform: 'gold', rule: '❌ NUNCA usar Singleton - usar Inyección de Dependencias', severity: 'CRITICAL' });
|
|
820
|
+
patterns.push({ platform: 'gold', rule: '❌ NUNCA dejar catch vacíos - siempre loggear o propagar (AST: common.error.empty_catch)', severity: 'CRITICAL' });
|
|
821
|
+
patterns.push({ platform: 'gold', rule: '❌ NUNCA añadir comentarios en el código - nombres autodescriptivos', severity: 'CRITICAL' });
|
|
822
|
+
patterns.push({ platform: 'gold', rule: '❌ NUNCA usar mocks/spies en producción - todo real contra BBDD/servicios', severity: 'CRITICAL' });
|
|
823
|
+
patterns.push({ platform: 'gold', rule: '❌ NUNCA usar --no-verify o GIT_BYPASS_HOOK sin autorización explícita verbal', severity: 'CRITICAL' });
|
|
824
|
+
patterns.push({ platform: 'gold', rule: '❌ NUNCA hardcodear secretos en código - usar variables de entorno', severity: 'CRITICAL' });
|
|
825
|
+
patterns.push({ platform: 'gold', rule: '❌ NUNCA silenciar errores - siempre loggear o propagar', severity: 'CRITICAL' });
|
|
826
|
+
patterns.push({ platform: 'gold', rule: '❌ NUNCA modificar librerías locales (node_modules/@pumuki/...) - reportar bugs', severity: 'CRITICAL' });
|
|
827
|
+
|
|
828
|
+
// OBLIGATORIOS Gold (comunes a todas las plataformas)
|
|
829
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO responder siempre en español', severity: 'MANDATORY' });
|
|
830
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO actuar como Arquitecto de Soluciones y Software Designer', severity: 'MANDATORY' });
|
|
831
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO seguir flujo BDD → TDD (Feature files → Specs → Implementación)', severity: 'MANDATORY' });
|
|
832
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO seguir Clean Architecture y Clean Code (capas, dependencias hacia adentro)', severity: 'MANDATORY' });
|
|
833
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO verificar SOLID (SRP, OCP, LSP, ISP, DIP) en cada cambio', severity: 'MANDATORY' });
|
|
834
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO preferir guard/early returns - evitar pyramid of doom', severity: 'MANDATORY' });
|
|
835
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO nombres autodescriptivos en inglés (todo el código)', severity: 'MANDATORY' });
|
|
836
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO comprobar que compila ANTES de sugerir cualquier cambio', severity: 'MANDATORY' });
|
|
837
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO analizar estructura existente antes de implementar', severity: 'MANDATORY' });
|
|
838
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO usar makeSUT pattern en tests', severity: 'MANDATORY' });
|
|
839
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO usar trackForMemoryLeaks en tests', severity: 'MANDATORY' });
|
|
840
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO preferir spies frente a stubs/mocks en tests', severity: 'MANDATORY' });
|
|
841
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO validar SIEMPRE entradas de usuario', severity: 'MANDATORY' });
|
|
842
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO sanitizar datos (prevenir XSS, SQL Injection)', severity: 'MANDATORY' });
|
|
843
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO principio de menor privilegio (permisos mínimos)', severity: 'MANDATORY' });
|
|
844
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO paginación en todas las listas', severity: 'MANDATORY' });
|
|
845
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO tests como documentación viva (AAA/Given-When-Then)', severity: 'MANDATORY' });
|
|
846
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO cobertura mínima 80%, objetivo 95%+ en lógica crítica', severity: 'MANDATORY' });
|
|
847
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO commits atómicos con Conventional Commits (feat:, fix:, etc.)', severity: 'MANDATORY' });
|
|
848
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO branch naming (feature/, bugfix/, hotfix/, refactor/)', severity: 'MANDATORY' });
|
|
849
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO i18n desde día 1 - no hardcodear strings', severity: 'MANDATORY' });
|
|
850
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO WCAG 2.1 AA mínimo para accesibilidad', severity: 'MANDATORY' });
|
|
851
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO "Measure twice, cut once" - planificar bien antes de implementar', severity: 'MANDATORY' });
|
|
852
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO YAGNI - You Ain\'t Gonna Need It (no sobre-ingeniería)', severity: 'MANDATORY' });
|
|
853
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO KISS - Keep It Simple, Stupid (solución más simple que funcione)', severity: 'MANDATORY' });
|
|
854
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO Fail Fast - validar precondiciones al inicio', severity: 'MANDATORY' });
|
|
855
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO inmutabilidad por defecto (const, readonly, let > var)', severity: 'MANDATORY' });
|
|
856
|
+
patterns.push({ platform: 'gold', rule: '✅ OBLIGATORIO composición > herencia', severity: 'MANDATORY' });
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return patterns;
|
|
860
|
+
}
|
|
861
|
+
|
|
579
862
|
/**
|
|
580
863
|
* AI Gate Check - MANDATORY at start of every AI response
|
|
581
864
|
* Returns BLOCKED or ALLOWED status with auto-fixes applied
|
|
865
|
+
* NOW INCLUDES: mandatory_rules that AI MUST read and follow
|
|
582
866
|
*/
|
|
583
867
|
async function aiGateCheck() {
|
|
584
868
|
const startedAt = Date.now();
|
|
@@ -683,19 +967,71 @@ async function aiGateCheck() {
|
|
|
683
967
|
}
|
|
684
968
|
}
|
|
685
969
|
|
|
970
|
+
let mandatoryRules = null;
|
|
971
|
+
let detectedPlatforms = [];
|
|
972
|
+
try {
|
|
973
|
+
const orchestrator = getCompositionRoot().getOrchestrator();
|
|
974
|
+
const contextDecision = await orchestrator.analyzeContext();
|
|
975
|
+
if (contextDecision && contextDecision.platforms) {
|
|
976
|
+
detectedPlatforms = contextDecision.platforms.map(p => p.platform || p);
|
|
977
|
+
}
|
|
978
|
+
const fallbackPlatforms = ['backend', 'frontend', 'ios', 'android'];
|
|
979
|
+
const platformsForRules = (detectedPlatforms.length > 0 ? detectedPlatforms : fallbackPlatforms)
|
|
980
|
+
.filter(Boolean);
|
|
981
|
+
const normalizedPlatforms = Array.from(new Set(platformsForRules));
|
|
982
|
+
const rulesData = await loadPlatformRules(normalizedPlatforms);
|
|
983
|
+
const rulesSample = rulesData.criticalRules.slice(0, 5).map(r => r.rule || r);
|
|
984
|
+
const rulesCount = rulesData.criticalRules.length;
|
|
985
|
+
mandatoryRules = {
|
|
986
|
+
platforms: normalizedPlatforms,
|
|
987
|
+
criticalRules: rulesData.criticalRules,
|
|
988
|
+
rulesLoaded: Object.keys(rulesData.rules),
|
|
989
|
+
totalRulesCount: rulesCount,
|
|
990
|
+
rulesSample: rulesSample,
|
|
991
|
+
proofOfRead: `✅ VERIFIED: ${rulesCount} critical rules loaded from ${Object.keys(rulesData.rules).join(', ')}`
|
|
992
|
+
};
|
|
993
|
+
} catch (error) {
|
|
994
|
+
if (process.env.DEBUG) {
|
|
995
|
+
process.stderr.write(`[MCP] Failed to load mandatory rules: ${error.message}\n`);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const fallbackPlatforms = ['backend', 'frontend', 'ios', 'android'];
|
|
999
|
+
const normalizedPlatforms = Array.from(new Set((detectedPlatforms.length > 0 ? detectedPlatforms : fallbackPlatforms).filter(Boolean)));
|
|
1000
|
+
mandatoryRules = {
|
|
1001
|
+
platforms: normalizedPlatforms,
|
|
1002
|
+
criticalRules: [],
|
|
1003
|
+
rulesLoaded: [],
|
|
1004
|
+
status: 'FAILED_TO_LOAD',
|
|
1005
|
+
error: `Failed to load rules content: ${error && error.message ? error.message : String(error)}`
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const rulesLoadedSuccessfully = mandatoryRules &&
|
|
1010
|
+
mandatoryRules.criticalRules &&
|
|
1011
|
+
mandatoryRules.criticalRules.length > 0;
|
|
1012
|
+
|
|
1013
|
+
if (!rulesLoadedSuccessfully) {
|
|
1014
|
+
violations.push('❌ RULES_NOT_LOADED: Critical platform rules could not be loaded. AI cannot proceed without reading mandatory rules.');
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const finalBlocked = isBlocked || !rulesLoadedSuccessfully;
|
|
1018
|
+
|
|
686
1019
|
return {
|
|
687
|
-
status:
|
|
1020
|
+
status: finalBlocked ? 'BLOCKED' : 'ALLOWED',
|
|
688
1021
|
timestamp: new Date().toISOString(),
|
|
689
1022
|
branch: currentBranch,
|
|
690
1023
|
violations,
|
|
691
1024
|
warnings,
|
|
692
1025
|
autoFixes,
|
|
693
|
-
|
|
1026
|
+
mandatory_rules: rulesLoadedSuccessfully
|
|
1027
|
+
? { ...mandatoryRules, status: 'LOADED_OK' }
|
|
1028
|
+
: mandatoryRules,
|
|
1029
|
+
summary: finalBlocked
|
|
694
1030
|
? `🚫 BLOCKED: ${violations.length} violation(s). Fix before proceeding.`
|
|
695
|
-
: `🚦 ALLOWED: Gate passed
|
|
696
|
-
instructions:
|
|
1031
|
+
: `🚦 ALLOWED: Gate passed. ${mandatoryRules.totalRulesCount} critical rules loaded and verified.`,
|
|
1032
|
+
instructions: finalBlocked
|
|
697
1033
|
? 'DO NOT proceed with user task. Announce violations and fix them first.'
|
|
698
|
-
: '
|
|
1034
|
+
: `✅ ${mandatoryRules.totalRulesCount} RULES LOADED. Sample: ${mandatoryRules.rulesSample.slice(0, 2).join(' | ')}... Review ALL rules in mandatory_rules.criticalRules before ANY code generation.`
|
|
699
1035
|
};
|
|
700
1036
|
};
|
|
701
1037
|
|
|
@@ -712,11 +1048,59 @@ async function aiGateCheck() {
|
|
|
712
1048
|
violations: ['❌ GATE_TIMEOUT: AI gate check timed out. Retry or run ai-start manually.'],
|
|
713
1049
|
warnings: [],
|
|
714
1050
|
autoFixes: [],
|
|
1051
|
+
mandatory_rules: {
|
|
1052
|
+
platforms: ['backend', 'frontend', 'ios', 'android'],
|
|
1053
|
+
criticalRules: [],
|
|
1054
|
+
rulesLoaded: [],
|
|
1055
|
+
warning: '⚠️ AI MUST read and follow these rules before ANY code generation or modification',
|
|
1056
|
+
error: 'Rules could not be loaded due to timeout'
|
|
1057
|
+
},
|
|
715
1058
|
summary: '🚫 BLOCKED: Gate check timed out.',
|
|
716
1059
|
instructions: 'DO NOT proceed with user task. Retry the gate check.'
|
|
717
1060
|
};
|
|
718
1061
|
}
|
|
719
1062
|
|
|
1063
|
+
/**
|
|
1064
|
+
* Read platform rules handler - returns critical rules for a specific platform
|
|
1065
|
+
*/
|
|
1066
|
+
async function readPlatformRulesHandler(params) {
|
|
1067
|
+
const platform = params.platform;
|
|
1068
|
+
if (!platform) {
|
|
1069
|
+
return {
|
|
1070
|
+
success: false,
|
|
1071
|
+
error: 'Platform is required. Use: ios, android, backend, or frontend'
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
try {
|
|
1076
|
+
const rulesData = await loadPlatformRules([platform]);
|
|
1077
|
+
const DynamicRulesLoader = require('../../application/services/DynamicRulesLoader');
|
|
1078
|
+
const loader = new DynamicRulesLoader();
|
|
1079
|
+
const fullContent = await loader.loadRule(`rules${platform}.mdc`);
|
|
1080
|
+
|
|
1081
|
+
return {
|
|
1082
|
+
success: true,
|
|
1083
|
+
platform,
|
|
1084
|
+
rulesLoaded: true,
|
|
1085
|
+
criticalRules: rulesData.criticalRules,
|
|
1086
|
+
fullRulesContent: fullContent,
|
|
1087
|
+
warning: `⚠️ YOU MUST FOLLOW ALL THESE RULES. Violations will block commits.`,
|
|
1088
|
+
instructions: [
|
|
1089
|
+
`❌ NEVER violate any rule marked with ❌ or NUNCA/PROHIBIDO`,
|
|
1090
|
+
`✅ ALWAYS follow rules marked with ✅ or OBLIGATORIO/SIEMPRE`,
|
|
1091
|
+
`🚨 If you violate these rules, the commit will be BLOCKED`,
|
|
1092
|
+
`📝 Read the fullRulesContent carefully before generating ANY code`
|
|
1093
|
+
]
|
|
1094
|
+
};
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
return {
|
|
1097
|
+
success: false,
|
|
1098
|
+
platform,
|
|
1099
|
+
error: `Failed to load rules: ${error.message}`
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
720
1104
|
/**
|
|
721
1105
|
* Validate and fix common issues
|
|
722
1106
|
*/
|
|
@@ -1010,6 +1394,21 @@ async function handleMcpMessage(message) {
|
|
|
1010
1394
|
name: 'ai_gate_check',
|
|
1011
1395
|
description: '🚦 MANDATORY gate check',
|
|
1012
1396
|
inputSchema: { type: 'object', properties: {} }
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
name: 'read_platform_rules',
|
|
1400
|
+
description: '📚 MANDATORY: Read platform-specific rules BEFORE any code generation. Returns critical rules that AI MUST follow.',
|
|
1401
|
+
inputSchema: {
|
|
1402
|
+
type: 'object',
|
|
1403
|
+
properties: {
|
|
1404
|
+
platform: {
|
|
1405
|
+
type: 'string',
|
|
1406
|
+
enum: ['ios', 'android', 'backend', 'frontend'],
|
|
1407
|
+
description: 'Platform to load rules for'
|
|
1408
|
+
}
|
|
1409
|
+
},
|
|
1410
|
+
required: ['platform']
|
|
1411
|
+
}
|
|
1013
1412
|
}
|
|
1014
1413
|
]
|
|
1015
1414
|
}
|
|
@@ -1043,6 +1442,9 @@ async function handleMcpMessage(message) {
|
|
|
1043
1442
|
case 'ai_gate_check':
|
|
1044
1443
|
result = await aiGateCheck();
|
|
1045
1444
|
break;
|
|
1445
|
+
case 'read_platform_rules':
|
|
1446
|
+
result = await readPlatformRulesHandler(toolParams);
|
|
1447
|
+
break;
|
|
1046
1448
|
default:
|
|
1047
1449
|
return {
|
|
1048
1450
|
jsonrpc: '2.0',
|
|
@@ -25,7 +25,6 @@ AUTO_MERGE_PR=${GITFLOW_AUTO_MERGE:-false}
|
|
|
25
25
|
PR_BASE_BRANCH=${GITFLOW_PR_BASE:-develop}
|
|
26
26
|
STRICT_ATOMIC=${GITFLOW_STRICT_ATOMIC:-true}
|
|
27
27
|
REQUIRE_TEST_RELATIONS=${GITFLOW_REQUIRE_TESTS:-true}
|
|
28
|
-
STRICT_CHECK=${GITFLOW_STRICT_CHECK:-false}
|
|
29
28
|
|
|
30
29
|
print_section() {
|
|
31
30
|
printf "${BLUE}═══════════════════════════════════════════════════════════════${NC}\n"
|
|
@@ -168,17 +167,16 @@ verify_atomic_commit() {
|
|
|
168
167
|
return 0
|
|
169
168
|
fi
|
|
170
169
|
|
|
171
|
-
local files=()
|
|
170
|
+
local -a files=()
|
|
172
171
|
while IFS= read -r file; do
|
|
173
|
-
[[ -
|
|
174
|
-
files+=("$file")
|
|
172
|
+
[[ -n "$file" ]] && files+=("$file")
|
|
175
173
|
done < <($GIT_BIN diff --name-only "${commit}^..${commit}")
|
|
176
174
|
if [[ "${#files[@]}" -eq 0 ]]; then
|
|
177
175
|
return 0
|
|
178
176
|
fi
|
|
179
177
|
|
|
180
|
-
local roots_list
|
|
181
|
-
|
|
178
|
+
local roots_list=""
|
|
179
|
+
local root_count=0
|
|
182
180
|
for file in "${files[@]}"; do
|
|
183
181
|
local root="${file%%/*}"
|
|
184
182
|
if [[ "$root" == "$file" ]]; then
|
|
@@ -193,36 +191,24 @@ verify_atomic_commit() {
|
|
|
193
191
|
;;
|
|
194
192
|
esac
|
|
195
193
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
for existing in "${roots_list[@]}"; do
|
|
200
|
-
if [[ "$existing" == "$root" ]]; then
|
|
201
|
-
seen=1
|
|
202
|
-
break
|
|
203
|
-
fi
|
|
204
|
-
done
|
|
205
|
-
fi
|
|
206
|
-
if [[ "$seen" -eq 0 ]]; then
|
|
207
|
-
roots_list+=("$root")
|
|
194
|
+
if [[ " $roots_list " != *" $root "* ]]; then
|
|
195
|
+
roots_list="${roots_list}${root} "
|
|
196
|
+
root_count=$((root_count + 1))
|
|
208
197
|
fi
|
|
209
198
|
done
|
|
210
199
|
|
|
211
|
-
local root_count=${#roots_list[@]}
|
|
212
200
|
if (( root_count > 1 )); then
|
|
213
201
|
local has_scripts=0
|
|
214
202
|
local has_tests=0
|
|
215
|
-
for root in
|
|
203
|
+
for root in $roots_list; do
|
|
216
204
|
[[ "$root" == "scripts" ]] && has_scripts=1
|
|
217
205
|
[[ "$root" == "tests" ]] && has_tests=1
|
|
218
206
|
done
|
|
219
|
-
|
|
220
207
|
if [[ $has_scripts -eq 1 && $has_tests -eq 1 && $root_count -eq 2 ]]; then
|
|
221
208
|
printf "${GREEN}✅ Commit %s toca scripts + tests (permitido para bugfixes/features con tests).${NC}\n" "$commit"
|
|
222
209
|
return 0
|
|
223
210
|
fi
|
|
224
|
-
|
|
225
|
-
printf "${RED}❌ Commit %s toca múltiples raíces (%s). Divide los cambios en commits atómicos.${NC}\n" "$commit" "$(printf "%s " "${roots_list[@]}")"
|
|
211
|
+
printf "${RED}❌ Commit %s toca múltiples raíces (%s). Divide los cambios en commits atómicos.${NC}\n" "$commit" "$roots_list"
|
|
226
212
|
return 1
|
|
227
213
|
fi
|
|
228
214
|
if (( root_count == 0 )); then
|
|
@@ -230,7 +216,7 @@ verify_atomic_commit() {
|
|
|
230
216
|
return 0
|
|
231
217
|
fi
|
|
232
218
|
local root_name
|
|
233
|
-
for root_name in
|
|
219
|
+
for root_name in $roots_list; do
|
|
234
220
|
printf "${GREEN}✅ Commit %s cumple atomicidad (raíz %s).${NC}\n" "$commit" "$root_name"
|
|
235
221
|
done
|
|
236
222
|
return 0
|
|
@@ -250,10 +236,9 @@ verify_pending_commits_atomic() {
|
|
|
250
236
|
return $?
|
|
251
237
|
fi
|
|
252
238
|
|
|
253
|
-
local commits=()
|
|
239
|
+
local -a commits=()
|
|
254
240
|
while IFS= read -r commit; do
|
|
255
|
-
[[ -
|
|
256
|
-
commits+=("$commit")
|
|
241
|
+
[[ -n "$commit" ]] && commits+=("$commit")
|
|
257
242
|
done < <($GIT_BIN rev-list "${base_ref}..${branch}")
|
|
258
243
|
local failed=0
|
|
259
244
|
for commit in "${commits[@]}"; do
|
|
@@ -384,50 +369,22 @@ cmd_check() {
|
|
|
384
369
|
local branch
|
|
385
370
|
branch=$(current_branch)
|
|
386
371
|
printf "${CYAN}📍 Rama actual: %s${NC}\n" "$branch"
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
ensure_evidence_fresh || failed=1
|
|
391
|
-
lint_hooks_system || failed=1
|
|
392
|
-
run_mobile_checks || failed=1
|
|
393
|
-
else
|
|
394
|
-
ensure_evidence_fresh || true
|
|
395
|
-
lint_hooks_system || true
|
|
396
|
-
run_mobile_checks || true
|
|
397
|
-
fi
|
|
372
|
+
ensure_evidence_fresh || true
|
|
373
|
+
lint_hooks_system || true
|
|
374
|
+
run_mobile_checks || true
|
|
398
375
|
print_sync_table
|
|
399
376
|
print_cleanup_candidates
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
verify_related_files_commit "HEAD" || failed=1
|
|
404
|
-
fi
|
|
405
|
-
if ! verify_pending_commits_atomic "$branch"; then
|
|
406
|
-
failed=1
|
|
407
|
-
fi
|
|
408
|
-
if [[ "$REQUIRE_TEST_RELATIONS" == "true" ]]; then
|
|
409
|
-
if ! verify_pending_commits_related "$branch"; then
|
|
410
|
-
failed=1
|
|
411
|
-
fi
|
|
412
|
-
fi
|
|
413
|
-
else
|
|
414
|
-
verify_atomic_commit "HEAD" || true
|
|
415
|
-
if [[ "$REQUIRE_TEST_RELATIONS" == "true" ]]; then
|
|
416
|
-
verify_related_files_commit "HEAD" || true
|
|
417
|
-
fi
|
|
377
|
+
verify_atomic_commit "HEAD" || true
|
|
378
|
+
if [[ "$REQUIRE_TEST_RELATIONS" == "true" ]]; then
|
|
379
|
+
verify_related_files_commit "HEAD" || true
|
|
418
380
|
fi
|
|
419
381
|
local pending
|
|
420
382
|
pending=$(unpushed_commits "$branch")
|
|
421
383
|
if [[ "$pending" != "0" ]]; then
|
|
422
384
|
printf "${YELLOW}⚠️ Commits sin subir (${pending}). Ejecuta git push.${NC}\n"
|
|
423
|
-
if [[ "${STRICT_CHECK}" == "true" ]]; then
|
|
424
|
-
failed=1
|
|
425
|
-
fi
|
|
426
385
|
else
|
|
427
386
|
printf "${GREEN}✅ No hay commits pendientes de push.${NC}\n"
|
|
428
387
|
fi
|
|
429
|
-
|
|
430
|
-
return $failed
|
|
431
388
|
}
|
|
432
389
|
|
|
433
390
|
cmd_cycle() {
|
|
@@ -580,7 +537,6 @@ main() {
|
|
|
580
537
|
;;
|
|
581
538
|
esac
|
|
582
539
|
}
|
|
583
|
-
|
|
584
540
|
is_test_file() {
|
|
585
541
|
local file="$1"
|
|
586
542
|
case "$file" in
|
|
@@ -681,14 +637,12 @@ verify_pending_commits_related() {
|
|
|
681
637
|
local base_ref="origin/${branch}"
|
|
682
638
|
|
|
683
639
|
if ! $GIT_BIN show-ref --verify --quiet "refs/remotes/origin/${branch}"; then
|
|
684
|
-
verify_related_files_commit "HEAD"
|
|
685
|
-
return $?
|
|
640
|
+
return verify_related_files_commit "HEAD"
|
|
686
641
|
fi
|
|
687
642
|
|
|
688
|
-
local commits=()
|
|
643
|
+
local -a commits=()
|
|
689
644
|
while IFS= read -r commit; do
|
|
690
|
-
[[ -
|
|
691
|
-
commits+=("$commit")
|
|
645
|
+
[[ -n "$commit" ]] && commits+=("$commit")
|
|
692
646
|
done < <($GIT_BIN rev-list "${base_ref}..${branch}")
|
|
693
647
|
local failed=0
|
|
694
648
|
local commit
|