diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,8 @@
 Changelog for Shake
 
+0.14.2
+    #198, add <//> operator to join FilePatterns
+    #198, fix the <.> and other extension methods to work with //
 0.14.1
     #196, change the links to point at http://www.shakebuild.com/
     Improve the error messages when &%> or &?> go wrong
diff --git a/docs/Manual.md b/docs/Manual.md
--- a/docs/Manual.md
+++ b/docs/Manual.md
@@ -80,7 +80,7 @@
 A rule describes the steps required to build a file. A rule has two components, a <tt><i>pattern</i></tt> and some <tt><i>actions</i></tt>:
 
 <pre>
-<i>pattern</i> *&gt; \out -> do
+<i>pattern</i> %&gt; \out -> do
     <i>actions</i>
 </pre>
 
@@ -324,10 +324,10 @@
 
 #### Lint
 
-Shake features a built in "lint" features to check the build system is well formed. To run `build --lint`. You are likely to catch more lint violations if you first `build clean`. Sadly, lint does _not_ catch missing dependencies. However, it does catch:
+Shake features a built in "lint" features to check the build system is well formed. To run use `build --lint`. You are likely to catch more lint violations if you first `build clean`. Sadly, lint does _not_ catch missing dependencies. However, it does catch:
 
-* The current directory does not change. You should never change the current directory within the build system as multiple rules running at the same time will share the current directory. You can still run `cmd` calls in different directories using the `Cwd` argument.
-* Dependencies are not modified after they are depended upon. The common reason for violating this check is that you one rule writing to a file which has a different rule associated with it.
+* Changing the current directory, typically with `setCurrentDirectory`. You should never change the current directory within the build system as multiple rules running at the same time share the current directory. You can still run `cmd` calls in different directories using the `Cwd` argument.
+* Outputs that change after Shake has built them. The usual cause of this error is if the rule for `foo` also writes to the file `bar`, despite `bar` having a different rule producing it.
 
 There is a performance penalty for building with `--lint`, but it is typically small.
 
diff --git a/shake.cabal b/shake.cabal
--- a/shake.cabal
+++ b/shake.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.10
 build-type:         Simple
 name:               shake
-version:            0.14.1
+version:            0.14.2
 license:            BSD3
 license-file:       LICENSE
 category:           Development
diff --git a/src/Development/Shake.hs b/src/Development/Shake.hs
--- a/src/Development/Shake.hs
+++ b/src/Development/Shake.hs
@@ -119,7 +119,7 @@
     need, want, (%>), (|%>), (?>), phony, (~>),
     (&%>), (&?>),
     orderOnly,
