maiass 5.15.6 → 5.15.8

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.
Files changed (2) hide show
  1. package/lib/commit.js +98 -10
  2. package/package.json +1 -1
package/lib/commit.js CHANGED
@@ -329,6 +329,77 @@ async function createAnonymousSubscriptionIfNeeded() {
329
329
  }
330
330
  }
331
331
 
332
+ /**
333
+ * Normalize a bullet-style AI commit suggestion into a deterministic on-disk
334
+ * format. Pure function — no side effects. The normalizer is deliberately
335
+ * conservative and content-preserving: it reformats whitespace/markers but
336
+ * never fabricates a subject and never drops body content.
337
+ *
338
+ * Contract:
339
+ * - Input is coerced via String(text); non-string input cannot throw.
340
+ * - CRLF (`\r\n`) and CR line endings are tolerated (trailing \r stripped).
341
+ * - Early return (input returned trimmed, unchanged) when the input has zero
342
+ * or one non-empty line — a bare title with no bullets, or empty input.
343
+ * - No-title passthrough: if the first non-empty line already starts with a
344
+ * bullet marker (`-` or `*`), the model produced bullets with no summary
345
+ * line. A deterministic normalizer must not invent a subject, so the input
346
+ * is returned trimmed and otherwise unchanged.
347
+ * - Otherwise the first non-empty line becomes the title (trailing whitespace
348
+ * stripped; no marker stripping — it is not a marker line by definition).
349
+ * - Each subsequent non-empty line that starts with a bullet marker becomes
350
+ * " - <content>" (exactly two-space indent, single dash; the original
351
+ * marker and leading indentation are stripped).
352
+ * - Each subsequent non-empty line that does NOT start with a marker is a
353
+ * soft-wrapped continuation of the previous line: it is appended to the
354
+ * previous bullet (joined with a single space). If no bullet exists yet,
355
+ * it is appended to the title line instead.
356
+ * - Blank lines between the title and bullets (and trailing blanks) are
357
+ * dropped.
358
+ * @param {string} text - raw AI suggestion (already trimmed/quote-stripped)
359
+ * @returns {string} normalized bullet-style message
360
+ */
361
+ function normalizeBulletMessage(text) {
362
+ const source = String(text);
363
+ // Normalize line endings so \r\n / \r inputs behave identically to \n.
364
+ const lines = source.split(/\r\n|\r|\n/);
365
+
366
+ // Guard: zero or one non-empty line (empty input or a bare title) is left
367
+ // untouched apart from outer trimming.
368
+ if (lines.filter(l => l.trim() !== '').length <= 1) {
369
+ return source.trim();
370
+ }
371
+
372
+ // No-title passthrough: the model emitted bullets with no summary line. Do
373
+ // not promote a bullet to a title — return the input trimmed, unchanged.
374
+ const firstNonEmpty = lines.find(l => l.trim() !== '');
375
+ if (/^\s*[-*]\s+/.test(firstNonEmpty)) {
376
+ return source.trim();
377
+ }
378
+
379
+ const isMarker = (line) => /^\s*[-*]\s+/.test(line);
380
+ const stripMarker = (line) => line.replace(/^\s*[-*]\s+/, '').trim();
381
+
382
+ const out = [];
383
+ let titleSet = false;
384
+ for (const raw of lines) {
385
+ const line = raw.replace(/\s+$/, '');
386
+ if (line.trim() === '') continue;
387
+ if (!titleSet) {
388
+ // First non-empty line is the title (guaranteed non-marker above).
389
+ out.push(line.trim());
390
+ titleSet = true;
391
+ continue;
392
+ }
393
+ if (isMarker(line)) {
394
+ out.push(' - ' + stripMarker(line));
395
+ } else {
396
+ // Soft-wrapped continuation: append to the previous line.
397
+ out[out.length - 1] = out[out.length - 1] + ' ' + line.trim();
398
+ }
399
+ }
400
+ return out.join('\n');
401
+ }
402
+
332
403
  /**
333
404
  * Get AI commit message suggestion
334
405
  * @param {Object} gitInfo - Git information object
@@ -466,20 +537,31 @@ async function getAICommitSuggestion(gitInfo) {
466
537
  }
467
538
 
468
539
  // Create AI prompt based on commit message style
540
+ //
541
+ // ⚠️ MAI-108 PROMPT CHANGE / ROLLBACK POINT: the bullet prompt below was
542
+ // rewritten (placeholders + anti-leak/anti-invent guard) because small/local
543
+ // models echoed the old few-shot example bullets verbatim. The PREVIOUS,
544
+ // long-standing prompt is production-proven against the 1min.ai chat path.
545
+ // If hosted-model commit-message QUALITY regresses, revert this block to its
546
+ // pre-change form: see git history of this file before commit dca4519 (PR #28).
469
547
  let prompt;
470
548
  if (commitMessageStyle === 'bullet') {
471
- prompt = `Analyze the following git diff and create a commit message with bullet points. Format as:
472
- Brief summary title
473
- - feat: add user authentication
474
- - fix(api): resolve syntax error
475
- - docs: update README
476
-
477
- Use past tense verbs. No blank line between title and bullets. Keep concise. Do not wrap the response in quotes.
549
+ prompt = `Write a git commit message for the diff below.
550
+ Format exactly like this template:
551
+ <one-line summary>
552
+ - <type>: <description>
553
+ - <type>: <description>
554
+ Rules:
555
+ - First line: a brief past-tense summary with no leading dash.
556
+ - Then one or more bullet lines, each indented by exactly two spaces, starting with "- ", then a conventional-commit type (feat, fix, docs, refactor, chore) and a colon.
557
+ - No blank line between the summary line and the bullets.
558
+ - Describe ONLY the actual changes in the diff. Do not output the literal words type or description or any angle brackets, and do not invent changes that are not in the diff.
559
+ - Do not wrap the response in quotes.
478
560
 
479
561
  Git diff:
480
562
  ${gitDiff}`;
481
563
  } else {
482
- prompt = `Analyze the following git diff and create a concise, descriptive commit message. Use conventional commit format when appropriate (feat:, fix:, docs:, etc.). Keep it under 72 characters for the first line.
564
+ prompt = `Analyze the following git diff and create a concise, descriptive commit message. Use conventional commit format when appropriate (feat:, fix:, docs:, etc.). Keep it under 72 characters for the first line. Describe only the actual changes in the diff; do not invent changes.
483
565
 
484
566
  Git diff:
485
567
  ${gitDiff}`;
@@ -577,11 +659,16 @@ ${gitDiff}`;
577
659
  }
578
660
 
579
661
  // Clean up any quotes that might wrap the entire response
580
- if ((suggestion.startsWith("'") && suggestion.endsWith("'")) ||
662
+ if ((suggestion.startsWith("'") && suggestion.endsWith("'")) ||
581
663
  (suggestion.startsWith('"') && suggestion.endsWith('"'))) {
582
664
  suggestion = suggestion.slice(1, -1).trim();
583
665
  }
584
-
666
+
667
+ // Deterministically normalize bullet format so it's model-independent.
668
+ if (commitMessageStyle === 'bullet') {
669
+ suggestion = normalizeBulletMessage(suggestion);
670
+ }
671
+
585
672
  // Extract credit information from billing data
586
673
  let creditsUsed, creditsRemaining;
587
674
  if (data.billing) {
@@ -1157,6 +1244,7 @@ export {
1157
1244
  getCommitMessage,
1158
1245
  handleStagedCommit,
1159
1246
  getAICommitSuggestion,
1247
+ normalizeBulletMessage,
1160
1248
  executeGitCommand,
1161
1249
  remoteExists
1162
1250
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "maiass",
3
3
  "type": "module",
4
- "version": "5.15.6",
4
+ "version": "5.15.8",
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": {