pumuki-ast-hooks 5.3.20 → 5.3.22

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 (95) hide show
  1. package/docs/RELEASE_NOTES.md +35 -0
  2. package/docs/VIOLATIONS_RESOLUTION_PLAN.md +60 -59
  3. package/package.json +3 -3
  4. package/scripts/hooks-system/.AI_TOKEN_STATUS.txt +1 -1
  5. package/scripts/hooks-system/.audit-reports/notifications.log +935 -0
  6. package/scripts/hooks-system/.audit-reports/token-monitor.log +2809 -0
  7. package/scripts/hooks-system/application/CompositionRoot.js +38 -22
  8. package/scripts/hooks-system/application/services/AutonomousOrchestrator.js +0 -18
  9. package/scripts/hooks-system/application/services/ContextDetectionEngine.js +0 -58
  10. package/scripts/hooks-system/application/services/DynamicRulesLoader.js +2 -12
  11. package/scripts/hooks-system/application/services/GitFlowService.js +0 -80
  12. package/scripts/hooks-system/application/services/GitTreeState.js +2 -5
  13. package/scripts/hooks-system/application/services/HookSystemScheduler.js +0 -4
  14. package/scripts/hooks-system/application/services/IntelligentCommitAnalyzer.js +0 -25
  15. package/scripts/hooks-system/application/services/IntelligentGitTreeMonitor.js +0 -11
  16. package/scripts/hooks-system/application/services/PlatformAnalysisService.js +0 -19
  17. package/scripts/hooks-system/application/services/PlatformDetectionService.js +0 -19
  18. package/scripts/hooks-system/application/services/PlaybookRunner.js +1 -22
  19. package/scripts/hooks-system/application/services/PredictiveHookAdvisor.js +0 -19
  20. package/scripts/hooks-system/application/services/RealtimeGuardPlugin.js +0 -25
  21. package/scripts/hooks-system/application/services/RealtimeGuardService.js +71 -41
  22. package/scripts/hooks-system/application/services/SmartDirtyTreeAnalyzer.js +0 -11
  23. package/scripts/hooks-system/application/services/commit/CommitMessageGenerator.js +0 -11
  24. package/scripts/hooks-system/application/services/commit/FeatureDetector.js +0 -11
  25. package/scripts/hooks-system/application/services/evidence/EvidenceContextManager.js +0 -25
  26. package/scripts/hooks-system/application/services/guard/GuardAutoManagerService.js +31 -21
  27. package/scripts/hooks-system/application/services/guard/GuardConfig.js +15 -18
  28. package/scripts/hooks-system/application/services/guard/GuardEventLogger.js +0 -11
  29. package/scripts/hooks-system/application/services/guard/GuardHealthReminder.js +0 -26
  30. package/scripts/hooks-system/application/services/guard/GuardHeartbeatMonitor.js +6 -20
  31. package/scripts/hooks-system/application/services/guard/GuardLockManager.js +0 -11
  32. package/scripts/hooks-system/application/services/guard/GuardMonitorLoop.js +0 -25
  33. package/scripts/hooks-system/application/services/guard/GuardNotificationHandler.js +0 -11
  34. package/scripts/hooks-system/application/services/guard/GuardProcessManager.js +23 -11
  35. package/scripts/hooks-system/application/services/guard/GuardRecoveryService.js +0 -11
  36. package/scripts/hooks-system/application/services/installation/ConfigurationGeneratorService.js +0 -18
  37. package/scripts/hooks-system/application/services/installation/FileSystemInstallerService.js +0 -18
  38. package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +1 -19
  39. package/scripts/hooks-system/application/services/installation/HookInstaller.js +62 -24
  40. package/scripts/hooks-system/application/services/installation/IdeIntegrationService.js +0 -11
  41. package/scripts/hooks-system/application/services/installation/InstallService.js +1 -25
  42. package/scripts/hooks-system/application/services/installation/McpConfigurator.js +2 -19
  43. package/scripts/hooks-system/application/services/installation/PlatformDetectorService.js +0 -11
  44. package/scripts/hooks-system/application/services/installation/VSCodeTaskConfigurator.js +0 -11
  45. package/scripts/hooks-system/application/services/logging/AuditLogger.js +0 -8
  46. package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +13 -15
  47. package/scripts/hooks-system/application/services/monitoring/ActivityMonitor.js +0 -33
  48. package/scripts/hooks-system/application/services/monitoring/AstMonitor.js +0 -27
  49. package/scripts/hooks-system/application/services/monitoring/DevDocsMonitor.js +0 -26
  50. package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +0 -18
  51. package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +4 -28
  52. package/scripts/hooks-system/application/services/monitoring/GitTreeMonitor.js +0 -28
  53. package/scripts/hooks-system/application/services/monitoring/GitTreeMonitorService.js +0 -26
  54. package/scripts/hooks-system/application/services/monitoring/HealthCheckProviders.js +0 -4
  55. package/scripts/hooks-system/application/services/monitoring/HealthCheckService.js +0 -25
  56. package/scripts/hooks-system/application/services/monitoring/HeartbeatMonitorService.js +0 -26
  57. package/scripts/hooks-system/application/services/monitoring/TokenMonitor.js +0 -26
  58. package/scripts/hooks-system/application/services/notification/MacNotificationSender.js +0 -11
  59. package/scripts/hooks-system/application/services/notification/NotificationCenterService.js +0 -18
  60. package/scripts/hooks-system/application/services/notification/NotificationDispatcher.js +0 -11
  61. package/scripts/hooks-system/application/services/notification/components/NotificationCooldownManager.js +0 -18
  62. package/scripts/hooks-system/application/services/notification/components/NotificationDeduplicator.js +0 -18
  63. package/scripts/hooks-system/application/services/notification/components/NotificationQueue.js +0 -11
  64. package/scripts/hooks-system/application/services/notification/components/NotificationRetryExecutor.js +0 -20
  65. package/scripts/hooks-system/application/services/platform/PlatformHeuristics.js +0 -19
  66. package/scripts/hooks-system/application/services/recovery/AutoRecoveryManager.js +0 -19
  67. package/scripts/hooks-system/application/services/smart-commit/CommitMessageSuggester.js +0 -11
  68. package/scripts/hooks-system/application/services/smart-commit/FileContextGrouper.js +0 -19
  69. package/scripts/hooks-system/application/services/smart-commit/SmartCommitSummaryBuilder.js +0 -4
  70. package/scripts/hooks-system/application/services/token/CursorTokenService.js +0 -20
  71. package/scripts/hooks-system/application/services/token/TokenMetricsService.js +2 -12
  72. package/scripts/hooks-system/application/services/token/TokenMonitorService.js +0 -19
  73. package/scripts/hooks-system/application/services/token/TokenStatusReporter.js +0 -12
  74. package/scripts/hooks-system/bin/cli.js +15 -1
  75. package/scripts/hooks-system/bin/guard-env.sh +18 -38
  76. package/scripts/hooks-system/bin/guard-supervisor.js +5 -515
  77. package/scripts/hooks-system/bin/session-loader.sh +3 -262
  78. package/scripts/hooks-system/bin/start-guards.sh +21 -184
  79. package/scripts/hooks-system/bin/update-evidence.sh +10 -1161
  80. package/scripts/hooks-system/config/project.config.json +1 -1
  81. package/scripts/hooks-system/domain/events/index.js +31 -24
  82. package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidAnalysisOrchestrator.js +3 -2
  83. package/scripts/hooks-system/infrastructure/ast/ast-core.js +12 -20
  84. package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +8 -18
  85. package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendPatternDetector.js +2 -1
  86. package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +18 -14
  87. package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend.js +196 -196
  88. package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +3 -2
  89. package/scripts/hooks-system/infrastructure/hooks/skill-activation-prompt.js +3 -2
  90. package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +5 -4
  91. package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +88 -0
  92. package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +17 -16
  93. package/scripts/hooks-system/infrastructure/shell/orchestrators/audit-orchestrator.sh +92 -54
  94. package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +3 -2
  95. package/scripts/hooks-system/infrastructure/validators/enforce-english-literals.js +6 -8
