stackmem 1.0.4 → 1.0.5

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 (3) hide show
  1. package/dist/cli.js +69 -12
  2. package/package.json +1 -1
  3. package/src/cli.ts +69 -12
package/dist/cli.js CHANGED
@@ -483,21 +483,60 @@ function isSchemaFile(filePath) {
483
483
  );
484
484
  }
485
485
 
486
+ const LOW_QUALITY_PREFIXES = [
487
+ "wip", "fix", "update", "misc", "temp", "test", "patch", "minor",
488
+ "tweak", "change", "stuff", "done", "commit", "save", "ok", "m", "x",
489
+ ];
490
+
491
+ // Filters out lazy/placeholder commit messages ("wip", "fix", "m", "...")
492
+ // so only messages worth remembering become decisions. Checked against the
493
+ // first word, not a raw prefix match, so a real message like "Migrate auth
494
+ // to JWT" isn't rejected just for starting with the letter "m".
495
+ function isQualityCommitMessage(message) {
496
+ const trimmed = message.trim();
497
+ if (trimmed.length <= 20) return false;
498
+ if (!trimmed.includes(" ")) return false;
499
+
500
+ const stripped = trimmed.replace(/\\s/g, "");
501
+ if (stripped.length > 0 && new Set(stripped).size === 1) return false;
502
+
503
+ const lowerTrimmed = trimmed.toLowerCase();
504
+ if (LOW_QUALITY_PREFIXES.includes(lowerTrimmed)) return false;
505
+
506
+ const firstWord = (lowerTrimmed.match(/^[a-z0-9]+/) || [""])[0];
507
+ if (LOW_QUALITY_PREFIXES.includes(firstWord)) return false;
508
+
509
+ return true;
510
+ }
511
+
486
512
  const savedItems = [];
487
513
 
488
514
  const commitMessage = git(["log", "-1", "--pretty=%B"]);
