diff --git a/.drone.yml b/.drone.yml
index 685e2bf..4358d7d 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -25,6 +25,7 @@ steps:
           CONF_GITEATOKEN:
               from_secret: gitea-token
       commands:
+          - make prepare-test
           - make test
     - name: store
       commands:
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..2d32453
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,20 @@
+[submodule "examples/cpplint/sillycode"]
+	path = examples/cpplint/sillycode
+	url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
+	branch = cpplint/sillycode
+[submodule "examples/compile/success"]
+	path = examples/compile/success
+	url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
+	branch = compile/success
+[submodule "examples/compile/error"]
+	path = examples/compile/error
+	url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
+	branch = compile/error
+[submodule "examples/dummy/success"]
+	path = examples/dummy/success
+	url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
+	branch = dummy/success
+[submodule "examples/dummy/error"]
+	path = examples/dummy/error
+	url = ssh://git@focs.ji.sjtu.edu.cn:2222/FOCS-dev/JOJ3-examples.git
+	branch = dummy/error
diff --git a/Makefile b/Makefile
index 3008575..5dace32 100644
--- a/Makefile
+++ b/Makefile
@@ -11,5 +11,8 @@ clean:
 	rm -rf $(BUILD_DIR)/*
 	rm -rf *.out
 
+prepare-test:
+	git submodule update --init --remote
+
 test:
 	go test -coverprofile cover.out -v ./...
diff --git a/README.md b/README.md
index 3f5d049..7fdbb0c 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,9 @@
 
 1. Make sure you are in a Unix-like OS (Linux, MacOS). For Windows, use [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install).
 
-2. Install [Go](https://go.dev/doc/install). Also make sure `make` and `git` are installed and all 3 programs are presented in `$PATH`.
+2. Install [Go](https://go.dev/doc/install). Also, make sure `make` and `git` are installed and all 3 programs are presented in `$PATH`.
 
-3. Enable cgroups v2 for your OS. Check [here](https://stackoverflow.com/a/73376219/13724598). So that you do not need root permission to run `go-judge`.
+3. Enable cgroup v2 for your OS. Check [here](https://stackoverflow.com/a/73376219/13724598). So that you do not need root permission to run `go-judge`.
 
 4. Clone [go-judge](https://github.com/criyle/go-judge).
 ```bash
@@ -20,40 +20,44 @@ $ # make sure you are in go-judge directory
 $ ./tmp/go-judge -enable-grpc -enable-debug -enable-metrics
 ```
 
-6. Check the functions of `joj3` with the `make test`, which should pass all the test cases. The cases used here are in `/examples`.
+6. Pull submodules. It might be slow, so only run it when necessary.
+
 ```bash
 $ # make sure you are in JOJ3 directory
+$ make prepare-test
+```
+
+7. Check the functions of `joj3` with the `make test`, which should pass all the test cases. The cases used here are in `/examples`.
+```bash
 $ make test
 go test -coverprofile cover.out -v ./...
         focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/dummy         coverage: 0.0% of statements
 ?       focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors        [no test files]
 ?       focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers  [no test files]
 ?       focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/healthcheck   [no test files]
-        focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox                coverage: 0.0% of statements
         focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy          coverage: 0.0% of statements
+        focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/cpplint          coverage: 0.0% of statements
+        focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox                coverage: 0.0% of statements
+        focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy            coverage: 0.0% of statements
+        focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage            coverage: 0.0% of statements
         focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/diff             coverage: 0.0% of statements
         focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/pkg/dummy         coverage: 0.0% of statements
-        focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage            coverage: 0.0% of statements
-        focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/dummy            coverage: 0.0% of statements
         focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers/resultstatus             coverage: 0.0% of statements
 === RUN   TestMain
 === RUN   TestMain/compile/success
-    main_test.go:101: stageResults: [{Name:compile Results:[{Score:0 Comment:}]} {Name:run Results:[{Score:100 Comment:executor status: run time: 1867950 ns, memory: 10813440 bytes} {Score:100 Comment:executor status: run time: 1948947 ns, memory: 10813440 bytes}]}]
 === RUN   TestMain/compile/error
-    main_test.go:101: stageResults: [{Name:compile Results:[{Score:0 Comment:Unexpected executor status: Nonzero Exit Status.}]}]
 === RUN   TestMain/dummy/success
-    main_test.go:101: stageResults: [{Name:dummy Results:[{Score:110 Comment:dummy comment + comment from toml conf}]}]
 === RUN   TestMain/dummy/error
-    main_test.go:101: stageResults: [{Name:dummy Results:[{Score:0 Comment:Unexpected executor status: Nonzero Exit Status.
-        Stderr: dummy negative score: -1}]}]
---- PASS: TestMain (0.39s)
-    --- PASS: TestMain/compile/success (0.36s)
+=== RUN   TestMain/cpplint/sillycode
+--- PASS: TestMain (0.42s)
+    --- PASS: TestMain/compile/success (0.28s)
     --- PASS: TestMain/compile/error (0.01s)
-    --- PASS: TestMain/dummy/success (0.02s)
+    --- PASS: TestMain/dummy/success (0.01s)
     --- PASS: TestMain/dummy/error (0.01s)
+    --- PASS: TestMain/cpplint/sillycode (0.11s)
 PASS
-coverage: 68.5% of statements
-ok      focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/joj3  0.403s  coverage: 68.5% of statements
+coverage: 72.4% of statements
+ok      focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/cmd/joj3  0.425s  coverage: 72.4% of statements
 ```
 
 ### For developers
@@ -66,7 +70,7 @@ $ pre-commit install
 pre-commit installed at .git/hooks/pre-commit
 ```
 
-3. You only need to run step 5 and 6 in quick start during development.
+3. You only need to run steps 5 and 7 in the quick start during development. If the test cases need to be updated, step 6 is also needed.
 
 ## Models
 
diff --git a/cmd/joj3/conf.go b/cmd/joj3/conf.go
index ec576ad..ef87bbf 100644
--- a/cmd/joj3/conf.go
+++ b/cmd/joj3/conf.go
@@ -89,7 +89,7 @@ func commitMsgToConf() (conf Conf, err error) {
 		return
 	}
 	msg := commit.Message
-	slog.Info("commit msg to conf", "msg", msg)
+	slog.Debug("commit msg to conf", "msg", msg)
 	// TODO: parse msg to conf name
 	conf = parseConfFile("conf.toml")
 	return
diff --git a/cmd/joj3/main.go b/cmd/joj3/main.go
index 154d8cf..4348b73 100644
--- a/cmd/joj3/main.go
+++ b/cmd/joj3/main.go
@@ -2,7 +2,6 @@ package main
 
 import (
 	"encoding/json"
-	"errors"
 	"log/slog"
 	"os"
 
@@ -10,7 +9,6 @@ import (
 	_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/parsers"
 	"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
 
-	"github.com/go-git/go-git/v5"
 	"github.com/jinzhu/copier"
 )
 
@@ -75,13 +73,8 @@ func outputResult(conf Conf, results []stage.StageResult) error {
 func main() {
 	conf, err := commitMsgToConf()
 	if err != nil {
-		// FIXME: just for local testing purpose
-		if errors.Is(err, git.ErrRepositoryNotExists) {
-			conf = parseConfFile("conf.toml")
-		} else {
-			slog.Error("no conf found", "error", err)
-			os.Exit(1)
-		}
+		slog.Error("no conf found", "error", err)
+		os.Exit(1)
 	}
 	setupSlog(conf)
 	stages := generateStages(conf)
diff --git a/cmd/joj3/main_test.go b/cmd/joj3/main_test.go
index 39db646..05e78f3 100644
--- a/cmd/joj3/main_test.go
+++ b/cmd/joj3/main_test.go
@@ -10,72 +10,70 @@ import (
 	"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage"
 )
 
-func compareStageResults(t *testing.T, actual, want []stage.StageResult) {
+func compareStageResults(t *testing.T, actual, expected []stage.StageResult, regex bool) {
 	t.Helper()
-	if len(actual) != len(want) {
-		t.Fatalf("len(actual) = %d, want %d", len(actual), len(want))
+	if len(actual) != len(expected) {
+		t.Fatalf("len(actual) = %d, expected %d", len(actual), len(expected))
 	}
 	for i := range actual {
-		if actual[i].Name != want[i].Name {
-			t.Errorf("actual[%d].Name = %s, want = %s", i, actual[i].Name,
-				want[i].Name)
+		if actual[i].Name != expected[i].Name {
+			t.Errorf("actual[%d].Name = %s, expected = %s", i, actual[i].Name,
+				expected[i].Name)
 		}
-		if len(actual[i].Results) != len(want[i].Results) {
-			t.Fatalf("len(actual[%d].Results) = %d, want = %d", i,
-				len(actual[i].Results), len(want[i].Results))
+		if len(actual[i].Results) != len(expected[i].Results) {
+			t.Fatalf("len(actual[%d].Results) = %d, expected = %d", i,
+				len(actual[i].Results), len(expected[i].Results))
 		}
 		for j := range actual[i].Results {
-			if actual[i].Results[j].Score != want[i].Results[j].Score {
-				t.Errorf("actual[%d].Results[%d].Score = %d, want = %d", i, j,
-					actual[i].Results[j].Score, want[i].Results[j].Score)
+			if actual[i].Results[j].Score != expected[i].Results[j].Score {
+				t.Errorf("actual[%d].Results[%d].Score = %d, expected = %d", i, j,
+					actual[i].Results[j].Score, expected[i].Results[j].Score)
 			}
-			r := regexp.MustCompile(want[i].Results[j].Comment)
-			if !r.MatchString(actual[i].Results[j].Comment) {
-				t.Errorf("actual[%d].Results[%d].Comment = %s, want RegExp = %s",
-					i, j, actual[i].Results[j].Comment,
-					want[i].Results[j].Comment)
+			if regex {
+				r := regexp.MustCompile(expected[i].Results[j].Comment)
+				if !r.MatchString(actual[i].Results[j].Comment) {
+					t.Errorf("actual[%d].Results[%d].Comment = %s, expected RegExp = %s",
+						i, j, actual[i].Results[j].Comment,
+						expected[i].Results[j].Comment)
+				}
+			} else if actual[i].Results[j].Comment != expected[i].Results[j].Comment {
+				t.Errorf("actual[%d].Results[%d].Comment = %s, expected = %s", i, j,
+					actual[i].Results[j].Comment, expected[i].Results[j].Comment)
 			}
 		}
 	}
 }
 
+func readStageResults(t *testing.T, path string) []stage.StageResult {
+	t.Helper()
+	file, err := os.Open(path)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer file.Close()
+	var results []stage.StageResult
+	err = json.NewDecoder(file).Decode(&results)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return results
+}
+
 func TestMain(t *testing.T) {
-	tests := []struct {
-		name string
-		want []stage.StageResult
-	}{
-		{"compile/success", []stage.StageResult{
-			{Name: "compile", Results: []stage.ParserResult{
-				{Score: 0, Comment: ""},
-			}},
-			{Name: "run", Results: []stage.ParserResult{
-				{Score: 100, Comment: "executor status: run time: \\d+ ns, memory: \\d+ bytes"},
-				{Score: 100, Comment: "executor status: run time: \\d+ ns, memory: \\d+ bytes"},
-			}},
-		}},
-		{"compile/error", []stage.StageResult{
-			{Name: "compile", Results: []stage.ParserResult{
-				{Score: 0, Comment: "Unexpected executor status: Nonzero Exit Status\\."},
-			}},
-		}},
-		{"dummy/success", []stage.StageResult{
-			{Name: "dummy", Results: []stage.ParserResult{
-				{Score: 110, Comment: "dummy comment \\+ comment from toml conf"},
-			}},
-		}},
-		{"dummy/error", []stage.StageResult{
-			{Name: "dummy", Results: []stage.ParserResult{
-				{Score: 0, Comment: "Unexpected executor status: Nonzero Exit Status\\.\\s*Stderr: dummy negative score: -1"},
-			}},
-		}},
+	tests := []string{
+		"compile/success",
+		"compile/error",
+		"dummy/success",
+		"dummy/error",
+		"cpplint/sillycode",
 	}
 	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
+		t.Run(tt, func(t *testing.T) {
 			origDir, err := os.Getwd()
 			if err != nil {
 				t.Fatal(err)
 			}
-			err = os.Chdir(fmt.Sprintf("../../examples/%s", tt.name))
+			err = os.Chdir(fmt.Sprintf("../../examples/%s", tt))
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -86,20 +84,18 @@ func TestMain(t *testing.T) {
 				}
 			}()
 			os.Args = []string{"./joj3"}
-			main()
 			outputFile := "joj3_result.json"
-			data, err := os.ReadFile(outputFile)
-			if err != nil {
-				t.Fatal(err)
-			}
 			defer os.Remove(outputFile)
-			var stageResults []stage.StageResult
-			err = json.Unmarshal(data, &stageResults)
-			if err != nil {
-				t.Fatal(err)
+			main()
+			stageResults := readStageResults(t, outputFile)
+			regex := true
+			expectedFile := "expected_regex.json"
+			if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
+				regex = false
+				expectedFile = "expected.json"
 			}
-			t.Logf("stageResults: %+v", stageResults)
-			compareStageResults(t, stageResults, tt.want)
+			expectedStageResults := readStageResults(t, expectedFile)
+			compareStageResults(t, stageResults, expectedStageResults, regex)
 		})
 	}
 }
diff --git a/examples/compile/error b/examples/compile/error
new file mode 160000
index 0000000..dc7be30
--- /dev/null
+++ b/examples/compile/error
@@ -0,0 +1 @@
+Subproject commit dc7be30db1fbfd83ecb78f214cff3f42732a1365
diff --git a/examples/compile/error/conf.toml b/examples/compile/error/conf.toml
deleted file mode 100644
index a52ff88..0000000
--- a/examples/compile/error/conf.toml
+++ /dev/null
@@ -1,61 +0,0 @@
-skipGitea = true
-logLevel = 0
-[[stages]]
-name = "compile"
-[stages.executor]
-name = "sandbox"
-[stages.executor.with.default]
-args = ["g++", "b.cc", "-o", "a"]
-env = ["PATH=/usr/bin:/bin"]
-cpuLimit = 10_000_000_000
-memoryLimit = 104_857_600
-procLimit = 50
-copyInCwd = true
-copyOut = ["stdout", "stderr"]
-copyOutCached = ["a"]
-[stages.executor.with.default.stdin]
-content = ""
-[stages.executor.with.default.stdout]
-name = "stdout"
-max = 4_096
-[stages.executor.with.default.stderr]
-name = "stderr"
-max = 4_096
-[stages.parser]
-name = "result-status"
-[stages.parser.with]
-score = 100
-comment = "compile done"
-[[stages]]
-name = "run"
-[stages.executor]
-name = "sandbox"
-[stages.executor.with.default]
-args = ["./a"]
-env = ["PATH=/usr/bin:/bin"]
-cpuLimit = 1_000_000_000
-memoryLimit = 104_857_600
-procLimit = 50
-copyOut = ["stdout", "stderr"]
-[stages.executor.with.default.stdout]
-name = "stdout"
-max = 4_096
-[stages.executor.with.default.stderr]
-name = "stderr"
-max = 4_096
-[stages.executor.with.default.copyInCached]
-a = "a"
-[[stages.executor.with.cases]]
-[stages.executor.with.cases.stdin]
-src = "./cases/1.in"
-[[stages.executor.with.cases]]
-[stages.executor.with.cases.stdin]
-src = "./cases/2.in"
-[stages.parser]
-name = "diff"
-[[stages.parser.with.cases]]
-score = 100
-stdoutPath = "./cases/1.out"
-[[stages.parser.with.cases]]
-score = 100
-stdoutPath = "./cases/2.out"
diff --git a/examples/compile/success b/examples/compile/success
new file mode 160000
index 0000000..21f6df7
--- /dev/null
+++ b/examples/compile/success
@@ -0,0 +1 @@
+Subproject commit 21f6df70d483f34a99b50335e0a1fbe50d3a82dd
diff --git a/examples/compile/success/a.cc b/examples/compile/success/a.cc
deleted file mode 100644
index 2c7ca74..0000000
--- a/examples/compile/success/a.cc
+++ /dev/null
@@ -1,6 +0,0 @@
-#include <iostream>
-int main() {
-  int a, b;
-  std::cin >> a >> b;
-  std::cout << a + b << '\n';
-}
diff --git a/examples/compile/success/cases/1.in b/examples/compile/success/cases/1.in
deleted file mode 100644
index 2fb73a0..0000000
--- a/examples/compile/success/cases/1.in
+++ /dev/null
@@ -1 +0,0 @@
-1 1
diff --git a/examples/compile/success/cases/1.out b/examples/compile/success/cases/1.out
deleted file mode 100644
index 0cfbf08..0000000
--- a/examples/compile/success/cases/1.out
+++ /dev/null
@@ -1 +0,0 @@
-2
diff --git a/examples/compile/success/cases/2.in b/examples/compile/success/cases/2.in
deleted file mode 100644
index b5f1e36..0000000
--- a/examples/compile/success/cases/2.in
+++ /dev/null
@@ -1 +0,0 @@
-1024 2048
diff --git a/examples/compile/success/cases/2.out b/examples/compile/success/cases/2.out
deleted file mode 100644
index 5fd86fd..0000000
--- a/examples/compile/success/cases/2.out
+++ /dev/null
@@ -1 +0,0 @@
-3072
diff --git a/examples/compile/success/conf.toml b/examples/compile/success/conf.toml
deleted file mode 100644
index e91739a..0000000
--- a/examples/compile/success/conf.toml
+++ /dev/null
@@ -1,60 +0,0 @@
-skipGitea = true
-[[stages]]
-name = "compile"
-[stages.executor]
-name = "sandbox"
-[stages.executor.with.default]
-args = ["g++", "a.cc", "-o", "a"]
-env = ["PATH=/usr/bin:/bin"]
-cpuLimit = 10_000_000_000
-memoryLimit = 104_857_600
-procLimit = 50
-copyInCwd = true
-copyOut = ["stdout", "stderr"]
-copyOutCached = ["a"]
-[stages.executor.with.default.stdin]
-content = ""
-[stages.executor.with.default.stdout]
-name = "stdout"
-max = 4_096
-[stages.executor.with.default.stderr]
-name = "stderr"
-max = 4_096
-[stages.parser]
-name = "result-status"
-[stages.parser.with]
-score = 100
-comment = "compile done"
-[[stages]]
-name = "run"
-[stages.executor]
-name = "sandbox"
-[stages.executor.with.default]
-args = ["./a"]
-env = ["PATH=/usr/bin:/bin"]
-cpuLimit = 1_000_000_000
-memoryLimit = 104_857_600
-procLimit = 50
-copyOut = ["stdout", "stderr"]
-[stages.executor.with.default.stdout]
-name = "stdout"
-max = 4_096
-[stages.executor.with.default.stderr]
-name = "stderr"
-max = 4_096
-[stages.executor.with.default.copyInCached]
-a = "a"
-[[stages.executor.with.cases]]
-[stages.executor.with.cases.stdin]
-src = "./cases/1.in"
-[[stages.executor.with.cases]]
-[stages.executor.with.cases.stdin]
-src = "./cases/2.in"
-[stages.parser]
-name = "diff"
-[[stages.parser.with.cases]]
-score = 100
-stdoutPath = "./cases/1.out"
-[[stages.parser.with.cases]]
-score = 100
-stdoutPath = "./cases/2.out"
diff --git a/examples/cpplint/sillycode b/examples/cpplint/sillycode
new file mode 160000
index 0000000..e650457
--- /dev/null
+++ b/examples/cpplint/sillycode
@@ -0,0 +1 @@
+Subproject commit e6504575379ef49c1495dc635f7aba36f57dddc8
diff --git a/examples/dummy/error b/examples/dummy/error
new file mode 160000
index 0000000..3ebebbc
--- /dev/null
+++ b/examples/dummy/error
@@ -0,0 +1 @@
+Subproject commit 3ebebbc913534fb4c4598d3ed313d1bb3ff29020
diff --git a/examples/dummy/error/conf.toml b/examples/dummy/error/conf.toml
deleted file mode 100644
index 7648609..0000000
--- a/examples/dummy/error/conf.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-skipGitea = true
-[[stages]]
-name = "dummy"
-[stages.executor]
-name = "sandbox"
-[stages.executor.with.default]
-args = ["./dummy", "--score", "-1"]
-env = ["PATH=/usr/bin:/bin"]
-cpuLimit = 10_000_000_000
-memoryLimit = 104_857_600
-procLimit = 50
-copyInCwd = true
-[stages.executor.with.default.copyIn.dummy]
-src = "./../../../build/dummy"
-copyOut = ["stdout", "stderr"]
-[stages.executor.with.default.stdin]
-content = ""
-[stages.executor.with.default.stdout]
-name = "stdout"
-max = 4_096
-[stages.executor.with.default.stderr]
-name = "stderr"
-max = 4_096
-[stages.parser]
-name = "dummy"
-[stages.parser.with]
-score = 10
-comment = " + comment from toml conf"
diff --git a/examples/dummy/success b/examples/dummy/success
new file mode 160000
index 0000000..4bca6f0
--- /dev/null
+++ b/examples/dummy/success
@@ -0,0 +1 @@
+Subproject commit 4bca6f0e4b5e263532b8410afecca615e90524e9
diff --git a/examples/dummy/success/conf.toml b/examples/dummy/success/conf.toml
deleted file mode 100644
index 7fef08c..0000000
--- a/examples/dummy/success/conf.toml
+++ /dev/null
@@ -1,28 +0,0 @@
-skipGitea = true
-[[stages]]
-name = "dummy"
-[stages.executor]
-name = "sandbox"
-[stages.executor.with.default]
-args = ["./dummy", "--score", "100"]
-env = ["PATH=/usr/bin:/bin"]
-cpuLimit = 10_000_000_000
-memoryLimit = 104_857_600
-procLimit = 50
-copyInCwd = true
-[stages.executor.with.default.copyIn.dummy]
-src = "./../../../build/dummy"
-copyOut = ["stdout", "stderr"]
-[stages.executor.with.default.stdin]
-content = ""
-[stages.executor.with.default.stdout]
-name = "stdout"
-max = 4_096
-[stages.executor.with.default.stderr]
-name = "stderr"
-max = 4_096
-[stages.parser]
-name = "dummy"
-[stages.parser.with]
-score = 10
-comment = " + comment from toml conf"
diff --git a/internal/stage/model.go b/internal/stage/model.go
index 848e4ec..1b052d8 100644
--- a/internal/stage/model.go
+++ b/internal/stage/model.go
@@ -159,11 +159,11 @@ type Stage struct {
 }
 
 type ParserResult struct {
-	Score   int
-	Comment string
+	Score   int    `json:"score"`
+	Comment string `json:"comment"`
 }
 
 type StageResult struct {
-	Name    string
-	Results []ParserResult
+	Name    string         `json:"name"`
+	Results []ParserResult `json:"results"`
 }