pumuki-ast-hooks 5.5.30 → 5.5.32

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki-ast-hooks",
3
- "version": "5.5.30",
3
+ "version": "5.5.32",
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",
@@ -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');
@@ -79,12 +79,19 @@ class GitEnvironmentService {
79
79
  return;
80
80
  }
81
81
 
82
+ // Install pre-commit hook
82
83
  const preCommitHook = this.getPreCommitHookContent();
83
84
  const preCommitPath = path.join(gitHooksDir, 'pre-commit');
84
-
85
85
  fs.writeFileSync(preCommitPath, preCommitHook);
86
86
  fs.chmodSync(preCommitPath, '755');
87
87
  this.logSuccess(' ✅ Installed pre-commit hook');
88
+
89
+ // Install pre-push hook (Git Flow enforcement)
90
+ const prePushHook = this.getPrePushHookContent();
91
+ const prePushPath = path.join(gitHooksDir, 'pre-push');
92
+ fs.writeFileSync(prePushPath, prePushHook);
93
+ fs.chmodSync(prePushPath, '755');
94
+ this.logSuccess(' ✅ Installed pre-push hook (Git Flow enforcement)');
88
95
  }
89
96
 
90
97
  getPreCommitHookContent() {
@@ -101,6 +108,26 @@ fi
101
108
  # Change to project root (where package.json is)
102
109
  cd "$(git rev-parse --show-toplevel)" || exit 1
103
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
+
104
131
  # Check if there are staged files
105
132
  STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\\.(ts|tsx|js|jsx|swift|kt)$' || true)
106
133
  if [ -z "$STAGED_FILES" ]; then
@@ -146,6 +173,107 @@ fi
146
173
 
147
174
  # Fallback: direct execution logic here...
148
175
  echo "⚠️ ast-intelligence-hooks not found"
176
+ exit 0
177
+ `;
178
+ }
179
+
180
+ getPrePushHookContent() {
181
+ return `#!/bin/bash
182
+ # AST Intelligence Hooks - Pre-push (Git Flow Enforcement)
183
+ # Auto-generated by pumuki-ast-hooks v${this.version}
184
+
185
+ # Check for bypass
186
+ if [[ -n "\${GIT_BYPASS_HOOK}" ]]; then
187
+ echo "⚠️ Bypassing Git Flow enforcement (GIT_BYPASS_HOOK=1)"
188
+ exit 0
189
+ fi
190
+
191
+ # Change to project root
192
+ cd "$(git rev-parse --show-toplevel)" || exit 1
193
+
194
+ # Check if we're pushing only tags (read from stdin)
195
+ while read local_ref local_sha remote_ref remote_sha; do
196
+ # If pushing a tag (refs/tags/*), allow it
197
+ if [[ "$local_ref" =~ ^refs/tags/ ]]; then
198
+ echo ""
199
+ echo "✅ Tag push detected - allowing release workflow"
200
+ echo ""
201
+ exit 0
202
+ fi
203
+ done
204
+
205
+ echo ""
206
+ echo "🔍 Validating Git Flow compliance before push..."
207
+ echo ""
208
+
209
+ # Get current branch
210
+ CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
211
+
212
+ if [[ -z "$CURRENT_BRANCH" ]]; then
213
+ echo "❌ Detached HEAD - cannot validate Git Flow"
214
+ exit 1
215
+ fi
216
+
217
+ # Protected branches - cannot push directly
218
+ PROTECTED_BRANCHES=("main" "master" "develop")
219
+ for protected in "\${PROTECTED_BRANCHES[@]}"; do
220
+ if [[ "$CURRENT_BRANCH" == "$protected" ]]; then
221
+ echo "❌ Cannot push directly to protected branch: $CURRENT_BRANCH"
222
+ echo ""
223
+ echo "Create a feature branch instead:"
224
+ echo " git checkout -b feature/my-feature"
225
+ echo ""
226
+ exit 1
227
+ fi
228
+ done
229
+
230
+ # Validate branch naming convention
231
+ if [[ ! "$CURRENT_BRANCH" =~ ^(feature|fix|hotfix|chore|docs|refactor|test|ci)/ ]]; then
232
+ echo "⚠️ Branch name '$CURRENT_BRANCH' doesn't follow Git Flow convention"
233
+ echo ""
234
+ echo "Expected patterns:"
235
+ echo " feature/description"
236
+ echo " fix/description"
237
+ echo " hotfix/description"
238
+ echo " chore/description"
239
+ echo ""
240
+ echo "This is a WARNING - push will continue"
241
+ echo ""
242
+ fi
243
+
244
+ # Check evidence freshness (optional - warn only)
245
+ EVIDENCE_FILE=".AI_EVIDENCE.json"
246
+ if [[ -f "$EVIDENCE_FILE" ]]; then
247
+ EVIDENCE_TS=$(jq -r '.timestamp // .severity_metrics.last_updated // empty' "$EVIDENCE_FILE" 2>/dev/null || echo "")
248
+ if [[ -n "$EVIDENCE_TS" ]]; then
249
+ # Parse timestamp and check age
250
+ CLEAN_TS=$(echo "$EVIDENCE_TS" | sed 's/\\.[0-9]*Z$/Z/')
251
+ EVIDENCE_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$CLEAN_TS" +%s 2>/dev/null || echo "0")
252
+ NOW_EPOCH=$(date +%s)
253
+ AGE=$((NOW_EPOCH - EVIDENCE_EPOCH))
254
+
255
+ if [[ $AGE -gt 300 ]]; then
256
+ echo "⚠️ Evidence is stale (\${AGE}s old, recommended <5min)"
257
+ echo " Consider running: npm run ast:refresh"
258
+ echo ""
259
+ fi
260
+ fi
261
+ fi
262
+
263
+ # Run gitflow-enforcer if available (optional validation)
264
+ ENFORCER_SCRIPT="scripts/hooks-system/infrastructure/shell/gitflow/gitflow-enforcer.sh"
265
+ if [[ -f "$ENFORCER_SCRIPT" ]]; then
266
+ if ! bash "$ENFORCER_SCRIPT" check 2>/dev/null; then
267
+ echo ""
268
+ echo "⚠️ Git Flow check completed with warnings (non-blocking)"
269
+ echo ""
270
+ fi
271
+ fi
272
+
273
+ echo ""
274
+ echo "✅ Git Flow validation passed. Proceeding with push..."
275
+ echo ""
276
+
149
277
  exit 0
150
278
  `;
151
279
  }
@@ -0,0 +1,424 @@
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 step1_validateBranch() {
106
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
107
+ log(COLORS.cyan, '📍 Step 1: Validate Branch');
108
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
109
+
110
+ const branch = getCurrentBranch();
111
+ log(COLORS.blue, `Current branch: ${branch}`);
112
+
113
+ if (isProtectedBranch(branch)) {
114
+ log(COLORS.red, `\n❌ Cannot run Git Flow cycle on protected branch: ${branch}`);
115
+ log(COLORS.yellow, '\nCreate a feature branch first:');
116
+ log(COLORS.yellow, ' git checkout -b feature/my-feature');
117
+ process.exit(1);
118
+ }
119
+
120
+ if (!isValidFeatureBranch(branch)) {
121
+ log(COLORS.yellow, `\n⚠️ Branch '${branch}' doesn't follow naming convention`);
122
+ log(COLORS.yellow, 'Expected: feature/, fix/, hotfix/, chore/, docs/, refactor/, test/, ci/');
123
+ log(COLORS.yellow, '\nContinuing anyway...');
124
+ }
125
+
126
+ log(COLORS.green, '✅ Branch validation passed');
127
+ return branch;
128
+ }
129
+
130
+ function step2_commitChanges(commitMessage) {
131
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
132
+ log(COLORS.cyan, '📝 Step 2: Commit Changes');
133
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
134
+
135
+ if (!hasUncommittedChanges()) {
136
+ log(COLORS.green, '✅ No uncommitted changes');
137
+ return;
138
+ }
139
+
140
+ log(COLORS.yellow, '⚠️ Uncommitted changes detected');
141
+
142
+ if (!hasStagedChanges()) {
143
+ log(COLORS.blue, 'Staging all changes...');
144
+ exec('git add -A');
145
+ }
146
+
147
+ const message = commitMessage || `chore: auto-commit changes on ${getCurrentBranch()}`;
148
+ log(COLORS.blue, `Committing: ${message}`);
149
+ exec(`git commit -m "${message}"`);
150
+ log(COLORS.green, '✅ Changes committed');
151
+ }
152
+
153
+ function step3_pushToOrigin(branch) {
154
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
155
+ log(COLORS.cyan, '🚀 Step 3: Push to Origin');
156
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
157
+
158
+ log(COLORS.blue, `Pushing ${branch} to origin...`);
159
+ exec(`git push -u origin ${branch}`);
160
+ log(COLORS.green, '✅ Pushed to origin');
161
+ }
162
+
163
+ function step4_createPR(branch, baseBranch, prTitle, prBody) {
164
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
165
+ log(COLORS.cyan, '📋 Step 4: Create Pull Request');
166
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
167
+
168
+ if (!isGitHubCliAvailable()) {
169
+ log(COLORS.yellow, '⚠️ GitHub CLI (gh) not available or not authenticated');
170
+ log(COLORS.yellow, ' Install: https://cli.github.com/');
171
+ log(COLORS.yellow, ' Auth: gh auth login');
172
+ log(COLORS.yellow, '\n Create PR manually at GitHub.');
173
+ return null;
174
+ }
175
+
176
+ if (prExists(branch)) {
177
+ log(COLORS.green, '✅ PR already exists for this branch');
178
+ const prUrl = execSilent(`gh pr view ${branch} --json url --jq .url`);
179
+ return prUrl;
180
+ }
181
+
182
+ const title = prTitle || execSilent('git log -1 --pretty=%s') || `Merge ${branch}`;
183
+ const body = prBody || `## Summary\nAutomated PR for ${branch}\n\n## Changes\n${execSilent(`git log --oneline origin/${baseBranch}..${branch}`) || '- Updates'}`;
184
+
185
+ log(COLORS.blue, `Creating PR: ${title}`);
186
+ log(COLORS.blue, `Base: ${baseBranch} ← Head: ${branch}`);
187
+
188
+ try {
189
+ const output = execSilent(`gh pr create --base ${baseBranch} --head ${branch} --title "${title}" --body "${body.replace(/"/g, '\\"')}"`);
190
+ if (output) {
191
+ const urlMatch = output.match(/https:\/\/github\.com\/.*\/pull\/\d+/);
192
+ const prUrl = urlMatch ? urlMatch[0] : output;
193
+ log(COLORS.green, `✅ PR created: ${prUrl}`);
194
+ return prUrl;
195
+ }
196
+ } catch (error) {
197
+ log(COLORS.red, `❌ Failed to create PR: ${error.message}`);
198
+ }
199
+
200
+ return null;
201
+ }
202
+
203
+ function step5_mergePR(prUrl, autoMerge) {
204
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
205
+ log(COLORS.cyan, '🔀 Step 5: Merge Pull Request');
206
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
207
+
208
+ if (!prUrl) {
209
+ log(COLORS.yellow, '⚠️ No PR URL available, skipping merge');
210
+ return false;
211
+ }
212
+
213
+ if (!autoMerge) {
214
+ log(COLORS.yellow, '⚠️ Auto-merge disabled. Merge PR manually.');
215
+ log(COLORS.blue, ` PR: ${prUrl}`);
216
+ return false;
217
+ }
218
+
219
+ log(COLORS.blue, 'Enabling auto-merge (squash)...');
220
+ try {
221
+ exec(`gh pr merge --auto --squash "${prUrl}"`, { ignoreError: true });
222
+ log(COLORS.green, '✅ Auto-merge enabled');
223
+ return true;
224
+ } catch (error) {
225
+ log(COLORS.yellow, '⚠️ Could not enable auto-merge (may require admin approval)');
226
+ return false;
227
+ }
228
+ }
229
+
230
+ function step6_cleanupBranches(baseBranch) {
231
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
232
+ log(COLORS.cyan, '🧹 Step 6: Cleanup Merged Branches');
233
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
234
+
235
+ // Fetch latest
236
+ exec('git fetch --prune', { silent: true, ignoreError: true });
237
+
238
+ const mergedBranches = getMergedBranches(baseBranch);
239
+
240
+ if (mergedBranches.length === 0) {
241
+ log(COLORS.green, '✅ No merged branches to clean');
242
+ return;
243
+ }
244
+
245
+ log(COLORS.blue, `Found ${mergedBranches.length} merged branches:`);
246
+ mergedBranches.forEach(b => log(COLORS.yellow, ` - ${b}`));
247
+
248
+ // Delete local merged branches
249
+ for (const branch of mergedBranches) {
250
+ log(COLORS.blue, `Deleting local: ${branch}`);
251
+ exec(`git branch -d "${branch}"`, { ignoreError: true, silent: true });
252
+ }
253
+
254
+ // Delete remote merged branches (if gh available)
255
+ if (isGitHubCliAvailable()) {
256
+ for (const branch of mergedBranches) {
257
+ log(COLORS.blue, `Deleting remote: origin/${branch}`);
258
+ exec(`git push origin --delete "${branch}"`, { ignoreError: true, silent: true });
259
+ }
260
+ }
261
+
262
+ log(COLORS.green, `✅ Cleaned ${mergedBranches.length} merged branches`);
263
+ }
264
+
265
+ function step7_syncBranches(baseBranch) {
266
+ log(COLORS.cyan, '\n═══════════════════════════════════════════════════════════════');
267
+ log(COLORS.cyan, '🔄 Step 7: Sync Branches');
268
+ log(COLORS.cyan, '═══════════════════════════════════════════════════════════════\n');
269
+
270
+ const currentBranch = getCurrentBranch();
271
+
272
+ log(COLORS.blue, 'Fetching from origin...');
273
+ exec('git fetch origin', { ignoreError: true, silent: true });
274
+
275
+ // Sync develop
276
+ if (execSilent('git show-ref --verify --quiet refs/heads/develop') !== null) {
277
+ log(COLORS.blue, 'Syncing develop...');
278
+ exec('git checkout develop && git pull origin develop', { ignoreError: true, silent: true });
279
+ }
280
+
281
+ // Sync main
282
+ log(COLORS.blue, 'Syncing main...');
283
+ exec('git checkout main && git pull origin main', { ignoreError: true, silent: true });
284
+
285
+ // Return to original branch
286
+ log(COLORS.blue, `Returning to ${currentBranch}...`);
287
+ exec(`git checkout ${currentBranch}`, { ignoreError: true, silent: true });
288
+
289
+ log(COLORS.green, '✅ Branches synchronized');
290
+ }
291
+
292
+ // ════════════════════════════════════════════════════════════════
293
+ // MAIN
294
+ // ════════════════════════════════════════════════════════════════
295
+
296
+ function printUsage() {
297
+ console.log(`
298
+ ${COLORS.cyan}═══════════════════════════════════════════════════════════════
299
+ 🐈 Pumuki Git Flow Cycle
300
+ ═══════════════════════════════════════════════════════════════${COLORS.reset}
301
+
302
+ ${COLORS.blue}Usage:${COLORS.reset} npm run ast:gitflow [options]
303
+
304
+ ${COLORS.blue}Options:${COLORS.reset}
305
+ --commit-message, -m <msg> Custom commit message
306
+ --pr-title <title> Custom PR title
307
+ --auto-merge Enable auto-merge after PR creation
308
+ --skip-cleanup Skip branch cleanup step
309
+ --skip-sync Skip branch sync step
310
+ --help, -h Show this help
311
+
312
+ ${COLORS.blue}Examples:${COLORS.reset}
313
+ npm run ast:gitflow
314
+ npm run ast:gitflow -- -m "feat: add new feature"
315
+ npm run ast:gitflow -- --auto-merge
316
+ npm run ast:gitflow -- --pr-title "My PR Title" --auto-merge
317
+
318
+ ${COLORS.blue}What it does:${COLORS.reset}
319
+ 1. Validates branch (must be feature/, fix/, etc.)
320
+ 2. Commits uncommitted changes
321
+ 3. Pushes to origin
322
+ 4. Creates PR (requires gh CLI)
323
+ 5. Optionally auto-merges PR
324
+ 6. Cleans up merged branches
325
+ 7. Syncs local branches with remote
326
+
327
+ ${COLORS.yellow}⚠️ Requires:${COLORS.reset}
328
+ - Git repository
329
+ - GitHub CLI (gh) for PR operations: https://cli.github.com/
330
+ `);
331
+ }
332
+
333
+ function parseArgs(args) {
334
+ const options = {
335
+ commitMessage: null,
336
+ prTitle: null,
337
+ prBody: null,
338
+ autoMerge: false,
339
+ skipCleanup: false,
340
+ skipSync: false,
341
+ help: false
342
+ };
343
+
344
+ for (let i = 0; i < args.length; i++) {
345
+ const arg = args[i];
346
+ switch (arg) {
347
+ case '--commit-message':
348
+ case '-m':
349
+ options.commitMessage = args[++i];
350
+ break;
351
+ case '--pr-title':
352
+ options.prTitle = args[++i];
353
+ break;
354
+ case '--pr-body':
355
+ options.prBody = args[++i];
356
+ break;
357
+ case '--auto-merge':
358
+ options.autoMerge = true;
359
+ break;
360
+ case '--skip-cleanup':
361
+ options.skipCleanup = true;
362
+ break;
363
+ case '--skip-sync':
364
+ options.skipSync = true;
365
+ break;
366
+ case '--help':
367
+ case '-h':
368
+ options.help = true;
369
+ break;
370
+ }
371
+ }
372
+
373
+ return options;
374
+ }
375
+
376
+ function main() {
377
+ const args = process.argv.slice(2);
378
+ const options = parseArgs(args);
379
+
380
+ if (options.help) {
381
+ printUsage();
382
+ process.exit(0);
383
+ }
384
+
385
+ log(COLORS.magenta, '\n🐈 Pumuki Git Flow Cycle - Starting...\n');
386
+
387
+ try {
388
+ // Step 1: Validate branch
389
+ const branch = step1_validateBranch();
390
+ const baseBranch = getBaseBranch();
391
+
392
+ // Step 2: Commit changes
393
+ step2_commitChanges(options.commitMessage);
394
+
395
+ // Step 3: Push to origin
396
+ step3_pushToOrigin(branch);
397
+
398
+ // Step 4: Create PR
399
+ const prUrl = step4_createPR(branch, baseBranch, options.prTitle, options.prBody);
400
+
401
+ // Step 5: Merge PR (optional)
402
+ step5_mergePR(prUrl, options.autoMerge);
403
+
404
+ // Step 6: Cleanup branches
405
+ if (!options.skipCleanup) {
406
+ step6_cleanupBranches(baseBranch);
407
+ }
408
+
409
+ // Step 7: Sync branches
410
+ if (!options.skipSync) {
411
+ step7_syncBranches(baseBranch);
412
+ }
413
+
414
+ log(COLORS.green, '\n═══════════════════════════════════════════════════════════════');
415
+ log(COLORS.green, '✅ Git Flow Cycle Complete!');
416
+ log(COLORS.green, '═══════════════════════════════════════════════════════════════\n');
417
+
418
+ } catch (error) {
419
+ log(COLORS.red, `\n❌ Git Flow Cycle failed: ${error.message}`);
420
+ process.exit(1);
421
+ }
422
+ }
423
+
424
+ main();