maiass 5.9.15 → 5.9.18

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/changelog.js CHANGED
@@ -149,9 +149,10 @@ export async function updateChangelog(changelogPath = '.', version) {
149
149
  } catch {}
150
150
 
151
151
  if (existingContent) {
152
- const lines = existingContent.split('\n');
153
- const firstVersion = lines[0]?.match(/^## (.+)$/)?.[1];
154
- const secondLine = lines[1];
152
+ // Normalize \r\n to \n for cross-platform compatibility (Windows writes \r\n)
153
+ const lines = existingContent.replace(/\r\n/g, '\n').split('\n');
154
+ const firstVersion = lines[0]?.match(/^## (.+)$/)?.[1]?.trim();
155
+ const secondLine = lines[1]?.trim();
155
156
 
156
157
  // Check if we're updating the same minor version on the same date
157
158
  const currentMinor = version.split('.').slice(0, 2).join('.');
@@ -186,9 +187,10 @@ export async function updateChangelog(changelogPath = '.', version) {
186
187
  await fs.access(changelogInternalFile);
187
188
 
188
189
  let existingContent = await fs.readFile(changelogInternalFile, 'utf8');
189
- const lines = existingContent.split('\n');
190
- const firstVersion = lines[0]?.match(/^## (.+)$/)?.[1];
191
- const secondLine = lines[1];
190
+ // Normalize \r\n to \n for cross-platform compatibility (Windows writes \r\n)
191
+ const lines = existingContent.replace(/\r\n/g, '\n').split('\n');
192
+ const firstVersion = lines[0]?.match(/^## (.+)$/)?.[1]?.trim();
193
+ const secondLine = lines[1]?.trim();
192
194
 
193
195
  // Check if we're updating the same minor version on the same date
194
196
  const currentMinor = version.split('.').slice(0, 2).join('.');
package/lib/commit.js CHANGED
@@ -282,7 +282,8 @@ async function getAICommitSuggestion(gitInfo) {
282
282
  let maiassToken = await createAnonymousSubscriptionIfNeeded();
283
283
 
284
284
  const aiHost = process.env.MAIASS_AI_HOST || 'https://pound.maiass.net';
285
- const aiEndpoint = aiHost + '/proxy';
285
+ const aiPath = process.env.MAIASS_AI_PATH || '/proxy';
286
+ const aiEndpoint = aiHost + aiPath;
286
287
  const aiModel = process.env.MAIASS_AI_MODEL || 'gpt-3.5-turbo';
287
288
  const aiTemperature = parseFloat(process.env.MAIASS_AI_TEMPERATURE || '0.7');
288
289
  const commitMessageStyle = process.env.MAIASS_AI_COMMIT_MESSAGE_STYLE || 'bullet';
package/lib/devlog.js CHANGED
@@ -1,20 +1,31 @@
1
1
  // Development logging utility for MAIASS
2
2
  // Node.js equivalent of the devlog.sh integration from maiass.sh
3
- import { exec } from 'child_process';
3
+ import { exec, execSync } from 'child_process';
4
4
  import { existsSync } from 'fs';
5
5
  import path from 'path';
6
6
  import colors from './colors.js';
7
7
  import { getGitInfo } from './git-info.js';
8
8
  import { logger } from './logger.js';
9
9
 
10
+ // Cache for devlog availability check (avoid repeated checks)
11
+ let _devlogAvailable = undefined;
12
+
10
13
  /**
11
- * Check if devlog.sh script exists and is executable
12
- * @returns {boolean} True if devlog.sh is available
14
+ * Check if devlog script exists and is available
15
+ * @returns {boolean} True if devlog.sh (unix) or devlog (windows cmdlet) is available
13
16
  */
14
17
  function isDevlogAvailable() {
15
- // On Windows, devlog.sh is never available
18
+ if (_devlogAvailable !== undefined) return _devlogAvailable;
19
+
16
20
  if (process.platform === 'win32') {
17
- return false;
21
+ // Check if devlog is a recognized cmdlet/command
22
+ try {
23
+ execSync('powershell.exe -NonInteractive -Command "Get-Command devlog -ErrorAction Stop"', { stdio: 'ignore' });
24
+ _devlogAvailable = true;
25
+ } catch {
26
+ _devlogAvailable = false;
27
+ }
28
+ return _devlogAvailable;
18
29
  }
19
30
  // Check if devlog.sh exists in common locations (sync check for immediate availability)
20
31
  const commonPaths = [
@@ -23,8 +34,7 @@ function isDevlogAvailable() {
23
34
  path.join(process.env.HOME || '', 'bin/devlog.sh'),
24
35
  path.join(process.env.HOME || '', '.local/bin/devlog.sh')
25
36
  ];
26
- // Quick sync check for file existence
27
- if (commonPaths.some(path => existsSync(path))) {
37
+ if (commonPaths.some(p => existsSync(p))) {
28
38
  return true;
29
39
  }
30
40
  // For PATH check, we'll assume it's available and let the async call handle errors
@@ -70,7 +80,7 @@ export function logThis(message, options = {}) {
70
80
 
71
81
  // If devlog.sh is not available, return null (equivalent to empty function in bash)
72
82
  if (!isDevlogAvailable()) {
73
- logger.debug(`devlog.sh not available, skipping log: ${message}`);
83
+ logger.debug(`devlog not available, skipping log: ${message}`);
74
84
  return null;
75
85
  }
76
86
 
@@ -81,11 +91,15 @@ export function logThis(message, options = {}) {
81
91
 
82
92
  // Escape the message for shell execution
83
93
  const escapedMessage = message.replace(/"/g, '\\"').replace(/\n/g, '; ');
84
-
85
- // Execute devlog.sh with the same parameters as the bash version (async, non-blocking)
86
- const command = `devlog.sh "${escapedMessage}" "?" "${project}" "${client}" "${jiraTicket}" "${subClient}"`;
87
-
88
- logger.debug(`Executing devlog.sh command: ${command}`);
94
+
95
+ let command;
96
+ if (process.platform === 'win32') {
97
+ command = `powershell.exe -ExecutionPolicy Bypass -NonInteractive -Command "devlog -s '${escapedMessage}' '?' '${project}' '${client}' '${jiraTicket}' '${subClient}'"`;
98
+ } else {
99
+ command = `devlog.sh "${escapedMessage}" "?" "${project}" "${client}" "${jiraTicket}" "${subClient}"`;
100
+ }
101
+
102
+ logger.debug(`Executing devlog command: ${command}`);
89
103
 
90
104
  // Execute asynchronously - don't block the main workflow (fire-and-forget)
91
105
  exec(command, { encoding: 'utf8' }, (error, stdout, stderr) => {
@@ -99,7 +113,7 @@ export function logThis(message, options = {}) {
99
113
 
100
114
  // Only log success confirmation, not the verbose stdout output
101
115
 
102
- logger.debug(`Logged to devlog.sh: ${escapedMessage}`) ;
116
+ logger.debug(`Logged to devlog: ${escapedMessage}`);
103
117
  });
104
118
 
105
119
  // Return immediately (don't wait for devlog.sh to complete)
@@ -70,261 +70,6 @@ async function checkRemoteExists(remoteName) {
70
70
  }
71
71
  }
72
72
 
73
- /**
74
- * Update changelog with new version and commit messages
75
- * @param {string} newVersion - New version number
76
- * @param {string} currentVersion - Current version (to get commits since this tag)
77
- */
78
- async function updateChangelog(newVersion, currentVersion) {
79
- const changelogPath = process.env.MAIASS_CHANGELOG_PATH || 'CHANGELOG.md';
80
- const changelogName = process.env.MAIASS_CHANGELOG_NAME || 'CHANGELOG.md';
81
- const fs = await import('fs/promises');
82
-
83
- try {
84
- // Determine the latest version from the top of the changelog file (not git tags)
85
- let lastVersion = null;
86
- let changelogLines = [];
87
- try {
88
- await fs.access(changelogPath);
89
- const changelogContent = await fs.readFile(changelogPath, 'utf8');
90
- changelogLines = changelogContent.split('\n');
91
- for (const line of changelogLines) {
92
- const match = line.match(/^##\s+(\d+\.\d+\.\d+)/);
93
- if (match) {
94
- lastVersion = match[1];
95
- break;
96
- }
97
- }
98
- } catch {
99
- // changelog does not exist yet
100
- }
101
- if (!lastVersion) {
102
- // Fallback: try package.json or all commits
103
- try {
104
- const pkg = JSON.parse(await fs.readFile('package.json', 'utf8'));
105
- if (pkg.version) lastVersion = pkg.version;
106
- } catch {}
107
- }
108
- log.blue(SYMBOLS.GEAR, lastVersion ? `Getting commits since last changelog version: ${lastVersion}` : 'No previous version found, getting all commits');
109
- // Get commit messages since last version for main changelog
110
- const gitLogCommand = lastVersion
111
- ? `git log ${lastVersion}..HEAD --pretty=format:"%B"`
112
- : `git log --pretty=format:"%B"`;
113
- const mainLogResult = executeGitCommand(gitLogCommand, true);
114
- let hasCommits = mainLogResult.success && mainLogResult.output.trim();
115
-
116
- if (!hasCommits) {
117
- log.warning(SYMBOLS.INFO, 'No commits found since last release');
118
- // Still proceed to update changelog with version entry
119
- }
120
-
121
- // Get commit messages for internal changelog (with author info, patch, and date)
122
- // Use git log to get patch version, date, author, and subject for each commit
123
- const internalGitLogCommand = lastVersion
124
- ? `git log ${lastVersion}..HEAD --pretty=format:"%h|%ad|%an|%s" --date=short`
125
- : `git log --pretty=format:"%h|%ad|%an|%s" --date=short`;
126
- const internalLogResult = executeGitCommand(internalGitLogCommand, true);
127
- if (!internalLogResult.success) {
128
- log.warning(SYMBOLS.WARNING, 'Failed to get commits for internal changelog');
129
- }
130
-
131
- let commitMessages = [];
132
- let internalCommitMessages = [];
133
-
134
- // Only process commits if we have any
135
- if (hasCommits) {
136
- // Group commit messages by date (using git log with date)
137
- const gitLogByDateCommand = lastVersion
138
- ? `git log ${lastVersion}..HEAD --pretty=format:"%ad|%B" --date=short`
139
- : `git log --pretty=format:"%ad|%B" --date=short`;
140
- const logByDateResult = executeGitCommand(gitLogByDateCommand, true);
141
- let groupedByDate = {};
142
- if (logByDateResult.success && logByDateResult.output.trim()) {
143
- logByDateResult.output.split('\n\n').forEach(entry => {
144
- // Each entry: 'YYYY-MM-DD|commit message'
145
- const [date, ...msgParts] = entry.split('|');
146
- const msg = msgParts.join('|').trim();
147
- if (!msg) return;
148
- if (/^merge branch/i.test(msg)) return; // Exclude merge branch commits
149
- // Remove JIRA ticket numbers
150
- const cleanedMsg = msg.replace(/\b[A-Z]+-[0-9]+\b:? ?/g, '');
151
- if (!groupedByDate[date]) groupedByDate[date] = [];
152
- groupedByDate[date].push(cleanedMsg);
153
- });
154
- }
155
- // Now format grouped commits for changelog
156
- commitMessages = [];
157
- Object.entries(groupedByDate).forEach(([date, msgs]) => {
158
- // Format date as '23 July 2025'
159
- const formattedDate = new Date(date).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' });
160
- commitMessages.push(`### ${formattedDate}`);
161
- msgs.forEach(m => {
162
- // Format as bullet points (with sub-bullets if multi-line)
163
- const lines = m.split('\n').filter(line => line.trim());
164
- if (lines.length === 1) {
165
- commitMessages.push(`- ${lines[0]}`);
166
- } else if (lines.length > 1) {
167
- commitMessages.push(`- ${lines[0]}`);
168
- lines.slice(1).forEach(l => commitMessages.push(`\t- ${l}`));
169
- }
170
- });
171
- commitMessages.push(''); // Blank line after each date group
172
- });
173
- }
174
-
175
- // Process internal changelog commits: group by patch version and date, include author and subject
176
- if (hasCommits && internalLogResult.success) {
177
- // Get all patch versions and their commit hashes between lastVersion and HEAD
178
- const patchLogCommand = lastVersion
179
- ? `git log ${lastVersion}..HEAD --pretty=format:"%h|%ad|%an|%s" --date=short`
180
- : `git log --pretty=format:"%h|%ad|%an|%s" --date=short`;
181
- const patchLogResult = executeGitCommand(patchLogCommand, true);
182
- let patchGroups = {};
183
- if (patchLogResult.success && patchLogResult.output.trim()) {
184
- patchLogResult.output.split('\n').forEach(line => {
185
- if (!line.trim()) return;
186
- const parts = line.split('|');
187
- if (parts.length < 4) return;
188
- const [hash, date, author, subject] = parts;
189
- if (/^merge branch/i.test(subject) || /^(ncl|bump|bumping|fixing merge conflicts|version bump|merged)/i.test(subject)) return;
190
- // Try to extract patch version from subject if present, else group all under newVersion
191
- // (This logic can be improved if commit messages always contain version, else just group all under newVersion)
192
- let patch = newVersion;
193
- // Optionally, extract patch version from subject, but fallback to newVersion
194
- // Format commit entry
195
- const commitEntry = `(${author}) ${subject}\n\t- hash: ${hash} date: ${date}`;
196
- if (!patchGroups[patch]) patchGroups[patch] = { date, commits: [] };
197
- patchGroups[patch].commits.push(commitEntry);
198
- });
199
- }
200
- // Flatten patchGroups into internalCommitMessages in patch version order (most recent first)
201
- internalCommitMessages = [];
202
- Object.entries(patchGroups).forEach(([patch, group]) => {
203
- internalCommitMessages.push(`## ${patch}`);
204
- internalCommitMessages.push(group.date);
205
- internalCommitMessages.push('');
206
- group.commits.forEach(c => internalCommitMessages.push(c));
207
- internalCommitMessages.push('');
208
- });
209
- }
210
-
211
- // Format commit messages with proper bullets (matching bash script logic)
212
- let formattedCommits = '';
213
- let shouldUpdateChangelog = true;
214
-
215
- if (commitMessages.length === 0) {
216
- log.warning(SYMBOLS.INFO, 'No relevant commits found for changelog - updating version header only');
217
- // Don't add any commit entries when no meaningful commits exist
218
- formattedCommits = '';
219
- shouldUpdateChangelog = true;
220
- } else {
221
- formattedCommits = commitMessages.map(commit => {
222
- const lines = commit.split('\n').filter(line => line.trim());
223
- if (lines.length === 0) return '';
224
-
225
- const firstLine = lines[0].trim();
226
-
227
- // Check if it's a single-line commit or multi-line commit
228
- if (lines.length === 1) {
229
- // Single line commit - just add main bullet if needed
230
- return firstLine.startsWith('- ') ? firstLine : `- ${firstLine}`;
231
- }
232
-
233
- // Multi-line commit - handle differently based on format
234
- if (firstLine.startsWith('- ')) {
235
- // AI commit with bullets - treat first line as main subject, rest as sub-bullets
236
- const subject = firstLine;
237
- const bodyLines = lines.slice(1);
238
-
239
- const formattedBody = bodyLines.map(line => {
240
- const trimmed = line.trim();
241
- if (!trimmed) return '';
242
-
243
- // If line already starts with bullet, just indent it
244
- if (trimmed.startsWith('- ')) {
245
- return `\t${trimmed}`;
246
- } else {
247
- // Non-bullet line, just indent without adding bullet
248
- return `\t${trimmed}`;
249
- }
250
- }).filter(line => line);
251
-
252
- return [subject, ...formattedBody].join('\n');
253
- } else {
254
- // Manual commit - add bullets to all lines
255
- return lines.map((line, index) => {
256
- const trimmed = line.trim();
257
- if (index === 0) {
258
- return `- ${trimmed}`; // Main bullet for subject
259
- } else if (trimmed) {
260
- // Check if line already has bullet to avoid double bullets
261
- if (trimmed.startsWith('- ')) {
262
- return `\t${trimmed}`; // Just indent, don't add extra bullet
263
- } else {
264
- return `\t- ${trimmed}`; // Add indented bullet
265
- }
266
- }
267
- return '';
268
- }).filter(line => line).join('\n');
269
- }
270
- }).filter(commit => commit).join('\n');
271
- }
272
-
273
- // Create date string (matching bash script format)
274
- const date = new Date().toLocaleDateString('en-GB', {
275
- day: 'numeric',
276
- month: 'long',
277
- year: 'numeric'
278
- });
279
-
280
- // Create date string with weekday for internal changelog (format: "24 November 2025 (Monday)")
281
- const now = new Date();
282
- const weekday = now.toLocaleDateString('en-GB', { weekday: 'long' });
283
- const dateWithWeekday = `${date} (${weekday.charAt(0).toUpperCase() + weekday.slice(1)})`;
284
-
285
- // Handle existing changelog or create new one
286
- let updatedContent;
287
- try {
288
- await fs.access(changelogPath);
289
- const currentContent = await fs.readFile(changelogPath, 'utf8');
290
-
291
- // Split current content into lines for slicing
292
- const lines = currentContent.split('\n');
293
- // Find the index of the second version heading (previous version)
294
- let restStartIdx = -1;
295
- for (let i = 1; i < lines.length; ++i) {
296
- if (/^##\s+\d+\.\d+\.\d+/.test(lines[i])) {
297
- restStartIdx = i;
298
- break;
299
- }
300
- }
301
- const restOfContent = restStartIdx !== -1 ? lines.slice(restStartIdx).join('\n') : '';
302
- // Always prepend new entry, never duplicate previous section
303
- if (formattedCommits.trim()) {
304
- updatedContent = `## ${newVersion}\n${date}\n\n${formattedCommits}\n\n${restOfContent}`;
305
- } else {
306
- updatedContent = `## ${newVersion}\n${date}\n\n${restOfContent}`;
307
- }
308
- } catch (error) {
309
- // Changelog doesn't exist - create new one
310
- if (formattedCommits.trim()) {
311
- updatedContent = `## ${newVersion}\n${date}\n\n${formattedCommits}\n`;
312
- } else {
313
- updatedContent = `## ${newVersion}\n${date}\n`;
314
- }
315
- }
316
-
317
- // Replace all newlines with OS-specific line endings for cross-platform compatibility
318
- const os = await import('os');
319
- const finalContent = updatedContent.replace(/\n/g, os.EOL);
320
- await fs.writeFile(changelogPath, finalContent, 'utf8');
321
- log.success(SYMBOLS.CHECKMARK, `Updated ${changelogName}`);
322
-
323
- } catch (error) {
324
- log.warning(SYMBOLS.WARNING, `Failed to update changelog: ${error.message}`);
325
- }
326
- }
327
-
328
73
  /**
329
74
  * Execute git command safely
330
75
  * @param {string} command - Git command to execute
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "maiass",
3
3
  "type": "module",
4
- "version": "5.9.15",
4
+ "version": "5.9.18",
5
5
  "description": "MAIASS - Modular AI-Augmented Semantic Scribe - Intelligent Git workflow automation",
6
6
  "main": "maiass.mjs",
7
7
  "bin": {