pumuki-ast-hooks 5.5.31 → 5.5.35

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 CHANGED
@@ -43,6 +43,7 @@
43
43
  - [Complete Architecture and Workflow](#complete-architecture-and-workflow)
44
44
  - [What is it?](#what-is-it)
45
45
  - [What problems does it solve?](#what-problems-does-it-solve)
46
+ - [Git Flow Automation](#git-flow-automation)
46
47
  - [Features](#features)
47
48
  - [Use Cases](#use-cases)
48
49
  - [Installation](#installation)
@@ -510,6 +511,70 @@ See [HOW_IT_WORKS.md](./docs/HOW_IT_WORKS.md#architecture-detection-by-platform)
510
511
  - **MEDIUM/LOW**: Generate warnings in reports
511
512
  - **Configurable**: Adjust levels according to your project
512
513
 
514
+ ### šŸ”„ Git Flow Automation
515
+
516
+ **Complete Git Flow workflow automation** with automatic enforcement:
517
+
518
+ - **Protected Branch Blocking**: Automatically blocks commits and pushes to `main`, `master`, and `develop` branches
519
+ - **Auto-Create Feature Branch**: Automatically creates feature branch when on `develop`/`main` based on changes
520
+ - **Smart Branch Naming**: Generates branch names based on file types (feature/, fix/, chore/, docs/, etc.)
521
+ - **Feature Cycle**: Execute complete Git Flow with `npm run ast:gitflow`
522
+ - **Release Cycle**: Create release PR from develop to main with `npm run ast:release`
523
+ - **Automatic PR Creation**: Creates Pull Requests using GitHub CLI (requires `gh`)
524
+ - **Optional Auto-Merge**: Automatically merges PRs with `--auto-merge` flag
525
+ - **Branch Cleanup**: Automatically deletes merged branches (local and remote)
526
+ - **Branch Synchronization**: Syncs `develop` and `main` with remote
527
+
528
+ **Feature Development Workflow:**
529
+ ```bash
530
+ # Work on develop/main (changes detected)
531
+
532
+ # Run complete cycle (auto-creates feature branch)
533
+ npm run ast:gitflow -- --auto-merge
534
+ ```
535
+
536
+ **Release Workflow:**
537
+ ```bash
538
+ # Switch to develop branch
539
+ git checkout develop
540
+
541
+ # Create release PR (develop → main)
542
+ npm run ast:release -- --auto-merge
543
+
544
+ # Or with version tag
545
+ npm run ast:release -- --tag 5.5.35 --auto-merge
546
+ ```
547
+
548
+ **Branch Naming Logic:**
549
+ - `fix/` - Files containing "fix", "bug", "error"
550
+ - `test/` - Test files or "spec" files
551
+ - `docs/` - Documentation files (README, CHANGELOG)
552
+ - `refactor/` - Files containing "refactor", "cleanup"
553
+ - `ci/` - CI/CD files (workflow, github actions)
554
+ - `chore/` - Config files, package.json
555
+ - `feature/` - Default for other changes
556
+
557
+ **Feature Cycle Options:**
558
+ ```bash
559
+ npm run ast:gitflow # Basic cycle (auto-creates branch if needed)
560
+ npm run ast:gitflow -- -m "feat: new feature" # Custom commit message
561
+ npm run ast:gitflow -- --auto-merge # Auto-merge PR
562
+ npm run ast:gitflow -- --skip-cleanup # Skip branch cleanup
563
+ npm run ast:gitflow -- --skip-sync # Skip branch sync
564
+ ```
565
+
566
+ **Release Cycle Options:**
567
+ ```bash
568
+ npm run ast:release # Create release PR
569
+ npm run ast:release -- --auto-merge # Auto-merge release PR
570
+ npm run ast:release -- --tag 5.5.35 # Create and push git tag
571
+ npm run ast:release -- --pr-title "Release v5.5.35" # Custom PR title
572
+ ```
573
+
574
+ **Hooks:**
575
+ - `pre-commit`: Blocks commits on protected branches + AST analysis
576
+ - `pre-push`: Blocks push to protected branches + validates naming
577
+
513
578
  ---
514
579
 
515
580
  ## Use Cases
@@ -568,6 +633,21 @@ hook-watch
568
633
  hook-status
569
634
  ```
570
635
 
636
+ ### 6. Git Flow Automation
637
+
638
+ ```bash
639
+ # Complete Git Flow cycle
640
+ npm run ast:gitflow
641
+
642
+ # With options
643
+ npm run ast:gitflow -- -m "feat: new feature" --auto-merge
644
+
645
+ # Using MCP tools (from IDE)
646
+ mcp0_auto_complete_gitflow
647
+ mcp0_sync_branches
648
+ mcp0_cleanup_stale_branches
649
+ ```
650
+
571
651
  ---
572
652
 
573
653
  ## Quick Start
@@ -1023,6 +1103,9 @@ hook-predict # Violation prediction
1023
1103
  hook-playbook # Execute playbook
1024
1104
 
1025
1105
  # Git Flow
1106
+ npm run ast:gitflow # Complete Git Flow cycle
1107
+ npm run ast:gitflow -- -m "msg" # With commit message
1108
+ npm run ast:gitflow -- --auto-merge # Auto-merge PR
1026
1109
  gitflow check # Verify Git Flow
1027
1110
  gitflow status # Current status
1028
1111
  gitflow workflow # View full workflow
@@ -1033,6 +1116,7 @@ gitflow workflow # View full workflow
1033
1116
  ```bash
1034
1117
  npm run audit # Full analysis
1035
1118
  npm run install-hooks # Install hooks
1119
+ npm run ast:gitflow # Complete Git Flow cycle
1036
1120
  npm test # Run tests
1037
1121
  npm run lint # Linter
1038
1122
  npm run typecheck # Type checking
@@ -1065,6 +1149,83 @@ For coding standards, see [CODE_STANDARDS.md](./docs/CODE_STANDARDS.md).
1065
1149
 
1066
1150
  ## šŸ“ Recent Changes
1067
1151
 
1152
+ ### Version 5.5.35 (2026-01-04)
1153
+
1154
+ **✨ New Features:**
1155
+ - **Git Flow Release Cycle**: New `npm run ast:release` command for creating release PRs (develop → main)
1156
+ - **Release Automation**: Automatic PR creation from develop to main with optional auto-merge
1157
+ - **Git Tag Support**: Optional git tag creation with `--tag` flag
1158
+
1159
+ **Technical Details:**
1160
+ - Separation of concerns: `ast:gitflow` for features, `ast:release` for releases
1161
+ - Release cycle steps:
1162
+ 1. Validates branch (must be develop)
1163
+ 2. Syncs develop with origin
1164
+ 3. Syncs main with origin
1165
+ 4. Creates PR: develop → main
1166
+ 5. Optionally auto-merges PR
1167
+ 6. Optionally creates git tag
1168
+ - Follows Git Flow best practices with clear separation between development and release workflows
1169
+
1170
+ ---
1171
+
1172
+ ### Version 5.5.34 (2026-01-04)
1173
+
1174
+ **✨ New Features:**
1175
+ - **Git Flow Auto-Create Branch**: Automatically creates feature branch when on `develop`/`main` based on changes
1176
+ - **Smart Branch Naming**: Generates branch names based on file types (feature/, fix/, chore/, docs/, etc.)
1177
+
1178
+ **Technical Details:**
1179
+ - When running `npm run ast:gitflow` on protected branch, script now:
1180
+ - Analyzes changed files to infer branch type
1181
+ - Generates descriptive branch name with timestamp
1182
+ - Creates and switches to new feature branch automatically
1183
+ - Continues with complete Git Flow cycle
1184
+ - Branch naming logic:
1185
+ - `fix/` - Files containing "fix", "bug", "error"
1186
+ - `test/` - Test files or "spec" files
1187
+ - `docs/` - Documentation files (README, CHANGELOG)
1188
+ - `refactor/` - Files containing "refactor", "cleanup"
1189
+ - `ci/` - CI/CD files (workflow, github actions)
1190
+ - `chore/` - Config files, package.json
1191
+ - `feature/` - Default for other changes
1192
+
1193
+ ---
1194
+
1195
+ ### Version 5.5.33 (2026-01-04)
1196
+
1197
+ **šŸ› Bug Fixes:**
1198
+ - **iOS Security Analyzer**: Fixed false positive in `ios.security.missing_ssl_pinning` rule
1199
+ - Now recognizes SSL pinning implementations using `URLSessionDelegate` + `URLAuthenticationChallenge`
1200
+ - **iOS Enterprise Analyzer**: Fixed same false positive in `ios.networking.missing_ssl_pinning` rule
1201
+
1202
+ **Technical Details:**
1203
+ - Previous implementation only checked for `ServerTrustPolicy` or `pinning` keywords
1204
+ - Files implementing SSL pinning with `URLSessionDelegate` were incorrectly flagged
1205
+ - Added detection for `URLSessionDelegate` + `URLAuthenticationChallenge` pattern
1206
+ - This fixes false positives on files like `SSLPinningDelegate.swift` that properly implement SSL pinning
1207
+
1208
+ ---
1209
+
1210
+ ### Version 5.5.32 (2026-01-04)
1211
+
1212
+ **✨ New Features:**
1213
+ - **Git Flow Automation**: Complete Git Flow cycle with `npm run ast:gitflow`
1214
+ - **Protected Branch Blocking**: Pre-commit hook now blocks commits on `main`, `master`, and `develop`
1215
+ - **Pre-Push Hook**: Automatically installed, blocks push to protected branches and validates naming conventions
1216
+ - **Auto PR Creation**: Creates Pull Requests using GitHub CLI (`gh`)
1217
+ - **Auto-Merge Support**: Optional auto-merge with `--auto-merge` flag
1218
+ - **Branch Cleanup**: Automatically deletes merged branches (local + remote)
1219
+ - **Branch Synchronization**: Syncs `develop` and `main` with remote
1220
+
1221
+ **šŸ”§ Improvements:**
1222
+ - Added `ast-gitflow` binary to package.json
1223
+ - Added `ast:gitflow` npm script
1224
+ - Updated GitEnvironmentService to install pre-push hook
1225
+ - Updated pre-commit hook to validate protected branches
1226
+
1227
+ ---
1228
+
1068
1229
  ### Version 5.5.25 (2026-01-04)
1069
1230
 
1070
1231
  **⚔ Performance Fix:**
@@ -1174,7 +1335,7 @@ Developed by **Pumuki TeamĀ®**
1174
1335
 
1175
1336
  - **Author**: Juan Carlos Merlos AlbarracĆ­n (Senior Software Architect - AI-Driven Development)
1176
1337
  - **Contact**: freelancemerlos@gmail.com
1177
- - **Version**: 5.5.16
1338
+ - **Version**: 5.5.35
1178
1339
  - **Repository**: [GitHub](https://github.com/SwiftEnProfundidad/ast-intelligence-hooks)
1179
1340
 
1180
1341
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki-ast-hooks",
3
- "version": "5.5.31",
3
+ "version": "5.5.35",
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": {
@@ -10,6 +10,7 @@
10
10
  "ast-install": "./bin/install.js",
11
11
  "ast-violations": "./bin/violations-api.js",
12
12
  "ast-check-version": "./bin/check-version.js",
13
+ "ast-gitflow": "./scripts/hooks-system/bin/gitflow-cycle.js",
13
14
  "ai-commit": "./bin/ai-commit.sh",
14
15
  "hook-run-orchestrator": "./bin/run-orchestrator.js",
15
16
  "hook-watch": "./bin/watch-hooks.js",
@@ -29,6 +30,8 @@
29
30
  "gitflow:status": "bash bin/gitflow status",
30
31
  "gitflow:workflow": "bash bin/gitflow workflow",
31
32
  "gitflow:reset": "bash bin/gitflow reset",
33
+ "ast:gitflow": "node scripts/hooks-system/bin/gitflow-cycle.js",
34
+ "ast:release": "node scripts/hooks-system/bin/gitflow-release.js",
32
35
  "violations": "node bin/violations",
33
36
  "violations:list": "node bin/violations list",
34
37
  "violations:show": "node bin/violations show",
@@ -41,7 +44,13 @@
41
44
  "build:ts": "tsc --noEmit",
42
45
  "typecheck": "tsc --noEmit",
43
46
  "ast:refresh": "node scripts/hooks-system/bin/update-evidence.sh",
44
- "ast:audit": "node scripts/hooks-system/infrastructure/ast/ast-intelligence.js"
47
+ "ast:audit": "node scripts/hooks-system/infrastructure/ast/ast-intelligence.js",
48
+ "ast:guard:start": "bash scripts/hooks-system/bin/evidence-guard start",
49
+ "ast:guard:stop": "bash scripts/hooks-system/bin/evidence-guard stop",
50
+ "ast:guard:restart": "bash scripts/hooks-system/bin/evidence-guard restart",
51
+ "ast:guard:status": "bash scripts/hooks-system/bin/evidence-guard status",
52
+ "ast:guard:logs": "bash scripts/hooks-system/bin/evidence-guard logs",
53
+ "ast:check-version": "node scripts/hooks-system/bin/check-version.js"
45
54
  },
46
55
  "keywords": [
47
56
  "ast",
@@ -116,6 +116,7 @@ class ConfigurationGeneratorService {
116
116
  packageJson.scripts['ast:guard:status'] = 'bash scripts/hooks-system/bin/evidence-guard status';
117
117
  packageJson.scripts['ast:guard:logs'] = 'bash scripts/hooks-system/bin/evidence-guard logs';
118
118
  packageJson.scripts['ast:check-version'] = 'node scripts/hooks-system/bin/check-version.js';
119
+ packageJson.scripts['ast:gitflow'] = 'node scripts/hooks-system/bin/gitflow-cycle.js';
119
120
 
120
121
  fs.writeFileSync(projectPackageJsonPath, JSON.stringify(packageJson, null, 2));
121
122
  this.logSuccess('npm scripts added');
@@ -108,6 +108,26 @@ fi
108
108
  # Change to project root (where package.json is)
109
109
  cd "$(git rev-parse --show-toplevel)" || exit 1
110
110
 
111
+ # ═══════════════════════════════════════════════════════════════
112
+ # GIT FLOW ENFORCEMENT: Block commits on protected branches
113
+ # ═══════════════════════════════════════════════════════════════
114
+ CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
115
+
116
+ if [[ "$CURRENT_BRANCH" == "main" ]] || [[ "$CURRENT_BRANCH" == "master" ]] || [[ "$CURRENT_BRANCH" == "develop" ]]; then
117
+ echo ""
118
+ echo "āŒ COMMIT BLOCKED: Cannot commit directly to protected branch: $CURRENT_BRANCH"
119
+ echo ""
120
+ echo "🐈 Pumuki says: Create a feature branch first!"
121
+ echo ""
122
+ echo " git checkout -b feature/my-feature"
123
+ echo " git checkout -b fix/my-fix"
124
+ echo " git checkout -b hotfix/urgent-fix"
125
+ echo ""
126
+ echo "Then run: npm run ast:gitflow"
127
+ echo ""
128
+ exit 1
129
+ fi
130
+
111
131
  # Check if there are staged files
112
132
  STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\\.(ts|tsx|js|jsx|swift|kt)$' || true)
113
133
  if [ -z "$STAGED_FILES" ]; then
@@ -116,7 +116,6 @@ class McpConfigurator {
116
116
  const mcpConfig = {
117
117
  mcpServers: {
118
118
  [serverId]: {
119
- type: 'stdio',
120
119
  command: nodePath,
121
120
  args: [
122
121
  entrypoint
@@ -0,0 +1,496 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git Flow Cycle - Complete automation
4
+ *
5
+ * Executes the full Git Flow cycle:
6
+ * 1. Validates current branch (must be feature/fix/hotfix/chore)
7
+ * 2. Commits uncommitted changes (optional)
8
+ * 3. Pushes to origin
9
+ * 4. Creates PR (requires gh CLI)
10
+ * 5. Optionally merges PR
11
+ * 6. Cleans up merged branches
12
+ * 7. Syncs local branches with remote
13
+ */
14
+
15
+ const { execSync } = require('child_process');
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const COLORS = {
20
+ reset: '\x1b[0m',
21
+ red: '\x1b[31m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ cyan: '\x1b[36m',
26
+ magenta: '\x1b[35m'
27
+ };
28
+
29
+ function log(color, message) {
30
+ console.log(`${color}${message}${COLORS.reset}`);
31
+ }
32
+
33
+ function exec(cmd, options = {}) {
34
+ try {
35
+ return execSync(cmd, {
36
+ encoding: 'utf-8',
37
+ stdio: options.silent ? 'pipe' : 'inherit',
38
+ ...options
39
+ });
40
+ } catch (error) {
41
+ if (options.ignoreError) {
42
+ return null;
43
+ }
44
+ throw error;
45
+ }
46
+ }
47
+
48
+ function execSilent(cmd) {
49
+ try {
50
+ return execSync(cmd, { encoding: 'utf-8', stdio: 'pipe' }).trim();
51
+ } catch (error) {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ function getCurrentBranch() {
57
+ return execSilent('git branch --show-current') || 'unknown';
58
+ }
59
+
60
+ function isProtectedBranch(branch) {
61
+ const protected = ['main', 'master', 'develop'];
62
+ return protected.includes(branch);
63
+ }
64
+
65
+ function isValidFeatureBranch(branch) {
66
+ return /^(feature|fix|hotfix|chore|docs|refactor|test|ci)\//.test(branch);
67
+ }
68
+
69
+ function hasUncommittedChanges() {
70
+ const status = execSilent('git status --porcelain');
71
+ return status && status.length > 0;
72
+ }
73
+
74
+ function hasStagedChanges() {
75
+ const staged = execSilent('git diff --cached --name-only');
76
+ return staged && staged.length > 0;
77
+ }
78
+
79
+ function getBaseBranch() {
80
+ // Check if develop exists
81
+ const hasDevelop = execSilent('git show-ref --verify --quiet refs/heads/develop');
82
+ return hasDevelop !== null ? 'develop' : 'main';
83
+ }
84
+
85
+ function getMergedBranches(baseBranch) {
86
+ const output = execSilent(`git branch --merged ${baseBranch}`);
87
+ if (!output) return [];
88
+ return output.split('\n')
89
+ .map(b => b.replace(/^\*?\s*/, '').trim())
90
+ .filter(b => b && !isProtectedBranch(b));
91
+ }
92
+
93
+ function isGitHubCliAvailable() {
94
+ return execSilent('gh auth status') !== null;
95
+ }
96
+
97
+ function prExists(branch) {
98
+ return execSilent(`gh pr view ${branch}`) !== null;
99
+ }
100
+
101
+ // ════════════════════════════════════════════════════════════════
102
+ // MAIN CYCLE STEPS
103
+ // ════════════════════════════════════════════════════════════════
104
+
105
+ function getChangedFiles() {
106
+ try {
107
+ const output = execSilent('git status --porcelain');
108
+ if (!output) return [];
109
+ return output.split('\n')
110
+ .filter(line => line.trim())
111
+ .map(line => {
112
+ const parts = line.trim().split(/\s+/);
113
+ return parts[1] || parts[0];
114
+ });
115
+ } catch (error) {
116
+ return [];
117
+ }
118
+ }
119
+
120
+ function inferBranchType(changedFiles) {
121
+ const fileTypes = changedFiles.join(' ').toLowerCase();
122
+
123
+ if (fileTypes.includes('fix') || fileTypes.includes('bug') || fileTypes.includes('error')) {
124
+ return 'fix';
125
+ }
126
+ if (fileTypes.includes('test') || fileTypes.includes('spec')) {
127
+ return 'test';
128
+ }
129
+ if (fileTypes.includes('doc') || fileTypes.includes('readme') || fileTypes.includes('changelog')) {
130
+ return 'docs';
131
+ }
132
+ if (fileTypes.includes('refactor') || fileTypes.includes('cleanup')) {
133
+ return 'refactor';
134
+ }
135
+ if (fileTypes.includes('ci') || fileTypes.includes('workflow') || fileTypes.includes('github')) {
136
+ return 'ci';
137
+ }
138
+ if (fileTypes.includes('chore') || fileTypes.includes('config') || fileTypes.includes('package')) {
139
+ return 'chore';
140
+ }
141
+ return 'feature';
142
+ }
143
+
144
+ function generateBranchName(changedFiles) {
145
+ const type = inferBranchType(changedFiles);
146
+
147
+ // Extract keywords from file paths
148
+ const keywords = changedFiles
149
+ .map(f => f.replace(/.*\//, '').replace(/\.[^.]+$/, ''))
150
+ .filter(f => f.length > 2)
151
+ .slice(0, 3);
152
+
153
+ let suffix = keywords.join('-') || 'update';
154
+ suffix = suffix.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
155
+
156
+ // Add timestamp to ensure uniqueness
157
+ const timestamp = Date.now().toString(36);
158
+ return `${type}/${suffix}-${timestamp}`;
159
+ }
160
+
161
+ function step1_validateBranch() {
162
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
163
+ log(COLORS.cyan, 'šŸ“ Step 1: Validate Branch');
164
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
165
+
166
+ let branch = getCurrentBranch();
167
+ log(COLORS.blue, `Current branch: ${branch}`);
168
+
169
+ if (isProtectedBranch(branch)) {
170
+ log(COLORS.yellow, `\nāš ļø On protected branch: ${branch}`);
171
+ log(COLORS.cyan, 'šŸ”„ Automatically creating feature branch...');
172
+
173
+ const changedFiles = getChangedFiles();
174
+ if (changedFiles.length === 0) {
175
+ log(COLORS.red, '\nāŒ No changes detected to create a feature branch');
176
+ process.exit(1);
177
+ }
178
+
179
+ const newBranch = generateBranchName(changedFiles);
180
+ log(COLORS.blue, `Creating branch: ${newBranch}`);
181
+
182
+ try {
183
+ exec(`git checkout -b ${newBranch}`);
184
+ branch = newBranch;
185
+ log(COLORS.green, `āœ… Created and switched to: ${newBranch}`);
186
+ } catch (error) {
187
+ log(COLORS.red, `\nāŒ Failed to create branch: ${error.message}`);
188
+ process.exit(1);
189
+ }
190
+ }
191
+
192
+ if (!isValidFeatureBranch(branch)) {
193
+ log(COLORS.yellow, `\nāš ļø Branch '${branch}' doesn't follow naming convention`);
194
+ log(COLORS.yellow, 'Expected: feature/, fix/, hotfix/, chore/, docs/, refactor/, test/, ci/');
195
+ log(COLORS.yellow, '\nContinuing anyway...');
196
+ }
197
+
198
+ log(COLORS.green, 'āœ… Branch validation passed');
199
+ return branch;
200
+ }
201
+
202
+ function step2_commitChanges(commitMessage) {
203
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
204
+ log(COLORS.cyan, 'šŸ“ Step 2: Commit Changes');
205
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
206
+
207
+ if (!hasUncommittedChanges()) {
208
+ log(COLORS.green, 'āœ… No uncommitted changes');
209
+ return;
210
+ }
211
+
212
+ log(COLORS.yellow, 'āš ļø Uncommitted changes detected');
213
+
214
+ if (!hasStagedChanges()) {
215
+ log(COLORS.blue, 'Staging all changes...');
216
+ exec('git add -A');
217
+ }
218
+
219
+ const message = commitMessage || `chore: auto-commit changes on ${getCurrentBranch()}`;
220
+ log(COLORS.blue, `Committing: ${message}`);
221
+ exec(`git commit -m "${message}"`);
222
+ log(COLORS.green, 'āœ… Changes committed');
223
+ }
224
+
225
+ function step3_pushToOrigin(branch) {
226
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
227
+ log(COLORS.cyan, 'šŸš€ Step 3: Push to Origin');
228
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
229
+
230
+ log(COLORS.blue, `Pushing ${branch} to origin...`);
231
+ exec(`git push -u origin ${branch}`);
232
+ log(COLORS.green, 'āœ… Pushed to origin');
233
+ }
234
+
235
+ function step4_createPR(branch, baseBranch, prTitle, prBody) {
236
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
237
+ log(COLORS.cyan, 'šŸ“‹ Step 4: Create Pull Request');
238
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
239
+
240
+ if (!isGitHubCliAvailable()) {
241
+ log(COLORS.yellow, 'āš ļø GitHub CLI (gh) not available or not authenticated');
242
+ log(COLORS.yellow, ' Install: https://cli.github.com/');
243
+ log(COLORS.yellow, ' Auth: gh auth login');
244
+ log(COLORS.yellow, '\n Create PR manually at GitHub.');
245
+ return null;
246
+ }
247
+
248
+ if (prExists(branch)) {
249
+ log(COLORS.green, 'āœ… PR already exists for this branch');
250
+ const prUrl = execSilent(`gh pr view ${branch} --json url --jq .url`);
251
+ return prUrl;
252
+ }
253
+
254
+ const title = prTitle || execSilent('git log -1 --pretty=%s') || `Merge ${branch}`;
255
+ const body = prBody || `## Summary\nAutomated PR for ${branch}\n\n## Changes\n${execSilent(`git log --oneline origin/${baseBranch}..${branch}`) || '- Updates'}`;
256
+
257
+ log(COLORS.blue, `Creating PR: ${title}`);
258
+ log(COLORS.blue, `Base: ${baseBranch} ← Head: ${branch}`);
259
+
260
+ try {
261
+ const output = execSilent(`gh pr create --base ${baseBranch} --head ${branch} --title "${title}" --body "${body.replace(/"/g, '\\"')}"`);
262
+ if (output) {
263
+ const urlMatch = output.match(/https:\/\/github\.com\/.*\/pull\/\d+/);
264
+ const prUrl = urlMatch ? urlMatch[0] : output;
265
+ log(COLORS.green, `āœ… PR created: ${prUrl}`);
266
+ return prUrl;
267
+ }
268
+ } catch (error) {
269
+ log(COLORS.red, `āŒ Failed to create PR: ${error.message}`);
270
+ }
271
+
272
+ return null;
273
+ }
274
+
275
+ function step5_mergePR(prUrl, autoMerge) {
276
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
277
+ log(COLORS.cyan, 'šŸ”€ Step 5: Merge Pull Request');
278
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
279
+
280
+ if (!prUrl) {
281
+ log(COLORS.yellow, 'āš ļø No PR URL available, skipping merge');
282
+ return false;
283
+ }
284
+
285
+ if (!autoMerge) {
286
+ log(COLORS.yellow, 'āš ļø Auto-merge disabled. Merge PR manually.');
287
+ log(COLORS.blue, ` PR: ${prUrl}`);
288
+ return false;
289
+ }
290
+
291
+ log(COLORS.blue, 'Enabling auto-merge (squash)...');
292
+ try {
293
+ exec(`gh pr merge --auto --squash "${prUrl}"`, { ignoreError: true });
294
+ log(COLORS.green, 'āœ… Auto-merge enabled');
295
+ return true;
296
+ } catch (error) {
297
+ log(COLORS.yellow, 'āš ļø Could not enable auto-merge (may require admin approval)');
298
+ return false;
299
+ }
300
+ }
301
+
302
+ function step6_cleanupBranches(baseBranch) {
303
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
304
+ log(COLORS.cyan, '🧹 Step 6: Cleanup Merged Branches');
305
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
306
+
307
+ // Fetch latest
308
+ exec('git fetch --prune', { silent: true, ignoreError: true });
309
+
310
+ const mergedBranches = getMergedBranches(baseBranch);
311
+
312
+ if (mergedBranches.length === 0) {
313
+ log(COLORS.green, 'āœ… No merged branches to clean');
314
+ return;
315
+ }
316
+
317
+ log(COLORS.blue, `Found ${mergedBranches.length} merged branches:`);
318
+ mergedBranches.forEach(b => log(COLORS.yellow, ` - ${b}`));
319
+
320
+ // Delete local merged branches
321
+ for (const branch of mergedBranches) {
322
+ log(COLORS.blue, `Deleting local: ${branch}`);
323
+ exec(`git branch -d "${branch}"`, { ignoreError: true, silent: true });
324
+ }
325
+
326
+ // Delete remote merged branches (if gh available)
327
+ if (isGitHubCliAvailable()) {
328
+ for (const branch of mergedBranches) {
329
+ log(COLORS.blue, `Deleting remote: origin/${branch}`);
330
+ exec(`git push origin --delete "${branch}"`, { ignoreError: true, silent: true });
331
+ }
332
+ }
333
+
334
+ log(COLORS.green, `āœ… Cleaned ${mergedBranches.length} merged branches`);
335
+ }
336
+
337
+ function step7_syncBranches(baseBranch) {
338
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
339
+ log(COLORS.cyan, 'šŸ”„ Step 7: Sync Branches');
340
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
341
+
342
+ const currentBranch = getCurrentBranch();
343
+
344
+ log(COLORS.blue, 'Fetching from origin...');
345
+ exec('git fetch origin', { ignoreError: true, silent: true });
346
+
347
+ // Sync develop
348
+ if (execSilent('git show-ref --verify --quiet refs/heads/develop') !== null) {
349
+ log(COLORS.blue, 'Syncing develop...');
350
+ exec('git checkout develop && git pull origin develop', { ignoreError: true, silent: true });
351
+ }
352
+
353
+ // Sync main
354
+ log(COLORS.blue, 'Syncing main...');
355
+ exec('git checkout main && git pull origin main', { ignoreError: true, silent: true });
356
+
357
+ // Return to original branch
358
+ log(COLORS.blue, `Returning to ${currentBranch}...`);
359
+ exec(`git checkout ${currentBranch}`, { ignoreError: true, silent: true });
360
+
361
+ log(COLORS.green, 'āœ… Branches synchronized');
362
+ }
363
+
364
+ // ════════════════════════════════════════════════════════════════
365
+ // MAIN
366
+ // ════════════════════════════════════════════════════════════════
367
+
368
+ function printUsage() {
369
+ console.log(`
370
+ ${COLORS.cyan}═══════════════════════════════════════════════════════════════
371
+ 🐈 Pumuki Git Flow Cycle
372
+ ═══════════════════════════════════════════════════════════════${COLORS.reset}
373
+
374
+ ${COLORS.blue}Usage:${COLORS.reset} npm run ast:gitflow [options]
375
+
376
+ ${COLORS.blue}Options:${COLORS.reset}
377
+ --commit-message, -m <msg> Custom commit message
378
+ --pr-title <title> Custom PR title
379
+ --auto-merge Enable auto-merge after PR creation
380
+ --skip-cleanup Skip branch cleanup step
381
+ --skip-sync Skip branch sync step
382
+ --help, -h Show this help
383
+
384
+ ${COLORS.blue}Examples:${COLORS.reset}
385
+ npm run ast:gitflow
386
+ npm run ast:gitflow -- -m "feat: add new feature"
387
+ npm run ast:gitflow -- --auto-merge
388
+ npm run ast:gitflow -- --pr-title "My PR Title" --auto-merge
389
+
390
+ ${COLORS.blue}What it does:${COLORS.reset}
391
+ 1. Validates branch (must be feature/, fix/, etc.)
392
+ 2. Commits uncommitted changes
393
+ 3. Pushes to origin
394
+ 4. Creates PR (requires gh CLI)
395
+ 5. Optionally auto-merges PR
396
+ 6. Cleans up merged branches
397
+ 7. Syncs local branches with remote
398
+
399
+ ${COLORS.yellow}āš ļø Requires:${COLORS.reset}
400
+ - Git repository
401
+ - GitHub CLI (gh) for PR operations: https://cli.github.com/
402
+ `);
403
+ }
404
+
405
+ function parseArgs(args) {
406
+ const options = {
407
+ commitMessage: null,
408
+ prTitle: null,
409
+ prBody: null,
410
+ autoMerge: false,
411
+ skipCleanup: false,
412
+ skipSync: false,
413
+ help: false
414
+ };
415
+
416
+ for (let i = 0; i < args.length; i++) {
417
+ const arg = args[i];
418
+ switch (arg) {
419
+ case '--commit-message':
420
+ case '-m':
421
+ options.commitMessage = args[++i];
422
+ break;
423
+ case '--pr-title':
424
+ options.prTitle = args[++i];
425
+ break;
426
+ case '--pr-body':
427
+ options.prBody = args[++i];
428
+ break;
429
+ case '--auto-merge':
430
+ options.autoMerge = true;
431
+ break;
432
+ case '--skip-cleanup':
433
+ options.skipCleanup = true;
434
+ break;
435
+ case '--skip-sync':
436
+ options.skipSync = true;
437
+ break;
438
+ case '--help':
439
+ case '-h':
440
+ options.help = true;
441
+ break;
442
+ }
443
+ }
444
+
445
+ return options;
446
+ }
447
+
448
+ function main() {
449
+ const args = process.argv.slice(2);
450
+ const options = parseArgs(args);
451
+
452
+ if (options.help) {
453
+ printUsage();
454
+ process.exit(0);
455
+ }
456
+
457
+ log(COLORS.magenta, '\n🐈 Pumuki Git Flow Cycle - Starting...\n');
458
+
459
+ try {
460
+ // Step 1: Validate branch
461
+ const branch = step1_validateBranch();
462
+ const baseBranch = getBaseBranch();
463
+
464
+ // Step 2: Commit changes
465
+ step2_commitChanges(options.commitMessage);
466
+
467
+ // Step 3: Push to origin
468
+ step3_pushToOrigin(branch);
469
+
470
+ // Step 4: Create PR
471
+ const prUrl = step4_createPR(branch, baseBranch, options.prTitle, options.prBody);
472
+
473
+ // Step 5: Merge PR (optional)
474
+ step5_mergePR(prUrl, options.autoMerge);
475
+
476
+ // Step 6: Cleanup branches
477
+ if (!options.skipCleanup) {
478
+ step6_cleanupBranches(baseBranch);
479
+ }
480
+
481
+ // Step 7: Sync branches
482
+ if (!options.skipSync) {
483
+ step7_syncBranches(baseBranch);
484
+ }
485
+
486
+ log(COLORS.green, '\n═══════════════════════════════════════════════════════════════');
487
+ log(COLORS.green, 'āœ… Git Flow Cycle Complete!');
488
+ log(COLORS.green, '═══════════════════════════════════════════════════════════════\n');
489
+
490
+ } catch (error) {
491
+ log(COLORS.red, `\nāŒ Git Flow Cycle failed: ${error.message}`);
492
+ process.exit(1);
493
+ }
494
+ }
495
+
496
+ main();
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Git Flow Release Cycle - Release automation
4
+ *
5
+ * Executes the release cycle (develop → main):
6
+ * 1. Validates current branch (must be develop)
7
+ * 2. Syncs develop with origin
8
+ * 3. Creates PR from develop to main
9
+ * 4. Optionally merges PR
10
+ * 5. Syncs main with origin
11
+ */
12
+
13
+ const { execSync } = require('child_process');
14
+
15
+ const COLORS = {
16
+ reset: '\x1b[0m',
17
+ red: '\x1b[31m',
18
+ green: '\x1b[32m',
19
+ yellow: '\x1b[33m',
20
+ blue: '\x1b[34m',
21
+ cyan: '\x1b[36m',
22
+ magenta: '\x1b[35m'
23
+ };
24
+
25
+ function log(color, message) {
26
+ console.log(`${color}${message}${COLORS.reset}`);
27
+ }
28
+
29
+ function exec(cmd, options = {}) {
30
+ try {
31
+ return execSync(cmd, {
32
+ encoding: 'utf-8',
33
+ stdio: options.silent ? 'pipe' : 'inherit',
34
+ ...options
35
+ });
36
+ } catch (error) {
37
+ if (options.ignoreError) {
38
+ return null;
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+
44
+ function execSilent(cmd) {
45
+ try {
46
+ return execSync(cmd, { encoding: 'utf-8', stdio: 'pipe' }).trim();
47
+ } catch (error) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function getCurrentBranch() {
53
+ return execSilent('git branch --show-current') || 'unknown';
54
+ }
55
+
56
+ function isProtectedBranch(branch) {
57
+ const protected = ['main', 'master', 'develop'];
58
+ return protected.includes(branch);
59
+ }
60
+
61
+ function hasUncommittedChanges() {
62
+ const status = execSilent('git status --porcelain');
63
+ return status && status.length > 0;
64
+ }
65
+
66
+ function isGitHubCliAvailable() {
67
+ return execSilent('gh auth status') !== null;
68
+ }
69
+
70
+ function prExists(base, head) {
71
+ return execSilent(`gh pr list --base ${base} --head ${head} --json number --jq '. | length'`) !== '0';
72
+ }
73
+
74
+ function getPrUrl(base, head) {
75
+ const output = execSilent(`gh pr list --base ${base} --head ${head} --json url --jq '.[0].url'`);
76
+ return output || null;
77
+ }
78
+
79
+ // ════════════════════════════════════════════════════════════════
80
+ // MAIN CYCLE STEPS
81
+ // ════════════════════════════════════════════════════════════════
82
+
83
+ function step1_validateBranch() {
84
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
85
+ log(COLORS.cyan, 'šŸ“ Step 1: Validate Branch');
86
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
87
+
88
+ const branch = getCurrentBranch();
89
+ log(COLORS.blue, `Current branch: ${branch}`);
90
+
91
+ if (branch !== 'develop') {
92
+ log(COLORS.red, `\nāŒ Release cycle must be run from develop branch`);
93
+ log(COLORS.yellow, `\nCurrent branch: ${branch}`);
94
+ log(COLORS.yellow, 'Switch to develop first:');
95
+ log(COLORS.yellow, ' git checkout develop');
96
+ process.exit(1);
97
+ }
98
+
99
+ if (hasUncommittedChanges()) {
100
+ log(COLORS.red, '\nāŒ Uncommitted changes detected');
101
+ log(COLORS.yellow, 'Commit or stash changes before creating release');
102
+ process.exit(1);
103
+ }
104
+
105
+ log(COLORS.green, 'āœ… Branch validation passed');
106
+ return branch;
107
+ }
108
+
109
+ function step2_syncDevelop() {
110
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
111
+ log(COLORS.cyan, 'šŸ”„ Step 2: Sync Develop');
112
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
113
+
114
+ log(COLORS.blue, 'Fetching from origin...');
115
+ exec('git fetch origin', { silent: true });
116
+
117
+ log(COLORS.blue, 'Pulling latest changes from develop...');
118
+ exec('git pull origin develop', { silent: true });
119
+
120
+ log(COLORS.green, 'āœ… Develop synced');
121
+ }
122
+
123
+ function step3_syncMain() {
124
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
125
+ log(COLORS.cyan, 'šŸ”„ Step 3: Sync Main');
126
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
127
+
128
+ log(COLORS.blue, 'Fetching from origin...');
129
+ exec('git fetch origin', { silent: true });
130
+
131
+ log(COLORS.blue, 'Pulling latest changes from main...');
132
+ exec('git pull origin main', { silent: true });
133
+
134
+ log(COLORS.green, 'āœ… Main synced');
135
+ }
136
+
137
+ function step4_createReleasePR(prTitle, prBody) {
138
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
139
+ log(COLORS.cyan, 'šŸ“‹ Step 4: Create Release PR');
140
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
141
+
142
+ if (!isGitHubCliAvailable()) {
143
+ log(COLORS.yellow, 'āš ļø GitHub CLI (gh) not available or not authenticated');
144
+ log(COLORS.yellow, ' Install: https://cli.github.com/');
145
+ log(COLORS.yellow, ' Auth: gh auth login');
146
+ log(COLORS.yellow, '\n Create PR manually at GitHub.');
147
+ return null;
148
+ }
149
+
150
+ const base = 'main';
151
+ const head = 'develop';
152
+
153
+ if (prExists(base, head)) {
154
+ const existingPrUrl = getPrUrl(base, head);
155
+ log(COLORS.green, 'āœ… Release PR already exists');
156
+ log(COLORS.blue, ` PR: ${existingPrUrl}`);
157
+ return existingPrUrl;
158
+ }
159
+
160
+ const title = prTitle || `Release: ${new Date().toISOString().split('T')[0]}`;
161
+ const body = prBody || `## Release Summary\n\nAutomated release from develop to main\n\n## Changes\n${execSilent('git log --oneline origin/main..develop') || '- See commit history'}`;
162
+
163
+ log(COLORS.blue, `Creating release PR: ${title}`);
164
+ log(COLORS.blue, `Base: ${base} ← Head: ${head}`);
165
+
166
+ try {
167
+ const output = execSilent(`gh pr create --base ${base} --head ${head} --title "${title}" --body "${body.replace(/"/g, '\\"')}"`);
168
+ if (output) {
169
+ const urlMatch = output.match(/https:\/\/github\.com\/.*\/pull\/\d+/);
170
+ const prUrl = urlMatch ? urlMatch[0] : output;
171
+ log(COLORS.green, `āœ… Release PR created: ${prUrl}`);
172
+ return prUrl;
173
+ }
174
+ } catch (error) {
175
+ log(COLORS.red, `āŒ Failed to create PR: ${error.message}`);
176
+ }
177
+
178
+ return null;
179
+ }
180
+
181
+ function step5_mergeReleasePR(prUrl, autoMerge) {
182
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
183
+ log(COLORS.cyan, 'šŸ”€ Step 5: Merge Release PR');
184
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
185
+
186
+ if (!prUrl) {
187
+ log(COLORS.yellow, 'āš ļø No PR URL available, skipping merge');
188
+ return false;
189
+ }
190
+
191
+ if (!autoMerge) {
192
+ log(COLORS.yellow, 'āš ļø Auto-merge disabled. Merge PR manually.');
193
+ log(COLORS.blue, ` PR: ${prUrl}`);
194
+ return false;
195
+ }
196
+
197
+ log(COLORS.blue, 'Enabling auto-merge (squash)...');
198
+ try {
199
+ exec(`gh pr merge --auto --squash "${prUrl}"`, { ignoreError: true });
200
+ log(COLORS.green, 'āœ… Auto-merge enabled');
201
+ return true;
202
+ } catch (error) {
203
+ log(COLORS.yellow, 'āš ļø Could not enable auto-merge (may require admin approval)');
204
+ return false;
205
+ }
206
+ }
207
+
208
+ function step6_tagRelease(version) {
209
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
210
+ log(COLORS.cyan, 'šŸ·ļø Step 6: Tag Release');
211
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
212
+
213
+ if (!version) {
214
+ log(COLORS.yellow, 'āš ļø No version specified, skipping tag');
215
+ return;
216
+ }
217
+
218
+ log(COLORS.blue, `Creating tag: v${version}`);
219
+ try {
220
+ exec(`git tag -a v${version} -m "Release v${version}"`);
221
+ exec(`git push origin v${version}`);
222
+ log(COLORS.green, `āœ… Tag v${version} created and pushed`);
223
+ } catch (error) {
224
+ log(COLORS.red, `āŒ Failed to create tag: ${error.message}`);
225
+ }
226
+ }
227
+
228
+ // ════════════════════════════════════════════════════════════════
229
+ // MAIN
230
+ // ════════════════════════════════════════════════════════════════
231
+
232
+ function printUsage() {
233
+ console.log(`
234
+ ${COLORS.cyan}═══════════════════════════════════════════════════════════════
235
+ 🐈 Pumuki Git Flow Release Cycle
236
+ ═══════════════════════════════════════════════════════════════${COLORS.reset}
237
+
238
+ ${COLORS.blue}Usage:${COLORS.reset} npm run ast:release [options]
239
+
240
+ ${COLORS.blue}Options:${COLORS.reset}
241
+ --pr-title <title> Custom PR title
242
+ --pr-body <body> Custom PR body
243
+ --auto-merge Enable auto-merge after PR creation
244
+ --tag <version> Create and push git tag
245
+ --help, -h Show this help
246
+
247
+ ${COLORS.blue}Examples:${COLORS.reset}
248
+ npm run ast:release
249
+ npm run ast:release -- --auto-merge
250
+ npm run ast:release -- --tag 5.5.35 --auto-merge
251
+ npm run ast:release -- --pr-title "Release v5.5.35" --auto-merge
252
+
253
+ ${COLORS.blue}What it does:${COLORS.reset}
254
+ 1. Validates branch (must be develop)
255
+ 2. Syncs develop with origin
256
+ 3. Syncs main with origin
257
+ 4. Creates PR: develop → main
258
+ 5. Optionally auto-merges PR
259
+ 6. Optionally creates git tag
260
+
261
+ ${COLORS.yellow}āš ļø Requires:${COLORS.reset}
262
+ - Git repository
263
+ - GitHub CLI (gh) for PR operations: https://cli.github.com/
264
+ - Must be on develop branch
265
+ - No uncommitted changes
266
+ `);
267
+ }
268
+
269
+ function parseArgs(args) {
270
+ const options = {
271
+ prTitle: null,
272
+ prBody: null,
273
+ autoMerge: false,
274
+ tag: null,
275
+ help: false
276
+ };
277
+
278
+ for (let i = 0; i < args.length; i++) {
279
+ const arg = args[i];
280
+ switch (arg) {
281
+ case '--pr-title':
282
+ options.prTitle = args[++i];
283
+ break;
284
+ case '--pr-body':
285
+ options.prBody = args[++i];
286
+ break;
287
+ case '--auto-merge':
288
+ options.autoMerge = true;
289
+ break;
290
+ case '--tag':
291
+ options.tag = args[++i];
292
+ break;
293
+ case '--help':
294
+ case '-h':
295
+ options.help = true;
296
+ break;
297
+ }
298
+ }
299
+
300
+ return options;
301
+ }
302
+
303
+ function main() {
304
+ const args = process.argv.slice(2);
305
+ const options = parseArgs(args);
306
+
307
+ if (options.help) {
308
+ printUsage();
309
+ process.exit(0);
310
+ }
311
+
312
+ log(COLORS.magenta, '\n🐈 Pumuki Git Flow Release Cycle - Starting...\n');
313
+
314
+ try {
315
+ // Step 1: Validate branch
316
+ step1_validateBranch();
317
+
318
+ // Step 2: Sync develop
319
+ step2_syncDevelop();
320
+
321
+ // Step 3: Sync main
322
+ step3_syncMain();
323
+
324
+ // Step 4: Create release PR
325
+ const prUrl = step4_createReleasePR(options.prTitle, options.prBody);
326
+
327
+ // Step 5: Merge PR (optional)
328
+ step5_mergeReleasePR(prUrl, options.autoMerge);
329
+
330
+ // Step 6: Tag release (optional)
331
+ step6_tagRelease(options.tag);
332
+
333
+ log(COLORS.green, '\n═══════════════════════════════════════════════════════════════');
334
+ log(COLORS.green, 'āœ… Git Flow Release Cycle Complete!');
335
+ log(COLORS.green, '═══════════════════════════════════════════════════════════════\n');
336
+
337
+ } catch (error) {
338
+ log(COLORS.red, `\nāŒ Git Flow Release Cycle failed: ${error.message}`);
339
+ process.exit(1);
340
+ }
341
+ }
342
+
343
+ main();
@@ -5,7 +5,7 @@
5
5
  "platforms": [
6
6
  "backend"
7
7
  ],
8
- "created": "2025-12-31T11:37:15.219Z"
8
+ "created": "2026-01-04T19:10:44.947Z"
9
9
  },
10
10
  "architecture": {
11
11
  "pattern": "FEATURE_FIRST_CLEAN_DDD",
@@ -294,7 +294,13 @@ class iOSEnterpriseAnalyzer {
294
294
  'Network code without custom NetworkError enum');
295
295
  }
296
296
 
297
- if (content.includes('URLSession') && !content.includes('serverTrustPolicy') && !content.includes('pinning')) {
297
+ // Check for SSL pinning implementation
298
+ const hasSSLPinningImplementation =
299
+ content.includes('serverTrustPolicy') ||
300
+ content.includes('pinning') ||
301
+ (content.includes('URLSessionDelegate') && content.includes('URLAuthenticationChallenge'));
302
+
303
+ if (content.includes('URLSession') && !hasSSLPinningImplementation) {
298
304
  this.addFinding('ios.networking.missing_ssl_pinning', 'medium', filePath, 1,
299
305
  'Consider SSL pinning for high-security apps');
300
306
  }
@@ -1544,7 +1544,13 @@ async function runIOSIntelligence(project, findings, platform) {
1544
1544
  );
1545
1545
  }
1546
1546
 
1547
- if (content.includes('URLSession') && content.includes('https') && !content.includes('ServerTrustPolicy') && !content.includes('pinning')) {
1547
+ // Check for SSL pinning implementation
1548
+ const hasSSLPinningImplementation =
1549
+ content.includes('ServerTrustPolicy') ||
1550
+ content.includes('pinning') ||
1551
+ (content.includes('URLSessionDelegate') && content.includes('URLAuthenticationChallenge'));
1552
+
1553
+ if (content.includes('URLSession') && content.includes('https') && !hasSSLPinningImplementation) {
1548
1554
  if (content.includes('production') || content.includes('release')) {
1549
1555
  pushFinding(
1550
1556
  "ios.security.missing_ssl_pinning",