@@ -57,8 +57,8 @@ function runFrontendIntelligence(project, findings, platform) {
57
57
  return 0.6745 * (x - med) / madValue;
58
58
  };
59
59
 
60
- const pOutlier = Number(process.env.AST_GODCLASS_P_OUTLIER || 90);
61
- const pExtreme = Number(process.env.AST_GODCLASS_P_EXTREME || 97);
60
+ const pOutlier = env.getNumber('AST_GODCLASS_P_OUTLIER', 90);
61
+ const pExtreme = env.getNumber('AST_GODCLASS_P_EXTREME', 97);
62
62
 
63
63
  const componentPattern = /^(export\s+)?(?:const|function)\s+([A-Z]\w+)\s*[=:].*(?:React\.FC|JSX\.Element|\(\)\s*=>|function)/gm;
64
64
  const metrics = [];
@@ -1129,10 +1129,10 @@ function runFrontendIntelligence(project, findings, platform) {
1129
1129
  }
1130
1130
  if (/page\.tsx$/.test(filePath)) {
1131
1131
  const candidate = filePath.replace(/\/app\//, '/e2e/').replace(/\.tsx$/, '.spec.ts');
1132
- try {
1133
- if (!fs.existsSync(candidate)) {
1134
- pushFinding("frontend.testing.missing_e2e", "info", sf, sf, "No E2E spec found for this page (heuristic)", findings);
1135
- }
1132
+ try {
1133
+ if (!fs.existsSync(candidate)) {
1134
+ pushFinding("frontend.testing.missing_e2e", "info", sf, sf, "No E2E spec found for this page (heuristic)", findings);
1135
+ }
1136
1136
  } catch (error) {
1137
1137
  if (process.env.DEBUG) {
1138
1138
  console.debug(`[frontend-ast] Failed to check E2E spec file: ${error.message}`);
@@ -1181,14 +1181,14 @@ function runFrontendIntelligence(project, findings, platform) {
1181
1181
  const extreme = totalComplexityZ >= godComponentBaseline.thresholds.extreme.totalComplexityZ || bodyLinesZ >= godComponentBaseline.thresholds.extreme.bodyLinesZ;
1182
1182
 
1183
1183
  if (extreme || (complexityOutlier && sizeOutlier)) {
1184
- pushFinding(
1185
- "frontend.solid.srp_god_component",
1186
- "critical",
1187
- sf,
1188
- sf,
1189
- `Component '${componentName}' has ${hookCount} hooks + ${functionCount} functions = ${totalComplexity} (z=${totalComplexityZ.toFixed(2)}), ${bodyLines} lines (z=${bodyLinesZ.toFixed(2)}) - split responsibilities (SRP)`,
1190
- findings
1191
- );
1184
+ pushFinding(
1185
+ "frontend.solid.srp_god_component",
1186
+ "critical",
1187
+ sf,
1188
+ sf,
1189
+ `Component '${componentName}' has ${hookCount} hooks + ${functionCount} functions = ${totalComplexity} (z=${totalComplexityZ.toFixed(2)}), ${bodyLines} lines (z=${bodyLinesZ.toFixed(2)}) - split responsibilities (SRP)`,
1190
+ findings
1191
+ );
1192
1192
  }
1193
1193
  }
1194
1194
  });
@@ -1823,269 +1823,269 @@ function runFrontendIntelligence(project, findings, platform) {
1823
1823
  }
1824
1824
 
1825
1825
  const useStateCallsInComponent = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
1826
- .filter(call => call.getExpression().getText() === 'useState');
1826
+ .filter(call => call.getExpression().getText() === 'useState');
1827
+
1828
+ sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
1829
+ const name = fn.getName();
1830
+ if (name && /^[A-Z]/.test(name)) {
1831
+ const useStatesInFn = useStateCallsInComponent.filter(call =>
1832
+ fn.getDescendants().includes(call)
1833
+ );
1834
+
1835
+ if (useStatesInFn.length >= 4) {
1836
+ pushFinding(
1837
+ "frontend.hooks.useState_overuse",
1838
+ "medium",
1839
+ sf,
1840
+ fn,
1841
+ `Component ${name} has ${useStatesInFn.length}+ useState. Consider useReducer for complex state. Benefits: Single state object, predictable updates, easier testing.`,
1842
+ findings
1843
+ );
1844
+ }
1845
+ }
1846
+ });
1847
+
1848
+ const contextCreations = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
1849
+ .filter(call => call.getExpression().getText() === 'createContext');
1827
1850
 
1828
- sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
1829
- const name = fn.getName();
1830
- if (name && /^[A-Z]/.test(name)) {
1831
- const useStatesInFn = useStateCallsInComponent.filter(call =>
1832
- fn.getDescendants().includes(call)
1851
+ if (contextCreations.length >= 3 && !sf.getFilePath().includes('context')) {
1852
+ pushFinding(
1853
+ "frontend.state.context_overuse",
1854
+ "medium",
1855
+ sf,
1856
+ sf,
1857
+ `Multiple Context creations (${contextCreations.length}). Consider Zustand for global state. Context rerenders all consumers. Zustand: Selective subscriptions, better performance.`,
1858
+ findings
1833
1859
  );
1860
+ }
1861
+
1862
+ sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
1863
+ const name = fn.getName();
1864
+ const body = fn.getBody()?.getText() || '';
1865
+ const hasHookCall = /use[A-Z]/.test(body);
1834
1866
 
1835
- if (useStatesInFn.length >= 4) {
1867
+ if (name && hasHookCall && !/^use[A-Z]/.test(name)) {
1836
1868
  pushFinding(
1837
- "frontend.hooks.useState_overuse",
1869
+ "frontend.hooks.naming_convention",
1838
1870
  "medium",
1839
1871
  sf,
1840
1872
  fn,
1841
- `Component ${name} has ${useStatesInFn.length}+ useState. Consider useReducer for complex state. Benefits: Single state object, predictable updates, easier testing.`,
1873
+ `Function ${name} uses hooks but doesn't start with 'use'. Rename to use${name[0].toUpperCase()}${name.slice(1)}. React ESLint requires 'use' prefix for hook functions.`,
1842
1874
  findings
1843
1875
  );
1844
1876
  }
1845
- }
1846
- });
1847
-
1848
- const contextCreations = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
1849
- .filter(call => call.getExpression().getText() === 'createContext');
1850
-
1851
- if (contextCreations.length >= 3 && !sf.getFilePath().includes('context')) {
1852
- pushFinding(
1853
- "frontend.state.context_overuse",
1854
- "medium",
1855
- sf,
1856
- sf,
1857
- `Multiple Context creations (${contextCreations.length}). Consider Zustand for global state. Context rerenders all consumers. Zustand: Selective subscriptions, better performance.`,
1858
- findings
1859
- );
1860
- }
1877
+ });
1861
1878
 
