diff --git a/README.md b/README.md
index 98a94e8..07ca674 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ $ # make sure you are in go-judge directory
 $ ./tmp/go-judge -http-addr 0.0.0.0:5050 -grpc-addr 0.0.0.0:5051 -monitor-addr 0.0.0.0:5052 -enable-grpc -enable-debug -enable-metrics
 ```
 
-6. Pull submodules. It might be slow, so only run it when necessary.
+6. Pull submodules. It might be slow, so only run it when the test branches are out of date.
 
 ```bash
 $ # make sure you are in JOJ3 directory
@@ -75,7 +75,7 @@ Check `Cmd` at <https://github.com/criyle/go-judge#rest-api-interface>.
 
 Some difference:
 
--   `CopyInCwd bool`: set to `true` to add everything in the current working directory to `CopyIn`.
+-   `CopyInDir string`: set to non-empty string to add everything in that directory to `CopyIn`.
 -   `CopyInCached map[string]string`: key: file name in the sandbox, value: file name used in `CopyOutCached`.
 -   `LocalFile`: now supports the relative path
 
@@ -88,6 +88,12 @@ Check the `Result` at <https://github.com/criyle/go-judge#rest-api-interface>.
 -   `Score int`: score of the stage.
 -   `Comment string`: comment on the stage.
 
+## Binaries (under `/cmd` and `/pkg`)
+
+### Sample
+
+Just a sample on how to write an executable that can be called by the executor.
+
 ### HealthCheck
 
 The repohealth check will return a json list to for check result. The structure follows the score-comment pattern.
@@ -95,3 +101,53 @@ The repohealth check will return a json list to for check result. The structure
 HealthCheck currently includes, `reposize`, `forbidden file`, `Metafile existence`, `non-ascii character` in file and message, `release tag`, and `ci files invariance` check.
 
 The workflow is `joj3` pass cli args to healthcheck binary. See `./cmd/healthcheck/main.go` to view all flags.
+
+## Executors (under `/internal/executors`)
+
+### Dummy
+
+Do not execute any command. Just return empty `ExecutorResult` slice.
+
+### Sandbox
+
+Run the commands in `go-judge` and output the `ExecutorResult` slice.
+
+## Parsers (under `/internal/parsers`)
+
+### Clang Tidy
+
+Parser for `clang-tidy`, check `/examples/clangtidy` on how to call `clang-tidy` with proper parameters.
+
+### Cppcheck
+
+Parser for `cppcheck`, check `/examples/cppcheck` on how to call `cppcheck` with proper parameters.
+
+### Cpplint
+
+Parser for `cpplint`, check `/examples/cpplint` on how to call `cpplint` with proper parameters.
+
+### Diff
+
+Compare the specified output of `ExecutorResult` with the content of the answer file. If they are the same, then score will be given. Just like a normal online judge system.
+
+### Dummy
+
+Does not parse the output of `ExecutorResult`. It just output what is set inside the configuration file as score and comment. Currently it is used to output metadata for `joint-teapot`.
+
+In `joint-teapot`, it will take the content before `-` of the comment of the first stage with name `metadata` as the exercise name and record in the scoreboard. (e.g. If the comment is `p2-s2-0xdeadbeef`, then the exercise name is `p2`.)
+
+### Healthcheck
+
+Parser for the `healthcheck` binary mentioned before.
+
+### Keyword
+
+Match the given keyword from the specified output of `ExecutorResult`. For each match, a deduction of score is given. Can be useful if we do not have a specific parser for a code quality tool. Check `/examples/keyword`.
+
+### Result Status
+
+Only check if all the status of the executor is `StatusAccepted`. Can be used to check whether the command run in the executor exit normally.
+
+### Sample
+
+Parser for the `sample` binary mentioned before. Only used as a sample.
diff --git a/cmd/joj3/conf.go b/cmd/joj3/conf.go
index bb5f484..544c9d0 100644
--- a/cmd/joj3/conf.go
+++ b/cmd/joj3/conf.go
@@ -85,22 +85,6 @@ func parseConfFile(path string, jobtype JobType) (conf Conf, err error) {
 		slog.Error("parse stages conf", "error", err)
 		return
 	}