-    FilePattern, (?==),
+    FilePattern, (?==), (<//>),
     needed, trackRead, trackWrite, trackAllow,
     -- * Directory rules
     doesFileExist, doesDirectoryExist, getDirectoryContents, getDirectoryFiles, getDirectoryDirs,
diff --git a/src/Development/Shake/FilePath.hs b/src/Development/Shake/FilePath.hs
--- a/src/Development/Shake/FilePath.hs
+++ b/src/Development/Shake/FilePath.hs
@@ -1,16 +1,26 @@
 
 -- | A module for 'FilePath' operations exposing "System.FilePath" plus some additional operations.
+--
+--   /Windows note:/ The extension methods ('<.>', 'takeExtension' etc) use the Posix variants since on
+--   Windows @\"\/\/*\" '<.>' \"txt\"@ produces @\"\/\/*\\\\.txt\"@
+--   (which is bad for 'Development.Shake.FilePattern' values).
 module Development.Shake.FilePath(
-    module System.FilePath,
+    module System.FilePath, module System.FilePath.Posix,
     dropDirectory1, takeDirectory1, normaliseEx,
     (-<.>),
     toNative, toStandard,
     exe
     ) where
 
-import System.FilePath
 import System.Info.Extra
 import qualified System.FilePath as Native
+
+import System.FilePath hiding
+    (splitExtension, takeExtension, replaceExtension, dropExtension, addExtension
+    ,hasExtension, (<.>), splitExtensions, takeExtensions, dropExtensions)
+import System.FilePath.Posix
+    (splitExtension, takeExtension, replaceExtension, dropExtension, addExtension
+    ,hasExtension, (<.>), splitExtensions, takeExtensions, dropExtensions)
 
 infixr 7  -<.>
 
diff --git a/src/Development/Shake/FilePattern.hs b/src/Development/Shake/FilePattern.hs
--- a/src/Development/Shake/FilePattern.hs
+++ b/src/Development/Shake/FilePattern.hs
@@ -1,13 +1,13 @@
 {-# LANGUAGE PatternGuards, ViewPatterns #-}
 
 module Development.Shake.FilePattern(
-    FilePattern, (?==),
+    FilePattern, (?==), (<//>),
     compatible, simple, extract, substitute,
     directories, directories1
     ) where
 
 import System.FilePath(isPathSeparator, pathSeparators, pathSeparator)
-import Data.List
+import Data.List.Extra
 import Data.Tuple.Extra
 import General.Extra
 
@@ -20,6 +20,16 @@
 --
 --   Most 'normaliseEx'd 'FilePath' values are suitable as 'FilePattern' values which match
 --   only that specific file. On Windows @\\@ is treated as equivalent to @\/@.
+--
+--   You can write 'FilePattern' values as a literal string, or build them
+--   up using the operators 'Development.Shake.FilePath.<.>', 'Development.Shake.FilePath.</>'
+--   and 'Development.Shake.<//>'. However, beware that:
+--
+-- * On Windows, use 'Development.Shake.FilePath.<.>' from "Development.Shake.FilePath" instead of from
+--   "System.FilePath" - otherwise @\"\/\/*\" \<.\> exe@ results in @\"\/\/*\\\\.exe\"@.
+--
+-- * If the second argument of 'Development.Shake.FilePath.</>' has a leading path separator (namely @\/@)
+--   then the second argument will be returned.
 type FilePattern = String
 
 
@@ -71,8 +81,6 @@
 match _ _ = []
 
 
-
-
 -- | Match a 'FilePattern' against a 'FilePath', There are only two special forms:
 --
 -- * @*@ matches an entire path component, excluding any separators.
@@ -100,6 +108,17 @@
 (?==) [s1,s2,'*'] | isPathSeparator s1 && isPathSeparator s2 = const True
 (?==) p = \x -> not $ null $ match pat (True, x)
     where pat = pattern $ lexer p
+
+infixr 5 <//>
+
+-- | Join two 'FilePattern' values by inserting two @\/@ characters between them.
+--   Will first remove any trailing path separators on the first argument, and any leading
+--   separators on the second.
+--
+-- > "dir" <//> "*" == "dir//*"
+(<//>) :: FilePattern -> FilePattern -> FilePattern
+a <//> b = dropWhileEnd isPathSeparator a ++ "//" ++ dropWhile isPathSeparator b
+
 
 ---------------------------------------------------------------------
 -- DIRECTORY PATTERNS
diff --git a/src/Test/Docs.hs b/src/Test/Docs.hs
--- a/src/Test/Docs.hs
+++ b/src/Test/Docs.hs
@@ -68,6 +68,7 @@
             ,"import Development.Shake.Util"
             ,"import Development.Shake.FilePath"
             ,"import System.Console.GetOpt"
+            ,"import System.Directory(setCurrentDirectory)"
             ,"import System.Exit"
             ,"import System.IO"] ++
             ["import " ++ reps '_' '.' (drop 5 $ takeBaseName out) | not $ "_md.hs" `isSuffixOf` out] ++
@@ -267,6 +268,7 @@
 
 types = words $
     "MVar IO String FilePath Maybe [String] Char ExitCode Change " ++
-    "Action Resource Assume FilePattern Lint Verbosity Rules CmdOption Int Double"
+    "Action Resource Assume FilePattern Development.Shake.FilePattern " ++
+    "Lint Verbosity Rules CmdOption Int Double"
 
 dupes = words "main progressSimple rules"
diff --git a/src/Test/FilePath.hs b/src/Test/FilePath.hs
--- a/src/Test/FilePath.hs
+++ b/src/Test/FilePath.hs
@@ -2,6 +2,7 @@
 module Test.FilePath(main) where
 
 import Development.Shake.FilePath
+import Development.Shake
 import qualified System.FilePath as Native
 import Test.Type
 import Test.QuickCheck
@@ -84,3 +85,13 @@
 
     searchPathSeparator === Native.searchPathSeparator
     pathSeparators === Native.pathSeparators
+
+    if isWindows then
+        toNative "//this is a///test\\here/" === "\\\\this is a\\\\\\test\\here\\"
+     else
+        toNative "//this is a///test\\here/" === "//this is a///test\\here/"
+
+    -- check common manipulations work
+    ("//*" <.> "foo") === "//*.foo"
+    toStandard ("a" </> "b" </> "c" <//> "*" <.> "exe") === "a/b/c//*.exe"
+    ("a" <//> "/b/c") === "a//b/c"
diff --git a/src/Test/FilePattern.hs b/src/Test/FilePattern.hs
--- a/src/Test/FilePattern.hs
+++ b/src/Test/FilePattern.hs
@@ -4,7 +4,6 @@
 import Development.Shake.FilePattern
 import Development.Shake.FilePath
 import Data.Tuple.Extra
-import System.Info.Extra
 import Test.Type
 
 main = shaken test $ \args obj -> return ()
@@ -13,7 +12,7 @@
 test build obj = do
     let f b pat file = assert (b == (pat ?== file)) $ show pat ++ " ?== " ++ show file ++ "\nEXPECTED: " ++ show b
     f True "//*.c" "foo/bar/baz.c"
-    f isWindows "\\\\*.c" "foo/bar\\baz.c"
+    f True (toNative "//*.c") "foo/bar\\baz.c"
     f True "*.c" "baz.c"
     f True "//*.c" "baz.c"
     f True "test.c" "test.c"
@@ -23,6 +22,9 @@
     f False "foo//bar" "foobar/bar"
     f False "foo//bar" "foo/foobar"
     f True "foo//bar" "foo/bar"
+    f True "foo/bar" (toNative "foo/bar")
+    f True (toNative "foo/bar") "foo/bar"
+    f True (toNative "foo/bar") (toNative "foo/bar")
 
     assert (compatible []) "compatible"
     assert (compatible ["//*a.txt","foo//a*.txt"]) "compatible"
@@ -30,6 +32,7 @@
     extract "//*a.txt" "foo/bar/testa.txt" === ["foo/bar/","test"]
     extract "//*a.txt" "testa.txt" === ["","test"]
     extract "//*a*.txt" "testada.txt" === ["","test","da"]
+    extract (toNative "//*a*.txt") "testada.txt" === ["","test","da"]
     substitute ["","test","da"] "//*a*.txt" === "testada.txt"
     substitute  ["foo/bar/","test"] "//*a.txt" === "foo/bar/testa.txt"
 
