diff --git a/DDC/War/Config.hs b/DDC/War/Config.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Config.hs
@@ -0,0 +1,66 @@
+
+module DDC.War.Config
+	( Config       (..)
+        , defaultConfig
+        , defaultTestSpec
+        , defaultNightlySpec)
+where
+import qualified DDC.War.Task.Test      as T
+import qualified DDC.War.Task.Nightly   as N
+import qualified BuildBox.Command.Mail  as B
+
+
+-- | Configuration information read from command line arguments.
+data Config
+	= Config 
+        { -- | Whether to emit debugging info for war.
+	  configDebug		        :: Bool
+
+          -- | Config for test mode.
+        , configTest                    :: Maybe T.Spec
+
+          -- | Config for nightly build mode.
+        , configNightly                 :: Maybe N.Spec }
+	deriving Show
+
+
+-- | Default configuration.
+defaultConfig :: Config
+defaultConfig
+        = Config
+        { configDebug                   = False
+        , configTest                    = Just defaultTestSpec
+        , configNightly                 = Nothing }
+
+
+defaultTestSpec :: T.Spec
+defaultTestSpec
+        = T.Spec
+        { T.specTestDirs                = []
+        , T.specWays                    = []
+        , T.specThreads                 = 1
+        , T.specFormatPathWidth         = 70
+        , T.specInteractive             = True
+        , T.specResultsFileAll          = Nothing
+        , T.specResultsFileFailed       = Nothing }
+
+
+defaultNightlySpec :: N.Spec
+defaultNightlySpec
+        = N.Spec
+        { N.specLocalBuildDir           = Nothing
+        , N.specRemoteSnapshotURL       = "http://code.ouroborus.net/ddc/snapshot/ddc-head-latest.tgz"
+        , N.specRemoteRepoURL           = "http://code.ouroborus.net/ddc/ddc-head"
+        , N.specBuildThreads            = 1
+        , N.specCleanup                 = False
+        , N.specContinuous              = Nothing
+        , N.specNow                     = False
+        , N.specLogUserHost             = Just $ "overlord@deluge.ouroborus.net"
+        , N.specLogRemoteDir            = Just $ "log/desire/ddc/head"
+        , N.specLogRemoteURL            = Just $ "http://log.ouroborus.net/desire/ddc/head"
+        , N.specMailer                  = Just $ B.MailerSendmail "sendmail" [] 
+        , N.specMailFrom                = Just $ "DDC Buildbot <overlord@ouroborus.net>"
+        , N.specMailTo                  = Just $ "disciple-cafe@googlegroups.com" }
+
+
+
diff --git a/DDC/War/Create.hs b/DDC/War/Create.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Create.hs
@@ -0,0 +1,60 @@
+
+-- | Creation of test jobs.
+--   We decide what to do based on what files are in a directory.
+--
+--   The following have specicial meaning. 
+--   They are in priority order, so if there is both a Main.sh and Main.ds file
+--   we run the Main.sh but don't then compile the Main.ds as well.
+--
+--      Main.sh                 Run this shell script.
+--
+--      Main.ds                 Compile with DDC and run the executable.
+--       Main.error.check        And expect compile failure, diff stdout with this file.
+--       Main.runerror.check     And expect run failure.
+--       Main.stdout.check       Diff run stdout with this file.
+--       Main.stderr.check       Diff run stderr with this file.
+--
+--      Main.dce                Compile with ddci-core and run the executable.
+--       Main.error.check        And expect compile failure, diff stdout with this file.
+--       Main.stdout.check       Diff run stdout with this file.
+--       Main.stderr.check       Diff run stderr with this file.
+--
+--      *.ds                    Compile with DDC, but there is no executable produced.
+--       *.error.check           And expect compile failure, diff stdout with this file.
+--
+--      Test.dcx                Run with ddci-core.
+--       Test.stdout.check       Diff run stdout with this file.
+--       
+module DDC.War.Create
+        ( Way   (..)
+        , create)
+where
+import DDC.War.Create.Way
+import DDC.War.Driver
+import Data.Maybe
+import Data.Set                                 (Set)
+import qualified DDC.War.Create.CreateMainSH    as CreateMainSH
+import qualified DDC.War.Create.CreateMainHS    as CreateMainHS
+import qualified DDC.War.Create.CreateMainDS    as CreateMainDS
+import qualified DDC.War.Create.CreateTestDS    as CreateTestDS
+import qualified DDC.War.Create.CreateDCX       as CreateDCX
+import qualified DDC.War.Create.CreateDCE       as CreateDCE
+
+
+-- | Create job chains based on this file.
+create  :: Way                  -- ^ Create tests for this way.
+        -> Set FilePath         -- ^ All files in the test directory.
+        -> FilePath             -- ^ Create test chains based on this file.
+        -> [Chain]
+
+create way allFiles filePath
+ =      catMaybes
+        [ creat way allFiles filePath
+        | creat <- 
+                [ CreateMainSH.create
+                , CreateMainHS.create
+                , CreateMainDS.create
+                , CreateTestDS.create
+                , CreateDCX.create
+                , CreateDCE.create  ]]
+
diff --git a/DDC/War/Create/CreateDCE.hs b/DDC/War/Create/CreateDCE.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Create/CreateDCE.hs
@@ -0,0 +1,87 @@
+
+
+module DDC.War.Create.CreateDCE
+        (create)
+where
+import DDC.War.Create.Way
+import DDC.War.Driver
+import System.FilePath
+import DDC.War.Job                              ()
+import Data.Set                                 (Set)
+import qualified DDC.War.Job.CompileDCE         as CompileDCE
+import qualified DDC.War.Job.RunExe             as RunExe
+import qualified DDC.War.Job.Diff               as Diff
+import qualified Data.Set                       as Set
+
+
+-- | Compile and run .dce files.
+create :: Way -> Set FilePath -> FilePath -> Maybe Chain
+create way allFiles filePath
+ | takeFileName filePath == "Main.dce"
+ = let  
+        sourceDir        = takeDirectory  filePath
+        buildDir         = sourceDir </> "war-" ++ wayName way
+        testName         = filePath
+
+        mainSH           = sourceDir </> "Main.sh"
+        mainBin          = buildDir  </> "Main.bin"
+        mainCompStdout   = buildDir  </> "Main.compile.stdout"
+        mainCompStderr   = buildDir  </> "Main.compile.stderr"
+        mainCompDiff     = buildDir  </> "Main.compile.stderr.diff"
+        mainRunStdout    = buildDir  </> "Main.run.stdout"
+        mainRunStderr    = buildDir  </> "Main.run.stderr"
+
+        mainErrorCheck   = sourceDir </> "Main.error.check"
+        shouldSucceed    = not $ Set.member mainErrorCheck allFiles
+
+        mainStdoutCheck  = sourceDir </> "Main.stdout.check"
+        mainStdoutDiff   = buildDir  </> "Main.run.stdout.diff"
+        shouldDiffStdout = Set.member mainStdoutCheck allFiles
+
+        mainStderrCheck  = sourceDir </> "Main.stderr.check"
+        mainStderrDiff   = buildDir  </> "Main.run.stderr.diff"
+        shouldDiffStderr = Set.member mainStderrCheck allFiles
+
+        -- compile the .ds into a .bin
+        compile          = jobOfSpec (JobId testName (wayName way))
+                         $ CompileDCE.Spec
+                                filePath
+                                buildDir mainCompStdout mainCompStderr
+                                (Just mainBin) shouldSucceed
+
+        -- run the binary
+        run              = jobOfSpec (JobId testName (wayName way))
+                         $ RunExe.Spec
+                                filePath 
+                                mainBin []
+                                mainRunStdout mainRunStderr
+                                True
+
+        -- diff errors produced by the compilation
+        diffError        = jobOfSpec (JobId testName (wayName way))
+                         $ Diff.Spec
+                                mainErrorCheck
+                                mainCompStderr mainCompDiff
+
+        -- diff the stdout of the run
+        diffStdout       = jobOfSpec (JobId testName (wayName way))
+                         $ Diff.Spec
+                                mainStdoutCheck
+                                mainRunStdout mainStdoutDiff
+
+        -- diff the stderr of the run
+        diffStderr       = jobOfSpec (JobId testName (wayName way))
+                         $ Diff.Spec
+                                mainStderrCheck
+                                mainRunStderr mainStderrDiff
+
+   in   if Set.member mainSH allFiles
+         then Nothing
+         else Just $ Chain 
+                $  [compile]
+                ++ (if shouldSucceed    then [run]        else [diffError])
+                ++ (if shouldDiffStdout then [diffStdout] else [])
+                ++ (if shouldDiffStderr then [diffStderr] else [])
+
+ | otherwise    = Nothing
+
diff --git a/DDC/War/Create/CreateDCX.hs b/DDC/War/Create/CreateDCX.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Create/CreateDCX.hs
@@ -0,0 +1,46 @@
+
+module DDC.War.Create.CreateDCX
+        (create)
+where
+import DDC.War.Create.Way
+import DDC.War.Driver
+import System.FilePath
+import DDC.War.Job                              ()
+import Data.Set                                 (Set)
+import qualified DDC.War.Job.RunDCX             as RunDCX
+import qualified DDC.War.Job.Diff               as Diff
+import qualified Data.Set                       as Set
+
+
+-- | Run .dcx files with the interpreter.
+create :: Way -> Set FilePath -> FilePath -> Maybe Chain
+create way allFiles filePath
+ | takeFileName filePath == "Test.dcx"
+ = let  
+        fileName         = takeFileName filePath
+        sourceDir        = takeDirectory  filePath
+        buildDir         = sourceDir </> "war-" ++ wayName way
+        testName         = filePath
+
+        testDDCiStdout   = buildDir  </> replaceExtension fileName ".ddci-core.stdout"
+        testDDCiStderr   = buildDir  </> replaceExtension fileName ".ddci-core.stderr"
+
+        testStdoutCheck  = sourceDir </> "Test.stdout.check"
+        testStdoutDiff   = buildDir  </> "Test.stdout.check.diff"
+        shouldDiffStdout = Set.member testStdoutCheck allFiles
+
+        jobRun           = jobOfSpec (JobId testName (wayName way))
+                         $ RunDCX.Spec
+                                filePath
+                                buildDir testDDCiStdout testDDCiStderr
+
+        jobDiff          = jobOfSpec (JobId testName (wayName way))
+                         $ Diff.Spec
+                                testStdoutCheck
+                                testDDCiStdout testStdoutDiff
+
+   in   Just $ Chain 
+                $  [jobRun] 
+                ++ (if shouldDiffStdout then [jobDiff] else [])
+
+ | otherwise    = Nothing
diff --git a/DDC/War/Create/CreateMainDS.hs b/DDC/War/Create/CreateMainDS.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Create/CreateMainDS.hs
@@ -0,0 +1,107 @@
+
+module DDC.War.Create.CreateMainDS
+        (create)
+where
+import DDC.War.Create.Way
+import DDC.War.Driver
+import System.FilePath
+import Data.Set                                 (Set)
+import DDC.War.Job                              ()
+import qualified DDC.War.Job.CompileDS          as CompileDS
+import qualified DDC.War.Job.RunExe             as RunExe
+import qualified DDC.War.Job.Diff               as Diff
+import qualified Data.Set                       as Set
+
+
+-- | Compile and run Main.ds files.
+create :: Way -> Set FilePath -> FilePath -> Maybe Chain
+create way allFiles filePath
+ | takeFileName filePath == "Main.ds"
+ = let  
+        sourceDir          = takeDirectory  filePath
+        buildDir           = sourceDir </> "war-" ++ wayName way
+        testName           = filePath
+
+        mainSH             = sourceDir </> "Main.sh"
+
+        -- Source ---------------------
+        -- If this exists then expect compilation to fail.
+        mainErrorCheck     = sourceDir </> "Main.error.check"
+
+        -- If this exists then expect the execuable to fail at runtime.
+        mainRunErrorCheck  = sourceDir </> "Main.runerror.check"
+
+        -- Expected output of successful executable.
+        mainStdoutCheck    = sourceDir </> "Main.stdout.check"
+        mainStderrCheck    = sourceDir </> "Main.stderr.check"
+
+        -- Output ---------------------
+        -- Where to put compiler output.
+        mainCompStdout     = buildDir  </> "Main.compile.stdout"
+        mainCompStderr     = buildDir  </> "Main.compile.stderr"
+        mainCompStderrDiff = buildDir  </> "Main.compile.stderr.diff"
+
+        -- Where to put exectuable output.
+        mainBin            = buildDir  </> "Main.bin"
+        mainRunStdout      = buildDir  </> "Main.run.stdout"
+        mainRunStdoutDiff  = buildDir  </> "Main.run.stdout.diff"
+        mainRunStderr      = buildDir  </> "Main.run.stderr"
+        mainRunStderrDiff  = buildDir  </> "Main.run.stderr.diff"
+
+        -- compile the .ds into a .bin
+        --   We expect the compile to fail if there is a Main.error.check file.
+        compShouldSucceed  = not $ Set.member mainErrorCheck allFiles
+        jobCompile         = jobOfSpec (JobId testName (wayName way))
+                           $ CompileDS.Spec
+                                filePath
+                                (wayOptsComp way) ["-M60M"]
+                                buildDir mainCompStdout mainCompStderr
+                                (Just mainBin) compShouldSucceed
+
+        -- If we expect the compile to fail, 
+        --   then diff errors produced by the compilation
+        shouldDiffCompError = not $ compShouldSucceed
+        jobDiffCompError    = jobOfSpec (JobId testName (wayName way))
+                            $ Diff.Spec
+                                mainErrorCheck
+                                mainCompStderr mainCompStderrDiff
+
+        -- If we expect the compile to succeed, then run the resulting binary.
+        --   We expect the run to fail if there is a Main.runerror.check file.
+        shouldRunExe        = compShouldSucceed
+        runShouldSucceed    = not $ Set.member mainRunErrorCheck allFiles
+        jobRunExe           = jobOfSpec (JobId testName (wayName way))
+                            $ RunExe.Spec
+                                filePath 
+                                mainBin []
+                                mainRunStdout mainRunStderr
+                                runShouldSucceed
+
+        -- If we expect the run to succeed,
+        --   then diff the stdout of the run.
+        shouldDiffStdout    = shouldRunExe && Set.member mainStdoutCheck allFiles
+        jobDiffStdout       = jobOfSpec (JobId testName (wayName way))
+                            $ Diff.Spec
+                                mainStdoutCheck
+                                mainRunStdout mainRunStdoutDiff
+
+        -- If we expect the run to succeed,
+        --   then diff the stderr of the run.
+        shouldDiffStderr    = shouldRunExe && Set.member mainStderrCheck allFiles
+        jobDiffStderr       = jobOfSpec (JobId testName (wayName way))
+                            $ Diff.Spec
+                                mainStderrCheck
+                                mainRunStderr mainRunStderrDiff
+
+   in   if Set.member mainSH allFiles
+         then Nothing
+         else Just $ Chain 
+                $  [jobCompile]
+                ++ (if shouldDiffCompError then [jobDiffCompError] else [])
+                ++ (if shouldRunExe        then [jobRunExe]        else [])
+                ++ (if shouldDiffStdout    then [jobDiffStdout]    else [])
+                ++ (if shouldDiffStderr    then [jobDiffStderr]    else [])
+
+ | otherwise    = Nothing
+
+
diff --git a/DDC/War/Create/CreateMainHS.hs b/DDC/War/Create/CreateMainHS.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Create/CreateMainHS.hs
@@ -0,0 +1,45 @@
+
+module DDC.War.Create.CreateMainHS
+        (create)
+where
+import DDC.War.Create.Way
+import DDC.War.Driver
+import System.FilePath
+import DDC.War.Job                              ()
+import Data.Set                                 (Set)
+import qualified DDC.War.Job.CompileHS          as CompileHS
+import qualified DDC.War.Job.RunExe             as RunExe
+
+
+-- | Compile and run Main.hs files.
+--   When we run the exectuable, pass it out build dir as the first argument.
+create :: Way -> Set FilePath -> FilePath -> Maybe Chain
+create way _allFiles filePath
+ | takeFileName filePath == "Main.hs"
+ = let  
+        sourceDir       = takeDirectory  filePath
+        buildDir        = sourceDir </> "war-" ++ wayName way
+        testName        = filePath
+
+        mainBin         = buildDir </> "Main.bin"
+        mainCompStdout  = buildDir </> "Main.compile.stdout"
+        mainCompStderr  = buildDir </> "Main.compile.stderr"
+        mainRunStdout   = buildDir </> "Main.run.stdout"
+        mainRunStderr   = buildDir </> "Main.run.stderr"
+
+        compile         = jobOfSpec (JobId testName (wayName way))
+                        $ CompileHS.Spec
+                                filePath []
+                                buildDir mainCompStdout mainCompStderr
+                                mainBin
+
+        run             = jobOfSpec (JobId testName (wayName way))
+                        $ RunExe.Spec
+                                filePath 
+                                mainBin [buildDir]
+                                mainRunStdout mainRunStderr
+                                True
+
+   in   Just $ Chain [compile, run]
+
+ | otherwise    = Nothing
diff --git a/DDC/War/Create/CreateMainSH.hs b/DDC/War/Create/CreateMainSH.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Create/CreateMainSH.hs
@@ -0,0 +1,46 @@
+
+module DDC.War.Create.CreateMainSH
+        (create)
+where
+import DDC.War.Create.Way
+import DDC.War.Driver
+import System.FilePath
+import Data.Set                                 (Set)
+import DDC.War.Job                              ()
+import qualified DDC.War.Job.Shell              as Shell
+import qualified DDC.War.Job.Diff               as Diff
+import qualified Data.Set                       as Set
+
+
+-- | Run Main.sh files.
+create :: Way -> Set FilePath -> FilePath -> Maybe Chain
+create way allFiles filePath
+ | takeFileName filePath == "Main.sh"
+ = let
+        sourceDir       = takeDirectory  filePath
+        buildDir        = sourceDir </> "war-" ++ wayName way
+        testName        = filePath
+
+
+        mainShellStdout         = buildDir  </> "Main.shell.stdout"
+        mainShellStderr         = buildDir  </> "Main.shell.stderr"
+        mainShellStderrDiff     = buildDir  </> "Main.compile.stderr.diff"
+        mainErrorCheck          = sourceDir </> "Main.error.check"
+        shouldSucceed           = not $ Set.member mainErrorCheck allFiles
+
+        shell           = jobOfSpec (JobId testName (wayName way))
+                        $ Shell.Spec 
+                                filePath sourceDir buildDir
+                                mainShellStdout mainShellStderr
+                                shouldSucceed
+
+        diffError       = jobOfSpec (JobId testName (wayName way))
+                        $ Diff.Spec
+                                mainErrorCheck
+                                mainShellStderr mainShellStderrDiff
+
+   in   Just $ Chain 
+         $  [shell] 
+         ++ (if shouldSucceed then [] else [diffError])
+
+ | otherwise    = Nothing
diff --git a/DDC/War/Create/CreateTestDS.hs b/DDC/War/Create/CreateTestDS.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Create/CreateTestDS.hs
@@ -0,0 +1,57 @@
+
+module DDC.War.Create.CreateTestDS
+        (create)
+where
+import DDC.War.Create.Way
+import DDC.War.Driver
+import System.FilePath
+import Data.List
+import DDC.War.Job                              ()
+import Data.Set                                 (Set)
+import qualified DDC.War.Job.CompileDS          as CompileDS
+import qualified DDC.War.Job.Diff               as Diff
+import qualified Data.Set                       as Set
+
+
+-- | Compile Test.ds files.
+create :: Way -> Set FilePath -> FilePath -> Maybe Chain
+create way allFiles filePath
+ | isSuffixOf ".ds" filePath
+ = let  
+        fileName        = takeFileName filePath
+        sourceDir       = takeDirectory  filePath
+        buildDir        = sourceDir </> "war-" ++ wayName way
+        testName        = filePath
+
+        mainDS          = sourceDir </> "Main.ds"
+        mainSH          = sourceDir </> "Main.sh"
+        testErrorCheck  = sourceDir </> replaceExtension fileName ".error.check"
+
+        testCompStdout  = buildDir  </> replaceExtension fileName ".compile.stdout"
+        testCompStderr  = buildDir  </> replaceExtension fileName ".compile.stderr"
+        testCompDiff    = buildDir  </> replaceExtension fileName ".compile.stderr.diff"
+        shouldSucceed   = not $ Set.member testErrorCheck allFiles
+
+        -- Compile the .ds file
+        compile         = jobOfSpec (JobId testName (wayName way))
+                        $ CompileDS.Spec
+                                filePath
+                                (wayOptsComp way) ["-M50M"]
+                                buildDir testCompStdout testCompStderr
+                                Nothing shouldSucceed
+
+        diffError       = jobOfSpec (JobId testName (wayName way))
+                        $ Diff.Spec
+                                testErrorCheck
+                                testCompStderr testCompDiff
+
+   in   -- Don't do anything if there is a Main.ds here.
+        -- This other .ds file is probably a part of a larger program.
+        if   Set.member mainDS allFiles
+          || Set.member mainSH allFiles
+           then Nothing
+           else Just $ Chain
+                        $ [compile] 
+                        ++ (if shouldSucceed then [] else [diffError])
+
+ | otherwise    = Nothing
diff --git a/DDC/War/Create/Way.hs b/DDC/War/Create/Way.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Create/Way.hs
@@ -0,0 +1,15 @@
+
+module DDC.War.Create.Way
+        (Way(..))
+where
+        
+
+-- | A way to build the test.
+--   This holds extra options to pass to the program.
+data Way
+        = Way   
+        { wayName       :: String 
+        , wayOptsComp   :: [String] 
+        , wayOptsRun    :: [String] }
+        deriving (Eq, Ord, Show)
+
diff --git a/DDC/War/Driver.hs b/DDC/War/Driver.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Driver.hs
@@ -0,0 +1,5 @@
+
+module DDC.War.Driver
+        (module DDC.War.Driver.Base)
+where
+import DDC.War.Driver.Base
diff --git a/DDC/War/Driver/Base.hs b/DDC/War/Driver/Base.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Driver/Base.hs
@@ -0,0 +1,116 @@
+
+module DDC.War.Driver.Base
+        ( Job           (..)
+        , JobId         (..)
+        , Chain         (..)
+        , Product       (..)
+        , Result        (..)
+        , prettyResult
+        , Spec          (..))
+where
+import BuildBox.Pretty
+import BuildBox
+import Data.Maybe
+import Data.List
+
+-- | A printable job identifier.
+data JobId
+        = JobId
+        { jobIdName             :: String 
+        , jobIdWay              :: String }
+        deriving Show
+
+
+
+-- | A single job to run.
+--   The exact specification is defined by the client.
+data Job
+        =  forall spec result. Spec spec result
+        => Job JobId String spec (Build result)
+
+instance Pretty Job where
+ ppr (Job jobId actionName spec _)
+  = text "Job" <+> text (show jobId) <+> text actionName <+> text (show spec)
+
+
+-- | A chain of jobs to run one after another.
+--   Jobs later in the list are dependent on earlier ones, so if a job fails
+--   then we skip the rest.
+data Chain
+        = Chain [Job]
+
+instance Pretty Chain where
+ ppr (Chain jobs)
+  =   text "Chain"
+  <+> ppr jobs
+
+
+-- | The product that we got when running a job.
+--   This is the information that the interactive interface needs to decide
+--   how to proceed. 
+data Product
+        = ProductStatus 
+        { productStatusMsg      :: Doc
+        , productStatusSuccess  :: Bool }
+
+        | ProductDiff   
+        { productDiffRef        :: FilePath
+        , productDiffOut        :: FilePath
+        , productDiffDiff       :: FilePath }
+
+
+-- | Description of a job and the product we got from running it.
+data Result
+        = Result 
+        { resultChainIx         :: Int
+        , resultJobIx           :: Int
+        , resultJobId           :: JobId
+        , resultJobActionName   :: String
+        , resultProduct         :: Product }
+
+
+prettyResult :: Int -> String -> Int -> Result -> Doc
+prettyResult chainsTotal prefix padWidth result
+        | Result chainIx jobIx jobId actionName product' <- result
+        , JobId  testName wayName                       <- jobId
+        = let   status
+                 = case product' of
+                        ProductStatus s _ -> s
+                        ProductDiff{}     -> text "diff"
+
+                testName'
+                 = fromMaybe testName (stripPrefix prefix testName)
+
+          in   parens (padR  (length $ show chainsTotal)
+                             (ppr chainIx)
+                   <>  text "."
+                   <>        (ppr jobIx))
+           <+> padL padWidth (text testName') 
+           <+> padL 5        (text wayName)
+           <+> padL 8        (text actionName)
+           <+> status
+
+        where 
+
+-- Spec -----------------------------------------------------------------------
+-- | Class of Job specifications.
+class (Show spec, Pretty result)
+        => Spec spec result | spec -> result where
+
+ -- | Get a short name to describe the job that this spec describes, 
+ --   eg "compile" or "run"
+ specActionName    :: spec -> String
+
+ -- | Wrap a specification into a job.
+ jobOfSpec         :: JobId -> spec -> Job
+ jobOfSpec jobId s = Job jobId (specActionName s) s (buildFromSpec s)
+
+ -- | Create a builder for this job specification.
+ buildFromSpec    :: spec -> Build result
+
+ -- | Make the job product from its result.
+ --   This cuts away information that the controller doesn't care about.
+ productOfResult  :: spec -> result -> Product
+
+
+
diff --git a/DDC/War/Driver/Chain.hs b/DDC/War/Driver/Chain.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Driver/Chain.hs
@@ -0,0 +1,72 @@
+
+module DDC.War.Driver.Chain
+        ( runChainWithTChan
+        , runJobWithTChan
+        , runChain
+        , runJob)
+where
+import DDC.War.Driver
+import BuildBox
+import Control.Concurrent.STM.TChan
+import Control.Monad
+import Control.Monad.STM
+
+
+-- Run a chain of jobs, optionally writing the results to this channel
+-- after each job finishes.
+runChainWithTChan
+        :: Maybe (TChan Result)
+        -> Int
+        -> Chain
+        -> Build [Result]
+
+runChainWithTChan mChannel ixChain (Chain jobs)
+ = zipWithM (runJobWithTChan mChannel ixChain) [0..] jobs
+
+
+-- | Run a job, optionally writing the result to this channel.
+runJobWithTChan
+        :: Maybe (TChan Result)
+        -> Int
+        -> Int
+        -> Job
+        -> Build Result
+
+runJobWithTChan mChannel ixChain ixJob job
+ = case mChannel of
+        Nothing 
+         ->     runJob ixChain ixJob job
+
+        Just channel
+         -> do  result  <- runJob ixChain ixJob job
+                io $ atomically $ writeTChan channel result
+                return result
+
+
+-- Run ------------------------------------------------------------------------
+-- | Run a job chain, returning the results.
+runChain :: Int                 -- ^ Index of this chain.
+         -> Chain               -- ^ Chain of jobs to run
+         -> Build [Result]      -- ^ Job results.
+
+runChain ixChain (Chain jobs)
+ = zipWithM (runJob ixChain) [0..] jobs
+
+
+-- | Run a single job, returning its result.
+runJob   :: Int                 -- ^ Index of this chain.
+         -> Int                 -- ^ Index of this job of the chain.
+         -> Job                 -- ^ The job to run.
+         -> Build Result
+
+runJob ixChain ixJob (Job jobId actionName spec builder)
+ = do   
+        -- Run the job.
+        result          <- builder
+
+        -- Convert the result into the product the controller wants.
+        let product'    = productOfResult spec result
+        let jobResult   = Result ixChain ixJob jobId actionName product'
+
+        return jobResult
+
diff --git a/DDC/War/Driver/Gang.hs b/DDC/War/Driver/Gang.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Driver/Gang.hs
@@ -0,0 +1,59 @@
+
+module DDC.War.Driver.Gang
+        ( forkChainsIO
+        , runChainIO)
+where
+import DDC.War.Driver.Base
+import DDC.War.Driver.Chain
+import BuildBox.Build.BuildState
+import BuildBox.Control.Gang
+import BuildBox
+import Control.Concurrent.STM.TChan
+import System.Random
+
+
+-- | Run some job chains.
+forkChainsIO
+        :: Int                  -- ^ Number of threads to use.
+        -> FilePath             -- ^ Scratch directory.
+        -> Maybe (TChan Result) -- ^ Channel to write job results into.
+        -> [Chain]              -- ^ Chains of jobs to sun.
+        -> IO Gang              -- ^ The gang now running the jobs.
+
+forkChainsIO numThreads dirScratch mChanResult chains
+ = do   
+        -- Fork a gang to run all the job chains.
+        gang    <- forkGangActions numThreads
+                $  zipWith (runChainIO dirScratch mChanResult)
+                        [1..]
+                        chains
+
+        return gang        
+
+
+-- | Run a chain of jobs in the IO monad,
+--   writing job results to the given channel when they finish.
+runChainIO 
+        :: FilePath
+        -> Maybe (TChan Result)
+        -> Int
+        -> Chain
+        -> IO ()
+
+runChainIO tmpDir mChanResult ixChain chain
+ = do   uid             <- getUniqueId          -- TODO: buildbox should handle this
+                                                --       should not need process id.
+        let state       = buildStateDefault uid tmpDir
+
+        runBuildWithState state
+         $ runChainWithTChan mChanResult ixChain chain
+
+        return ()
+
+
+-- | Get a unique(ish) id for this process.
+--   The random seeds the global generator with the cpu time in psecs,
+--   which should be good enough.
+getUniqueId :: IO Integer
+getUniqueId
+        = randomRIO (0, 1000000000)     
diff --git a/DDC/War/Interface/Controller.hs b/DDC/War/Interface/Controller.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Interface/Controller.hs
@@ -0,0 +1,246 @@
+
+-- | Gang controller and user interface.
+module DDC.War.Interface.Controller
+	( Config        (..)
+	, controller)
+where
+import DDC.War.Driver
+import BuildBox.Pretty
+import BuildBox.Control.Gang
+import System.IO
+import Control.Concurrent
+import Control.Concurrent.STM.TChan
+import Control.Monad.STM
+import Control.Monad
+import Data.Maybe
+import Data.List
+import DDC.War.Interface.VT100      as VT100
+import qualified System.Cmd
+
+
+data Config
+        = Config
+        { -- | Allow the user to control the driver from the console, 
+          --   and ask what to do interactively if a file is different than expected.
+          configInteractive     :: Bool 
+
+          -- | Use VT100 colors in the output.
+        , configColoredOutput   :: Bool
+
+          -- | Pad test names out to this column width.
+        , configFormatPathWidth :: Int 
+
+          -- | Suppress this prefix from the front of displayed test names.
+          --   Test names usually have fully qualified paths,
+          --   but we wont want to display the "/Users/whoever/devel/code/ddc/test"
+          --   prefix.
+        , configSuppressPrefix  :: String }
+        deriving Show
+
+	
+
+-- | Gang controller and user interface for the war test driver.
+--   This should be forked into its own thread, so it runs concurrently
+--   with the gang that is actually executing the tests.
+controller 
+	:: Config
+	-> Gang
+	-> Int		       -- ^ Total number of chains.
+	-> TChan Result        -- ^ Channel to receive results from.
+	-> IO [Result]
+
+controller config gang chainsTotal chanResult
+ = liftM reverse $ go_start []
+ where	
+	-- See if there is an input on the console.
+        go_start :: [Result] -> IO [Result]
+	go_start jobResults
+
+         | configInteractive config
+         = hReady stdin >>= \gotInput
+           -> if gotInput
+                then go_input jobResults
+                else go_checkResult jobResults
+
+         | otherwise
+         = go_checkResult jobResults
+
+	
+	-- We've got input on the console, wait for already running tests then bail out.
+	go_input jobResults
+	 = do	putStrLn "Interrupt. Waiting for running jobs (CTRL-C kills)..."
+	 	flushGang gang
+	 	return jobResults
+
+
+	-- See if any job results have been written to our input channel.
+	go_checkResult jobResults
+ 	 =  (atomically $ isEmptyTChan chanResult) >>= \empty'
+	 -> if empty'
+	     then do
+		gangState	<- getGangState gang
+		if gangState == GangFinished
+
+		 -- No job results in channel, and the gang has finished.
+		 -- We're done, so return to caller.
+		 then do
+			return jobResults
+
+		 -- No job results in channel, but the gang is still running.
+		 -- Spin until something else happens.
+		 else do
+			threadDelay 10000
+			go_start jobResults
+
+	     -- We've got a job result from the input channel.
+	     -- Read the result and pass it off to the result handler.
+	     else do
+		jobResult <- atomically $ readTChan chanResult
+		keepGoing <- handleResult config gang chainsTotal jobResult
+		if keepGoing
+		 then go_start (jobResult : jobResults)
+		 else do
+			killGang gang
+			return (jobResult : jobResults)
+
+
+-- | Handle a job result.
+--   Returns True if the controller should continue, 
+--   or False if we should shut down and return to the caller.
+handleResult :: Config -> Gang -> Int -> Result -> IO Bool
+handleResult config gang chainsTotal
+        (Result chainIx jobIx jobId actionName product')
+ | JobId testName wayName       <- jobId
+ , ProductStatus status _       <- product'
+ = do   let testName2    = fromMaybe testName 
+                                (stripPrefix (configSuppressPrefix config) testName)
+
+        let width        = configFormatPathWidth config
+
+        putStrLn 
+         $ render 
+         $ parens (padR (length $ show chainsTotal)
+                        (ppr $ chainIx) 
+                        <> text "."
+                        <> ppr jobIx
+                        <> text "/" 
+                        <> ppr chainsTotal)
+           <+> padL width (text testName2)
+           <+> padL 5     (text wayName)
+           <+> padL 8     (text actionName)
+           <+> colorizeStatus config actionName (render status)
+
+        hFlush stdout
+        return True
+
+ -- If a file is different than expected in interactive mode,
+ --   then ask the user what to do about it.
+ | ProductDiff fileRef fileOut fileDiff <- product'
+ , configInteractive config
+ = do	
+	putStr	$  "\n"
+		++ "-- Output Differs  -------------------------------------------------------------\n"
+		++ "   expected file: " ++ fileRef	++	"\n"
+		++ "     actual file: " ++ fileOut	++ 	"\n"
+		++ replicate 80 '-' ++ "\n"
+
+	-- Show the difference
+	str	<- readFile fileDiff
+	putStr	str
+	hFlush stdout
+	
+	-- Ask the user what to do about it, 
+	--  and pause the gang while we're waiting for user input
+	pauseGang gang
+	keepGoing	<- handleResult_askDiff fileRef fileOut fileDiff
+	when keepGoing
+	 $ resumeGang gang
+
+	return keepGoing
+
+ -- If a file is different than expected in batch mode,
+ --   then just print the status.
+ | ProductDiff{}        <- product'
+ = handleResult config gang chainsTotal 
+        $ Result chainIx jobIx jobId actionName
+        $ ProductStatus (text "failed") False
+
+ -- Bogus pattern match to avoid warning.
+ | otherwise
+ = error "DDC.War.Interface.Controller.handleResult: no match"
+
+
+handleResult_askDiff :: FilePath -> FilePath -> FilePath -> IO Bool
+handleResult_askDiff fileRef fileOut fileDiff
+ = do	putStr	$  replicate 80 '-' ++ "\n"
+		++ "    (ENTER) continue   (e) show expected    (a) show actual\n"
+		++ "    (CONTROL-C) quit   (u) update expected\n"
+		++ "\n"
+		++ "? "
+
+	hFlush stdout
+	cmd	<- hGetLine stdin
+
+	let result
+		-- continue, ignoring that the test gave a different result.
+		| ""		<- cmd
+		= return True
+
+		-- Print the expected output
+		| ('e': _)	<- cmd
+		= do	str	<- readFile fileRef
+			putStr	$  replicate 80 '-' ++ "\n"
+			putStr	str
+			handleResult_askDiff fileRef fileOut fileDiff
+
+		-- Print the actual output
+		| ('a': _)	<- cmd
+		= do	str	<- readFile fileOut
+			putStr	$  replicate 80 '-' ++ "\n"
+			putStr	str
+			handleResult_askDiff fileRef fileOut fileDiff
+
+		-- Update the expected output with the actual one
+		| ('u': _)	<- cmd
+		= do	System.Cmd.system 
+				$ "cp " ++ fileOut ++ " " ++ fileRef
+
+			return True
+
+		-- Invalid Command
+		| otherwise
+		= do	putStr	 $ "Invalid command.\n"
+			handleResult_askDiff fileRef fileOut fileDiff
+	
+	result
+
+
+colorizeStatus :: Config -> String -> String -> Doc
+colorizeStatus config jobName status
+        | not $ configColoredOutput config
+        = text status
+
+        | isPrefixOf "ok" status
+        = colorDoc [Foreground Green] (text status)
+
+        | isPrefixOf "success" status
+        , jobName == "run"
+        = colorDoc [Foreground Green] (text status)
+
+        | isPrefixOf "success" status
+        , jobName == "compile"
+        = colorDoc [Foreground Blue]  (text status)
+
+        | isPrefixOf "failed"  status
+        = colorDoc [Foreground Red]   (text status)
+
+        | otherwise
+        = text status
+
+
+colorDoc :: [VT100.Mode] -> Doc -> Doc
+colorDoc vmode doc
+        = text 
+        $ concat [ setMode vmode
+                 , render doc
+                 , setMode [Reset] ]
diff --git a/DDC/War/Interface/VT100.hs b/DDC/War/Interface/VT100.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Interface/VT100.hs
@@ -0,0 +1,157 @@
+{-# OPTIONS -fno-warn-missing-signatures #-}
+-- | Generation of VT100 terminal control codes.
+module DDC.War.Interface.VT100 
+        ( Color(..),            codeOfColor
+        , Mode (..),            codeOfMode
+        , ModeState (..),       modeStateInit, applyMode
+        
+        , eraseDisplay
+        , eraseLine
+        , setCursorPos
+        , moveCursorUp
+        , moveCursorDown
+        , moveCursorLeft
+        , moveCursorRight
+        , saveCursorPosition
+        , restoreCursorPosition
+        , setMode )
+where
+import Data.List
+
+-- Color ----------------------------------------------------------------------
+-- | VT100 / ANSI colors
+data Color
+        = Black
+        | Red
+        | Green
+        | Yellow
+        | Blue
+        | Magenta
+        | Cyan
+        | White
+        deriving (Show, Eq, Ord)
+
+-- | Control code offset for rendering colors.
+--      Need to add 30 for foreground, 40 for background to get real code.
+codeOfColor :: Color -> Int
+codeOfColor color
+ = case color of
+        Black   -> 0
+        Red     -> 1
+        Green   -> 2
+        Yellow  -> 3
+        Blue    -> 4
+        Magenta -> 5
+        Cyan    -> 6
+        White   -> 7
+        
+
+-- Mode -----------------------------------------------------------------------
+-- | Rendering mode commands.
+data Mode
+        = Reset
+        | Bold
+        | Dim
+        | Underscore
+        | Blink
+        | Reverse
+        | Concealed
+        | Foreground Color
+        | Background Color
+        deriving (Show, Eq, Ord)
+
+
+-- | Control codes for rendering mode commands.
+codeOfMode :: Mode -> Int
+codeOfMode mode
+ = case mode of
+        Reset                   -> 0
+        Bold                    -> 1
+        Dim                     -> 3
+        Underscore              -> 4
+        Blink                   -> 5
+        Reverse                 -> 7
+        Concealed               -> 8
+        Foreground color        -> 30 + codeOfColor color
+        Background color        -> 40 + codeOfColor color
+
+
+-- ModeState ------------------------------------------------------------------
+-- Model of VT100 terminal state, to help keep track what state
+--      the terminal is in after accepting a string of commands.
+
+data ModeState
+        = ModeState
+        { sBold         :: Bool
+        , sDim          :: Bool
+        , sUnderscore   :: Bool
+        , sBlink        :: Bool
+        , sReverse      :: Bool
+        , sConcealed    :: Bool
+        , sForeground   :: Color
+        , sBackground   :: Color }
+        deriving (Show, Eq)
+        
+modeStateInit
+        = ModeState
+        { sBold         = False
+        , sDim          = False
+        , sUnderscore   = False
+        , sBlink        = False
+        , sReverse      = False
+        , sConcealed    = False
+        , sForeground   = White
+        , sBackground   = Black }
+
+
+applyMode :: ModeState -> Mode -> ModeState
+applyMode state m
+ = case m of
+        Reset           -> modeStateInit
+        Bold            -> state { sBold        = True }
+        Dim             -> state { sDim         = True }
+        Underscore      -> state { sUnderscore  = True }
+        Blink           -> state { sBlink       = True }
+        Reverse         -> state { sReverse     = True }
+        Concealed       -> state { sConcealed   = True }
+        Foreground c    -> state { sForeground  = c }
+        Background c    -> state { sBackground  = c }
+
+
+-- Commands -------------------------------------------------------------------
+
+-- | Erase entire display.
+eraseDisplay            = "\27[2J"
+
+-- | Erase current line of display.
+eraseLine               = "\27[K"
+
+-- | Set the position of the cursor.
+setCursorPos x y        = "\27[" ++ show y ++ ";" ++ show x ++ "H"
+
+-- | Move cursor up some number of lines.
+moveCursorUp n          = "\27[" ++ show n ++ "A"
+
+-- | Move cursor down some number of lines.
+moveCursorDown n        = "\27[" ++ show n ++ "B"
+
+-- | Move cursor right / forward some number of columns.
+moveCursorRight n       = "\27[" ++ show n ++ "C"
+
+-- | Move cursor left / backward some number of columns.
+moveCursorLeft n        = "\27[" ++ show n ++ "D"
+
+-- | Save cursor position 
+saveCursorPosition      = "\27[s"
+
+-- | Restore cursor position
+restoreCursorPosition   = "\27[u"
+
+-- | Set graphics mode
+setMode modes
+        = "\27[" 
+        ++ concat 
+                (intersperse ";" 
+                $ map (show . codeOfMode) modes) 
+        ++ "m"
+        
diff --git a/DDC/War/Job.hs b/DDC/War/Job.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Job.hs
@@ -0,0 +1,70 @@
+{-# LANGUAGE ExistentialQuantification #-}
+{-# OPTIONS -fno-warn-orphans #-}
+module DDC.War.Job where
+import DDC.War.Driver.Base
+import qualified DDC.War.Job.CompileDCE as CompileDCE
+import qualified DDC.War.Job.CompileDS  as CompileDS
+import qualified DDC.War.Job.CompileHS  as CompileHS
+import qualified DDC.War.Job.Diff       as Diff
+import qualified DDC.War.Job.RunDCX     as RunDCX
+import qualified DDC.War.Job.RunExe     as RunExe
+import qualified DDC.War.Job.Shell      as Shell
+import BuildBox.Pretty
+
+
+instance Spec CompileDCE.Spec CompileDCE.Result where
+ specActionName _               = "compile"
+ buildFromSpec                  = CompileDCE.build
+ productOfResult _ result       
+        = ProductStatus (ppr result) (CompileDCE.resultSuccess result)
+
+
+instance Spec CompileDS.Spec  CompileDS.Result where
+ specActionName _               = "compile"
+ buildFromSpec                  = CompileDS.build
+ productOfResult _ result       
+        = ProductStatus (ppr result) (CompileDS.resultSuccess result)
+
+
+instance Spec CompileHS.Spec  CompileHS.Result where
+ specActionName _               = "compile"
+ buildFromSpec                  = CompileHS.build
+ productOfResult _ result
+        = ProductStatus (ppr result) (CompileHS.resultSuccess result)
+
+
+instance Spec Diff.Spec       Diff.Result where
+ specActionName _               = "diff"
+ buildFromSpec                  = Diff.build
+ productOfResult _ result
+  = case result of
+        Diff.ResultSame                 
+         -> ProductStatus (ppr result) True
+
+        Diff.ResultDiff ref out diff    
+         -> ProductDiff ref out diff
+
+
+instance Spec RunDCX.Spec     RunDCX.Result where
+ specActionName _               = "run"
+ buildFromSpec                  = RunDCX.build
+ productOfResult _ result
+        = ProductStatus (ppr result) (RunDCX.resultSuccess result)
+
+
+instance Spec RunExe.Spec     RunExe.Result where
+ specActionName _               = "run"
+ buildFromSpec                  = RunExe.build
+ productOfResult _ result
+        = ProductStatus (ppr result) (RunExe.resultSuccess result)
+
+
+instance Spec Shell.Spec      Shell.Result where
+ specActionName _               = "shell"
+ buildFromSpec                  = Shell.build
+ productOfResult _ result
+        = ProductStatus (ppr result) (Shell.resultSuccess result)
+
+
+
+
diff --git a/DDC/War/Job/CompileDCE.hs b/DDC/War/Job/CompileDCE.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Job/CompileDCE.hs
@@ -0,0 +1,125 @@
+
+module DDC.War.Job.CompileDCE
+        ( Spec  (..)
+        , Result(..)
+        , resultSuccess
+        , build)
+where
+import BuildBox.Command.File
+import BuildBox.Command.System
+import BuildBox.Build.Benchmark
+import BuildBox.Data.Physical
+import BuildBox.IO.Directory
+import BuildBox.Pretty
+import BuildBox
+import System.FilePath
+import Control.Monad
+import Data.List
+
+
+-- | Use ddci-core to compile/make file a DCE file
+data Spec
+        = Spec
+        { -- | Root source file of the program (the 'Main.ds')
+          specFile               :: FilePath 
+                                
+          -- | Scratch dir to do the build in.
+        , specScratchDir         :: String
+
+           -- | Put what DDC says to stdout here.
+        , specCompileStdout      :: FilePath
+                
+           -- | Put what DDC says to stderr here.
+        , specCompileStderr      :: FilePath 
+
+          -- | If Just, then we're making an executable, and put the binary here.
+          --   Otherwise simply compile it
+        , specMaybeMainBin       :: Maybe FilePath 
+                
+          -- | True if the compile is expected to succeed, else not.
+        , specShouldSucceed      :: Bool }
+        deriving Show
+
+
+data Result
+        = ResultSuccess Seconds
+        | ResultUnexpectedFailure
+        | ResultUnexpectedSuccess
+        deriving Show
+
+resultSuccess :: Result -> Bool
+resultSuccess result
+ = case result of
+        ResultSuccess{} -> True
+        _               -> False
+
+
+instance Pretty Result where
+ ppr result
+  = case result of 
+        ResultSuccess seconds   -> text "success" <+> parens (ppr seconds)
+        ResultUnexpectedFailure -> text "failed"
+        ResultUnexpectedSuccess -> text "unexpected"
+
+
+-- Build ----------------------------------------------------------------------
+-- | Compile a Disciple Core Sea source file.
+build :: Spec -> Build Result
+build   (Spec   srcDCE
+                buildDir mainCompOut mainCompErr
+                mMainBin shouldSucceed)
+
+ = do   needs srcDCE
+        needs "bin/ddci-core"
+
+        -- The directory holding the Main.dce file.
+        let (srcDir, _srcFile)  = splitFileName srcDCE
+                
+        -- Touch the .dce files to the build directory to ensure they're built.
+        sources <- io
+                $  liftM (filter (\f -> isSuffixOf ".dce" f))
+                $  lsFilesIn srcDir
+
+        ssystemq $ "touch " ++ (intercalate " " sources)
+
+        -- ensure the output directory exists
+        ensureDir buildDir
+
+        -- Do the compile.
+        let compile
+                | Just mainBin  <- mMainBin
+                = do    -- If there is an existing binary then remove it.
+                        ssystemq $ "rm -f " ++ mainBin
+
+                        -- Build the program.
+                        timeBuild
+                         $ systemTee False 
+                                ("bin/ddci-core"
+                                ++ " -set output "      ++ mainBin
+                                ++ " -set outputdir "   ++ buildDir
+                                ++ " -make "            ++ srcDCE)
+                                ""
+
+                -- Compile the program.
+                | otherwise
+                = do    timeBuild
+                         $ systemTee False
+                                ("bin/ddci-core"
+                                ++ " -set outputdir "   ++ buildDir
+                                ++ " -compile "         ++ srcDCE)
+                                ""
+        (time, (code, strOut, strErr))
+                <- compile
+
+        atomicWriteFile mainCompOut strOut
+        atomicWriteFile mainCompErr strErr
+
+        case code of
+         ExitFailure _
+          | shouldSucceed       -> return ResultUnexpectedFailure
+        
+         ExitSuccess
+          | not shouldSucceed   -> return ResultUnexpectedSuccess
+
+         _                      -> return $ ResultSuccess time
+
diff --git a/DDC/War/Job/CompileDS.hs b/DDC/War/Job/CompileDS.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Job/CompileDS.hs
@@ -0,0 +1,137 @@
+
+module DDC.War.Job.CompileDS
+	( Spec         (..)
+        , Result       (..)
+        , resultSuccess
+        , build)
+where
+import BuildBox.Command.File
+import BuildBox.Command.System
+import BuildBox.Build.Benchmark
+import BuildBox.Data.Physical
+import BuildBox.IO.Directory
+import BuildBox.Pretty
+import BuildBox
+import System.FilePath
+import Control.Monad
+import Data.List
+
+
+-- | Use DDC to compile a source file.
+data Spec
+        = Spec
+        { -- | Root source file of the program (the 'Main.ds')
+          specFile               :: FilePath 
+                
+          -- | Extra DDC options for building in this way.
+        , specOptionsDDC         :: [String] 
+                
+          -- | Extra GHC RTS options for building in this way.
+        , specOptionsRTS         :: [String]
+                
+          -- | Scratch dir to do the build in.
+        , specScratchDir         :: String
+
+          -- | Put what DDC says to stdout here.
+        , specCompileStdout      :: FilePath
+                
+          -- | Put what DDC says to stderr here.
+        , specCompileStderr      :: FilePath 
+
+          -- | If Just, then we're making an executable, and put the binary here.
+          --   Otherwise simply compile it
+        , specMaybeMainBin       :: Maybe FilePath 
+                
+          -- | True if the compile is expected to succeed, else not.
+        , specShouldSucceed      :: Bool }
+        deriving Show
+
+
+data Result
+        = ResultSuccess Seconds
+        | ResultUnexpectedSuccess
+        | ResultUnexpectedFailure
+        deriving Show
+
+resultSuccess :: Result -> Bool
+resultSuccess result
+ = case result of
+        ResultSuccess{} -> True
+        _               -> False
+
+
+instance Pretty Result where
+ ppr result 
+  = case result of
+        ResultSuccess seconds    -> text "success" <+> parens (ppr seconds)
+        ResultUnexpectedFailure  -> text "failed"
+        ResultUnexpectedSuccess  -> text "unexpected"
+
+
+-- | Compile a Disciple source file.
+build :: Spec -> Build Result
+build (Spec     srcDS optionsDDC optionsRTS
+		buildDir mainCompOut mainCompErr
+		mMainBin shouldSucceed)
+
+ = do	needs srcDS
+        needs "bin/ddc"
+	
+	-- The directory holding the Main.ds file.
+	let (srcDir, _srcFile)	= splitFileName srcDS
+		
+	-- Touch the .ds files to the build directory to ensure they're built.
+	sources	<- io
+		$  liftM (filter (\f -> isSuffixOf ".ds" f || isSuffixOf ".build" f))
+		$  lsFilesIn srcDir
+
+	ssystemq $ "touch " ++ (intercalate " " sources)
+
+	-- ensure the output directory exists
+	ensureDir buildDir
+
+	-- Do the compile.
+	let compile
+		| Just mainBin	<- mMainBin
+		= do	
+			-- If there is an existing binary then remove it.
+			ssystemq $ "rm -f " ++ mainBin
+
+			-- Build the program.
+	 		timeBuild 
+	 		 $ systemTee False 
+				("bin/ddc"
+				++ " -v -make "	  ++ srcDS
+				++ " -o "	  ++ mainBin
+				++ " -outputdir " ++ buildDir
+				++ " " 		  ++ intercalate " " optionsDDC
+				++ " +RTS "	  ++ intercalate " " optionsRTS)
+				""
+
+
+		-- Compile the program.
+		| otherwise
+		= do	timeBuild
+	 		 $ systemTee False
+				("bin/ddc"
+				++ " -c "	  ++ srcDS
+				++ " -outputdir " ++ buildDir
+				++ " " 		  ++ intercalate " " optionsDDC
+				++ " +RTS "	  ++ intercalate " " optionsRTS)
+				""
+
+	(time, (code, strOut, strErr))
+		<- compile
+	
+        atomicWriteFile mainCompOut strOut
+        atomicWriteFile mainCompErr strErr
+
+        case code of
+         ExitFailure _
+          | shouldSucceed       -> return ResultUnexpectedFailure
+        
+         ExitSuccess
+          | not shouldSucceed   -> return ResultUnexpectedSuccess
+
+         _                      -> return $ ResultSuccess time
+
diff --git a/DDC/War/Job/CompileHS.hs b/DDC/War/Job/CompileHS.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Job/CompileHS.hs
@@ -0,0 +1,119 @@
+
+module DDC.War.Job.CompileHS
+	( Spec         (..)
+        , Result       (..)
+        , resultSuccess
+        , build)
+where
+import BuildBox.Command.File
+import BuildBox.Command.System
+import BuildBox.Build.Benchmark
+import BuildBox.Data.Physical
+import BuildBox.IO.Directory
+import BuildBox.Pretty
+import BuildBox
+import System.FilePath
+import System.Directory
+import Control.Monad
+import Data.List
+
+
+-- | Use GHC to compile/make file.
+data Spec
+        = Spec 
+        { -- | Root source file of the program (the 'Main.ds')
+          specFile               :: FilePath 
+                
+          -- | Extra DDC options for building in this way.
+        , specOptionsGHC         :: [String] 
+                                
+          -- | Scratch dir to do the build in.
+        , specScratchDir         :: String
+
+          -- | Put what DDC says to stdout here.
+        , specCompileStdout      :: FilePath
+                
+          -- | Put what DDC says to stderr here.
+        , specCompileStderr      :: FilePath 
+
+          -- | If Just, then we're making an executable, and put the binary here.
+          --   Otherwise simply compile it
+        , specMainBin            :: FilePath }
+        deriving Show
+
+
+data Result
+        = ResultSuccess Seconds
+        | ResultFailure
+        deriving Show
+
+resultSuccess :: Result -> Bool
+resultSuccess result
+ = case result of
+        ResultSuccess{} -> True
+        _               -> False
+
+
+instance Pretty Result where
+ ppr result 
+  = case result of
+        ResultSuccess seconds   -> text "success" <+> parens (ppr seconds)
+        ResultFailure           -> text "failed"
+
+
+-- | Compile a Haskell Source File
+build :: Spec -> Build Result
+build  (Spec    srcHS _optionsGHC
+		buildDir mainCompOut mainCompErr
+		mainBin)
+
+ = do	needs srcHS
+	
+	-- The directory holding the Main.hs file.
+	let (srcDir, srcFile)	= splitFileName srcHS
+		
+	-- Copy the .hs files to the build directory.
+	-- This freshens them and ensures we won't conflict with other make jobs
+	-- running on the same source files, but in different ways.
+	ensureDir buildDir
+	sources	<- io
+		$  liftM (filter (\f -> isSuffixOf ".hs" f))
+		$  lsFilesIn srcDir
+
+	_ <- ssystemq $ "cp " ++ (intercalate " " sources) ++ " " ++ buildDir
+
+	-- The copied version of the root source file.
+	let srcCopyHS	= buildDir ++ "/" ++ srcFile
+	let buildMk	= buildDir ++ "/build.mk"
+	
+	-- We're doing this via a makefile so we get the ghc flags and options
+	-- from the DDC build system. The paths in the make file need to be in
+	-- posix form with '/' as the separators.
+	currentDir	<- io $ getCurrentDirectory
+	let hacks f	= map (\z -> if z == '\\' then '/' else z) f
+	let mainBin'	= hacks $ makeRelative currentDir mainBin
+	let srcCopyHS'	= hacks $ makeRelative currentDir srcCopyHS
+	
+	genBuildMk buildMk mainBin' srcCopyHS'
+	
+	(time, (code, strOut, strErr))
+	  <- timeBuild
+	  $  systemTee False
+		("make -f " ++ buildMk)
+		""
+	atomicWriteFile mainCompOut strOut
+	atomicWriteFile mainCompErr strErr
+
+        case code of
+         ExitSuccess    -> return $ ResultSuccess time
+         ExitFailure _  -> return ResultFailure
+		
+
+genBuildMk :: FilePath -> String -> String -> Build ()
+genBuildMk outfile mainBin mainHs
+ = do	let str	= "# Generated Makefile\n\n"
+ 		++ "include make/build.mk\n\n"
+                ++ mainBin ++ " : " ++ mainHs ++ "\n"
+                ++ "\t$(GHC) $(GHC_LANGUAGE) $(DDC_PACKAGES) -ipackages/ddc-main --make $^ -o $@\n\n"
+	io $ writeFile outfile str
+
diff --git a/DDC/War/Job/Diff.hs b/DDC/War/Job/Diff.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Job/Diff.hs
@@ -0,0 +1,72 @@
+
+module DDC.War.Job.Diff
+	( Spec         (..)
+        , Result       (..)
+        , resultSuccess 
+        , build)
+where
+import BuildBox.Command.File
+import BuildBox.Command.System
+import BuildBox.Pretty
+import BuildBox
+
+
+-- | Diff two files.
+data Spec
+        = Spec
+        { -- | The baseline file.
+          specFile       :: FilePath 
+                
+          -- | File produced that we want to compare with the baseline.
+        , specFileOut    :: FilePath 
+                
+          -- | Put the result of the diff here.
+        , specFileDiff   :: FilePath }
+        deriving Show
+
+
+data Result
+        = ResultSame
+
+        | ResultDiff 
+        { resultFileRef  :: FilePath
+        , resultFileOut  :: FilePath
+        , resultFileDiff :: FilePath }
+
+
+resultSuccess :: Result -> Bool
+resultSuccess result
+ = case result of
+        ResultSame{}    -> True
+        _               -> False
+
+
+instance Pretty Result where
+ ppr result
+  = case result of
+        ResultSame      -> text "ok"
+        ResultDiff{}    -> text "diff"
+
+
+
+-- | Compare two files for differences.
+build :: Spec -> Build Result
+build (Spec fileRef fileOut fileDiff)
+ = do   needs fileRef
+	needs fileOut
+	
+	let diffExe	= "diff"
+	
+	-- Run the binary.
+	(_code, strOut, _strErr)
+	 <- systemTee False 
+	 	(diffExe ++ " " ++ fileRef ++ " " ++ fileOut)
+		""
+	
+	-- Write its output to file.
+	atomicWriteFile fileDiff strOut
+
+	if strOut == ""
+	 then return $ ResultSame
+	 else return $ ResultDiff fileRef fileOut fileDiff
+
diff --git a/DDC/War/Job/RunDCX.hs b/DDC/War/Job/RunDCX.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Job/RunDCX.hs
@@ -0,0 +1,78 @@
+
+module DDC.War.Job.RunDCX
+	( Spec    (..)
+        , Result  (..)
+        , resultSuccess
+        , build)
+where
+import BuildBox.Command.File
+import BuildBox.Command.System
+import BuildBox.Build.Benchmark
+import BuildBox.Data.Physical
+import BuildBox.Pretty
+import BuildBox
+import System.Directory
+
+
+-- | Feed a file into DDCi-core        
+data Spec
+        = Spec
+        { -- | Root source file of the program (the 'Main.ds')
+          specFile               :: FilePath 
+                
+          -- | Scratch dir to do the build in.
+        , specScratchDir         :: String
+
+          -- | Put what DDC says to stdout here.
+        , specCompileStdout      :: FilePath
+                
+          -- | Put what DDC says to stderr here.
+        , specCompileStderr      :: FilePath }
+        deriving Show
+
+
+data Result
+        = ResultSuccess Seconds
+        | ResultFailure
+        deriving Show
+
+
+resultSuccess :: Result -> Bool
+resultSuccess result
+ = case result of
+        ResultSuccess{} -> True
+        _               -> False
+
+
+instance Pretty Result where
+ ppr result 
+  = case result of
+        ResultSuccess seconds   -> text "success" <+> parens (ppr seconds)
+        ResultFailure           -> text "failed"
+
+
+-- | Compile a Haskell Source File
+build :: Spec -> Build Result
+build (Spec     srcDCX
+		buildDir testRunStdout testRunStderr)
+
+ = do	needs srcDCX
+        needs "bin/ddci-core"
+
+	-- ensure the output directory exists
+	ensureDir buildDir
+
+	ddciBin' <- io $ canonicalizePath "bin/ddci-core"
+
+	(time, (code, strOut, strErr))
+	  <- timeBuild
+	  $  systemTee False
+		(ddciBin' ++ " --batch " ++ srcDCX)
+		""
+	atomicWriteFile testRunStdout strOut
+	atomicWriteFile testRunStderr strErr
+
+        case code of
+         ExitSuccess    -> return $ ResultSuccess time
+         _              -> return $ ResultFailure
+
diff --git a/DDC/War/Job/RunExe.hs b/DDC/War/Job/RunExe.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Job/RunExe.hs
@@ -0,0 +1,86 @@
+
+module DDC.War.Job.RunExe
+	( Spec         (..)
+        , Result       (..)
+        , resultSuccess
+        , build)
+where
+import BuildBox.Build.Benchmark
+import BuildBox.Command.File
+import BuildBox.Command.System
+import BuildBox.Data.Physical
+import BuildBox.Pretty
+import BuildBox
+import Data.List
+
+-- | Run a binary.
+data Spec
+        = Spec
+        { -- | The main source file this binary was built from.
+          specFileSrc    :: FilePath
+
+          -- | Binary to run.
+        , specFileBin    :: FilePath 
+        
+          -- | Command line arguments to pass.
+        , specCmdArgs    :: [String]
+
+          -- | Put what binary said on stdout here.
+        , specRunStdout  :: FilePath
+                
+          -- | Put what binary said on stderr here.
+        , specRunStderr  :: FilePath 
+
+          -- | True if we expect the executable to succeed.
+        , specShouldSucceed :: Bool }
+        deriving Show
+
+
+data Result
+        = ResultSuccess Seconds
+        | ResultUnexpectedFailure
+        | ResultUnexpectedSuccess
+        deriving Show
+
+
+resultSuccess :: Result -> Bool
+resultSuccess result
+ = case result of
+        ResultSuccess{} -> True
+        _               -> False
+
+
+instance Pretty Result where
+ ppr result 
+  = case result of
+        ResultSuccess seconds   -> text "success" <+> parens (ppr seconds)
+        ResultUnexpectedFailure -> text "failed"
+        ResultUnexpectedSuccess -> text "unexpected"
+
+
+-- | Run a binary
+build :: Spec -> Build Result
+build (Spec     _fileName
+		mainBin args 
+                mainRunOut mainRunErr
+                shouldSucceed)
+ = do	
+        needs mainBin
+ 
+	-- Run the binary.
+	(time, (code, strOut, strErr))
+	 <- timeBuild
+	 $  systemTee False (mainBin ++ " " ++ intercalate " " args) ""
+
+	-- Write its output to files.
+	atomicWriteFile mainRunOut strOut
+	atomicWriteFile mainRunErr strErr
+	
+        case code of
+         ExitFailure _
+          | shouldSucceed       -> return ResultUnexpectedFailure
+
+         ExitSuccess    
+          | not shouldSucceed   -> return ResultUnexpectedSuccess
+
+         _                      -> return $ ResultSuccess time
diff --git a/DDC/War/Job/Shell.hs b/DDC/War/Job/Shell.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Job/Shell.hs
@@ -0,0 +1,89 @@
+
+module DDC.War.Job.Shell
+	( Spec     (..)
+        , Result   (..)
+        , resultSuccess
+        , build)
+where
+import BuildBox.Command.File
+import BuildBox.Command.System
+import BuildBox.Build.Benchmark
+import BuildBox.Data.Physical
+import BuildBox.Pretty
+import BuildBox
+
+
+-- | Run a shell script.
+data Spec
+        = Spec
+        { -- | Shell script to run
+          specShellSource        :: FilePath
+
+          -- | Source dir that the script is in.
+        , specSourceDir          :: FilePath
+
+          -- | Scratch dir that the script can write files to.
+        , specScratchDir         :: FilePath
+
+          -- | Put what DDC says to stdout here.
+        , specShellStdout        :: FilePath
+                
+          -- | Put what DDC says to stderr here.
+        , specShellStderr        :: FilePath 
+
+          -- | True if the compile is expected to succeed, else not.
+        , specShouldSucceed      :: Bool }
+        deriving Show
+
+
+data Result
+        = ResultSuccess Seconds
+        | ResultUnexpectedSuccess
+        | ResultUnexpectedFailure
+        deriving Show
+
+
+resultSuccess :: Result -> Bool
+resultSuccess result
+ = case result of
+        ResultSuccess{} -> True
+        _               -> False
+
+
+instance Pretty Result where
+ ppr result 
+  = case result of
+        ResultSuccess seconds   -> text "success" <+> parens (ppr seconds)
+        ResultUnexpectedFailure -> text "failed"
+        ResultUnexpectedSuccess -> text "unexpected"
+
+
+-- | Run a binary
+build :: Spec -> Build Result
+build   (Spec   mainSH sourceDir scratchDir
+		mainRunOut mainRunErr
+		shouldSucceed)
+ = do	
+        needs mainSH
+	ensureDir scratchDir
+	
+	-- Run the binary.
+	(time, (code, strOut, strErr))
+	 <- timeBuild
+	 $  systemTee False 
+		("sh " ++ mainSH ++ " " ++ sourceDir ++ " " ++ scratchDir) 
+		""
+		
+	-- Write its output to files.
+	atomicWriteFile mainRunOut strOut
+	atomicWriteFile mainRunErr strErr
+		
+        case code of
+         ExitSuccess
+          | shouldSucceed       -> return $ ResultSuccess time
+          | otherwise           -> return ResultUnexpectedSuccess
+
+         ExitFailure _
+          | shouldSucceed       -> return ResultUnexpectedFailure
+          | otherwise           -> return $ ResultSuccess time
+
diff --git a/DDC/War/Option.hs b/DDC/War/Option.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Option.hs
@@ -0,0 +1,169 @@
+
+module DDC.War.Option
+        (parseOptions)
+where
+import DDC.War.Create.Way
+import DDC.War.Config
+import Data.Char
+import DDC.War.Task.Test                as T
+import DDC.War.Task.Nightly             as N
+import qualified BuildBox.Command.Mail  as B
+import qualified BuildBox.Data.Schedule as B
+
+
+parseOptions :: [String] -> Config -> IO Config
+parseOptions [] _       
+ = do   printUsage Nothing
+        error "Nothing to do..."
+
+parseOptions args _
+ | elem "-help" args || elem "--help" args
+ = do   printUsage Nothing
+        error "Nothing to do..."
+
+parseOptions args0 config0
+ = return $ eat args0 config0
+ where
+  eat [] config = config
+  eat args@(arg : rest) config
+        | elem arg ["-d", "-debug"]
+        = eat rest $ config { configDebug = True }
+
+        | "-nightly" : dir : more     <- args
+        = case eatn more defaultNightlySpec of
+           Left  badArg  -> error $ "Invalid argument " ++ show badArg
+           Right nspec   -> config { configNightly = Just (nspec { specLocalBuildDir = Just dir }) }
+
+        | otherwise
+        = case eatt args defaultTestSpec of
+           Left  badArg  -> error $ "Invalid argument " ++ show badArg
+           Right tspec   -> config { configTest    = Just tspec }
+
+
+  -- Parse options for test mode
+  eatt [] spec = Right spec
+  eatt args@(arg : rest) spec
+        | elem arg ["-b", "-batch"]
+        = eatt rest $ spec { T.specInteractive = False }
+
+        | "-j" : sThreads : more     <- args
+        , all isDigit sThreads
+        = eatt more $ spec { T.specThreads       = read sThreads}
+
+        | "-results" : file : more <- args
+        = eatt more $ spec { T.specResultsFileAll    = Just file }
+
+        | "-results-failed" : file : more <- args
+        = eatt more $ spec { T.specResultsFileFailed = Just file }
+
+        | "+compway" : name : flags  <- args
+        , (wayFlags, more)           <- break (\x -> take 1 x == "+") flags
+        = eatt more $ spec { T.specWays = T.specWays spec ++ [Way name wayFlags []] }
+
+        | "+runway"  : name : flags  <- args
+        , (wayFlags, more)           <- break (\x -> take 1 x == "+") flags
+        = eatt more $ spec { T.specWays = T.specWays spec ++ [Way name [] wayFlags] }
+
+        | '-' : _       <- arg
+        = Left arg
+
+        -- Accept dirs for test mode
+        | otherwise
+        = eatt rest  $ spec { specTestDirs  = specTestDirs spec ++ [arg]}
+
+
+  -- Parse options for nightly mode.
+  eatn [] spec = Right spec
+  eatn args@(_arg:_rest) spec
+        | "-build-dir" : dir : more     <- args
+        = eatn more $ spec { N.specLocalBuildDir = Just dir }
+
+        | "-daily" : time : more   <- args
+        = eatn more $ spec { N.specContinuous = Just $ B.Daily $ read time }
+
+        | "-now" : more                 <- args
+        = eatn more $ spec { N.specNow = True }
+
+        | "-cleanup" : more             <- args
+        = eatn more $ spec { N.specCleanup = True }
+
+        | "-sendmail" : more            <- args
+        = eatn more $ spec { N.specMailer   = Just $ B.MailerSendmail "sendmail" [] }
+
+        | "-msmtp" : port : more        <- args
+        = eatn more $ spec { N.specMailer   = Just $ B.MailerMSMTP "msmtp" (Just $ read port) }
+
+        | "-mail-from" : addr : more    <- args
+        = eatn more $ spec { N.specMailFrom = Just addr }
+
+        | "-mail-to" : addr : more      <- args
+        = eatn more $ spec { N.specMailTo   = Just addr }
+
+        | "-log-userhost" : str : more  <- args
+        = eatn more $ spec { N.specLogUserHost = Just str }
+
+        | "-log-remote-dir" : dir : more <- args
+        = eatn more $ spec { N.specLogRemoteDir = Just dir }
+
+        | "-log-remote-url" : url : more <- args
+        = eatn more $ spec { N.specLogRemoteURL = Just url }
+
+        | "-build-threads" : threads : more <- args
+        , all isDigit threads
+        , t     <- read threads
+        , t > 0
+        = eatn more $ spec { N.specBuildThreads = t}
+
+  eatn (arg : _) _
+        = Left arg
+
+
+printUsage :: Maybe String -> IO ()
+printUsage badArg
+   = putStr 
+   $ unlines
+   $ maybe [] (\arg -> ["invalid argument " ++ arg]) badArg
+   ++   [ "Usage: war [flags]"
+        , "  -help                         Display this help."
+        , "  -debug, -d                    Emit debugging info for the war test driver."
+        , "  -nightly <DIR>                Become the nightly buildbot in this directory."
+        , ""
+        , " Test mode:     war <TESTDIR> ..."
+        , "  -batch, -b                    Don't interactively ask what to do if a test fails."
+        , "  -j <INT>                      Set number of threads (jobs) to use." 
+        , "  -results        <FILE>        Log test results to this file."
+        , "  -results-failed <FILE>        Log failed tests to this file."
+        , "  +compway <NAME> [OPTIONS]     Also compile with these DDC options."
+        , "  +runway  <NAME> [OPTIONS]     Also run executables with these RTS options."
+        , ""
+        , " Buildbot mode: war -nightly <DIR> [flags] ..."
+        , "  -cleanup                      Delete build dir after a successful build."
+        , "  -daily          <HH:MM:SS>    Build every day at this time (in UTC)"
+        , "  -now                           ... and also do a build right now."
+        , ""
+        , "  -sendmail                     (*) Use   sendmail to report build results"
+        , "  -msmtp          <PORT>           (or) msmtp instead"
+        , "  -mail-from      <ADDRESS>     (*) ... and send from this address"
+        , "  -mail-to        <ADDRESS>     (*) ... to this address."
+        , ""
+        , "  -log-userhost <USER@HOSTNAME> (*) Use 'scp' to copy logs to this server"
+        , "  -log-remote-dir <DIR>         (*) ... to this remote directory"
+        , "  -log-remote-url <URL>         (*) ... with the logs appearing at this public URL."
+        , ""
+        , "  -ddc-snapshot   <URL>         (*) Download DDC snapshot from this URL."
+        , "  -ddc-repository <URL>         (*) Update snapshot with this repository."
+        , "  -build-threads  <INT>         (*) Threads to use when building DDC."
+        , ""
+        , "  (*) Defaults are:"
+        , "   -sendmail"
+        , "   -mail-from      \"DDC Buildbot <overlord@ouroborus.net>\""
+        , "   -mail-to        disciple-cafe@googlegroups.com"
+        , "   -log-userhost   overlord@ouroborus.net"
+        , "   -log-remote-dir log/desire/ddc/head"
+        , "   -log-remote-url http://log.ouroborus.net/desire/ddc/head"
+        , "   -ddc-snapshot   http://code.ouroborus.net/ddc/snapshot/ddc-head-latest.tgz"
+        , "   -ddc-repository http://code.ouroborus.net/ddc/ddc-head"
+        , "   -build-threads   1"
+        , ""
+        ]
+
diff --git a/DDC/War/Task/Nightly.hs b/DDC/War/Task/Nightly.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Task/Nightly.hs
@@ -0,0 +1,279 @@
+{-# OPTIONS -fno-warn-unused-matches #-}
+-- | Run the nightly DDC build.
+module DDC.War.Task.Nightly
+        ( Spec   (..)
+        , Result (..)
+        , build)
+where
+import BuildBox.Command.Mail
+import BuildBox.Command.System
+import BuildBox.Command.File
+import BuildBox.Command.Environment
+import BuildBox.Control.Cron
+import BuildBox.Pretty
+import BuildBox.Build
+import BuildBox.Time
+import BuildBox                 as BuildBox
+import Control.Monad
+import Data.Char
+
+import System.FilePath.Posix    as Remote
+import System.FilePath          as Local
+
+
+-- Spec -----------------------------------------------------------------------
+data Spec
+        = Spec
+        {  -- | Use this scratch directory to perform the build.
+          specLocalBuildDir     :: Maybe FilePath 
+
+          -- | URL of DDC snapshot.tgz
+        , specRemoteSnapshotURL :: String
+
+          -- | URL of DDC repository, used to update the snapshot.
+        , specRemoteRepoURL     :: String 
+
+          -- | Number of threads to use when building.
+        , specBuildThreads      :: Int
+
+          -- | Cleanup after build
+        , specCleanup           :: Bool
+
+          -- | If set build continuously
+        , specContinuous        :: Maybe When
+
+          -- | If we're doing a continuous build, also build once on startup
+        , specNow               :: Bool
+
+
+          -- | Mailer to use to send build results,
+          --   or Nothing if to not send mail.
+        , specMailer            :: Maybe Mailer
+
+          -- | Send mail from this address.
+        , specMailFrom          :: Maybe String 
+
+          -- | Send mail to this address.
+        , specMailTo            :: Maybe String 
+
+
+          -- | User and host name to copy logs to eg 'overlord@deluge.ouroborus.net'
+        , specLogUserHost       :: Maybe String
+
+          -- | Copy logs into this directory on the server
+        , specLogRemoteDir      :: Maybe String
+
+          -- | HTTP address of where the above logs appear
+        , specLogRemoteURL      :: Maybe String }
+
+
+        deriving Show
+
+
+-- Result ---------------------------------------------------------------------
+data Result
+        = ResultSuccess
+        | ResultFailure
+
+
+instance Pretty Result where
+ ppr result
+  = case result of
+        ResultSuccess   -> text "success"
+        ResultFailure   -> text "failure"
+
+
+-- Build ----------------------------------------------------------------------
+-- | Run the nightly DDC build.
+build :: Spec -> Build Result
+build spec
+ = case specContinuous spec of
+        -- Do a one-shot build.
+        Nothing         
+         -> buildCatch spec
+
+        -- Continuous build
+        Just whence 
+         -> do  outLn $ "* Starting cron loop"
+                cronLoop 
+                 $  makeSchedule 
+                        [ ("nightly"
+                          , whence
+                          , if specNow spec 
+                                then Just Immediate 
+                                else Nothing
+                          , buildCatch spec >> return ()) ]
+
+                -- The cron loop will quit if the build died and we couldn't
+                -- send a failure message. If that happens then give up completely.
+                return ResultFailure
+
+
+buildCatch :: Spec -> Build Result
+buildCatch spec
+ = BuildBox.catch 
+        (buildProject spec) 
+        (\err -> do
+                postFailure spec err
+                return  ResultFailure)
+
+
+buildProject :: Spec -> Build Result
+buildProject spec
+ = do   
+        strTime         <- io $ getStampyTime
+        let Just localBuildDir = specLocalBuildDir spec 
+        let buildDir           = localBuildDir Local.</> strTime
+
+        let urlSnapshot  = specRemoteSnapshotURL spec
+        let urlRepo      = specRemoteRepoURL     spec
+        let buildThreads = specBuildThreads      spec
+
+        ensureDir buildDir
+        inDir     buildDir
+         $ do 
+                outLn "* Creating log directory"
+                ensureDir "log"
+                
+                outLn "* Downloading snapshot"
+                (getOut, getErr) <- ssystem $ "wget --progress=bar " ++ urlSnapshot
+                io $ writeFile "log/01-wget.stdout" getOut
+                io $ writeFile "log/01-wget.stderr" getErr
+
+                needs (takeFileName urlSnapshot)
+                outLn "* Unpacking snapshot"
+                ssystem $ "tar zxf " ++ takeFileName urlSnapshot
+
+                inDir "ddc-head"
+                 $ do
+                        outLn "* Updating shapshot"
+                        (darcsOut, darcsErr) <- ssystem $ "darcs pull -av " ++ urlRepo
+                        io $ writeFile "../log/02-darcs.stdout" darcsOut
+                        io $ writeFile "../log/02-darcs.stderr" darcsErr
+
+                        outLn "* Writing build config"
+                        needs "make"
+                        io $ writeFile "make/config-override.mk" 
+                           $ unlines ["THREADS = " ++ show buildThreads]
+
+                        outLn "* Building project"
+                        needs "Makefile"
+                        (makeOut, makeErr) <- ssystem $ "make nightly"
+                        io $ writeFile "../log/03-make.stdout" makeOut
+                        io $ writeFile "../log/03-make.stderr" makeErr
+
+                        outLn "* Copying results"
+                        needs "war.results"
+                        needs "war.failed"
+                        ssystem "cp war.results ../log"
+                        ssystem "cp war.failed  ../log"
+
+                        return ()
+
+                -- Copy logs to remote host.
+                copyLogs spec strTime
+
+                -- Send mail reporting build success including failed tests.
+                strFailed       <- io $ readFile "log/war.failed"
+                postSuccess spec strTime strFailed
+
+        when (specCleanup spec)
+         $ do   outLn "* Cleaning up"
+                clobberDir buildDir
+
+        return ResultSuccess
+
+
+-- | Copy logs to the remote host
+copyLogs :: Spec -> String -> Build ()
+copyLogs spec strTime
+ | Just userHost <- specLogUserHost  spec
+ , Just logDir   <- specLogRemoteDir spec
+ = do   
+        outLn $ "* Copying logs to " ++ userHost
+
+        let dir = logDir Remote.</> strTime
+
+        -- Make the destination directory.
+        sesystemq $ "ssh " ++ userHost ++ " mkdir -p " ++ dir
+
+        -- Copy the files.
+        sesystemq $ "scp -r log/* " ++ userHost ++ ":" ++ dir
+
+        return ()
+
+ | otherwise    = return ()
+
+
+-- | Send mail reporting build success.
+postSuccess :: Spec -> String -> String -> Build ()
+postSuccess spec strTime strFailed
+ | Just mailer          <- specMailer   spec
+ , Just addrFrom        <- specMailFrom spec
+ , Just addrTo          <- specMailTo   spec
+ = do   
+        outLn "* Reporting build success"
+
+        -- The overall build succeeded, but some tests might have failed.
+        let nFailed     =  length $ lines strFailed
+
+        -- Create message subject.
+        hostId          <- getHostId
+        let subject     
+                | nFailed == 0  
+                = "DDC Build Success (" ++ hostId ++ ")"
+
+                | otherwise     
+                = "DDC Build Success (" ++ hostId ++ ") with " ++ show nFailed ++ " failed tests"
+
+        -- Create message body.
+        let strLog      
+                = case specLogRemoteURL spec of
+                   Nothing  -> ""
+                   Just url -> "Logs are at: " ++ url ++ "/" ++ strTime ++ "\n\n"
+
+        let body        = strLog ++ strFailed
+
+        -- Send the mail
+        mail    <- createMailWithCurrentTime addrFrom addrTo subject body
+
+        sendMailWithMailer mail mailer
+
+-- don't send mail.
+postSuccess _ _ _       = return ()
+
+
+-- | Send mail reporting build failure.
+postFailure :: Spec -> BuildError -> Build ()
+postFailure spec err
+ | Just mailer          <- specMailer   spec
+ , Just addrFrom        <- specMailFrom spec
+ , Just addrTo          <- specMailTo   spec
+ = do   
+        outLn "* Reporting build failure"
+
+        -- Create message subject.
+        hostId          <- getHostId
+        let subject     =  "DDC Build FAILURE (" ++ hostId ++ ")"
+
+        -- Create message body.
+        let body        = render $ ppr err
+
+        -- Send the mail
+        mail    <- createMailWithCurrentTime addrFrom addrTo subject body
+
+        sendMailWithMailer mail mailer
+
+-- don't send mail
+postFailure spec strFailed = return ()
+
+
+-- | Build a string identifying this host
+getHostId :: Build String
+getHostId
+ = do
+        hostName        <- liftM (takeWhile (not . (==) '.')) $ getHostName
+        machName        <- liftM (takeWhile (not . isSpace))  $ sesystemq "uname -m"
+        osName          <- liftM (takeWhile (not . isSpace))  $ sesystemq "uname"
+        return $ hostName ++ "." ++ machName ++ "." ++ osName
+
diff --git a/DDC/War/Task/Test.hs b/DDC/War/Task/Test.hs
new file mode 100644
--- /dev/null
+++ b/DDC/War/Task/Test.hs
@@ -0,0 +1,200 @@
+
+module DDC.War.Task.Test
+        ( Spec          (..)
+        , Result        (..)
+        , build)
+where
+import DDC.War.Create
+
+import qualified DDC.War.Interface.Controller   as Controller
+
+import DDC.War.Driver                           (Chain(..))
+import qualified DDC.War.Driver                 as Driver
+import qualified DDC.War.Driver.Gang            as Driver
+
+import BuildBox.Control.Gang
+import BuildBox.IO.Directory
+import BuildBox.Pretty
+import BuildBox
+
+import System.Directory
+import Control.Concurrent
+import Control.Concurrent.STM.TChan
+import Control.Monad
+import Control.Monad.STM
+import Control.Exception
+import Data.List
+
+import qualified Data.Sequence          as Seq
+import qualified Data.Foldable          as Seq
+import qualified Data.Set               as Set
+import qualified Data.Traversable       as Seq
+
+
+-- | Run regression tests.
+data Spec
+        = Spec
+        { -- | Start looking for tests from these directories.
+          specTestDirs          :: [FilePath]
+
+          -- | Ways to run each test.
+        , specWays              :: [Way]
+
+          -- | Number of concurrent threads to use.
+        , specThreads           :: Int
+
+          -- | Ask user what to do about unexpected test outputs interactively.
+        , specInteractive       :: Bool 
+
+          -- | Pad test names out to this column width in log files.
+        , specFormatPathWidth   :: Int
+
+          -- | Write all test results to this file.
+        , specResultsFileAll    :: Maybe FilePath
+
+          -- | Write only failed test results to this file
+        , specResultsFileFailed :: Maybe FilePath }
+        deriving Show
+
+
+data Result
+        = ResultSuccess
+        deriving Show
+
+
+instance Pretty Result where
+ ppr result
+  = case result of
+        ResultSuccess   -> text "success"
+
+
+build :: Spec -> Build Result
+build spec
+ = do
+        currentDir      <- io $ getCurrentDirectory
+
+        -- All the starting test directories from the command line.
+        testDirs       <- io $ mapM (makeRelativeToCurrentDirectory <=< canonicalizePath)
+                        $ specTestDirs spec
+
+        -- Trace all the files reachable from these directories.
+        testFilesRaw   <- io $ liftM (join . Seq.fromList)
+                        $  mapM traceFilesFrom testDirs
+                
+        -- Canonicalize all the paths and put them in a set (which sorts them)
+        testFilesSet   <- io $ liftM (Set.fromList . Seq.toList)
+                        $  Seq.mapM canonicalizePath
+                        $  testFilesRaw
+
+        -- Skip over files with 'skip' in the path,
+        --  and don't decend into our own build dirs.
+        let testFilesSorted
+                = filter (not . isInfixOf "skip-")
+                $ filter (not . isInfixOf "-skip")
+                $ filter (not . isInfixOf "war-")
+                $ Set.toList testFilesSet
+
+        let testFilesSortedSet
+                = Set.fromList testFilesSorted
+
+        -- Create test chains based on the files we have.
+        let ways'
+                = case specWays spec of
+                   []   -> [Way "std" [] []]
+                   ways -> ways
+
+        let chains :: [Chain]
+            chains = concat 
+                [ concat $ map (\way -> create way testFilesSortedSet file) ways'
+                | file <- testFilesSorted]
+
+        -- Suppress this prefix from the front of test names when we display them.
+        let prefix = currentDir ++ "/"
+
+        -- Run all the chains.
+        results <- io $ runChainsWithControllerIO prefix spec chains
+        
+        -- Write all results to file if we were asked for it
+        let chainsTotal = length chains
+        let pathWidth   = specFormatPathWidth spec
+        let pprResult   = render . Driver.prettyResult chainsTotal prefix pathWidth
+        (case specResultsFileAll spec of
+          Nothing       -> return ()
+          Just file     -> io   $ writeFile file 
+                                $ unlines 
+                                $ map pprResult
+                                $ results)
+
+        -- Write failed results to file if we were asked for it.
+        let wasSuccess p
+                = case p of
+                        Driver.ProductStatus _ True     -> True
+                        _                               -> False
+        (case specResultsFileFailed spec of 
+          Nothing       -> return ()
+          Just file     -> io   $ writeFile file
+                                $ unlines
+                                $ map pprResult
+                                $ filter (not . wasSuccess . Driver.resultProduct)
+                                $ results)
+
+        return ResultSuccess
+
+
+-- | Fork threads to run job chains.
+--      We display test results interactivly on the console,
+--      as well as allowing the user to interrupt by pressing ENTER.
+--
+--      In batch mode: if we get a ResultDiff saying a test file is different
+--      then just treat it as failed.
+--
+--      In non-batch mode: if we get a ResultDiff then use the controller
+--      to ask the user what to do about it interactively.
+--
+runChainsWithControllerIO
+        :: String               -- ^ Suppress this prefix from the front of test names.
+        -> Spec                 -- ^ Build configuration.
+        -> [Chain]              -- ^ Chains of jobs to run.
+        -> IO [Driver.Result]
+
+runChainsWithControllerIO prefix spec chains
+ = do   
+        -- Count the total number of chains for the status display.
+        let chainsTotal = length chains
+
+        -- Create a new channel to communicate between the test driver  and the
+        -- controller. As each test finishes, the driver writes the result to the
+        -- channel, and the controller reads the results and displays them 
+        -- on the console.
+        (chanResult :: TChan Driver.Result)
+                <- atomically $ newTChan
+        
+        -- Fork a gang to run all the job chains.
+        gang    <- Driver.forkChainsIO 
+                        (specThreads spec) "/tmp"
+                        (Just chanResult) chains
+
+        -- Fork the controller to display results and manage user input.
+        --   When the controller it done it also writes all the results
+        --   it received to an MVar to send them back to the main thread.
+        let configController
+                = Controller.Config
+                { Controller.configFormatPathWidth = specFormatPathWidth spec
+                , Controller.configInteractive     = specInteractive spec 
+                , Controller.configColoredOutput   = specInteractive spec
+                , Controller.configSuppressPrefix  = prefix }
+
+        varResults      <- newEmptyMVar
+        forkIO 
+         $ do   results <- Controller.controller configController gang chainsTotal chanResult
+                putMVar varResults results
+         `finally` (putMVar varResults [])
+
+        -- Wait for the controller to finish.
+        results <- takeMVar varResults
+
+        -- Wait until the gang is finished running chains, 
+        -- or has been killed by the controller.
+        joinGang gang
+
+        return results
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+--------------------------------------------------------------------------------
+The Disciplined Disciple Compiler License (MIT style)
+
+Copyrite (K) 2007-2012 The Disciplined Disciple Compiler Strike Force
+All rights reversed.
+
+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.
+
+-------------------------------------------------------------------------------
+Under Australian law copyright is free and automatic.
+By contributing to DDC authors grant all rights they have regarding their
+contributions to the other members of the Disciplined Disciple Compiler Strike
+Force, past, present and future, as well as placing their contributions under
+the above license.
+
+Use "darcs show authors" to get a list of Strike Force members.
+
+--------------------------------------------------------------------------------
+Redistributions of libraries in ./external are governed by their own licenses:
+
+  - TinyPTC   GNU Lesser General Public License
+  
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,43 @@
+
+import DDC.War.Config
+import DDC.War.Option
+import BuildBox.Pretty
+import BuildBox
+import System.Environment
+import qualified DDC.War.Task.Nightly           as N
+import qualified DDC.War.Task.Test              as T
+
+
+main :: IO ()
+main 
+ = do	-- Parse command line options, and exit if they're no good.
+	args    <- getArgs
+	config  <- parseOptions args defaultConfig
+	
+        case configNightly config of
+         Nothing 
+          -> let Just spec = configTest config
+             in  mainTest spec
+
+         Just spec      
+          -> mainNightly  spec
+
+
+-- | Run tests from the provided directories
+mainTest :: T.Spec -> IO ()
+mainTest spec
+ = let 
+   in do
+        result  <- runBuild "/tmp" $ T.build spec
+        case result of
+         Left err       -> error    $ render $ ppr err
+         Right _        -> return ()
+
+
+-- | Run the nightly build.
+mainNightly :: N.Spec -> IO ()
+mainNightly spec
+ = do   result  <- runBuild "/tmp"  $ N.build spec
+        case result of
+         Left err       -> error    $ render $ ppr err
+         Right result'  -> putStrLn $ render $ ppr result'
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/ddc-war.cabal b/ddc-war.cabal
new file mode 100644
--- /dev/null
+++ b/ddc-war.cabal
@@ -0,0 +1,68 @@
+Name:           ddc-war
+Version:        0.2.1.1
+License:        MIT
+License-file:   LICENSE
+Author:         The Disciplined Disciple Compiler Strike Force
+Maintainer:     Ben Lippmeier <benl@ouroborus.net>
+Build-Type:     Simple
+Cabal-Version:  >=1.6
+Stability:      experimental
+Category:       Compilers/Interpreters
+Homepage:       http://disciple.ouroborus.net
+Bug-reports:    disciple@ouroborus.net
+Synopsis:       Disciplined Disciple Compiler test driver and buildbot.
+Description:    Disciplined Disciple Compiler test driver and buildbot.
+
+Executable ddc-war
+  Build-depends:
+        base            == 4.5.*,
+        containers      == 0.4.*,
+        stm             == 2.2.*,
+        directory       == 1.1.*,
+        buildbox        == 2.1.*,
+        random          == 1.0.*,
+        process         == 1.1.*,
+        filepath        == 1.3.*
+
+  Main-is:
+        Main.hs
+  
+  Other-modules:
+        DDC.War.Create.CreateDCE
+        DDC.War.Create.CreateDCX
+        DDC.War.Create.CreateMainDS
+        DDC.War.Create.CreateMainHS
+        DDC.War.Create.CreateMainSH
+        DDC.War.Create.CreateTestDS
+        DDC.War.Create.Way
+        DDC.War.Driver.Base
+        DDC.War.Driver.Chain
+        DDC.War.Driver.Gang
+        DDC.War.Interface.Controller
+        DDC.War.Interface.VT100
+        DDC.War.Job.CompileDCE
+        DDC.War.Job.CompileDS
+        DDC.War.Job.CompileHS
+        DDC.War.Job.Diff
+        DDC.War.Job.RunDCX
+        DDC.War.Job.RunExe
+        DDC.War.Job.Shell
+        DDC.War.Task.Nightly
+        DDC.War.Task.Test
+        DDC.War.Config
+        DDC.War.Create
+        DDC.War.Driver
+        DDC.War.Job
+        DDC.War.Option
+
+  Extensions:
+        PatternGuards
+        ExistentialQuantification
+        MultiParamTypeClasses
+        FunctionalDependencies
+        ScopedTypeVariables
+
+  ghc-options:
+        -Wall -O2
+        -fno-warn-unused-do-bind
+        -threaded