-
-	if err = d.Validate(&conf); err != nil {
-		slog.Error("validate stages conf", "error", err)
-		return
-	}
-
-	filteredStages := []Stage{}
-
-	for _, stage := range conf.Stages {
-		if filterStage(stage, jobtype) {
-			filteredStages = append(filteredStages, stage)
-		}
-	}
-
-	conf.Stages = filteredStages
-
 	return
 }
 
diff --git a/internal/executors/sandbox/convert.go b/internal/executors/sandbox/convert.go
index 827dd15..c739f79 100644
--- a/internal/executors/sandbox/convert.go
+++ b/internal/executors/sandbox/convert.go
@@ -28,7 +28,7 @@ func convertPBCmd(cmd []stage.Cmd) []*pb.Request_CmdType {
 			CpuSetLimit:       c.CPUSetLimit,
 			DataSegmentLimit:  c.DataSegmentLimit,
 			AddressSpaceLimit: c.AddressSpaceLimit,
-			CopyIn:            convertPBCopyIn(c.CopyIn, c.CopyInCwd),
+			CopyIn:            convertPBCopyIn(c.CopyIn, c.CopyInDir),
 			CopyOut:           convertPBCopyOut(c.CopyOut),
 			CopyOutCached:     convertPBCopyOut(c.CopyOutCached),
 			CopyOutMax:        c.CopyOutMax,
@@ -39,9 +39,11 @@ func convertPBCmd(cmd []stage.Cmd) []*pb.Request_CmdType {
 	return ret
 }
 
-func convertPBCopyIn(copyIn map[string]stage.CmdFile, copyInCwd bool) map[string]*pb.Request_File {
-	if copyInCwd {
-		_ = filepath.Walk(".",
+func convertPBCopyIn(
+	copyIn map[string]stage.CmdFile, copyInDir string,
+) map[string]*pb.Request_File {
+	if copyInDir != "" {
+		_ = filepath.Walk(copyInDir,
 			func(path string, info os.FileInfo, err error) error {
 				if err != nil {
 					return nil
diff --git a/internal/parsers/cppcheck/score.go b/internal/parsers/cppcheck/score.go
index d6028fb..dbdf81e 100644
--- a/internal/parsers/cppcheck/score.go
+++ b/internal/parsers/cppcheck/score.go
@@ -29,7 +29,7 @@ func severityFromString(severityString string) (Severity, error) {
 	case "information":
 		return INFORMATION, nil
 	default:
-		return UNKNOWN, fmt.Errorf("unkown severity type \"%s\" for cppcheck", severityString)
+		return UNKNOWN, fmt.Errorf("unknown severity type \"%s\" for cppcheck", severityString)
 	}
 }
 
diff --git a/internal/stage/model.go b/internal/stage/model.go
index dba945a..224b687 100644
--- a/internal/stage/model.go
+++ b/internal/stage/model.go
@@ -46,7 +46,7 @@ type Cmd struct {
 
 	CopyIn       map[string]CmdFile `json:"copyIn"`
 	CopyInCached map[string]string  `json:"copyInCached"`
-	CopyInCwd    bool               `json:"copyInCwd"`
+	CopyInDir    string             `json:"copyInDir"`
 
 	CopyOut       []string `json:"copyOut"`
 	CopyOutCached []string `json:"copyOutCached"`
diff --git a/pkg/healthcheck/commit.go b/pkg/healthcheck/commit.go
index 8b1d579..375046e 100644
--- a/pkg/healthcheck/commit.go
+++ b/pkg/healthcheck/commit.go
@@ -7,7 +7,6 @@ import (
 	"unicode"
 
 	"github.com/go-git/go-git/v5"
-	"github.com/go-git/go-git/v5/plumbing/object"
 )
 
 // nonAsciiMsg checks for non-ASCII characters in the commit message.
@@ -28,23 +27,19 @@ func NonAsciiMsg(root string) error {
 		slog.Error("getting reference", "err", err)
 		return fmt.Errorf("error getting reference: %v", err)
 	}
-	commits, err := repo.Log(&git.LogOptions{From: ref.Hash()})
+
+	commit, err := repo.CommitObject(ref.Hash())
 	if err != nil {
-		slog.Error("getting commits", "err", err)
-		return fmt.Errorf("error getting commits from reference %s: %v", ref.Hash(), err)
+		slog.Error("getting latest commit", "err", err)
+		return fmt.Errorf("error getting latest commit: %v", err)
 	}
 
-	var msgs []string
-	err = commits.ForEach(func(c *object.Commit) error {
-		msgs = append(msgs, c.Message)
+	msg := commit.Message
+	if msg == "" {
 		return nil
-	})
-	if err != nil {
-		slog.Error("iterating commits", "err", err)
-		return fmt.Errorf("error iterating commits: %v", err)
 	}
 
-	var nonAsciiMsgs []string
+	var isCommitLegal bool = true
 	// List of prefixes to ignore in the commit message
 	ignoredPrefixes := []string{
 		"Co-authored-by:",
@@ -53,35 +48,31 @@ func NonAsciiMsg(root string) error {
 		"Reviewed-on:",
 	}
 
-	for _, msg := range msgs {
-		if msg == "" {
+	// Split message by lines and ignore specific lines with prefixes
+	lines := strings.Split(msg, "\n")
+	for _, line := range lines {
+		trimmedLine := strings.TrimSpace(line)
+		ignore := false
+		for _, prefix := range ignoredPrefixes {
+			if strings.HasPrefix(trimmedLine, prefix) {
+				ignore = true
+				break
+			}
+		}
+		if ignore {
 			continue
 		}
-		// Split message by lines and ignore specific lines with prefixes
-		lines := strings.Split(msg, "\n")
-		for _, line := range lines {
-			trimmedLine := strings.TrimSpace(line)
-			ignore := false
-			for _, prefix := range ignoredPrefixes {
-				if strings.HasPrefix(trimmedLine, prefix) {
-					ignore = true
-					break
-				}
-			}
-			if ignore {
-				continue
-			}
-			// Check for non-ASCII characters in the rest of the lines
-			for _, c := range line {
-				if c > unicode.MaxASCII {
-					nonAsciiMsgs = append(nonAsciiMsgs, msg)
-					break
-				}
+		// Check for non-ASCII characters in the rest of the lines
+		for _, c := range line {
+			if c > unicode.MaxASCII {
+				isCommitLegal = false
+				break
 			}
 		}
 	}
-	if len(nonAsciiMsgs) > 0 {
-		return fmt.Errorf("Non-ASCII characters in commit messages:\n%s", strings.Join(nonAsciiMsgs, "\n"))
+
+	if !isCommitLegal {
+		return fmt.Errorf("Non-ASCII characters in commit messages:\n%s", msg)
 	}
 	return nil
 }
diff --git a/pkg/healthcheck/meta.go b/pkg/healthcheck/meta.go
index 2e78d50..a78da6b 100644
--- a/pkg/healthcheck/meta.go
+++ b/pkg/healthcheck/meta.go
@@ -25,7 +25,7 @@ func getMetas(rootDir string, fileList []string) ([]string, string, error) {
 	matched := false
 	umatchedRes := ""
 
-	// TODO: it seems that there is no good find subsitution now
+	// TODO: it seems that there is no good find substitution now
 	// modify current code if exist a better solution
 	for i, regex := range regexList {
 		for _, file := range files {
diff --git a/scripts/prepare_test_repos.sh b/scripts/prepare_test_repos.sh
index c679ab4..270567e 100755
--- a/scripts/prepare_test_repos.sh
+++ b/scripts/prepare_test_repos.sh
@@ -18,14 +18,14 @@ for submodule in $submodules; do
         else
             cd $repo_dir
             git fetch --all
-            cd -
+            cd - > /dev/null
         fi
     fi
     repo_names[$repo_name]=1
     cd $repo_dir
     git checkout -q $branch
     git reset -q --hard origin/$branch
-    cd -
+    cd - > /dev/null
     submodule_dir="$submodules_dir/$repo_name/$submodule"
     mkdir -p $submodule_dir
     cp -rT $repo_dir $submodule_dir
diff --git a/scripts/run_foreach_test_repos.sh b/scripts/run_foreach_test_repos.sh
index 6789f3e..9710f5b 100755
--- a/scripts/run_foreach_test_repos.sh
+++ b/scripts/run_foreach_test_repos.sh
@@ -17,5 +17,5 @@ for submodule in $submodules; do
             mv -f "joj3_result.json" "expected.json"
         fi
     fi
-    cd -
+    cd - > /dev/null
 done