maiass 5.13.0 → 5.14.0
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/lib/arg-utils.js +54 -0
- package/lib/bootstrap.js +5 -0
- package/lib/commit.js +94 -42
- package/lib/flag-validator.js +8 -2
- package/lib/maiass-command.js +9 -2
- package/lib/maiass-pipeline.js +16 -8
- package/lib/maiass-variables.js +1 -1
- package/maiass.mjs +46 -6
- package/package.json +1 -1
- package/templates/ci/github-version-bump.yml +5 -2
package/lib/arg-utils.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Pure argv parsing helpers shared between maiass.mjs and unit tests.
|
|
2
|
+
//
|
|
3
|
+
// Kept dependency-free and side-effect-free so it can be imported in a unit
|
|
4
|
+
// test without triggering maiass.mjs's top-level CLI bootstrap.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract the -m / --message commit-message value from argv (MAI-51 parity).
|
|
8
|
+
*
|
|
9
|
+
* Supported forms (single value only — NOT git-style repeated -m):
|
|
10
|
+
* -m <value>
|
|
11
|
+
* --message <value>
|
|
12
|
+
* --message=<value>
|
|
13
|
+
*
|
|
14
|
+
* The value is taken VERBATIM (no escape-sequence interpretation, no newline
|
|
15
|
+
* collapsing). Only the whole-string leading/trailing whitespace is trimmed,
|
|
16
|
+
* and that trimming happens at the point of use (lib/commit.js) so that empty /
|
|
17
|
+
* whitespace-only values still fall through to the "No commit message provided"
|
|
18
|
+
* path. Here we return the raw string.
|
|
19
|
+
*
|
|
20
|
+
* Last occurrence wins if the flag appears more than once.
|
|
21
|
+
*
|
|
22
|
+
* @param {string[]} argv
|
|
23
|
+
* @returns {{ message: string|null, valueIndices: Set<number> }}
|
|
24
|
+
* message — the raw message string, or null if -m/--message not present.
|
|
25
|
+
* valueIndices — argv positions of the VALUE token only (the space-separated
|
|
26
|
+
* form's value). The flag tokens (-m/--message) are NOT included
|
|
27
|
+
* because (a) they start with '-' so command derivation already
|
|
28
|
+
* skips them, and (b) the flag-validator still needs to validate
|
|
29
|
+
* them against the allow-list. The value token is recorded so
|
|
30
|
+
* command derivation doesn't mistake it for a command/bump type
|
|
31
|
+
* and so the validator skips it if it happens to start with '-'.
|
|
32
|
+
*/
|
|
33
|
+
export function extractMessageFlag(argv) {
|
|
34
|
+
let message = null;
|
|
35
|
+
const valueIndices = new Set();
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const arg = argv[i];
|
|
38
|
+
if (arg === '-m' || arg === '--message') {
|
|
39
|
+
if (i + 1 < argv.length) {
|
|
40
|
+
message = argv[i + 1];
|
|
41
|
+
valueIndices.add(i + 1);
|
|
42
|
+
} else {
|
|
43
|
+
// Flag given with no following token — treat as empty so it falls
|
|
44
|
+
// through to the "No commit message provided" error rather than
|
|
45
|
+
// silently consuming nothing.
|
|
46
|
+
message = '';
|
|
47
|
+
}
|
|
48
|
+
} else if (arg.startsWith('--message=')) {
|
|
49
|
+
message = arg.slice('--message='.length);
|
|
50
|
+
valueIndices.add(i);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { message, valueIndices };
|
|
54
|
+
}
|
package/lib/bootstrap.js
CHANGED
|
@@ -612,6 +612,11 @@ MAIASS_STAGINGBRANCH=${config.branches.staging}
|
|
|
612
612
|
|
|
613
613
|
# Example: Personal debug settings
|
|
614
614
|
#MAIASS_DEBUG=true
|
|
615
|
+
|
|
616
|
+
# Example: devlog tagging (optional)
|
|
617
|
+
#MAIASS_DEVLOG_CLIENT="yourclient"
|
|
618
|
+
#MAIASS_DEVLOG_SUBCLIENT=""
|
|
619
|
+
#MAIASS_DEVLOG_PROJECT="yourproject"
|
|
615
620
|
`;
|
|
616
621
|
fs.writeFileSync('.env.maiass.local', localContent, 'utf8');
|
|
617
622
|
log.success(SYMBOLS.CHECKMARK, 'Created .env.maiass.local for personal settings');
|
package/lib/commit.js
CHANGED
|
@@ -628,20 +628,47 @@ async function getMultiLineCommitMessage(jiraTicket) {
|
|
|
628
628
|
* @returns {Promise<string>} Final commit message
|
|
629
629
|
*/
|
|
630
630
|
async function getCommitMessage(gitInfo, options = {}) {
|
|
631
|
-
const { silent = false } = options;
|
|
632
|
-
|
|
631
|
+
const { silent = false, providedMessage = null } = options;
|
|
632
|
+
|
|
633
633
|
// Load environment config to ensure tokens are loaded from secure storage
|
|
634
634
|
loadEnvironmentConfig();
|
|
635
|
-
|
|
635
|
+
|
|
636
636
|
const aiMode = process.env.MAIASS_AI_MODE || 'ask';
|
|
637
637
|
const maiassToken = process.env.MAIASS_AI_TOKEN;
|
|
638
638
|
const jiraTicket = gitInfo.jiraTicket;
|
|
639
|
-
|
|
639
|
+
|
|
640
640
|
// Display JIRA ticket if found
|
|
641
641
|
if (jiraTicket) {
|
|
642
642
|
log.info(SYMBOLS.INFO, `Jira Ticket Number: ${jiraTicket}`);
|
|
643
643
|
}
|
|
644
|
-
|
|
644
|
+
|
|
645
|
+
// -m / --message: verbatim, non-interactive commit message (MAI-XX parity).
|
|
646
|
+
//
|
|
647
|
+
// When supplied, this short-circuits BEFORE any AI proxy call and before any
|
|
648
|
+
// interactive prompt — so it works with no token and no credit, and never
|
|
649
|
+
// hits the network. The message is used EXACTLY as given except for trimming
|
|
650
|
+
// leading/trailing whitespace of the whole string: internal newlines,
|
|
651
|
+
// indentation and blank lines are preserved verbatim (do NOT reformat — the
|
|
652
|
+
// canonical "title\n - bullet" layout with no blank line between title and
|
|
653
|
+
// bullets must survive intact for changelog parsing). Escape sequences are
|
|
654
|
+
// NOT interpreted (a literal "\n" stays literal).
|
|
655
|
+
//
|
|
656
|
+
// Empty / whitespace-only → return '' so the caller emits the existing
|
|
657
|
+
// "No commit message provided" error and does not commit.
|
|
658
|
+
if (providedMessage !== null && providedMessage !== undefined) {
|
|
659
|
+
const trimmed = providedMessage.trim();
|
|
660
|
+
if (!trimmed) {
|
|
661
|
+
return '';
|
|
662
|
+
}
|
|
663
|
+
// Prepend the Jira ticket if detected and not already present (matches the
|
|
664
|
+
// AI / manual paths). The caller (handleStagedCommit) also guards against
|
|
665
|
+
// double-prepend via startsWith, so this is idempotent.
|
|
666
|
+
if (jiraTicket && !trimmed.startsWith(jiraTicket)) {
|
|
667
|
+
return `${jiraTicket} ${trimmed}`;
|
|
668
|
+
}
|
|
669
|
+
return trimmed;
|
|
670
|
+
}
|
|
671
|
+
|
|
645
672
|
let useAI = false;
|
|
646
673
|
let aiSuggestion = null;
|
|
647
674
|
|
|
@@ -787,27 +814,34 @@ async function getCommitMessage(gitInfo, options = {}) {
|
|
|
787
814
|
* @returns {Promise<boolean>} True if commit was successful
|
|
788
815
|
*/
|
|
789
816
|
async function handleStagedCommit(gitInfo, options = {}) {
|
|
790
|
-
const { silent = false } = options;
|
|
791
|
-
// Check if there are actually staged changes
|
|
792
|
-
|
|
817
|
+
const { silent = false, dryRun = false, forceStaged = false, providedMessage = null } = options;
|
|
818
|
+
// Check if there are actually staged changes.
|
|
819
|
+
// In dry-run with forceStaged, nothing was really staged (the `git add` was
|
|
820
|
+
// only previewed), so skip this guard — the working-tree changes are what
|
|
821
|
+
// *would* be committed.
|
|
822
|
+
if (!forceStaged && (!gitInfo.status || gitInfo.status.stagedCount === 0)) {
|
|
793
823
|
log.warning(SYMBOLS.INFO, 'Nothing to commit, working tree clean');
|
|
794
824
|
return true;
|
|
795
825
|
}
|
|
796
|
-
|
|
797
|
-
// Show
|
|
798
|
-
|
|
826
|
+
|
|
827
|
+
// Show the changes that are (or would be) committed. For a forced dry run
|
|
828
|
+
// nothing is actually staged, so preview the working-tree changes instead.
|
|
829
|
+
const diffCommand = forceStaged
|
|
830
|
+
? 'git diff HEAD --name-status'
|
|
831
|
+
: 'git diff --cached --name-status';
|
|
832
|
+
const stagedOutput = executeGitCommand(diffCommand, true);
|
|
799
833
|
if (!stagedOutput) {
|
|
800
834
|
log.warning(SYMBOLS.INFO, 'No staged changes to show');
|
|
801
835
|
return true;
|
|
802
836
|
}
|
|
803
|
-
|
|
804
|
-
log.critical(SYMBOLS.INFO, 'Staged changes detected:');
|
|
805
|
-
|
|
837
|
+
|
|
838
|
+
log.critical(SYMBOLS.INFO, forceStaged ? 'Changes that would be committed:' : 'Staged changes detected:');
|
|
839
|
+
|
|
806
840
|
// Display the staged changes
|
|
807
841
|
console.log(stagedOutput);
|
|
808
|
-
|
|
842
|
+
|
|
809
843
|
// Get commit message
|
|
810
|
-
const commitMessage = await getCommitMessage(gitInfo, { silent });
|
|
844
|
+
const commitMessage = await getCommitMessage(gitInfo, { silent, providedMessage });
|
|
811
845
|
if (!commitMessage) {
|
|
812
846
|
log.error(SYMBOLS.CROSS, 'No commit message provided');
|
|
813
847
|
return false;
|
|
@@ -826,24 +860,28 @@ async function handleStagedCommit(gitInfo, options = {}) {
|
|
|
826
860
|
if (jiraTicket && finalCommitMessage && !finalCommitMessage.startsWith(jiraTicket)) {
|
|
827
861
|
finalCommitMessage = `${jiraTicket} ${finalCommitMessage}`;
|
|
828
862
|
}
|
|
829
|
-
|
|
830
|
-
|
|
863
|
+
if (dryRun) {
|
|
864
|
+
// Preview the commit instead of performing it
|
|
865
|
+
const previewMessage = finalCommitMessage.split('\n')[0];
|
|
866
|
+
console.log(colors.BBlue(`${SYMBOLS.INFO} Dry run - would commit with message: "${previewMessage}"`));
|
|
867
|
+
} else {
|
|
868
|
+
// Write commit message to a temp file on all platforms — avoids shell quoting and injection risks
|
|
831
869
|
const tmpFile = path.join(os.tmpdir(), `maiass-commit-msg-${Date.now()}.txt`);
|
|
832
870
|
fs.writeFileSync(tmpFile, finalCommitMessage, { encoding: 'utf8' });
|
|
833
871
|
result = executeGitCommand(`git commit -F "${tmpFile}"`, quietMode);
|
|
834
872
|
fs.unlinkSync(tmpFile);
|
|
873
|
+
|
|
874
|
+
if (result === null) {
|
|
875
|
+
log.error(SYMBOLS.CROSS, 'Commit failed');
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
log.success(SYMBOLS.CHECKMARK, 'Changes committed successfully');
|
|
880
|
+
|
|
881
|
+
// Log the commit to devlog.sh (equivalent to logthis in maiass.sh)
|
|
882
|
+
logCommit(commitMessage, gitInfo);
|
|
835
883
|
}
|
|
836
|
-
|
|
837
|
-
if (result === null) {
|
|
838
|
-
log.error(SYMBOLS.CROSS, 'Commit failed');
|
|
839
|
-
return false;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
log.success(SYMBOLS.CHECKMARK, 'Changes committed successfully');
|
|
843
|
-
|
|
844
|
-
// Log the commit to devlog.sh (equivalent to logthis in maiass.sh)
|
|
845
|
-
logCommit(commitMessage, gitInfo);
|
|
846
|
-
|
|
884
|
+
|
|
847
885
|
// Ask about pushing to remote
|
|
848
886
|
if (remoteExists('origin')) {
|
|
849
887
|
let reply;
|
|
@@ -872,13 +910,17 @@ async function handleStagedCommit(gitInfo, options = {}) {
|
|
|
872
910
|
}
|
|
873
911
|
|
|
874
912
|
if (reply === 'y') {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
log.success(SYMBOLS.CHECKMARK, 'Commit pushed.');
|
|
913
|
+
if (dryRun) {
|
|
914
|
+
// Preview the push instead of performing it
|
|
915
|
+
console.log(colors.BBlue(`${SYMBOLS.INFO} Dry run - would push to origin ${gitInfo.branch} (git push --set-upstream origin ${gitInfo.branch})`));
|
|
879
916
|
} else {
|
|
880
|
-
|
|
881
|
-
|
|
917
|
+
const pushResult = executeGitCommand(`git push --set-upstream origin ${gitInfo.branch}`, false);
|
|
918
|
+
if (pushResult !== null) {
|
|
919
|
+
log.success(SYMBOLS.CHECKMARK, 'Commit pushed.');
|
|
920
|
+
} else {
|
|
921
|
+
log.error(SYMBOLS.CROSS, 'Push failed');
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
882
924
|
}
|
|
883
925
|
}
|
|
884
926
|
} else {
|
|
@@ -898,7 +940,7 @@ async function handleStagedCommit(gitInfo, options = {}) {
|
|
|
898
940
|
* @returns {Promise<boolean>} True if process completed successfully
|
|
899
941
|
*/
|
|
900
942
|
export async function commitThis(options = {}) {
|
|
901
|
-
const { autoStage = false, commitsOnly = false, silent = false } = options;
|
|
943
|
+
const { autoStage = false, commitsOnly = false, silent = false, dryRun = false, providedMessage = null } = options;
|
|
902
944
|
|
|
903
945
|
// Get git information
|
|
904
946
|
const gitInfo = getGitInfo();
|
|
@@ -947,21 +989,26 @@ export async function commitThis(options = {}) {
|
|
|
947
989
|
reply = await getSingleCharInput('Do you want to stage and commit them? [y/N] ');
|
|
948
990
|
}
|
|
949
991
|
if (reply === 'y') {
|
|
992
|
+
if (dryRun) {
|
|
993
|
+
// Preview the stage instead of performing it
|
|
994
|
+
console.log(colors.BBlue(`${SYMBOLS.INFO} Dry run - would stage all changes (git add -A)`));
|
|
995
|
+
return await handleStagedCommit(gitInfo, { silent, dryRun, forceStaged: true, providedMessage });
|
|
996
|
+
}
|
|
950
997
|
// Stage all changes
|
|
951
998
|
const stageResult = executeGitCommand('git add -A', false);
|
|
952
999
|
if (stageResult === null) {
|
|
953
1000
|
log.error(SYMBOLS.CROSS, 'Failed to stage changes');
|
|
954
1001
|
return false;
|
|
955
1002
|
}
|
|
956
|
-
|
|
1003
|
+
|
|
957
1004
|
// Refresh git info to get updated status
|
|
958
1005
|
const updatedGitInfo = getGitInfo();
|
|
959
|
-
return await handleStagedCommit(updatedGitInfo, { silent });
|
|
1006
|
+
return await handleStagedCommit(updatedGitInfo, { silent, dryRun, providedMessage });
|
|
960
1007
|
} else {
|
|
961
1008
|
// Check if there are staged changes to commit
|
|
962
1009
|
if (status.stagedCount > 0) {
|
|
963
1010
|
log.info(SYMBOLS.INFO, 'Proceeding with staged changes only');
|
|
964
|
-
return await handleStagedCommit(gitInfo, { silent });
|
|
1011
|
+
return await handleStagedCommit(gitInfo, { silent, dryRun, providedMessage });
|
|
965
1012
|
}
|
|
966
1013
|
|
|
967
1014
|
// Handle the case where user declined to stage and there are no staged changes
|
|
@@ -979,20 +1026,25 @@ export async function commitThis(options = {}) {
|
|
|
979
1026
|
}
|
|
980
1027
|
}
|
|
981
1028
|
} else {
|
|
1029
|
+
if (dryRun) {
|
|
1030
|
+
// Preview the stage instead of performing it
|
|
1031
|
+
console.log(colors.BBlue(`${SYMBOLS.INFO} Dry run - would stage all changes (git add -A)`));
|
|
1032
|
+
return await handleStagedCommit(gitInfo, { silent, dryRun, forceStaged: true, providedMessage });
|
|
1033
|
+
}
|
|
982
1034
|
// Auto-stage all changes
|
|
983
1035
|
const stageResult = executeGitCommand('git add -A', false);
|
|
984
1036
|
if (stageResult === null) {
|
|
985
1037
|
console.log(colors.Red(`${SYMBOLS.CROSS} Failed to stage changes`));
|
|
986
1038
|
return false;
|
|
987
1039
|
}
|
|
988
|
-
|
|
1040
|
+
|
|
989
1041
|
// Refresh git info and commit
|
|
990
1042
|
const updatedGitInfo = getGitInfo();
|
|
991
|
-
return await handleStagedCommit(updatedGitInfo, { silent });
|
|
1043
|
+
return await handleStagedCommit(updatedGitInfo, { silent, dryRun, providedMessage });
|
|
992
1044
|
}
|
|
993
1045
|
} else if (status.stagedCount > 0) {
|
|
994
1046
|
// Only staged changes present, proceed directly to commit
|
|
995
|
-
return await handleStagedCommit(gitInfo, { silent });
|
|
1047
|
+
return await handleStagedCommit(gitInfo, { silent, dryRun, providedMessage });
|
|
996
1048
|
}
|
|
997
1049
|
|
|
998
1050
|
return true;
|
package/lib/flag-validator.js
CHANGED
|
@@ -89,13 +89,19 @@ export function suggestFlag(unknown, candidates) {
|
|
|
89
89
|
* Validate the argv flags for a given subcommand.
|
|
90
90
|
* @param {string[]} args - The raw argv tail (process.argv.slice(2))
|
|
91
91
|
* @param {string[]} subcommandFlags - FLAGS export from the active subcommand handler
|
|
92
|
+
* @param {Set<number>} [ignoreIndices] - argv positions to skip entirely. Used
|
|
93
|
+
* for option VALUES that may legitimately start with '-' (e.g. the -m/--message
|
|
94
|
+
* value, where a caller might pass `-m "-- not a flag --"`). Without this, such
|
|
95
|
+
* a value token would be misread as an unknown flag and rejected.
|
|
92
96
|
* @returns {{valid: true} | {valid: false, flag: string, suggestion: string|null, validFlags: string[]}}
|
|
93
97
|
*/
|
|
94
|
-
export function validateFlags(args, subcommandFlags = []) {
|
|
98
|
+
export function validateFlags(args, subcommandFlags = [], ignoreIndices = new Set()) {
|
|
95
99
|
// Dedupe so callers don't have to worry about overlap between the always-
|
|
96
100
|
// valid set and a subcommand's FLAGS (e.g. account-info also lists --json).
|
|
97
101
|
const validFlags = Array.from(new Set([...ALWAYS_VALID_FLAGS, ...subcommandFlags]));
|
|
98
|
-
for (
|
|
102
|
+
for (let i = 0; i < args.length; i++) {
|
|
103
|
+
const arg = args[i];
|
|
104
|
+
if (ignoreIndices.has(i)) continue;
|
|
99
105
|
if (!arg.startsWith('-')) continue;
|
|
100
106
|
// Support `--tag=value` form — only the name portion is validated.
|
|
101
107
|
const flagName = arg.split('=')[0];
|
package/lib/maiass-command.js
CHANGED
|
@@ -19,6 +19,10 @@ export const FLAGS = [
|
|
|
19
19
|
'--force', '-f',
|
|
20
20
|
'--silent', '-s',
|
|
21
21
|
'--tag', '-t',
|
|
22
|
+
// -m / --message <value>: supply the commit message verbatim, non-interactively
|
|
23
|
+
// (MAI-XX node+bash parity). Bypasses AI + interactive prompt. Works with the
|
|
24
|
+
// default workflow and commits-only (-c / -ac).
|
|
25
|
+
'--message', '-m',
|
|
22
26
|
];
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -34,7 +38,9 @@ export async function handleMaiassCommand(args) {
|
|
|
34
38
|
'dry-run': dryRun,
|
|
35
39
|
tag,
|
|
36
40
|
force,
|
|
37
|
-
silent
|
|
41
|
+
silent,
|
|
42
|
+
// Verbatim commit message from -m/--message (MAI-XX). null when not supplied.
|
|
43
|
+
message: providedMessage = null
|
|
38
44
|
} = args;
|
|
39
45
|
|
|
40
46
|
// Extract version bump from positional arguments if provided
|
|
@@ -63,7 +69,8 @@ export async function handleMaiassCommand(args) {
|
|
|
63
69
|
dryRun,
|
|
64
70
|
tag,
|
|
65
71
|
force,
|
|
66
|
-
silent
|
|
72
|
+
silent,
|
|
73
|
+
providedMessage
|
|
67
74
|
});
|
|
68
75
|
|
|
69
76
|
if (result.success) {
|
package/lib/maiass-pipeline.js
CHANGED
|
@@ -246,16 +246,18 @@ async function validateAndHandleBranching(options = {}) {
|
|
|
246
246
|
* @returns {Promise<Object>} Commit result
|
|
247
247
|
*/
|
|
248
248
|
async function handleCommitWorkflow(branchInfo, options = {}) {
|
|
249
|
-
const { commitsOnly = false, autoStage = false, silent = false } = options;
|
|
250
|
-
|
|
249
|
+
const { commitsOnly = false, autoStage = false, silent = false, dryRun = false, providedMessage = null } = options;
|
|
250
|
+
|
|
251
251
|
logger.header(SYMBOLS.INFO, 'Commit Workflow Phase');
|
|
252
|
-
|
|
252
|
+
|
|
253
253
|
try {
|
|
254
254
|
// Run the commit workflow
|
|
255
255
|
const commitSuccess = await commitThis({
|
|
256
256
|
autoStage,
|
|
257
257
|
commitsOnly,
|
|
258
|
-
silent
|
|
258
|
+
silent,
|
|
259
|
+
dryRun,
|
|
260
|
+
providedMessage
|
|
259
261
|
});
|
|
260
262
|
|
|
261
263
|
// Check if commit workflow succeeded
|
|
@@ -829,7 +831,9 @@ export async function runMaiassPipeline(options = {}) {
|
|
|
829
831
|
dryRun = false,
|
|
830
832
|
tag = false,
|
|
831
833
|
force = false,
|
|
832
|
-
silent = false
|
|
834
|
+
silent = false,
|
|
835
|
+
// Verbatim commit message from -m/--message (MAI-XX). null = not supplied.
|
|
836
|
+
providedMessage = null
|
|
833
837
|
} = options;
|
|
834
838
|
|
|
835
839
|
// Display branded header with MAIASS's own version (not project version)
|
|
@@ -867,7 +871,7 @@ export async function runMaiassPipeline(options = {}) {
|
|
|
867
871
|
|
|
868
872
|
// Phase 2: Commit Workflow
|
|
869
873
|
logger.debug('Phase 2: Commit Workflow');
|
|
870
|
-
const commitResult = await handleCommitWorkflow(branchInfo, { commitsOnly, autoStage, silent });
|
|
874
|
+
const commitResult = await handleCommitWorkflow(branchInfo, { commitsOnly, autoStage, silent, dryRun, providedMessage });
|
|
871
875
|
|
|
872
876
|
if (!commitResult.success) {
|
|
873
877
|
// Check if it was cancelled by user vs actual failure
|
|
@@ -886,9 +890,13 @@ export async function runMaiassPipeline(options = {}) {
|
|
|
886
890
|
// If commits-only mode, stop here
|
|
887
891
|
if (commitsOnly) {
|
|
888
892
|
console.log();
|
|
889
|
-
|
|
893
|
+
if (dryRun) {
|
|
894
|
+
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Commits-only mode dry run completed (no changes made)`));
|
|
895
|
+
} else {
|
|
896
|
+
console.log(colors.BGreen(`${SYMBOLS.CHECKMARK} Commits-only mode completed successfully`));
|
|
897
|
+
}
|
|
890
898
|
console.log(colors.BBlue(`${SYMBOLS.INFO} Current branch: ${getCurrentBranch()}`));
|
|
891
|
-
return { success: true, phase: 'commits-only' };
|
|
899
|
+
return { success: true, phase: 'commits-only', dryRun };
|
|
892
900
|
}
|
|
893
901
|
|
|
894
902
|
console.log();
|
package/lib/maiass-variables.js
CHANGED
|
@@ -33,7 +33,7 @@ export const MAIASS_VARIABLES = {
|
|
|
33
33
|
'MAIASS_VERSION_PRIMARY_FILE': { default: '', description: 'Primary version file path' },
|
|
34
34
|
'MAIASS_VERSION_PRIMARY_TYPE': { default: '', description: 'Primary version file type' },
|
|
35
35
|
'MAIASS_VERSION_PRIMARY_LINE_START': { default: '', description: 'Line start pattern for version' },
|
|
36
|
-
'MAIASS_VERSION_SECONDARY_FILES': { default: '', description: 'Secondary version files (
|
|
36
|
+
'MAIASS_VERSION_SECONDARY_FILES': { default: '', description: 'Secondary version files (pipe-separated file:type:pattern entries)' },
|
|
37
37
|
|
|
38
38
|
// Branch configuration
|
|
39
39
|
'MAIASS_DEVELOPBRANCH': { default: 'develop', description: 'Development branch name' },
|
package/maiass.mjs
CHANGED
|
@@ -55,11 +55,25 @@ import { SYMBOLS } from './lib/symbols.js';
|
|
|
55
55
|
import { bootstrapProject } from './lib/bootstrap.js';
|
|
56
56
|
import { createGithubAction, showGitlabExcerpt, showBitbucketExcerpt } from './lib/ci-templates.js';
|
|
57
57
|
import { validateFlags } from './lib/flag-validator.js';
|
|
58
|
+
import { extractMessageFlag } from './lib/arg-utils.js';
|
|
58
59
|
|
|
59
60
|
// Simple CLI setup for pkg compatibility
|
|
60
61
|
const args = process.argv.slice(2);
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
|
|
63
|
+
// -m / --message <value>: a verbatim commit message supplied non-interactively
|
|
64
|
+
// (MAI-XX node+bash parity). The flag CONSUMES the next token as its value, so
|
|
65
|
+
// that value must NOT be treated as the command name / version-bump type during
|
|
66
|
+
// command derivation below. Supported forms: `-m <value>`, `--message <value>`,
|
|
67
|
+
// `--message=<value>`. Single value only (no git-style repeated -m).
|
|
68
|
+
//
|
|
69
|
+
// extractMessageFlag returns { message, valueIndices } where valueIndices marks
|
|
70
|
+
// argv positions that belong to the flag (the flag token and, for the space-
|
|
71
|
+
// separated form, its value) so command derivation can skip them.
|
|
72
|
+
const { message: providedMessage, valueIndices: messageArgIndices } = extractMessageFlag(args);
|
|
73
|
+
|
|
74
|
+
// Skip flags (starting with -) AND the -m/--message value token to find the
|
|
75
|
+
// first meaningful positional argument (command / version-bump).
|
|
76
|
+
const firstArg = args.find((a, i) => !a.startsWith('-') && !messageArgIndices.has(i));
|
|
63
77
|
|
|
64
78
|
// Handle --setup/--bootstrap flag early
|
|
65
79
|
if (args.includes('--setup') || args.includes('--bootstrap')) {
|
|
@@ -146,7 +160,10 @@ const SUBCOMMAND_FLAGS = {
|
|
|
146
160
|
};
|
|
147
161
|
|
|
148
162
|
const activeSubcommandFlags = SUBCOMMAND_FLAGS[command] ?? [];
|
|
149
|
-
|
|
163
|
+
// Skip the -m/--message value token during validation — it may legitimately
|
|
164
|
+
// begin with '-' (e.g. `-m "-- breaking change --"`) and must not be treated
|
|
165
|
+
// as an unknown flag.
|
|
166
|
+
const flagValidation = validateFlags(args, activeSubcommandFlags, messageArgIndices);
|
|
150
167
|
|
|
151
168
|
if (!flagValidation.valid) {
|
|
152
169
|
console.error(colors.Red(`${SYMBOLS.CROSS} Error: Unrecognized option '${flagValidation.flag}' for command '${command}'`));
|
|
@@ -167,6 +184,14 @@ if (!flagValidation.valid) {
|
|
|
167
184
|
process.exit(1);
|
|
168
185
|
}
|
|
169
186
|
|
|
187
|
+
// An explicitly-supplied but empty/whitespace-only -m/--message is a usage
|
|
188
|
+
// error — fail fast with a non-zero exit (parity with bash, which aborts here).
|
|
189
|
+
// An absent flag (providedMessage === null) is unaffected.
|
|
190
|
+
if (providedMessage !== null && providedMessage.trim() === '') {
|
|
191
|
+
console.error(colors.Red(`${SYMBOLS.CROSS} Error: --message/-m requires a non-empty value`));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
170
195
|
// Apply auto-mode env vars now that validation has passed. Setting these
|
|
171
196
|
// before validation would leak MAIASS_AUTO_* into process.env for commands
|
|
172
197
|
// that reject --auto (e.g. `account-info --auto`) — see MAI-43 code review.
|
|
@@ -221,6 +246,8 @@ if (args.includes('--help') || args.includes('-h') || command === 'help') {
|
|
|
221
246
|
console.log(' --auto, -a Full auto — stage, commit, push, merge to develop, bump version');
|
|
222
247
|
console.log(' --auto-commit, -ac Auto-yes for commit phase only — stops after commit (no merge, no bump)');
|
|
223
248
|
console.log(' --commits-only, -c Generate AI commits without version management');
|
|
249
|
+
console.log(' --message, -m <msg> Use this commit message verbatim (skips AI + the message prompt; no credit/token needed).');
|
|
250
|
+
console.log(' Supplies the message only; for fully unattended use combine with -ac (or --auto-stage).');
|
|
224
251
|
console.log(' --auto-stage Automatically stage all changes');
|
|
225
252
|
console.log(' --setup, --bootstrap Run interactive project setup');
|
|
226
253
|
console.log(' --help, -h Show this help message');
|
|
@@ -263,7 +290,12 @@ if (args.includes('--show-bb-excerpt')) { showBitbucketExcerpt(); process.exit(
|
|
|
263
290
|
'.env.maiass.local',
|
|
264
291
|
`# .env.maiass.local — personal/local MAIASS settings (never committed)\n` +
|
|
265
292
|
`# Generated on first run: ${new Date().toISOString()}\n` +
|
|
266
|
-
`# Use this file for personal overrides, e.g. MAIASS_AI_MODE=autosuggest\n
|
|
293
|
+
`# Use this file for personal overrides, e.g. MAIASS_AI_MODE=autosuggest\n` +
|
|
294
|
+
`\n` +
|
|
295
|
+
`# Example: devlog tagging (optional)\n` +
|
|
296
|
+
`#MAIASS_DEVLOG_CLIENT="yourclient"\n` +
|
|
297
|
+
`#MAIASS_DEVLOG_SUBCLIENT=""\n` +
|
|
298
|
+
`#MAIASS_DEVLOG_PROJECT="yourproject"\n`,
|
|
267
299
|
'utf8'
|
|
268
300
|
);
|
|
269
301
|
|
|
@@ -326,7 +358,9 @@ if (args.includes('--show-bb-excerpt')) { showBitbucketExcerpt(); process.exit(
|
|
|
326
358
|
case 'maiass':
|
|
327
359
|
// Handle the main MAIASS workflow
|
|
328
360
|
await handleMaiassCommand({
|
|
329
|
-
|
|
361
|
+
// Positionals must exclude the -m/--message value token (it doesn't start
|
|
362
|
+
// with '-' but is NOT a command/bump-type positional).
|
|
363
|
+
_: process.argv.slice(2).filter((arg, i) => !arg.startsWith('-') && !messageArgIndices.has(i)),
|
|
330
364
|
// -ac is equivalent to -c (commits-only) plus auto-yes prompts
|
|
331
365
|
'commits-only': args.includes('--commits-only') || args.includes('-c') || args.includes('--auto-commit') || args.includes('-ac'),
|
|
332
366
|
'auto-stage': args.includes('--auto-stage'),
|
|
@@ -335,7 +369,10 @@ if (args.includes('--show-bb-excerpt')) { showBitbucketExcerpt(); process.exit(
|
|
|
335
369
|
'dry-run': args.includes('--dry-run') || args.includes('-d'),
|
|
336
370
|
force: args.includes('--force') || args.includes('-f'),
|
|
337
371
|
silent: args.includes('--silent') || args.includes('-s'),
|
|
338
|
-
tag: getArgValue(args, '--tag') || getArgValue(args, '-t')
|
|
372
|
+
tag: getArgValue(args, '--tag') || getArgValue(args, '-t'),
|
|
373
|
+
// Verbatim commit message supplied via -m/--message (or null). Threaded
|
|
374
|
+
// through the pipeline to the commit phase; bypasses AI + interactive.
|
|
375
|
+
message: providedMessage
|
|
339
376
|
});
|
|
340
377
|
break;
|
|
341
378
|
|
|
@@ -363,3 +400,6 @@ function getArgValue(args, flag) {
|
|
|
363
400
|
}
|
|
364
401
|
return null;
|
|
365
402
|
}
|
|
403
|
+
|
|
404
|
+
// extractMessageFlag is imported from ./lib/arg-utils.js — extracted (MAI-51)
|
|
405
|
+
// so it can be unit-tested without booting the CLI on import.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maiass",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.14.0",
|
|
5
5
|
"description": "AI commit messages, version bumps, and changelogs from one command. Stages, commits, merges, tags. Anonymous on first run — no email, no card.",
|
|
6
6
|
"main": "maiass.mjs",
|
|
7
7
|
"bin": {
|
|
@@ -50,8 +50,11 @@ jobs:
|
|
|
50
50
|
|
|
51
51
|
- uses: actions/setup-node@v4
|
|
52
52
|
with:
|
|
53
|
-
node-version: '
|
|
54
|
-
cache:
|
|
53
|
+
node-version: '24'
|
|
54
|
+
# No cache: this workflow only does `npm install -g maiass`, which
|
|
55
|
+
# needs no project-level npm cache. Setting cache: 'npm' makes
|
|
56
|
+
# setup-node fail on projects without a Node lock file (e.g.
|
|
57
|
+
# WordPress/PHP plugins): "Dependencies lock file is not found".
|
|
55
58
|
|
|
56
59
|
- name: Install maiass
|
|
57
60
|
run: npm install -g maiass --no-fund --no-audit
|