mustard-claude 3.0.16 → 3.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustard-claude",
3
- "version": "3.0.16",
3
+ "version": "3.0.18",
4
4
  "description": "Framework-agnostic CLI for Claude Code project setup",
5
5
  "type": "module",
6
6
  "bin": {
@@ -111,7 +111,7 @@ process.stdin.on('end', () => {
111
111
  hookSpecificOutput: {
112
112
  hookEventName: 'PreToolUse',
113
113
  permissionDecision: 'allow',
114
- updatedInput: { command: rewritten }
114
+ updatedInput: { command: `${rewritten} 2>/dev/null` }
115
115
  }
116
116
  }));
117
117
  process.exit(0);
@@ -28,6 +28,18 @@ const SECRET_PATTERNS = [
28
28
  { name: 'Generic Secret Assignment', re: /(?:secret|password|passwd|api_key|apikey|token|auth_token)\s*[:=]\s*["'][^"']{8,}["']/gi },
29
29
  ];
30
30
 
31
+ // File name patterns that commonly trigger false positives on generic patterns
32
+ // (seeds with hashed passwords, error code constants, test fixtures, etc.)
33
+ const FP_FILE_PATTERNS = [
34
+ /[Ss]eeder/, // DatabaseSeeder.cs, UserSeeder.cs
35
+ /[Ss]eed[s]?\./, // Seeds.cs, seed.ts
36
+ /ErrorCode/i, // ApiExceptionErrorCodes.cs, ErrorCodes.ts
37
+ /Exception.*Code/i, // ExceptionCodes, ExceptionErrorCodes
38
+ /\.d\.ts$/, // Type declaration files
39
+ /\.test\./, // Test files
40
+ /\.spec\./, // Spec files
41
+ ];
42
+
31
43
  // ── Ignore lists ────────────────────────────────────────────────────
32
44
  const IGNORE_DIRS = new Set([
33
45
  'node_modules', '.git', 'dist', 'bin', 'obj', '.next', 'vendor',
@@ -73,11 +85,17 @@ function scanFile(filePath, results) {
73
85
  let content;
74
86
  try { content = fs.readFileSync(filePath, 'utf8'); } catch { return; }
75
87
 
88
+ // Check if file matches false-positive suppression patterns
89
+ const baseName = path.basename(filePath);
90
+ const isFpFile = FP_FILE_PATTERNS.some(re => re.test(baseName));
91
+
76
92
  // Secret pattern matching
77
93
  for (const { name, re } of SECRET_PATTERNS) {
78
94
  re.lastIndex = 0;
79
95
  const match = re.exec(content);
80
96
  if (match) {
97
+ // Skip generic patterns on known false-positive files
98
+ if (isFpFile && name === 'Generic Secret Assignment') continue;
81
99
  // Find line number
82
100
  const beforeMatch = content.substring(0, match.index);
83
101
  const line = (beforeMatch.match(/\n/g) || []).length + 1;
@@ -597,8 +597,9 @@ function getGitDirtyFiles(subprojectPath) {
597
597
  if (!trimmed) continue;
598
598
  // Format: "XY filename" or "XY filename -> newname"
599
599
  const filePath = trimmed.substring(3).split(" -> ").pop().trim();
600
+ const fileName = path.basename(filePath);
600
601
  const ext = path.extname(filePath).toLowerCase();
601
- if (!sourceExts.has(ext)) continue;
602
+ if (!sourceExts.has(ext) && !MANIFEST_FILES.has(fileName)) continue;
602
603
  // Skip ignored directories
603
604
  const parts = filePath.split("/");
604
605
  if (parts.some((p) => ignoreNames.has(p) || p === "migrations")) continue;
@@ -627,6 +628,26 @@ const SOURCE_IGNORE_PATTERNS = [
627
628
 
628
629
  const SOURCE_EXTENSIONS = new Set([".cs", ".ts", ".tsx", ".js", ".jsx", ".dart"]);
629
630
 
631
+ /**
632
+ * Manifest files that affect project behavior without changing source code.
633
+ * Changes to these files (dependency upgrades, SDK bumps) should invalidate
634
+ * the source hash even when no source file changed.
635
+ */
636
+ const MANIFEST_FILES = new Set([
637
+ // Flutter/Dart
638
+ "pubspec.yaml", "pubspec.lock",
639
+ // Node.js
640
+ "package.json", "pnpm-lock.yaml", "package-lock.json", "yarn.lock",
641
+ // .NET
642
+ "Directory.Packages.props", "Directory.Build.props", "nuget.config",
643
+ // Go
644
+ "go.mod", "go.sum",
645
+ // Rust
646
+ "Cargo.toml", "Cargo.lock",
647
+ // Python
648
+ "pyproject.toml", "requirements.txt", "poetry.lock",
649
+ ]);
650
+
630
651
  /**
631
652
  * Recursively collect source files from a directory.
632
653
  * Respects ignore patterns and extension filters.
@@ -660,7 +681,7 @@ function collectSourceFiles(dir, maxDepth = 10, currentDepth = 0) {
660
681
  results.push(...collectSourceFiles(fullPath, maxDepth, currentDepth + 1));
661
682
  } else if (entry.isFile()) {
662
683
  const ext = path.extname(entry.name).toLowerCase();
663
- if (SOURCE_EXTENSIONS.has(ext)) {
684
+ if (SOURCE_EXTENSIONS.has(ext) || MANIFEST_FILES.has(entry.name)) {
664
685
  results.push(relFromRoot);
665
686
  }
666
687
  }
@@ -909,6 +930,17 @@ function main() {
909
930
  }
910
931
  const subprojectPaths = submodulePaths;
911
932
 
933
+ // Load previous cache for hash comparison (anti-stale detection)
934
+ let previousCache = null;
935
+ try {
936
+ const cachePath = path.join(ROOT, ".claude", ".detect-cache.json");
937
+ if (fs.existsSync(cachePath)) {
938
+ previousCache = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
939
+ }
940
+ } catch {
941
+ // no previous cache — treat all as changed
942
+ }
943
+
912
944
  // 2. Filter to only those with a CLAUDE.md, then build subproject entries
913
945
  const subprojects = [];
914
946
  const detectedAgentsSet = new Set();
@@ -944,6 +976,10 @@ function main() {
944
976
  // Detect git dirty state (uncommitted source file changes)
945
977
  const gitDirty = getGitDirtyFiles(normalizedPath);
946
978
 
979
+ // Compare current hash against previous cache to detect stale state
980
+ const prevHash = previousCache?.sourceHashes?.[name];
981
+ const hashChanged = !prevHash || prevHash !== sourceHashes[name];
982
+
947
983
  subprojects.push({
948
984
  name,
949
985
  path: normalizedPath,
@@ -951,6 +987,7 @@ function main() {
951
987
  agent,
952
988
  commands,
953
989
  stackSummary,
990
+ hashChanged,
954
991
  ...(gitDirty.dirty ? { gitDirty: true, gitDirtyCount: gitDirty.files.length } : {}),
955
992
  });
956
993
  }