1862
- sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
1863
- const name = fn.getName();
1864
- const body = fn.getBody()?.getText() || '';
1865
- const hasHookCall = /use[A-Z]/.test(body);
1879
+ const fullText = sf.getFullText();
1880
+ const hasLargeImports = fullText.includes('import * as') ||
1881
+ fullText.match(/import\s+\{[^}]{200,}\}/);
1866
1882
 
1867
- if (name && hasHookCall && !/^use[A-Z]/.test(name)) {
1883
+ if (hasLargeImports) {
1868
1884
  pushFinding(
1869
- "frontend.hooks.naming_convention",
1885
+ "frontend.performance.large_imports",
1870
1886
  "medium",
1871
1887
  sf,
1872
- fn,
1873
- `Function ${name} uses hooks but doesn't start with 'use'. Rename to use${name[0].toUpperCase()}${name.slice(1)}. React ESLint requires 'use' prefix for hook functions.`,
1888
+ sf,
1889
+ 'Large import detected. Use tree-shaking: import { Button } from \'@mui/material/Button\' (not from \'@mui/material\'). Reduces bundle size by importing only needed components.',
1874
1890
  findings
1875
1891
  );
1876
1892
  }
1877
- });
1878
1893
 
1879
- const fullText = sf.getFullText();
1880
- const hasLargeImports = fullText.includes('import * as') ||
1881
- fullText.match(/import\s+\{[^}]{200,}\}/);
1882
-
1883
- if (hasLargeImports) {
1884
- pushFinding(
1885
- "frontend.performance.large_imports",
1886
- "medium",
1887
- sf,
1888
- sf,
1889
- 'Large import detected. Use tree-shaking: import { Button } from \'@mui/material/Button\' (not from \'@mui/material\'). Reduces bundle size by importing only needed components.',
1890
- findings
1891
- );
1892
- }
1894
+ if (content.includes('class') && content.includes('extends Component') &&
1895
+ !content.includes('componentDidCatch') && !content.includes('getDerivedStateFromError')) {
1893
1896
 
1894
- if (content.includes('class') && content.includes('extends Component') &&
1895
- !content.includes('componentDidCatch') && !content.includes('getDerivedStateFromError')) {
1897
+ const hasChildrenRender = content.includes('render()') && content.includes('children');
1896
1898
 
1897
- const hasChildrenRender = content.includes('render()') && content.includes('children');
1899
+ if (hasChildrenRender) {
1900
+ pushFinding(
1901
+ "frontend.error_handling.missing_error_boundary",
1902
+ "medium",
1903
+ sf,
1904
+ sf,
1905
+ 'Component renders children without error boundary. Add: componentDidCatch(error, errorInfo) { logError(error); }. Prevents whole app crash from single component error.',
1906
+ findings
1907
+ );
1908
+ }
1909
+ }
1910
+
1911
+ const hasRoutes = content.includes('Route') || content.includes('router');
1912
+ const hasLazy = content.includes('React.lazy') || content.includes('dynamic(');
1898
1913
 
1899
- if (hasChildrenRender) {
1914
+ if (hasRoutes && !hasLazy && sf.getFilePath().includes('app')) {
1900
1915
  pushFinding(
1901
- "frontend.error_handling.missing_error_boundary",
1916
+ "frontend.performance.missing_lazy_loading",
1902
1917
  "medium",
1903
1918
  sf,
1904
1919
  sf,
1905
- 'Component renders children without error boundary. Add: componentDidCatch(error, errorInfo) { logError(error); }. Prevents whole app crash from single component error.',
1920
+ 'Routes without lazy loading. Use: const Dashboard = lazy(() => import(\'./Dashboard\')). Wrap with <Suspense>. Reduces initial bundle, faster First Contentful Paint.',
1906
1921
  findings
1907
1922
  );
1908
1923
  }
1909
- }
1910
1924
 
1911
- const hasRoutes = content.includes('Route') || content.includes('router');
1912
- const hasLazy = content.includes('React.lazy') || content.includes('dynamic(');
1913
-
1914
- if (hasRoutes && !hasLazy && sf.getFilePath().includes('app')) {
1915
- pushFinding(
1916
- "frontend.performance.missing_lazy_loading",
1917
- "medium",
1918
- sf,
1919
- sf,
1920
- 'Routes without lazy loading. Use: const Dashboard = lazy(() => import(\'./Dashboard\')). Wrap with <Suspense>. Reduces initial bundle, faster First Contentful Paint.',
1921
- findings
1922
- );
1923
- }
1924
1925
 
1926
+ if (sf.getFilePath().includes('layout.tsx') || sf.getFilePath().includes('page.tsx')) {
1927
+ const hasMetadata = content.includes('metadata') || content.includes('generateMetadata');
1928
+
1929
+ if (!hasMetadata) {
1930
+ pushFinding(
1931
+ "frontend.seo.missing_metadata",
1932
+ "low",
1933
+ sf,
1934
+ sf,
1935
+ 'Missing SEO metadata. Export: export const metadata = { title, description, openGraph }. Improves Google ranking, social media previews.',
1936
+ findings
1937
+ );
1938
+ }
1939
+ }
1925
1940
 
1926
- if (sf.getFilePath().includes('layout.tsx') || sf.getFilePath().includes('page.tsx')) {
1927
- const hasMetadata = content.includes('metadata') || content.includes('generateMetadata');
1941
+ if (sf.getFilePath().includes('sitemap')) {
1942
+ const hasDynamicRoutes = content.includes('fetch') || content.includes('database');
1928
1943
 
1929
- if (!hasMetadata) {
1930
- pushFinding(
1931
- "frontend.seo.missing_metadata",
1932
- "low",
1933
- sf,
1934
- sf,
1935
- 'Missing SEO metadata. Export: export const metadata = { title, description, openGraph }. Improves Google ranking, social media previews.',
1936
- findings
1937
- );
1944
+ if (!hasDynamicRoutes && content.length < 200) {
1945
+ pushFinding(
1946
+ "frontend.seo.static_sitemap",
1947
+ "low",
1948
+ sf,
1949
+ sf,
1950
+ 'Static sitemap. Generate dynamically: fetch all routes from API/DB. Update automatically when content changes. Better SEO indexing.',
1951
+ findings
1952
+ );
1953
+ }
1938
1954
  }
1939
- }
1940
1955
 
1941
- if (sf.getFilePath().includes('sitemap')) {
1942
- const hasDynamicRoutes = content.includes('fetch') || content.includes('database');
1956
+ if (sf.getFilePath().includes('robots')) {
1957
+ const hasDisallow = content.includes('Disallow');
1943
1958
 
1944
- if (!hasDynamicRoutes && content.length < 200) {
1945
- pushFinding(
1946
- "frontend.seo.static_sitemap",
1947
- "low",
1948
- sf,
1949
- sf,
1950
- 'Static sitemap. Generate dynamically: fetch all routes from API/DB. Update automatically when content changes. Better SEO indexing.',
1951
- findings
1952
- );
1959
+ if (!hasDisallow) {
1960
+ pushFinding(
1961
+ "frontend.seo.robots_config",
1962
+ "low",
1963
+ sf,
1964
+ sf,
1965
+ 'Robots.txt incomplete. Add Disallow rules for: /api, /admin, /_next. Prevents search engines indexing private routes.',
1966
+ findings
1967
+ );
1968
+ }
1953
1969
  }
1954
- }
1955
1970
 
1956
- if (sf.getFilePath().includes('robots')) {
1957
- const hasDisallow = content.includes('Disallow');
1971
+ if (sf.getFilePath().includes('manifest')) {
1972
+ const hasIcons = content.includes('icons');
1973
+ const hasThemeColor = content.includes('theme_color');
1974
+
1975
+ if (!hasIcons || !hasThemeColor) {
1976
+ pushFinding(
1977
+ "frontend.pwa.manifest_incomplete",
1978
+ "low",
1979
+ sf,
1980
+ sf,
1981
+ 'PWA manifest incomplete. Add: icons (192x192, 512x512), theme_color, background_color, display: standalone. Enables Add to Home Screen.',
1982
+ findings
1983
+ );
1984
+ }
1985
+ }
1958
1986
 
1959
- if (!hasDisallow) {
1987
+ if (content.includes('serviceWorker') && !content.includes('unregister')) {
1960
1988
  pushFinding(
1961
- "frontend.seo.robots_config",
1989
+ "frontend.pwa.missing_sw_unregister",
1962
1990
  "low",
1963
1991
  sf,
1964
1992
  sf,
1965
- 'Robots.txt incomplete. Add Disallow rules for: /api, /admin, /_next. Prevents search engines indexing private routes.',
1993
+ 'Service Worker registration without unregister fallback. Add: if (!production) navigator.serviceWorker.unregister(). Prevents caching issues in development.',
1966
1994
  findings
1967
1995
  );
1968
1996
  }
1969
- }
1970
1997
 
1971
- if (sf.getFilePath().includes('manifest')) {
1972
- const hasIcons = content.includes('icons');
1973
- const hasThemeColor = content.includes('theme_color');
1998
+ if (sf.getFilePath().includes('lighthouse') || sf.getFilePath().includes('performance')) {
1999
+ const hasThresholds = content.includes('performance') && content.includes('accessibility');
2000
+
2001
+ if (!hasThresholds) {
2002
+ pushFinding(
2003
+ "frontend.performance.lighthouse_monitoring",
2004
+ "low",
2005
+ sf,
2006
+ sf,
2007
+ 'Lighthouse config without thresholds. Set CI thresholds: performance: 90, accessibility: 95, best-practices: 90, seo: 90. Prevents performance regressions.',
2008
+ findings
2009
+ );
2010
+ }
2011
+ }
2012
+
2013
+ const hasWebVitals = content.includes('reportWebVitals') || content.includes('web-vitals');
2014
+ const hasAnalytics = content.includes('analytics') || content.includes('gtag');
1974
2015
 
1975
- if (!hasIcons || !hasThemeColor) {
2016
+ if (hasWebVitals && !hasAnalytics) {
1976
2017
  pushFinding(
1977
- "frontend.pwa.manifest_incomplete",
2018
+ "frontend.monitoring.web_vitals_no_analytics",
1978
2019
  "low",
1979
2020
  sf,
1980
2021
  sf,
1981
- 'PWA manifest incomplete. Add: icons (192x192, 512x512), theme_color, background_color, display: standalone. Enables Add to Home Screen.',
2022
+ 'Web Vitals captured but not sent to analytics. Send to Google Analytics: gtag(\'event\', metric.name, { value: metric.value }). Track LCP, FID, CLS in production.',
1982
2023
  findings
1983
2024
  );
1984
2025
  }
1985
- }
1986
-
1987
- if (content.includes('serviceWorker') && !content.includes('unregister')) {
1988
- pushFinding(
1989
- "frontend.pwa.missing_sw_unregister",
1990
- "low",
1991
- sf,
1992
- sf,
1993
- 'Service Worker registration without unregister fallback. Add: if (!production) navigator.serviceWorker.unregister(). Prevents caching issues in development.',
1994
- findings
1995
- );
1996
- }
1997
-
1998
- if (sf.getFilePath().includes('lighthouse') || sf.getFilePath().includes('performance')) {
1999
- const hasThresholds = content.includes('performance') && content.includes('accessibility');
2000
2026
 
2001
- if (!hasThresholds) {
2027
+ if (sf.getFilePath().includes('layout') && !content.includes('analytics') && !content.includes('gtag')) {
2002
2028
  pushFinding(
2003
- "frontend.performance.lighthouse_monitoring",
2029
+ "frontend.monitoring.missing_analytics",
2004
2030
  "low",
2005
2031
  sf,
2006
2032
  sf,
2007
- 'Lighthouse config without thresholds. Set CI thresholds: performance: 90, accessibility: 95, best-practices: 90, seo: 90. Prevents performance regressions.',
2033
+ 'Root layout without analytics. Add Google Analytics: <Script src="https://www.googletagmanager.com/gtag/js" />. Track user behavior, conversion rates.',
2008
2034
  findings
2009
2035
  );
2010
2036
  }
2011
- }
2012
2037
 
2013
- const hasWebVitals = content.includes('reportWebVitals') || content.includes('web-vitals');
2014
- const hasAnalytics = content.includes('analytics') || content.includes('gtag');
2015
-
2016
- if (hasWebVitals && !hasAnalytics) {
2017
- pushFinding(
2018
- "frontend.monitoring.web_vitals_no_analytics",
2019
- "low",
2020
- sf,
2021
- sf,
2022
- 'Web Vitals captured but not sent to analytics. Send to Google Analytics: gtag(\'event\', metric.name, { value: metric.value }). Track LCP, FID, CLS in production.',
2023
- findings
2024
- );
2025
- }
2026
-
2027
- if (sf.getFilePath().includes('layout') && !content.includes('analytics') && !content.includes('gtag')) {
2028
- pushFinding(
2029
- "frontend.monitoring.missing_analytics",
2030
- "low",
2031
- sf,
2032
- sf,
2033
- 'Root layout without analytics. Add Google Analytics: <Script src="https://www.googletagmanager.com/gtag/js" />. Track user behavior, conversion rates.',
2034
- findings
2035
- );
2036
- }
2037
-
2038
- if (sf.getFilePath().includes('error.tsx') && !content.includes('Sentry') && !content.includes('captureException')) {
2039
- pushFinding(
2040
- "frontend.monitoring.missing_error_tracking",
2041
- "low",
2042
- sf,
2043
- sf,
2044
- 'Error page without error tracking. Install: npm i @sentry/nextjs. Track production errors: Sentry.captureException(error). Get alerts when users hit errors.',
2045
- findings
2046
- );
2047
- }
2048
-
2049
- if (content.includes('if (') && /feature|experiment|rollout|beta/i.test(content)) {
2050
- const hasFeatureFlagLib = content.includes('unleash') || content.includes('launchdarkly') || content.includes('flagsmith');
2051
-
2052
- if (!hasFeatureFlagLib) {
2038
+ if (sf.getFilePath().includes('error.tsx') && !content.includes('Sentry') && !content.includes('captureException')) {
2053
2039
  pushFinding(
2054
- "frontend.devops.hardcoded_feature_flag",
2040
+ "frontend.monitoring.missing_error_tracking",
2055
2041
  "low",
2056
2042
  sf,
2057
2043
  sf,
2058
- 'Hardcoded feature flag. Use feature flag service: Unleash, LaunchDarkly, Flagsmith. Benefits: Toggle features without deployment, gradual rollout, A/B testing.',
2044
+ 'Error page without error tracking. Install: npm i @sentry/nextjs. Track production errors: Sentry.captureException(error). Get alerts when users hit errors.',
2059
2045
  findings
2060
2046
  );
2061
2047
  }
2062
- }
2063
2048
 
2064
- if (content.includes('variant') && content.includes('Math.random')) {
2065
- pushFinding(
2066
- "frontend.devops.manual_ab_testing",
2067
- "low",
2068
- sf,
2069
- sf,
2070
- 'Manual A/B testing with Math.random. Use: Google Optimize, Optimizely, VWO. Benefits: Statistical significance, reporting, targeting rules.',
2071
- findings
2072
- );
2073
- }
2049
+ if (content.includes('if (') && /feature|experiment|rollout|beta/i.test(content)) {
2050
+ const hasFeatureFlagLib = content.includes('unleash') || content.includes('launchdarkly') || content.includes('flagsmith');
2074
2051
 
2075
- if (content.includes('fetch') && !content.includes('cache') && !content.includes('revalidate')) {
2076
- const isFetchInServerComponent = content.includes('async function') && !content.includes('\'use client\'');
2052
+ if (!hasFeatureFlagLib) {
2053
+ pushFinding(
2054
+ "frontend.devops.hardcoded_feature_flag",
2055
+ "low",
2056
+ sf,
2057
+ sf,
2058
+ 'Hardcoded feature flag. Use feature flag service: Unleash, LaunchDarkly, Flagsmith. Benefits: Toggle features without deployment, gradual rollout, A/B testing.',
2059
+ findings
2060
+ );
2061
+ }
2062
+ }
2077
2063
 
2078
- if (isFetchInServerComponent) {
2064
+ if (content.includes('variant') && content.includes('Math.random')) {
2079
2065
  pushFinding(
2080
- "frontend.performance.missing_cache_strategy",
2066
+ "frontend.devops.manual_ab_testing",
2081
2067
  "low",
2082
2068
  sf,
2083
2069
  sf,
2084
- 'Fetch without cache strategy. Add: { cache: \'force-cache\' } or { next: { revalidate: 3600 } }. Default behavior may change. Be explicit.',
2070
+ 'Manual A/B testing with Math.random. Use: Google Optimize, Optimizely, VWO. Benefits: Statistical significance, reporting, targeting rules.',
2085
2071
  findings
2086
2072
  );
2087
2073
  }
2088
- }
2074
+
2075
+ if (content.includes('fetch') && !content.includes('cache') && !content.includes('revalidate')) {
2076
+ const isFetchInServerComponent = content.includes('async function') && !content.includes('\'use client\'');
2077
+
2078
+ if (isFetchInServerComponent) {
2079
+ pushFinding(
2080
+ "frontend.performance.missing_cache_strategy",
2081
+ "low",
2082
+ sf,
2083
+ sf,
2084
+ 'Fetch without cache strategy. Add: { cache: \'force-cache\' } or { next: { revalidate: 3600 } }. Default behavior may change. Be explicit.',
2085
+ findings
2086
+ );
2087
+ }
2088
+ }
2089
2089
  });
2090
2090
  }
2091
2091
 
@@ -815,8 +815,9 @@ class iOSASTIntelligentAnalyzer {
815
815
  return 0.6745 * (x - med) / madValue;
816
816
  };
817
817
 
818
- const pOutlier = Number(process.env.AST_GODCLASS_P_OUTLIER || 90);
819
- const pExtreme = Number(process.env.AST_GODCLASS_P_EXTREME || 97);
818
+ const env = require('../../../../config/env');
819
+ const pOutlier = env.getNumber('AST_GODCLASS_P_OUTLIER', 90);
820
+ const pExtreme = env.getNumber('AST_GODCLASS_P_EXTREME', 97);
820
821
 
821
822
  const methods = this.godClassCandidates.map(c => c.methodsCount);
822
823
  const props = this.godClassCandidates.map(c => c.propertiesCount);
@@ -3,8 +3,9 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { execSync } = require('child_process');
6
+ const env = require('../../../config/env');
6
7
 
7
- const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
8
+ const projectDir = env.get('CLAUDE_PROJECT_DIR', process.cwd());
8
9
  const rulesPath = path.join(projectDir, '.cursor', 'ai-skills', 'skill-rules.json');
9
10
  const mdcRulesDir = path.join(projectDir, '.cursor', 'rules');
10
11
  const debugLogPath = path.join(projectDir, '.audit_tmp', 'skill-activation-debug.log');
@@ -54,7 +55,7 @@ function sendMacNotification(title, message) {
54
55
  return;
55
56
  }
56
57
 
57
- const enabled = process.env.HOOK_GUARD_ENABLE_SKILL_ACTIVATION !== '0';
58
+ const enabled = env.getBool('HOOK_GUARD_ENABLE_SKILL_ACTIVATION', true);
58
59
  if (!enabled) {
59
60
  appendDebugLog('Notifications disabled via HOOK_GUARD_ENABLE_SKILL_ACTIVATION');
60
61
  return;
@@ -1,13 +1,14 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const env = require('../../../config/env');
3
4
 
4
5
  const UnifiedLogger = require('../../application/services/logging/UnifiedLogger');
5
6
 
6
7
  function createUnifiedLogger({
7
8
  repoRoot = process.cwd(),
8
9
  component = 'HookSystem',
9
- consoleLevel = process.env.HOOK_LOG_CONSOLE_LEVEL || 'info',
10
- fileLevel = process.env.HOOK_LOG_FILE_LEVEL || 'debug',
10
+ consoleLevel = env.get('HOOK_LOG_CONSOLE_LEVEL', 'info'),
11
+ fileLevel = env.get('HOOK_LOG_FILE_LEVEL', 'debug'),
11
12
  fileName = null,
12
13
  defaultData = {}
13
14
  } = {}) {
@@ -17,8 +18,8 @@ function createUnifiedLogger({
17
18
  const logFileName = fileName || `${component.replace(/\s+/g, '-').toLowerCase()}.log`;
18
19
  const filePath = path.join(reportsDir, logFileName);
19
20
 
20
- const maxSizeBytes = Number(process.env.HOOK_LOG_MAX_SIZE || 5 * 1024 * 1024);
21
- const maxFiles = Number(process.env.HOOK_LOG_MAX_FILES || 5);
21
+ const maxSizeBytes = env.getNumber('HOOK_LOG_MAX_SIZE', 5 * 1024 * 1024);
22
+ const maxFiles = env.getNumber('HOOK_LOG_MAX_FILES', 5);
22
23
 
23
24
  return new UnifiedLogger({
24
25
  component,
@@ -53,6 +53,94 @@ function resolveRepoRoot() {
53
53
 
54
54
  const REPO_ROOT = resolveRepoRoot();
55
55
 
56
+ const MCP_LOCK_DIR = path.join(REPO_ROOT, '.audit_tmp', 'mcp-singleton.lock');
57
+ const MCP_LOCK_PID = path.join(MCP_LOCK_DIR, 'pid');
58
+
59
+ function isPidRunning(pid) {
60
+ if (!pid || !Number.isFinite(pid) || pid <= 0) return false;
61
+ try {
62
+ process.kill(pid, 0);
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ function safeReadPid(filePath) {
70
+ try {
71
+ if (!fs.existsSync(filePath)) return null;
72
+ const raw = String(fs.readFileSync(filePath, 'utf8') || '').trim();
73
+ const pid = Number(raw);
74
+ if (!Number.isFinite(pid) || pid <= 0) return null;
75
+ return pid;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ function removeLockDir() {
82
+ try {
83
+ if (fs.existsSync(MCP_LOCK_PID)) {
84
+ fs.unlinkSync(MCP_LOCK_PID);
85
+ }
86
+ } catch {
87
+ // ignore
88
+ }
89
+ try {
90
+ if (fs.existsSync(MCP_LOCK_DIR)) {
91
+ fs.rmdirSync(MCP_LOCK_DIR);
92
+ }
93
+ } catch {
94
+ // ignore
95
+ }
96
+ }
97
+
98
+ function acquireSingletonLock() {
99
+ try {
100
+ fs.mkdirSync(path.join(REPO_ROOT, '.audit_tmp'), { recursive: true });
101
+ } catch {
102
+ // ignore
103
+ }
104
+
105
+ try {
106
+ fs.mkdirSync(MCP_LOCK_DIR);
107
+ } catch (error) {
108
+ const existingPid = safeReadPid(MCP_LOCK_PID);
109
+ if (existingPid && isPidRunning(existingPid)) {
110
+ process.stderr.write(`[MCP] Another instance is already running (pid ${existingPid}). Exiting.\n`);
111
+ process.exit(0);
112
+ }
113
+
114
+ removeLockDir();
115
+ fs.mkdirSync(MCP_LOCK_DIR);
116
+ }
117
+
118
+ try {
119
+ fs.writeFileSync(MCP_LOCK_PID, String(process.pid), { encoding: 'utf8' });
120
+ } catch {
121
+ // ignore
122
+ }
123
+
124
+ const cleanup = () => {
125
+ const pid = safeReadPid(MCP_LOCK_PID);
126
+ if (pid === process.pid) {
127
+ removeLockDir();
128
+ }
129
+ };
130
+
131
+ process.on('exit', cleanup);
132
+ process.on('SIGINT', () => {
133
+ cleanup();
134
+ process.exit(0);
135
+ });
136
+ process.on('SIGTERM', () => {
137
+ cleanup();
138
+ process.exit(0);
139
+ });
140
+ }
141
+
142
+ acquireSingletonLock();
143
+
56
144
  // Lazy-loaded CompositionRoot - only initialized when first needed
57
145
  let _compositionRoot = null;
58
146
  function getCompositionRoot() {