maiass 5.10.5 → 5.12.2

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.
@@ -0,0 +1,27 @@
1
+ # Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We are committed to making participation in this project a respectful and harassment-free experience for everyone.
6
+
7
+ ## Our Standards
8
+
9
+ Expected behaviour:
10
+ - Be respectful and constructive in all interactions
11
+ - Accept feedback gracefully
12
+ - Focus on what is best for the project and its users
13
+
14
+ Unacceptable behaviour:
15
+ - Harassment, insults, or personal attacks of any kind
16
+ - Trolling or deliberately disruptive behaviour
17
+ - Publishing others' private information without consent
18
+
19
+ ## Enforcement
20
+
21
+ Instances of unacceptable behaviour may be reported by contacting the maintainer at [mark@pottie.com](mailto:mark@pottie.com). All reports will be reviewed and responded to.
22
+
23
+ Maintainers reserve the right to remove, edit, or reject comments, commits, and other contributions that violate this code of conduct.
24
+
25
+ ## Attribution
26
+
27
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
@@ -0,0 +1,38 @@
1
+ # Contributing to MAIASS
2
+
3
+ Thanks for your interest in contributing!
4
+
5
+ ## Reporting Issues
6
+
7
+ - Search [existing issues](https://github.com/vsmash/nodemaiass/issues) before opening a new one
8
+ - Include your OS, Node.js version, and `maiass --version` output
9
+ - For bugs, include the full error output and the command you ran
10
+
11
+ ## Pull Requests
12
+
13
+ 1. Fork the repo and create a branch from `develop`
14
+ 2. Make your changes following the code style below
15
+ 3. Add or update tests if relevant (`npm run test:unit`)
16
+ 4. Ensure all tests pass (`npm run test:unit && npm test`)
17
+ 5. Submit a PR against `develop` — not `main`
18
+
19
+ ## Code Style
20
+
21
+ - ES modules (`import`/`export`, `.js` extensions)
22
+ - No comments unless the *why* is non-obvious
23
+ - No new dependencies without discussion
24
+
25
+ ## Development Setup
26
+
27
+ ```bash
28
+ git clone https://github.com/vsmash/nodemaiass.git
29
+ cd nodemaiass
30
+ npm install
31
+ node maiass.mjs --help
32
+ ```
33
+
34
+ See [docs/development.md](docs/development.md) for full details.
35
+
36
+ ## Questions
37
+
38
+ Open a [GitHub Discussion](https://github.com/vsmash/nodemaiass/discussions) or file an issue.
package/lib/bootstrap.js CHANGED
@@ -583,9 +583,9 @@ MAIASS_STAGINGBRANCH=${config.branches.staging}
583
583
  #MAIASS_BITBUCKET_WORKSPACE=
584
584
  #MAIASS_BITBUCKET_REPO_SLUG=
585
585
 
586
- # Pull Request Configuration
587
- #MAIASS_STAGING_PULLREQUESTS=on
588
- #MAIASS_MAIN_PULLREQUESTS=on
586
+ # Pull Request Configuration — when 'on', open PR URL instead of direct merge.
587
+ # Also auto-engaged if a direct push to develop is rejected (e.g. branch protection).
588
+ #MAIASS_DEVELOP_PULLREQUESTS=off
589
589
 
590
590
  # Logging Configuration
591
591
  #MAIASS_LOGGING=true
@@ -69,7 +69,7 @@ function displayConfig(config, options = {}) {
69
69
  'AI Integration': ['MAIASS_AI_MODE', 'MAIASS_AI_TOKEN', 'MAIASS_AI_MODEL', 'MAIASS_AI_TEMPERATURE', 'MAIASS_AI_HOST', 'MAIASS_AI_MAX_CHARACTERS', 'MAIASS_AI_COMMIT_MESSAGE_STYLE'],
70
70
  'Git Branches': ['MAIASS_DEVELOPBRANCH', 'MAIASS_STAGINGBRANCH', 'MAIASS_MAINBRANCH'],
71
71
  'Repository Settings': ['MAIASS_REPO_TYPE', 'MAIASS_GITHUB_OWNER', 'MAIASS_GITHUB_REPO', 'MAIASS_BITBUCKET_WORKSPACE', 'MAIASS_BITBUCKET_REPO_SLUG'],
72
- 'Pull Requests': ['MAIASS_STAGING_PULLREQUESTS', 'MAIASS_MAIN_PULLREQUESTS'],
72
+ 'Pull Requests': ['MAIASS_DEVELOP_PULLREQUESTS'],
73
73
  'Version Management': ['MAIASS_VERSION_PATH', 'MAIASS_VERSION_PRIMARY_FILE', 'MAIASS_VERSION_PRIMARY_TYPE', 'MAIASS_VERSION_PRIMARY_LINE_START', 'MAIASS_VERSION_SECONDARY_FILES'],
74
74
  'Changelog': ['MAIASS_CHANGELOG_PATH', 'MAIASS_CHANGELOG_NAME', 'MAIASS_CHANGELOG_INTERNAL_NAME']
75
75
  };
@@ -115,7 +115,7 @@ export function writeConfig(configPath, config, options = {}) {
115
115
  'AI': ['MAIASS_AI_MODE', 'MAIASS_AI_TOKEN', 'MAIASS_AI_MODEL', 'MAIASS_AI_TEMPERATURE', 'MAIASS_AI_ENDPOINT', 'MAIASS_AI_MAX_CHARACTERS', 'MAIASS_AI_COMMIT_MESSAGE_STYLE'],
116
116
  'Branches': ['MAIASS_DEVELOPBRANCH', 'MAIASS_STAGINGBRANCH', 'MAIASS_MAINBRANCH'],
117
117
  'Repository': ['MAIASS_REPO_TYPE', 'MAIASS_GITHUB_OWNER', 'MAIASS_GITHUB_REPO', 'MAIASS_BITBUCKET_WORKSPACE', 'MAIASS_BITBUCKET_REPO_SLUG'],
118
- 'Pull Requests': ['MAIASS_STAGING_PULLREQUESTS', 'MAIASS_MAIN_PULLREQUESTS'],
118
+ 'Pull Requests': ['MAIASS_DEVELOP_PULLREQUESTS'],
119
119
  'Versioning': ['MAIASS_VERSION_PATH', 'MAIASS_VERSION_PRIMARY_FILE', 'MAIASS_VERSION_PRIMARY_TYPE', 'MAIASS_VERSION_PRIMARY_LINE_START', 'MAIASS_VERSION_SECONDARY_FILES'],
120
120
  'Changelog': ['MAIASS_CHANGELOG_PATH', 'MAIASS_CHANGELOG_NAME', 'MAIASS_CHANGELOG_INTERNAL_NAME'],
121
121
  'Other': []
@@ -16,6 +16,7 @@ import { getSingleCharInput, getLineInput } from './input-utils.js';
16
16
  import { updateChangelog as updateChangelogNew } from './changelog.js';
17
17
  import { displayHeader } from './header.js';
18
18
  import { checkForUpdates } from './update-check.js';
19
+ import { createPullRequest } from './pull-request.js';
19
20
  import { execSync } from 'child_process';
20
21
  import path from 'path';
21
22
  import fs from 'fs';
@@ -317,9 +318,34 @@ async function handleMergeToDevelop(branchInfo, commitResult, options = {}) {
317
318
  return { success: true, simplified: true };
318
319
  }
319
320
 
321
+ // autoMerge (-a): skip prompts and proceed with merge. If direct merge is
322
+ // blocked, fall back to opening a PR in the browser (auto mode = "do whatever
323
+ // is needed without asking").
324
+ const autoMerge = process.env.MAIASS_AUTO_MERGE_TO_DEVELOP === 'true';
325
+
326
+ // PR flow: if MAIASS_DEVELOP_PULLREQUESTS=on, push source and open a PR URL
327
+ // instead of doing a direct merge. The pipeline ends here — the user merges
328
+ // the PR on the platform, then re-runs maiass on develop for the version bump.
329
+ if (process.env.MAIASS_DEVELOP_PULLREQUESTS === 'on') {
330
+ log.blue(SYMBOLS.INFO, `MAIASS_DEVELOP_PULLREQUESTS=on — opening PR instead of direct merge`);
331
+ const prResult = createPullRequest({
332
+ source: currentBranch,
333
+ target: developBranch,
334
+ title: `Merge ${currentBranch} into ${developBranch}`
335
+ });
336
+ if (!prResult.success) {
337
+ log.error(SYMBOLS.CROSS, `Failed to create pull request: ${prResult.error}`);
338
+ return { success: false, error: prResult.error };
339
+ }
340
+ log.success(SYMBOLS.CHECKMARK, `Pull request opened in browser`);
341
+ log.blue(SYMBOLS.INFO, `${prResult.url}`);
342
+ log.blue(SYMBOLS.INFO, `After merging the PR, re-run maiass on ${developBranch} to bump the version.`);
343
+ return { success: true, pullRequest: true, url: prResult.url };
344
+ }
345
+
320
346
  // Determine tagging strategy for this version bump
321
347
  const taggingDecision = shouldTagRelease(versionBump, tag);
322
-
348
+
323
349
  // Prompt user for merge (and tagging if needed)
324
350
  if (!force) {
325
351
  log.blue(SYMBOLS.INFO, `Ready to merge changes to ${developBranch} branch`);
@@ -401,14 +427,58 @@ async function handleMergeToDevelop(branchInfo, commitResult, options = {}) {
401
427
  }
402
428
 
403
429
  log.success(SYMBOLS.CHECKMARK, `Successfully merged ${currentBranch} into ${developBranch}`);
404
-
430
+
431
+ // Detect blocked direct push (e.g. branch protection requires PR).
432
+ // Run a dry-run push — if it fails, the eventual push will fail too. Only do
433
+ // this when the remote actually exists; otherwise there's nothing to dry-run.
434
+ if (remoteExists()) {
435
+ const dryRun = executeGitCommand(`git push --dry-run origin ${developBranch}`, true);
436
+ if (!dryRun.success) {
437
+ log.warning(SYMBOLS.WARNING, `Direct push to ${developBranch} appears blocked (likely branch protection)`);
438
+
439
+ // Decide whether to fall back to PR flow:
440
+ // - autoMerge (-am): yes, automatically — that's exactly what -am promises
441
+ // - interactive: prompt the user
442
+ let useFallback = autoMerge;
443
+ if (!autoMerge) {
444
+ const reply = await getSingleCharInput(`Open a pull request instead? [Y/n] `);
445
+ useFallback = reply !== 'n';
446
+ } else {
447
+ log.info(SYMBOLS.INFO, '-am mode — falling back to PR flow');
448
+ }
449
+
450
+ if (useFallback) {
451
+ // Roll back the local merge so the working copy is back to clean develop,
452
+ // then return to the original branch so we push the right ref for the PR
453
+ executeGitCommand(`git reset --hard HEAD~1`);
454
+ await switchToBranch(originalBranch || currentBranch);
455
+
456
+ const prResult = createPullRequest({
457
+ source: currentBranch,
458
+ target: developBranch,
459
+ title: `Merge ${currentBranch} into ${developBranch}`
460
+ });
461
+ if (!prResult.success) {
462
+ log.error(SYMBOLS.CROSS, `Failed to create pull request: ${prResult.error}`);
463
+ return { success: false, error: prResult.error };
464
+ }
465
+ log.success(SYMBOLS.CHECKMARK, `Pull request opened in browser`);
466
+ log.blue(SYMBOLS.INFO, `${prResult.url}`);
467
+ log.blue(SYMBOLS.INFO, `After merging the PR, re-run maiass on ${developBranch} to bump the version.`);
468
+ return { success: true, pullRequest: true, url: prResult.url };
469
+ }
470
+ // User declined PR fallback — continue and let the later push fail with a clear error
471
+ log.warning(SYMBOLS.WARNING, `Continuing with direct merge — push may fail at version bump stage`);
472
+ }
473
+ }
474
+
405
475
  // Log the merge to devlog.sh (equivalent to logthis in maiass.sh)
406
476
  logMerge(currentBranch, developBranch, originalGitInfo, 'Merged');
407
-
408
- return {
409
- success: true,
410
- merged: true,
411
- taggingDecision: taggingDecision
477
+
478
+ return {
479
+ success: true,
480
+ merged: true,
481
+ taggingDecision: taggingDecision
412
482
  };
413
483
  }
414
484
 
@@ -458,12 +528,19 @@ async function handleVersionManagement(branchInfo, mergeResult, options = {}) {
458
528
 
459
529
  logger.header(SYMBOLS.INFO, 'Version Management Phase');
460
530
 
461
- // Must be on develop branch for version management — check before reading version files
531
+ // Must be on develop branch for version management — check before reading version files.
532
+ // If the user explicitly asked for a bump (passed major/minor/patch), this is an error.
533
+ // Otherwise it's a graceful skip — they may have run maiass on a feature branch without
534
+ // intending a version bump, or the merge was deferred to a PR.
462
535
  const currentBranch = getCurrentBranch();
463
536
  if (currentBranch !== developBranch) {
464
- console.error(colors.Red(`${SYMBOLS.CROSS} Version management must be done on ${developBranch} branch`));
465
- console.error(colors.Red(`${SYMBOLS.CROSS} Current branch: ${currentBranch}`));
466
- return { success: false, error: `Not on ${developBranch} branch` };
537
+ if (versionBumpExplicit) {
538
+ console.error(colors.Red(`${SYMBOLS.CROSS} Version management must be done on ${developBranch} branch`));
539
+ console.error(colors.Red(`${SYMBOLS.CROSS} Current branch: ${currentBranch}`));
540
+ return { success: false, error: `Not on ${developBranch} branch` };
541
+ }
542
+ log.blue(SYMBOLS.INFO, `Skipping version management — on ${currentBranch}, not ${developBranch}`);
543
+ return { success: true, skipped: true, reason: 'not on develop' };
467
544
  }
468
545
 
469
546
  // Pull latest develop from remote before reading version files.
@@ -710,6 +787,19 @@ async function handleReleaseBranchWorkflow(newVersion, versionInfo, developBranc
710
787
  }
711
788
 
712
789
  logger.success(SYMBOLS.CHECKMARK, `Release workflow completed: ${versionInfo.current} → ${newVersion}`);
790
+
791
+ // Return to the user's original branch unless they started on develop or
792
+ // a release branch. Mirrors handleSimpleVersionBump so the user isn't left
793
+ // stranded on develop after a release runs from a feature branch.
794
+ const originalBranch = originalGitInfo?.currentBranch;
795
+ if (originalBranch && originalBranch !== developBranch && !originalBranch.startsWith('release/')) {
796
+ logger.info(SYMBOLS.INFO, `Returning to original branch: ${originalBranch}`);
797
+ const switched = await switchToBranch(originalBranch);
798
+ if (!switched) {
799
+ logger.warning(SYMBOLS.WARNING, `Failed to switch back to ${originalBranch}`);
800
+ }
801
+ }
802
+
713
803
  return {
714
804
  success: true,
715
805
  version: newVersion,
@@ -825,6 +915,15 @@ export async function runMaiassPipeline(options = {}) {
825
915
  logger.info(SYMBOLS.INFO, 'Thank you for using MAIASS!');
826
916
  return { success: true, cancelled: true, phase: 'merge-cancelled' };
827
917
  }
918
+
919
+ // If PR flow was used, the merge will happen on the platform — stop the
920
+ // pipeline here. Version management will run on the next maiass invocation
921
+ // after the user merges the PR.
922
+ if (mergeResult.pullRequest) {
923
+ console.log();
924
+ logger.info(SYMBOLS.INFO, 'Thank you for using MAIASS!');
925
+ return { success: true, pullRequest: true, url: mergeResult.url };
926
+ }
828
927
 
829
928
  console.log('');
830
929
 
@@ -6,8 +6,14 @@ import path from 'path';
6
6
  export const MAIASS_VARIABLES = {
7
7
  // Core system variables
8
8
  'MAIASS_DEBUG': { default: 'false', description: 'Enable debug mode' },
9
- 'MAIASS_AUTOPUSH_COMMITS': { default: 'false', description: 'Automatically push commits' },
10
9
  'MAIASS_BRAND': { default: 'MAIASS', description: 'Brand name for display' },
10
+
11
+ // Auto-yes flags — set by -a (all four) or -ac (first three only).
12
+ // Setting any of these in .env.maiass makes that prompt auto-approve permanently.
13
+ 'MAIASS_AUTO_STAGE_UNSTAGED': { default: 'false', description: 'Auto-stage unstaged changes during commit phase' },
14
+ 'MAIASS_AUTO_PUSH_COMMITS': { default: 'false', description: 'Auto-push commits to origin' },
15
+ 'MAIASS_AUTO_APPROVE_AI_SUGGESTIONS': { default: 'false', description: 'Auto-approve AI commit message suggestions' },
16
+ 'MAIASS_AUTO_MERGE_TO_DEVELOP': { default: 'false', description: 'Auto-merge feature branch into develop without prompting' },
11
17
  'MAIASS_VERBOSITY': { default: 'brief', description: 'Verbosity level (brief/normal/verbose)' },
12
18
  'MAIASS_LOGGING': { default: 'false', description: 'Enable logging to file' },
13
19
  'MAIASS_LOG_FILE': { default: 'maiass.log', description: 'Log file name' },
@@ -46,9 +52,9 @@ export const MAIASS_VARIABLES = {
46
52
  // Release configuration
47
53
  'MAIASS_AUTO_TAG_RELEASES': { default: 'true', description: 'Automatically tag releases without prompting' },
48
54
 
49
- // Pull request configuration
50
- 'MAIASS_STAGING_PULLREQUESTS': { default: 'on', description: 'Enable staging pull requests' },
51
- 'MAIASS_MAIN_PULLREQUESTS': { default: 'on', description: 'Enable main branch pull requests' },
55
+ // Pull request configuration — when 'on', push source and open PR URL instead of direct merge.
56
+ // Also auto-engaged when a direct push to develop is rejected (e.g. branch protection).
57
+ 'MAIASS_DEVELOP_PULLREQUESTS': { default: 'off', description: 'Open PR for merges into develop instead of direct merge' },
52
58
 
53
59
  // Repository provider configuration
54
60
  'MAIASS_BITBUCKET_WORKSPACE': { default: '', description: 'Bitbucket workspace name' },
@@ -0,0 +1,140 @@
1
+ import { execSync, spawn } from 'child_process';
2
+ import { detectRepoProvider } from './repo-provider.js';
3
+
4
+ /**
5
+ * Build a provider-specific URL that opens the PR creation form pre-filled
6
+ * with source/target branches and a title.
7
+ *
8
+ * @param {Object} args
9
+ * @param {string} args.provider - 'github' | 'bitbucket' | 'gitlab'
10
+ * @param {string} args.owner - Repo owner / workspace
11
+ * @param {string} args.repo - Repo name / slug
12
+ * @param {string} args.source - Source branch (the changes)
13
+ * @param {string} args.target - Target branch (where they're merging into)
14
+ * @param {string} [args.title] - Optional PR title
15
+ * @returns {string|null} The URL, or null if provider unsupported
16
+ */
17
+ export function buildPullRequestUrl({ provider, owner, repo, source, target, title = '' }) {
18
+ const encodedTitle = encodeURIComponent(title);
19
+
20
+ if (provider === 'github') {
21
+ // GitHub's quick_pull form: /compare/target...source?quick_pull=1&title=...
22
+ const base = `https://github.com/${owner}/${repo}/compare/${target}...${source}?quick_pull=1`;
23
+ return title ? `${base}&title=${encodedTitle}` : base;
24
+ }
25
+
26
+ if (provider === 'bitbucket') {
27
+ // Bitbucket: /pull-requests/new?source=...&dest=...&title=...
28
+ const base = `https://bitbucket.org/${owner}/${repo}/pull-requests/new?source=${source}&dest=${target}`;
29
+ return title ? `${base}&title=${encodedTitle}` : base;
30
+ }
31
+
32
+ if (provider === 'gitlab') {
33
+ // GitLab: /-/merge_requests/new?merge_request[source_branch]=...&...
34
+ const params = new URLSearchParams({
35
+ 'merge_request[source_branch]': source,
36
+ 'merge_request[target_branch]': target
37
+ });
38
+ if (title) params.set('merge_request[title]', title);
39
+ return `https://gitlab.com/${owner}/${repo}/-/merge_requests/new?${params.toString()}`;
40
+ }
41
+
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * Open a URL in the user's default browser using the platform-native command.
47
+ * Uses spawn so it doesn't block, and detaches so the parent can exit cleanly.
48
+ *
49
+ * @param {string} url
50
+ * @returns {boolean} True if the open command was launched
51
+ */
52
+ export function openInBrowser(url) {
53
+ const platform = process.platform;
54
+ let cmd, args;
55
+
56
+ if (platform === 'darwin') {
57
+ cmd = 'open';
58
+ args = [url];
59
+ } else if (platform === 'win32') {
60
+ cmd = 'cmd';
61
+ args = ['/c', 'start', '""', url];
62
+ } else {
63
+ // Linux + others: xdg-open is the standard
64
+ cmd = 'xdg-open';
65
+ args = [url];
66
+ }
67
+
68
+ try {
69
+ const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
70
+ child.unref();
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Push a branch to origin, setting upstream if needed.
79
+ * Used before opening the PR URL so the source branch exists on the remote.
80
+ */
81
+ function pushBranchToOrigin(branch) {
82
+ try {
83
+ execSync(`git push --set-upstream origin ${branch}`, { stdio: ['pipe', 'pipe', 'pipe'] });
84
+ return { success: true };
85
+ } catch (error) {
86
+ // Fall back to a plain push if upstream is already set
87
+ try {
88
+ execSync(`git push origin ${branch}`, { stdio: ['pipe', 'pipe', 'pipe'] });
89
+ return { success: true };
90
+ } catch (innerError) {
91
+ return { success: false, error: innerError.message };
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Push the source branch and open a PR-creation URL in the browser.
98
+ * Mirrors the bashmaiass `perform_merge_operation` PR path.
99
+ *
100
+ * @param {Object} args
101
+ * @param {string} args.source - Source branch
102
+ * @param {string} args.target - Target branch
103
+ * @param {string} [args.title] - Optional PR title
104
+ * @param {Object} [deps] - Injectable dependencies for testing
105
+ * @param {Function} [deps.detect] - Provider detection function (defaults to detectRepoProvider)
106
+ * @param {Function} [deps.push] - Branch push function (defaults to pushBranchToOrigin)
107
+ * @param {Function} [deps.open] - Browser open function (defaults to openInBrowser)
108
+ * @returns {{success: boolean, url?: string, error?: string}}
109
+ */
110
+ export function createPullRequest({ source, target, title }, deps = {}) {
111
+ const detect = deps.detect || detectRepoProvider;
112
+ const push = deps.push || pushBranchToOrigin;
113
+ const open = deps.open || openInBrowser;
114
+
115
+ const repoInfo = detect();
116
+ if (!repoInfo) {
117
+ return { success: false, error: 'Could not detect repository provider from git remote' };
118
+ }
119
+
120
+ const url = buildPullRequestUrl({
121
+ provider: repoInfo.provider,
122
+ owner: repoInfo.owner,
123
+ repo: repoInfo.repo,
124
+ source,
125
+ target,
126
+ title
127
+ });
128
+ if (!url) {
129
+ return { success: false, error: `Unsupported provider: ${repoInfo.provider}` };
130
+ }
131
+
132
+ // Push source branch so the PR has commits to compare against
133
+ const pushResult = push(source);
134
+ if (!pushResult.success) {
135
+ return { success: false, error: `Failed to push ${source}: ${pushResult.error}` };
136
+ }
137
+
138
+ open(url);
139
+ return { success: true, url };
140
+ }
@@ -0,0 +1,98 @@
1
+ import { execSync } from 'child_process';
2
+
3
+ /**
4
+ * Parse a git remote URL and extract provider, owner, and repo.
5
+ * Supports common GitHub, Bitbucket, and GitLab URL formats:
6
+ *
7
+ * https://github.com/owner/repo.git
8
+ * git@github.com:owner/repo.git
9
+ * https://bitbucket.org/workspace/slug.git
10
+ * git@bitbucket.org:workspace/slug.git
11
+ * https://gitlab.com/group/project.git
12
+ * git@gitlab.com:group/project.git
13
+ *
14
+ * @param {string} url - Remote URL (e.g. from `git config --get remote.origin.url`)
15
+ * @returns {{provider: string, owner: string, repo: string} | null}
16
+ */
17
+ export function parseRemoteUrl(url) {
18
+ if (!url) return null;
19
+
20
+ // Match host + path from either SSH (git@host:path) or HTTPS (https://host/path) form
21
+ const sshMatch = url.match(/^git@([^:]+):(.+?)(?:\.git)?\/?$/);
22
+ const httpsMatch = url.match(/^https?:\/\/(?:[^@]+@)?([^/]+)\/(.+?)(?:\.git)?\/?$/);
23
+
24
+ const match = sshMatch || httpsMatch;
25
+ if (!match) return null;
26
+
27
+ const host = match[1].toLowerCase();
28
+ const path = match[2];
29
+
30
+ // Need at least owner/repo — anything less can't be a valid remote
31
+ const parts = path.split('/');
32
+ if (parts.length < 2) return null;
33
+
34
+ // Last segment is repo; everything before is the owner (handles GitLab subgroups)
35
+ const repo = parts.pop();
36
+ const owner = parts.join('/');
37
+
38
+ let provider = null;
39
+ if (host.includes('github.com')) provider = 'github';
40
+ else if (host.includes('bitbucket.org')) provider = 'bitbucket';
41
+ else if (host.includes('gitlab.com') || host.includes('gitlab.')) provider = 'gitlab';
42
+ else return null;
43
+
44
+ return { provider, owner, repo };
45
+ }
46
+
47
+ /**
48
+ * Read the origin remote URL via git. Returns null if no remote or not a git repo.
49
+ */
50
+ export function getOriginUrl() {
51
+ try {
52
+ const url = execSync('git config --get remote.origin.url', {
53
+ encoding: 'utf8',
54
+ stdio: ['pipe', 'pipe', 'pipe']
55
+ }).trim();
56
+ return url || null;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Detect repo provider/owner/repo from the current git remote.
64
+ * Respects existing env vars so the user can override auto-detection in `.env.maiass`.
65
+ *
66
+ * Sets the following on `process.env` if not already set:
67
+ * MAIASS_REPO_PROVIDER, MAIASS_GITHUB_OWNER, MAIASS_GITHUB_REPO,
68
+ * MAIASS_BITBUCKET_WORKSPACE, MAIASS_BITBUCKET_REPO_SLUG
69
+ *
70
+ * @returns {{provider: string, owner: string, repo: string} | null}
71
+ */
72
+ export function detectRepoProvider() {
73
+ // Respect explicit override — if all the relevant vars are already set, skip detection
74
+ const overrideProvider = process.env.MAIASS_REPO_PROVIDER;
75
+ if (overrideProvider) {
76
+ return {
77
+ provider: overrideProvider,
78
+ owner: process.env.MAIASS_GITHUB_OWNER || process.env.MAIASS_BITBUCKET_WORKSPACE || '',
79
+ repo: process.env.MAIASS_GITHUB_REPO || process.env.MAIASS_BITBUCKET_REPO_SLUG || ''
80
+ };
81
+ }
82
+
83
+ const url = getOriginUrl();
84
+ const parsed = parseRemoteUrl(url);
85
+ if (!parsed) return null;
86
+
87
+ // Cache to env so the rest of the pipeline can read consistently
88
+ process.env.MAIASS_REPO_PROVIDER = parsed.provider;
89
+ if (parsed.provider === 'github') {
90
+ process.env.MAIASS_GITHUB_OWNER = parsed.owner;
91
+ process.env.MAIASS_GITHUB_REPO = parsed.repo;
92
+ } else if (parsed.provider === 'bitbucket') {
93
+ process.env.MAIASS_BITBUCKET_WORKSPACE = parsed.owner;
94
+ process.env.MAIASS_BITBUCKET_REPO_SLUG = parsed.repo;
95
+ }
96
+
97
+ return parsed;
98
+ }
package/maiass.mjs CHANGED
@@ -94,16 +94,33 @@ if (firstArg && versionBumpTypes.includes(firstArg)) {
94
94
  command = 'maiass';
95
95
  }
96
96
 
97
- // Handle --auto flag (must be before other processing)
98
- if (args.includes('--auto') || args.includes('-a')) {
99
- // Override all auto-yes variables for non-interactive mode
97
+ // Auto modes:
98
+ // -a / --auto: full auto stage, push, merge to develop, version bump
99
+ // (kept identical to historical behaviour for CI compatibility)
100
+ // -ac / --auto-commit: auto-yes for commit phase only — stops after commit
101
+ // (no merge, no bump). Useful for CI runs that just want
102
+ // the AI commit captured without touching develop.
103
+ const isAutoCommit = args.includes('--auto-commit') || args.includes('-ac');
104
+ const isAuto = !isAutoCommit && (args.includes('--auto') || args.includes('-a'));
105
+
106
+ if (isAuto || isAutoCommit) {
107
+ // Shared auto-yes vars for both modes
100
108
  process.env.MAIASS_AUTO_STAGE_UNSTAGED = 'true';
101
109
  process.env.MAIASS_AUTO_PUSH_COMMITS = 'true';
102
- process.env.MAIASS_AUTO_MERGE_TO_DEVELOP = 'true';
103
110
  process.env.MAIASS_AUTO_APPROVE_AI_SUGGESTIONS = 'true';
104
-
111
+ }
112
+
113
+ if (isAuto) {
114
+ // -a: also auto-merge to develop (legacy behaviour)
115
+ process.env.MAIASS_AUTO_MERGE_TO_DEVELOP = 'true';
116
+ if (process.env.MAIASS_DEBUG === 'true') {
117
+ logger.debug('[DEBUG] Auto mode enabled — full pipeline runs unattended');
118
+ }
119
+ } else if (isAutoCommit) {
120
+ // -ac: stop after commit phase. Implemented by treating it as commits-only
121
+ process.env.MAIASS_AUTO_FINISH_AFTER_COMMIT = 'true';
105
122
  if (process.env.MAIASS_DEBUG === 'true') {
106
- logger.debug('[DEBUG] Auto-mode enabled - all prompts will be skipped');
123
+ logger.debug('[DEBUG] Auto-commit mode enabled stops after commit phase');
107
124
  }
108
125
  }
109
126
 
@@ -124,6 +141,7 @@ const validFlags = [
124
141
  '--version', '-v',
125
142
  '--account-info',
126
143
  '--auto', '-a',
144
+ '--auto-commit', '-ac',
127
145
  '--commits-only', '-c',
128
146
  '--auto-stage',
129
147
  '--setup', '--bootstrap',
@@ -182,7 +200,8 @@ if (args.includes('--help') || args.includes('-h') || command === 'help') {
182
200
  console.log(' account-info Show your account status (masked token)');
183
201
  console.log('\nOptions:');
184
202
  console.log(' --account-info Show your account status (masked token)');
185
- console.log(' --auto Enable all auto-yes functionality (non-interactive mode)');
203
+ console.log(' --auto, -a Full auto stage, commit, push, merge to develop, bump version');
204
+ console.log(' --auto-commit, -ac Auto-yes for commit phase only — stops after commit (no merge, no bump)');
186
205
  console.log(' --commits-only, -c Generate AI commits without version management');
187
206
  console.log(' --auto-stage Automatically stage all changes');
188
207
  console.log(' --setup, --bootstrap Run interactive project setup');
@@ -279,7 +298,8 @@ if (args.includes('--show-bb-excerpt')) { showBitbucketExcerpt(); process.exit(
279
298
  // Handle the main MAIASS workflow
280
299
  await handleMaiassCommand({
281
300
  _: process.argv.slice(2).filter(arg => !arg.startsWith('-')),
282
- 'commits-only': args.includes('--commits-only') || args.includes('-c'),
301
+ // -ac is equivalent to -c (commits-only) plus auto-yes prompts
302
+ 'commits-only': args.includes('--commits-only') || args.includes('-c') || args.includes('--auto-commit') || args.includes('-ac'),
283
303
  'auto-stage': args.includes('--auto-stage'),
284
304
  'auto': args.includes('--auto') || args.includes('-a'),
285
305
  'version-bump': versionBump,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "maiass",
3
3
  "type": "module",
4
- "version": "5.10.5",
4
+ "version": "5.12.2",
5
5
  "description": "AI commit message generator, semantic versioning, and changelog automation for Git. One command stages, commits with AI, bumps version, and merges branches. Free credits on install — no sign-up needed.",
6
6
  "main": "maiass.mjs",
7
7
  "bin": {
@@ -24,6 +24,8 @@
24
24
  "maiass.mjs",
25
25
  "setup-env.js",
26
26
  "README.md",
27
+ "CONTRIBUTING.md",
28
+ "CODE_OF_CONDUCT.md",
27
29
  "LICENSE"
28
30
  ],
29
31
  "engines": {