489
- cmSave("decision", commitMessage);
490
- savedItems.push(\`decision: \${commitMessage}\`);
491
515
 
492
- const changedFiles = git(["diff-tree", "--no-commit-id", "-r", "--name-only", "HEAD"])
516
+ const isFirstCommit = (() => {
517
+ try {
518
+ execFileSync("git", ["rev-parse", "HEAD~1"], { stdio: "pipe" });
519
+ return false;
520
+ } catch {
521
+ return true;
522
+ }
523
+ })();
524
+
525
+ const changedFiles = git(
526
+ isFirstCommit
527
+ ? ["diff-tree", "--root", "--no-commit-id", "-r", "--name-only", "HEAD"]
528
+ : ["diff-tree", "--no-commit-id", "-r", "--name-only", "HEAD"]
529
+ )
493
530
  .split("\\n")
494
531
  .filter(Boolean);
495
532
 
496
533
  let diff = "";
497
- try {
498
- diff = execFileSync("git", ["diff", "HEAD~1", "HEAD"], { encoding: "utf-8" });
499
- } catch {
500
- diff = "";
534
+ if (!isFirstCommit) {
535
+ try {
536
+ diff = execFileSync("git", ["diff", "HEAD~1", "HEAD"], { encoding: "utf-8" });
537
+ } catch {
538
+ diff = "";
539
+ }
501
540
  }
502
541
 
503
542
  const lines = diff.split("\\n");
@@ -505,7 +544,7 @@ let currentFile = null;
505
544
  let isNewFile = false;
506
545
  let inDependenciesSection = false;
507
546
 
508
- for (const line of lines) {
547
+ for (const line of isFirstCommit ? [] : lines) {
509
548
  if (line.startsWith("diff --git")) {
510
549
  isNewFile = false;
511
550
  inDependenciesSection = false;
@@ -555,10 +594,20 @@ for (const line of lines) {
555
594
  if (line.startsWith("-") && !line.startsWith("---")) {
556
595
  const codeLine = line.slice(1).trim();
557
596
 
558
- if (codeLine.startsWith("import") || codeLine.startsWith("require")) {
559
- const pathMatch = codeLine.match(/['"]([^'"]+)['"]/);
560
- const importPath = pathMatch ? pathMatch[1] : codeLine;
561
- const content = \`removed dependency: \${importPath}\`;
597
+ const importMatch = line.match(
598
+ /^-\\s*(import\\s+.*\\s+from\\s+['"]([^'"]+)['"]|import\\s+['"]([^'"]+)['"])/
599
+ );
600
+ const requireMatch = line.match(
601
+ /^-\\s*(?:const|let|var)\\s+\\S+\\s*=\\s*require\\(['"]([^'"]+)['"]\\)/
602
+ );
603
+ const removedModulePath = importMatch
604
+ ? importMatch[2] || importMatch[3]
605
+ : requireMatch
606
+ ? requireMatch[1]
607
+ : null;
608
+
609
+ if (removedModulePath) {
610
+ const content = \`removed dependency: \${removedModulePath}\`;
562
611
  cmSave("rejection", content);
563
612
  savedItems.push(\`rejection: \${content}\`);
564
613
  }
@@ -614,6 +663,14 @@ for (const line of lines) {
614
663
  }
615
664
  }
616
665
 
666
+ // Saved last, after every diff-detected memory, and only if it clears the
667
+ // quality filter — diff analysis always runs regardless of commit message.
668
+ if (isQualityCommitMessage(commitMessage)) {
669
+ const trimmedMessage = commitMessage.trim();
670
+ cmSave("decision", trimmedMessage);
671
+ savedItems.push(\`decision: \${trimmedMessage}\`);
672
+ }
673
+
617
674
  console.log(\`coding-memory post-commit summary (\${changedFiles.length} file(s) changed):\`);
618
675
  for (const item of savedItems) {
619
676
  console.log(\` - \${item}\`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackmem",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -573,21 +573,60 @@ function isSchemaFile(filePath) {
573
573
  );
574
574
  }
575
575
 
576
+ const LOW_QUALITY_PREFIXES = [
577
+ "wip", "fix", "update", "misc", "temp", "test", "patch", "minor",
578
+ "tweak", "change", "stuff", "done", "commit", "save", "ok", "m", "x",
579
+ ];
580
+
581
+ // Filters out lazy/placeholder commit messages ("wip", "fix", "m", "...")
582
+ // so only messages worth remembering become decisions. Checked against the
583
+ // first word, not a raw prefix match, so a real message like "Migrate auth
584
+ // to JWT" isn't rejected just for starting with the letter "m".
585
+ function isQualityCommitMessage(message) {
586
+ const trimmed = message.trim();
587
+ if (trimmed.length <= 20) return false;
588
+ if (!trimmed.includes(" ")) return false;
589
+
590
+ const stripped = trimmed.replace(/\\s/g, "");
591
+ if (stripped.length > 0 && new Set(stripped).size === 1) return false;
592
+
593
+ const lowerTrimmed = trimmed.toLowerCase();
594
+ if (LOW_QUALITY_PREFIXES.includes(lowerTrimmed)) return false;
595
+
596
+ const firstWord = (lowerTrimmed.match(/^[a-z0-9]+/) || [""])[0];
597
+ if (LOW_QUALITY_PREFIXES.includes(firstWord)) return false;
598
+
599
+ return true;
600
+ }
601
+
576
602
  const savedItems = [];
577
603
 
578
604
  const commitMessage = git(["log", "-1", "--pretty=%B"]);
579
- cmSave("decision", commitMessage);
580
- savedItems.push(\`decision: \${commitMessage}\`);
581
605
 
582
- const changedFiles = git(["diff-tree", "--no-commit-id", "-r", "--name-only", "HEAD"])
606
+ const isFirstCommit = (() => {
607
+ try {
608
+ execFileSync("git", ["rev-parse", "HEAD~1"], { stdio: "pipe" });
609
+ return false;
610
+ } catch {
611
+ return true;
612
+ }
613
+ })();
614
+
615
+ const changedFiles = git(
616
+ isFirstCommit
617
+ ? ["diff-tree", "--root", "--no-commit-id", "-r", "--name-only", "HEAD"]
618
+ : ["diff-tree", "--no-commit-id", "-r", "--name-only", "HEAD"]
619
+ )
583
620
  .split("\\n")
584
621
  .filter(Boolean);
585
622
 
586
623
  let diff = "";
587
- try {
588
- diff = execFileSync("git", ["diff", "HEAD~1", "HEAD"], { encoding: "utf-8" });
589
- } catch {
590
- diff = "";
624
+ if (!isFirstCommit) {
625
+ try {
626
+ diff = execFileSync("git", ["diff", "HEAD~1", "HEAD"], { encoding: "utf-8" });
627
+ } catch {
628
+ diff = "";
629
+ }
591
630
  }
592
631
 
593
632
  const lines = diff.split("\\n");
@@ -595,7 +634,7 @@ let currentFile = null;
595
634
  let isNewFile = false;
596
635
  let inDependenciesSection = false;
597
636
 
598
- for (const line of lines) {
637
+ for (const line of isFirstCommit ? [] : lines) {
599
638
  if (line.startsWith("diff --git")) {
600
639
  isNewFile = false;
601
640
  inDependenciesSection = false;
@@ -645,10 +684,20 @@ for (const line of lines) {
645
684
  if (line.startsWith("-") && !line.startsWith("---")) {
646
685
  const codeLine = line.slice(1).trim();
647
686
 
648
- if (codeLine.startsWith("import") || codeLine.startsWith("require")) {
649
- const pathMatch = codeLine.match(/['"]([^'"]+)['"]/);
650
- const importPath = pathMatch ? pathMatch[1] : codeLine;
651
- const content = \`removed dependency: \${importPath}\`;
687
+ const importMatch = line.match(
688
+ /^-\\s*(import\\s+.*\\s+from\\s+['"]([^'"]+)['"]|import\\s+['"]([^'"]+)['"])/
689
+ );
690
+ const requireMatch = line.match(
691
+ /^-\\s*(?:const|let|var)\\s+\\S+\\s*=\\s*require\\(['"]([^'"]+)['"]\\)/
692
+ );
693
+ const removedModulePath = importMatch
694
+ ? importMatch[2] || importMatch[3]
695
+ : requireMatch
696
+ ? requireMatch[1]
697
+ : null;
698
+
699
+ if (removedModulePath) {
700
+ const content = \`removed dependency: \${removedModulePath}\`;
652
701
  cmSave("rejection", content);
653
702
  savedItems.push(\`rejection: \${content}\`);
654
703
  }
@@ -704,6 +753,14 @@ for (const line of lines) {
704
753
  }
705
754
  }
706
755
 
756
+ // Saved last, after every diff-detected memory, and only if it clears the
757
+ // quality filter — diff analysis always runs regardless of commit message.
758
+ if (isQualityCommitMessage(commitMessage)) {
759
+ const trimmedMessage = commitMessage.trim();
760
+ cmSave("decision", trimmedMessage);
761
+ savedItems.push(\`decision: \${trimmedMessage}\`);
762
+ }
763
+
707
764
  console.log(\`coding-memory post-commit summary (\${changedFiles.length} file(s) changed):\`);
708
765
  for (const item of savedItems) {
709
766
  console.log(\` - \${item}\`);