diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Pedro Tacla Yamada
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/file-modules.cabal b/file-modules.cabal
new file mode 100644
--- /dev/null
+++ b/file-modules.cabal
@@ -0,0 +1,44 @@
+name: file-modules
+version: 0.1.0.0
+cabal-version: >=1.10
+build-type: Simple
+license: MIT
+license-file: LICENSE
+copyright: Copyright (c) Pedro Tacla Yamada 2015
+maintainer: tacla.yamada@gmail.com
+homepage: https://github.com/yamadapc/stack-run-auto
+synopsis: Takes a Haskell source-code file and outputs the modules it
+          . imports. Follows links to local modules as well.
+category: Development
+author: Pedro Tacla Yamada
+
+library
+    exposed-modules:
+        Development.FileModules
+    build-depends:
+        MissingH -any,
+        directory -any,
+        async -any,
+        base >=4.5 && <5,
+        filepath -any,
+        haskell-src-exts >=1.17 && <2,
+        stm -any,
+        stm-containers -any
+    default-language: Haskell2010
+    hs-source-dirs: src
+
+executable file-modules
+    main-is: Main.hs
+    build-depends:
+        base >=4.5 && <5,
+        MissingH -any,
+        directory -any,
+        async -any,
+        base >=4.5 && <5,
+        filepath -any,
+        haskell-src-exts >=1.17 && <2,
+        stm -any,
+        stm-containers -any
+    default-language: Haskell2010
+    hs-source-dirs: src
+
diff --git a/src/Development/FileModules.hs b/src/Development/FileModules.hs
new file mode 100644
--- /dev/null
+++ b/src/Development/FileModules.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE RecordWildCards #-}
+module Development.FileModules where
+
+import           Control.Concurrent.Async (mapConcurrently)
+import           Control.Concurrent.STM
+import           Control.Monad            (forM)
+import           Data.String.Utils        (split)
+import           Language.Haskell.Exts    (ImportDecl (..),
+                                           ModuleHeadAndImports (..),
+                                           ModuleName (..), NonGreedy (..),
+                                           ParseResult (..), parse)
+import qualified STMContainers.Set        as Set
+import           System.Directory
+import           System.FilePath
+
+fileModulesRecur :: Set.Set String -> FilePath -> IO [String]
+fileModulesRecur seen fname = do
+    modules <- fileModules fname
+    modules' <- flip mapConcurrently modules $ \m -> do
+        let pth = takeDirectory fname </> joinPath (split "." m) ++ ".hs"
+        isSeen <- atomically $ Set.lookup pth seen
+        if isSeen
+           then return [m]
+           else do
+               atomically $ Set.insert pth seen
+               isLocalModule <- doesFileExist pth
+               if isLocalModule
+                   -- If we're hitting a local modules, ignore it on the
+                   -- output (this may not be what we want)
+                   then fileModulesRecur seen pth
+                   else return [m]
+    return (concat modules')
+
+fileModules :: FilePath -> IO [String]
+fileModules fname = do
+    result <- parse <$> readFile fname
+    case result of
+        (ParseOk (NonGreedy{..})) -> do
+            let (ModuleHeadAndImports _ _ mimports) = unNonGreedy
+            forM mimports $ \imp ->
+                let ModuleName iname = importModule imp
+                in return iname
+        (ParseFailed _ _) -> error $ "Failed to parse module in " ++ fname
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,15 @@
+module Main where
+
+import           Control.Concurrent.Async (mapConcurrently)
+import           Control.Monad            (forM_)
+import           Data.List.Utils          (uniq)
+import           Development.FileModules
+import qualified STMContainers.Set        as Set
+import           System.Environment       (getArgs)
+
+main :: IO ()
+main = do
+    args <- getArgs
+    seen <- Set.newIO
+    modules <- mapConcurrently (fileModulesRecur seen) args
+    forM_ (uniq (concat modules)) putStrLn
