diff --git a/README.md b/README.md
index 482e61c..14502cb 100644
--- a/README.md
+++ b/README.md
@@ -48,11 +48,11 @@ The configuration file is created by matching the `level` and `step` ids between
 
 Tutorial description.
 
-## L1 This is a level with id = 1
+## 1. This is a level with id = 1
 
 This level has two steps...
 
-### L1S1 First step
+### 1.1 First step
 
 The first step with id L1S1. The Step id should start with the level id.
 
@@ -62,7 +62,7 @@ The first step with id L1S1. The Step id should start with the level id.
 - The second hint that will show
 - The third and final hint, as it is last in order
 
-### L1S2 The second step
+### 1.2 The second step
 
 The second step...
 ```
@@ -72,10 +72,10 @@ The second step...
 ```yaml
 ---
 levels:
-  - id: L1
+  - id: "1"
     config: {}
     steps:
-      - id: L1S1
+      - id: "1.1"
         setup:
           files:
             - package.json
@@ -86,7 +86,7 @@ levels:
             - package.json
           commands:
             - npm install
-      - id: L1S2
+      - id: "1.2"
         setup:
           files:
             - src/server.js
@@ -104,23 +104,19 @@ commit 8e0e3a42ae565050181fdb68298114df21467a74 (HEAD -> v2, origin/v2)
 Author: creator <author@email.com>
 Date:   Sun May 3 16:16:01 2020 -0700
 
-    L1S1Q setup step 1 for level 1
+    1.1 setup for level 1, step 1
 
 commit 9499611fc9b311040dcabaf2d98439fc0c356cc9
 Author: creator <author@email.com>
 Date:   Sun May 3 16:13:37 2020 -0700
 
-    L1S2A checkout solution for level 1, step 2
+    1.1S solution for level 1, step 1
 
 commit c5c62041282579b495d3589b2eb1fdda2bcd7155
 Author: creator <author@email.com>
 Date:   Sun May 3 16:11:42 2020 -0700
 
-    L1S2Q setup level 1, step 2
+    1.2 setup for level 1, step 2
 ```
 
-Note that the step `L1S2` has two commits, one with the suffix `Q` and another one with `A`. The suffixes mean `Question` and `Answer`, respectively, and refer to the unit tests and the commit that makes them pass.
-
-Steps defined as questions are **required** as they are meant to set the task to be executed by the student. The answer is optional and should be used when a commit must be loaded to verify the student's solution.
-
-If there are multiple commits for a level or step, they are captured in order.
+Note that the step `1.1` has two commits, one with the suffix `S`. The first commit refers to the required tests and setup, while the second optional commit contains the solution. If there are multiple commits for a level or step, they are captured in order.
diff --git a/package-lock.json b/package-lock.json
index 1c870ac..30e5a83 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "@coderoad/cli",
-  "version": "0.3.1",
+  "version": "0.4.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 5bdd89d..85e3bc4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@coderoad/cli",
-  "version": "0.3.1",
+  "version": "0.4.0",
   "description": "A CLI to build the configuration file for Coderoad Tutorials",
   "keywords": [
     "coderoad",
diff --git a/src/build.ts b/src/build.ts
index 150dbc0..cd7dbc2 100644
--- a/src/build.ts
+++ b/src/build.ts
@@ -102,7 +102,7 @@ async function build(args: string[]) {
   try {
     const valid = validateSchema(skeletonSchema, skeleton);
     if (!valid) {
-      console.error("Tutorial validation failed. See above to see what to fix");
+      console.error("Skeleton validation failed. See above to see what to fix");
       return;
     }
   } catch (e) {
diff --git a/src/schema/meta.ts b/src/schema/meta.ts
index 71bf7b8..97479f4 100644
--- a/src/schema/meta.ts
+++ b/src/schema/meta.ts
@@ -95,5 +95,40 @@ export default {
       },
       additionalProperties: false,
     },
+    setup_action_without_commits: {
+      type: "object",
+      description:
+        "A collection of files/commands that run when a level/step or solution is loaded",
+      properties: {
+        files: {
+          $ref: "#/definitions/file_array",
+        },
+        commands: {
+          $ref: "#/definitions/command_array",
+        },
+        watchers: {
+          type: "array",
+          items: {
+            $ref: "#/definitions/file_path",
+            // uniqueItems: true,
+          },
+          description:
+            "An array file paths that, when updated, will trigger the test runner to run",
+        },
+        filter: {
+          type: "string",
+          description:
+            "A regex pattern that will be passed to the test runner to limit the number of tests running",
+          examples: ["^TestSuiteName"],
+        },
+        subtasks: {
+          type: "boolean",
+          description:
+            'A feature that shows subtasks: all active test names and the status of the tests (pass/fail). Use together with "filter"',
+          examples: [true],
+        },
+      },
+      additionalProperties: false,
+    },
   },
 };
diff --git a/src/schema/skeleton.ts b/src/schema/skeleton.ts
index 4f57b63..35c9353 100644
--- a/src/schema/skeleton.ts
+++ b/src/schema/skeleton.ts
@@ -53,9 +53,9 @@ export default {
               examples: ["coderoad"],
             },
             setup: {
-              $ref: "#/definitions/setup_action",
+              $ref: "#/definitions/setup_action_without_commits",
               description:
-                "Setup commits or commands used for setting up the test runner on tutorial launch",
+                "Setup actions or commands used for setting up the test runner on tutorial launch",
             },
           },
           required: ["command", "args"],
@@ -135,9 +135,9 @@ export default {
             examples: ["L1", "L11"],
           },
           setup: {
-            $ref: "#/definitions/setup_action",
+            $ref: "#/definitions/setup_action_without_commits",
             description:
-              "An optional point for loading commits, running commands or opening files",
+              "An optional point for running actions, commands or opening files",
           },
           steps: {
             type: "array",
@@ -152,18 +152,18 @@ export default {
                 setup: {
                   allOf: [
                     {
-                      $ref: "#/definitions/setup_action",
+                      $ref: "#/definitions/setup_action_without_commits",
                       description:
-                        "A point for loading commits. It can also run commands and/or open files",
+                        "A point for running actions, commands and/or opening files",
                     },
                   ],
                 },
                 solution: {
                   allOf: [
                     {
-                      $ref: "#/definitions/setup_action",
+                      $ref: "#/definitions/setup_action_without_commits",
                       description:
-                        "The solution commits that can be loaded if the user gets stuck. It can also run commands and/or open files",
+                        "The solution can be loaded if the user gets stuck. It can run actions, commands and/or open files",
                     },
                     {
                       required: [],
diff --git a/src/schema/tutorial.ts b/src/schema/tutorial.ts
index 6a605c9..062c9cc 100644
--- a/src/schema/tutorial.ts
+++ b/src/schema/tutorial.ts
@@ -211,7 +211,7 @@ export default {
                   examples: ["Have you tried doing X?"],
                 },
               },
-              required: ["content", "setup", "solution"],
+              required: ["content", "setup"],
             },
           },
         },
diff --git a/src/utils/commits.ts b/src/utils/commits.ts
index 48fdee2..17c2956 100644
--- a/src/utils/commits.ts
+++ b/src/utils/commits.ts
@@ -1,6 +1,7 @@
 import * as fs from "fs";
 import util from "util";
 import * as path from "path";
+import { ListLogSummary } from "simple-git/typings/response";
 import gitP, { SimpleGit } from "simple-git/promise";
 import { validateCommitOrder } from "./validateCommits";
 
@@ -15,6 +16,54 @@ type GetCommitOptions = {
 
 export type CommitLogObject = { [position: string]: string[] };
 
+
+
+export function parseCommits(logs: ListLogSummary<any>): { [hash: string]: string[]} {
+  // Filter relevant logs
+  const commits: CommitLogObject = {};
+  const positions: string[] = [];
+
+  for (const commit of logs.all) {
+    const matches = commit.message.match(
+      /^(?<init>INIT)|(L?(?<levelId>\d+)[S|\.]?(?<stepId>\d+)?(?<stepType>[Q|A|T|S])?)/
+    );
+
+    if (matches && matches.length) {
+      // Use an object of commit arrays to collect all commits
+      const { groups } = matches
+      let position
+      if (groups.init) {
+        position = 'INIT'
+      } else if (groups.levelId && groups.stepId) {
+        let stepType
+        // @deprecated Q
+        if (!groups.stepType || ['Q', 'T'].includes(groups.stepType)) {
+          stepType = 'T' // test
+          // @deprecated A
+        } else if (!groups.stepType || ['A', 'S'].includes(groups.stepType)) {
+          stepType = 'S' // solution
+        }
+        position = `${groups.levelId}.${groups.stepId}:${stepType}`
+      } else if (groups.levelId) {
+        position = groups.levelId
+      } else {
+        console.warn(`No matcher for commit "${commit.message}"`)
+      }
+      commits[position] = [...(commits[position] || []), commit.hash]
+      positions.unshift(position);
+    } else {
+      const initMatches = commit.message.match(/^INIT/);
+      if (initMatches && initMatches.length) {
+        commits.INIT = [...(commits.INIT || []), commit.hash]
+        positions.unshift("INIT");
+      }
+    }
+  }
+  // validate order
+  validateCommitOrder(positions);
+  return commits;
+}
+
 export async function getCommits({
   localDir,
   codeBranch,
@@ -49,48 +98,16 @@ export async function getCommits({
   // track the original branch in case of failure
   const originalBranch = branches.current;
 
-  // Filter relevant logs
-  const commits: CommitLogObject = {};
-
   try {
     // Checkout the code branches
     await git.checkout(codeBranch);
 
     // Load all logs
     const logs = await git.log();
-    const positions: string[] = [];
-
-    for (const commit of logs.all) {
-      const matches = commit.message.match(
-        /^(?<stepId>(?<levelId>L\d+)(S\d+))(?<stepType>[QA])?/
-      );
-
-      if (matches && matches.length) {
-        // Use an object of commit arrays to collect all commits
-        const position = matches[0];
-        if (!commits[position]) {
-          // does not exist, create the list
-          commits[position] = [commit.hash];
-        } else {
-          // add to the list
-          commits[position].push(commit.hash);
-        }
-        positions.unshift(position);
-      } else {
-        const initMatches = commit.message.match(/^INIT/);
-        if (initMatches && initMatches.length) {
-          if (!commits.INIT) {
-            // does not exist, create the list
-            commits.INIT = [commit.hash];
-          } else {
-            // add to the list
-            commits.INIT.push(commit.hash);
-          }
-          positions.unshift("INIT");
-        }
-      }
-    }
-    validateCommitOrder(positions);
+
+    const commits = parseCommits(logs);
+
+    return commits;
   } catch (e) {
     console.error("Error with checkout or commit matching");
     throw new Error(e.message);
@@ -100,6 +117,4 @@ export async function getCommits({
     // cleanup the tmp directory
     await rmdir(tmpDir, { recursive: true });
   }
-
-  return commits;
 }
diff --git a/src/utils/parse.ts b/src/utils/parse.ts
index f8bf49d..443fbf5 100644
--- a/src/utils/parse.ts
+++ b/src/utils/parse.ts
@@ -4,10 +4,7 @@ import * as T from "../../typings/tutorial";
 
 type TutorialFrame = {
   summary: T.TutorialSummary;
-  levels: {
-    [levelKey: string]: T.Level;
-  };
-  steps: { [stepKey: string]: Partial<T.Step> };
+  levels: T.Level[];
 };
 
 export function parseMdContent(md: string): TutorialFrame | never {
@@ -33,8 +30,7 @@ export function parseMdContent(md: string): TutorialFrame | never {
       title: "",
       description: "",
     },
-    levels: {},
-    steps: {},
+    levels: [],
   };
 
   // Capture summary
@@ -49,23 +45,20 @@ export function parseMdContent(md: string): TutorialFrame | never {
     mdContent.summary.description = summaryMatch.groups.tutorialDescription.trim();
   }
 
-  let current = { level: "0", step: "0" };
+  let current = { level: -1, step: -1 };
   // Identify each part of the content
   parts.forEach((section: string) => {
     // match level
-    const levelRegex = /^(#{2}\s(?<levelId>L\d+)\s(?<levelTitle>.*)[\n\r]*(>\s(?<levelSummary>.*))?[\n\r]+(?<levelContent>[^]*))/;
+    const levelRegex = /^(#{2}\s(?<levelId>L?\d+\.?)\s(?<levelTitle>.*)[\n\r]*(>\s(?<levelSummary>.*))?[\n\r]+(?<levelContent>[^]*))/;
     const levelMatch: RegExpMatchArray | null = section.match(levelRegex);
+
     if (levelMatch && levelMatch.groups) {
-      const {
-        levelId,
-        levelTitle,
-        levelSummary,
-        levelContent,
-      } = levelMatch.groups;
+      current = { level: current.level + 1, step: -1 };
+      const { levelTitle, levelSummary, levelContent } = levelMatch.groups;
 
       // @ts-ignore
-      mdContent.levels[levelId] = {
-        id: levelId,
+      mdContent.levels[current.level] = {
+        id: (current.level + 1).toString(),
         title: levelTitle.trim(),
         summary:
           levelSummary && levelSummary.trim().length
@@ -75,23 +68,22 @@ export function parseMdContent(md: string): TutorialFrame | never {
                 omission: "...",
               }),
         content: levelContent.trim(),
+        steps: [],
       };
-      current = { level: levelId, step: "0" };
     } else {
       // match step
-      const stepRegex = /^(#{3}\s(?<stepId>(?<levelId>L\d+)S\d+)\s(?<stepTitle>.*)[\n\r]+(?<stepContent>[^]*))/;
+      const stepRegex = /^(#{3}\s(?<stepTitle>.*)[\n\r]+(?<stepContent>[^]*))/;
       const stepMatch: RegExpMatchArray | null = section.match(stepRegex);
       if (stepMatch && stepMatch.groups) {
+        current = { level: current.level, step: current.step + 1 };
         const { stepId, stepContent } = stepMatch.groups;
-
-        mdContent.steps[stepId] = {
-          id: stepId,
+        mdContent.levels[current.level].steps[current.step] = {
+          id: `${current.level + 1}.${current.step + 1}`,
           content: stepContent.trim(),
         };
-        current = { ...current, step: stepId };
       } else {
         // parse hints from stepContent
-        const hintDetectRegex = /^(#{4}\sHINTS[\n\r]+(\*\s(?<hintContent>[^]*))[\n\r]+)+/;
+        const hintDetectRegex = /^(#{4}\sHINTS[\n\r]+([\*|\-]\s(?<hintContent>[^]*))[\n\r]+)+/;
         const hintMatch = section.match(hintDetectRegex);
         if (!!hintMatch) {
           const hintItemRegex = /[\n\r]+\*\s/;
@@ -100,7 +92,7 @@ export function parseMdContent(md: string): TutorialFrame | never {
             .slice(1) // remove #### HINTS
             .map((h) => h.trim());
           if (hints.length) {
-            mdContent.steps[current.step].hints = hints;
+            mdContent.levels[current.level].steps[current.step].hints = hints;
           }
         }
       }
@@ -135,33 +127,30 @@ export function parse(params: ParseParams): any {
     };
   }
 
-  // merge content and tutorial
-  if (params.skeleton.levels && params.skeleton.levels.length) {
-    parsed.levels = params.skeleton.levels
-      .map((level: T.Level, levelIndex: number) => {
-        const levelContent = mdContent.levels[level.id];
+  // merge content levels and tutorial
 
-        if (!levelContent) {
-          return null;
-        }
+  parsed.levels = mdContent.levels.map(
+    (mdLevel: T.Level, mdLevelIndex: number) => {
+      // add level setup commits
+      let level: T.Level = { ...mdLevel };
 
-        level = { ...level, ...levelContent };
-
-        // add level setup commits
-        const levelSetupKey = level.id;
-        if (params.commits[levelSetupKey]) {
-          level.setup = {
-            ...(level.setup || {}),
-            commits: params.commits[levelSetupKey],
-          };
-        }
+      const configLevel = params.skeleton.levels[mdLevelIndex];
 
+      if (configLevel) {
         // add level step commits
-        try {
-          level.steps = (level.steps || []).map(
-            (step: T.Step, stepIndex: number) => {
-              const stepKey = `${levelSetupKey}S${stepIndex + 1}`;
-              const stepSetupKey = `${stepKey}Q`;
+        const { steps, ...configLevelProps } = configLevel;
+        level = { ...configLevelProps, ...level };
+        if (steps) {
+          steps.forEach((step: T.Step, index: number) => {
+            try {
+              const mdStep = level.steps[index];
+
+              step = {
+                ...step,
+                ...mdStep,
+              };
+
+              const stepSetupKey = `${step.id}:T`;
               if (params.commits[stepSetupKey]) {
                 if (!step.setup) {
                   step.setup = {
@@ -171,7 +160,7 @@ export function parse(params: ParseParams): any {
                 step.setup.commits = params.commits[stepSetupKey];
               }
 
-              const stepSolutionKey = `${stepKey}A`;
+              const stepSolutionKey = `${step.id}:S`;
               if (params.commits[stepSolutionKey]) {
                 if (!step.solution) {
                   step.solution = {
@@ -180,27 +169,35 @@ export function parse(params: ParseParams): any {
                 }
                 step.solution.commits = params.commits[stepSolutionKey];
               }
+            } catch (error) {
+              console.error("Error parsing level steps");
+              console.warn(JSON.stringify(level.steps));
+              console.error(error.message);
+            }
+            // update level step
+            level.steps[index] = step;
+          });
+        }
+      }
 
-              // add markdown
-              const stepMarkdown: Partial<T.Step> = mdContent.steps[step.id];
-              if (stepMarkdown) {
-                step = { ...step, ...stepMarkdown };
-              }
+      if (params.commits[level.id]) {
+        if (!level.setup) {
+          level.setup = {};
+        }
+        level.setup.commits = params.commits[level.id];
+      }
 
-              step.id = `${stepKey}`;
-              return step;
-            }
-          );
-        } catch (error) {
-          console.log(JSON.stringify(level.steps));
-          console.error("Error parsing level steps");
-          console.error(error.message);
+      // @deprecated L1 system
+      if (params.commits[`L${level.id}`]) {
+        if (!level.setup) {
+          level.setup = {};
         }
+        level.setup.commits = params.commits[`L${level.id}`];
+      }
 
-        return level;
-      })
-      .filter((l: T.Level | null) => !!l);
-  }
+      return level;
+    }
+  );
 
   return parsed;
 }
diff --git a/src/utils/validateCommits.ts b/src/utils/validateCommits.ts
index 80d714d..f4d8890 100644
--- a/src/utils/validateCommits.ts
+++ b/src/utils/validateCommits.ts
@@ -3,35 +3,61 @@
 export function validateCommitOrder(positions: string[]): boolean {
   // loop over positions
   const errors: number[] = [];
-  let previous = { level: 0, step: 0 };
-  let current = { level: 0, step: 0 };
+  let previous = { level: 0, step: 0, type: "" };
+  let current = { level: 0, step: 0, type: "" };
   positions.forEach((position: string, index: number) => {
     if (position === "INIT") {
       if (previous.level !== 0 && previous.step !== 0) {
         errors.push(index);
       }
-      current = { level: 0, step: 0 };
+      current = { level: 0, step: 0, type: "" };
       return;
     } else {
-      const levelMatch = position.match(/^L([0-9]+)Q?$/);
-      const stepMatch = position.match(/^L([0-9]+)S([0-9]+)[Q|A]?$/);
+      // @deprecate - remove L|Q
+      const levelMatch = position.match(/^(?<level>[0-9]+)$/);
+      // @deprecate - remove S|Q|A
+      const stepMatch = position.match(
+        /^(?<level>[0-9]+)\.(?<step>[0-9]+):(?<stepType>[T|S])$/
+      );
       if (levelMatch) {
         // allows next level or step
-        const [_, levelString] = levelMatch;
+        const levelString = levelMatch?.groups?.level;
+        if (!levelString) {
+          console.warn(`No commit level match for ${position}`);
+          return;
+        }
         const level = Number(levelString);
-        current = { level, step: 0 };
+        current = { level, step: 0, type: "" };
       } else if (stepMatch) {
         // allows next level or step
-        const [_, levelString, stepString] = stepMatch;
+        if (!stepMatch?.groups?.level || !stepMatch?.groups.step) {
+          console.warn(`No commit step match for ${position}`);
+          return;
+        }
+        const { level: levelString, step: stepString } = stepMatch.groups;
+
         const level = Number(levelString);
         const step = Number(stepString);
-        current = { level, step };
+        const type = stepMatch?.groups.stepType;
+
+        const sameStep = previous.level === level && previous.step === step;
+
+        if (
+          // tests should come before the solution
+          (sameStep && type === "T" && previous.type === "S") ||
+          // step should have tests
+          (!sameStep && type === "S")
+        ) {
+          errors.push(index);
+        }
+        current = { level, step, type };
       } else {
         // error
         console.warn(`Invalid commit position: ${position}`);
         return;
       }
       if (
+        // levels or steps are out of order
         current.level < previous.level ||
         (current.level === previous.level && current.step < previous.step)
       ) {
diff --git a/src/utils/validateMarkdown.ts b/src/utils/validateMarkdown.ts
index c4d13bb..9933c67 100644
--- a/src/utils/validateMarkdown.ts
+++ b/src/utils/validateMarkdown.ts
@@ -24,11 +24,11 @@ const validations: Validation[] = [
     },
   },
   {
-    message: "should have a level `##` with a format of `L[0-9]+`",
+    message: "should have a level `##` with a format of `[0-9]+.`",
     validate: (t) => {
       const headers = t.match(/^#{2}\s(.+)$/gm) || [];
       for (const header of headers) {
-        if (!header.match(/^#{2}\s(L\d+)\s(.+)$/)) {
+        if (!header.match(/^#{2}\s(\d+\.)\s(.+)$/)) {
           return false;
         }
       }
@@ -36,11 +36,11 @@ const validations: Validation[] = [
     },
   },
   {
-    message: "should have a step `###` with a format of `L[0-9]+S[0-9]+`",
+    message: "should have a step `###` with a format of `[0-9].[0-9]+`",
     validate: (t) => {
       const headers = t.match(/^#{3}\s(.+)$/gm) || [];
       for (const header of headers) {
-        if (!header.match(/^#{3}\s(L\d+)S\d+/)) {
+        if (!header.match(/^#{3}\s(\d+\.\d+)/)) {
           return false;
         }
       }
@@ -60,9 +60,9 @@ export function validateMarkdown(md: string): boolean {
   for (const v of validations) {
     if (!v.validate(text)) {
       valid = false;
-      if (process.env.NODE_ENV !== "test") {
-        console.warn(v.message);
-      }
+      // if (process.env.NODE_ENV !== "test") {
+      console.warn(v.message);
+      // }
     }
   }
 
diff --git a/tests/commitOrder.test.ts b/tests/commitOrder.test.ts
index 38e1035..640b25f 100644
--- a/tests/commitOrder.test.ts
+++ b/tests/commitOrder.test.ts
@@ -1,47 +1,61 @@
 import { validateCommitOrder } from "../src/utils/validateCommits";
 
 describe("commitOrder", () => {
-  it("should return true if order is valid", () => {
-    const positions = ["INIT", "L1", "L1S1", "L1S2", "L2", "L2S1"];
-    const result = validateCommitOrder(positions);
-    expect(result).toBe(true);
-  });
-  it("should return true if valid with duplicates", () => {
-    const positions = [
-      "INIT",
-      "INIT",
-      "L1",
-      "L1",
-      "L1S1",
-      "L1S1",
-      "L1S2",
-      "L1S2",
-      "L2",
-      "L2",
-      "L2S1",
-      "L2S1",
-    ];
-    const result = validateCommitOrder(positions);
-    expect(result).toBe(true);
-  });
-  it("should return false if INIT is out of order", () => {
-    const positions = ["INIT", "L1", "L1S1", "L1S2", "INIT", "L2", "L2S1"];
-    const result = validateCommitOrder(positions);
-    expect(result).toBe(false);
-  });
-  it("should return false if level after step is out of order", () => {
-    const positions = ["INIT", "L1", "L1S1", "L1S2", "L2S1", "L2"];
-    const result = validateCommitOrder(positions);
-    expect(result).toBe(false);
-  });
-  it("should return false if level is out of order", () => {
-    const positions = ["INIT", "L1", "L3", "L2"];
-    const result = validateCommitOrder(positions);
-    expect(result).toBe(false);
-  });
-  it("should return false if step is out of order", () => {
-    const positions = ["INIT", "L1", "L1S1", "L1S3", "L1S2"];
-    const result = validateCommitOrder(positions);
-    expect(result).toBe(false);
+  describe("#.# format", () => {
+    it("should return true if order is valid", () => {
+      const positions = ["INIT", "1", "1.1:T", "1.2:T", "2", "2.1:T"];
+      const result = validateCommitOrder(positions);
+      expect(result).toBe(true);
+    });
+    it("should return true if valid with duplicates", () => {
+      const positions = [
+        "INIT",
+        "INIT",
+        "1",
+        "1",
+        "1.1:T",
+        "1.1:T",
+        "1.1:S",
+        "1.1:S",
+        "1.2:T",
+        "1.2:S",
+        "2",
+        "2",
+        "2.1:T",
+        "2.1:S",
+      ];
+      const result = validateCommitOrder(positions);
+      expect(result).toBe(true);
+    });
+    it("should return false if INIT is out of order", () => {
+      const positions = ["INIT", "1", "1.1:T", "1.2:T", "INIT", "2", "2.1:T"];
+      const result = validateCommitOrder(positions);
+      expect(result).toBe(false);
+    });
+    it("should return false if level after step is out of order", () => {
+      const positions = ["INIT", "1", "1.1:T", "1.2:T", "2.1:T", "2"];
+      const result = validateCommitOrder(positions);
+      expect(result).toBe(false);
+    });
+    it("should return false if level is out of order", () => {
+      const positions = ["INIT", "1", "3", "2"];
+      const result = validateCommitOrder(positions);
+      expect(result).toBe(false);
+    });
+    it("should return false if step is out of order", () => {
+      const positions = ["INIT", "1", "1.1:T", "1.3:T", "1.2:T"];
+      const result = validateCommitOrder(positions);
+      expect(result).toBe(false);
+    });
+    it("should return false if solution is before step", () => {
+      const positions = ["INIT", "1", "1.1:S", "1.1:T", "1.2:T"];
+      const result = validateCommitOrder(positions);
+      expect(result).toBe(false);
+    });
+    it("should return false if solution but no test step", () => {
+      const positions = ["INIT", "1", "1.1:S", "1.2:T"];
+      const result = validateCommitOrder(positions);
+      expect(result).toBe(false);
+    });
   });
 });
diff --git a/tests/commitParse.test.ts b/tests/commitParse.test.ts
new file mode 100644
index 0000000..00e1548
--- /dev/null
+++ b/tests/commitParse.test.ts
@@ -0,0 +1,151 @@
+import { parseCommits } from "../src/utils/commits";
+
+describe("commitParse", () => {
+  it("should parse out #. commits", () => {
+    const logs = {
+      all: [
+        {
+          message: "INIT",
+          hash: "1",
+        },
+        {
+          message: "1. First Level",
+          hash: "2",
+        },
+        {
+          message: "1.1 First Step",
+          hash: "3",
+        },
+      ],
+      total: 2,
+      latest: {},
+    };
+    const commits = parseCommits(logs);
+    expect(commits).toEqual({
+      INIT: ["1"],
+      "1": ["2"],
+      "1.1:T": ["3"],
+    });
+  });
+  // @deprecated - remove L#
+  it("should parse out L# commits", () => {
+    const logs = {
+      all: [
+        {
+          message: "INIT",
+          hash: "1",
+        },
+        {
+          message: "L1 First Level",
+          hash: "2",
+        },
+        {
+          message: "L1S1 First Step",
+          hash: "3",
+        },
+      ],
+      total: 2,
+      latest: {},
+    };
+    const commits = parseCommits(logs);
+    expect(commits).toEqual({
+      INIT: ["1"],
+      "1": ["2"],
+      "1.1:T": ["3"],
+    });
+  });
+  // @deprecated - remove with QA
+  it("should parse out #.Q|A commits", () => {
+    const logs = {
+      all: [
+        {
+          message: "INIT",
+          hash: "1",
+        },
+        {
+          message: "1. First Level",
+          hash: "2",
+        },
+        {
+          message: "1.1Q First Step",
+          hash: "3",
+        },
+        {
+          message: "1.1A First Step Solution",
+          hash: "4",
+        },
+      ],
+      total: 2,
+      latest: {},
+    };
+    const commits = parseCommits(logs);
+    expect(commits).toEqual({
+      INIT: ["1"],
+      "1": ["2"],
+      "1.1:T": ["3"],
+      "1.1:S": ["4"],
+    });
+  });
+  it("should parse out #.T|S commits", () => {
+    const logs = {
+      all: [
+        {
+          message: "INIT",
+          hash: "1",
+        },
+        {
+          message: "1. First Level",
+          hash: "2",
+        },
+        {
+          message: "1.1T First Step",
+          hash: "3",
+        },
+        {
+          message: "1.1S First Step Solution",
+          hash: "4",
+        },
+      ],
+      total: 2,
+      latest: {},
+    };
+    const commits = parseCommits(logs);
+    expect(commits).toEqual({
+      INIT: ["1"],
+      "1": ["2"],
+      "1.1:T": ["3"],
+      "1.1:S": ["4"],
+    });
+  });
+  it("should parse out #._|S commits", () => {
+    const logs = {
+      all: [
+        {
+          message: "INIT",
+          hash: "1",
+        },
+        {
+          message: "1. First Level",
+          hash: "2",
+        },
+        {
+          message: "1.1 First Step",
+          hash: "3",
+        },
+        {
+          message: "1.1S First Step Solution",
+          hash: "4",
+        },
+      ],
+      total: 2,
+      latest: {},
+    };
+    const commits = parseCommits(logs);
+    expect(commits).toEqual({
+      INIT: ["1"],
+      "1": ["2"],
+      "1.1:T": ["3"],
+      "1.1:S": ["4"],
+    });
+  });
+});
diff --git a/tests/markdown.test.ts b/tests/markdown.test.ts
index 9b90028..0ab15bf 100644
--- a/tests/markdown.test.ts
+++ b/tests/markdown.test.ts
@@ -5,7 +5,7 @@ describe("validate markdown", () => {
     const md = `
 Description.
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 > Level's summary: a short description of the level's content in one line.
 
@@ -19,7 +19,7 @@ Description.
 
 # Another Title
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 > Level's summary: a short description of the level's content in one line.
 
@@ -29,7 +29,7 @@ Some text that describes the level`;
 Description.
 
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 > Level's summary: a short description of the level's content in one line.
 
@@ -45,7 +45,7 @@ Some text that describes the level
   it("should return false if missing a summary description", () => {
     const md = `# A Title
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 > Level's summary: a short description of the level's content in one line.
 
@@ -79,10 +79,11 @@ A description
 
 Some text that describes the level
 
-### A Step
+### Missing step id
 
 First step
 `;
+    expect(validateMarkdown(md)).toBe(false);
   });
 
   it("should return true for valid markdown", () => {
@@ -90,13 +91,13 @@ First step
 
 Description.
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 > Level's summary: a short description of the level's content in one line.
 
 Some text that describes the level
 
-### L1S1
+### 1.1
 
 First Step`;
     expect(validateMarkdown(md)).toBe(true);
@@ -114,19 +115,19 @@ Should not be a problem
 \`\`\`
 
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 > Level's summary: a short description of the level's content in one line.
 
 Some text that describes the level
 
 \`\`\`
-## Another Level in markdown
+## 2. Another Level in markdown
 
 Should not be an issue
 \`\`\`
 
-### L1S1
+### 1.1
 
 First Step`;
     expect(validateMarkdown(md)).toBe(true);
diff --git a/tests/parse.test.ts b/tests/parse.test.ts
index c4d6ca9..77369c4 100644
--- a/tests/parse.test.ts
+++ b/tests/parse.test.ts
@@ -32,7 +32,7 @@ Short description to be shown as a tutorial's subtitle.
     
 Description.
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 > Level's summary: a short description of the level's content in one line.
 
@@ -40,7 +40,7 @@ Some text
 `;
 
       const skeleton = {
-        levels: [{ id: "L1" }],
+        levels: [{ id: "1" }],
       };
 
       const result = parse({
@@ -51,7 +51,7 @@ Some text
       const expected = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Put Level's title here",
             summary:
               "Level's summary: a short description of the level's content in one line.",
@@ -68,7 +68,7 @@ Some text
     
 Description.
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 > Level's summary: a short description of the level's content in one line.
 
@@ -78,7 +78,7 @@ Some text
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             setup: { files: [], commits: [] },
             solution: { files: [], commits: [] },
             steps: [],
@@ -93,7 +93,7 @@ Some text
       const expected = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Put Level's title here",
             summary:
               "Level's summary: a short description of the level's content in one line.",
@@ -112,12 +112,12 @@ Some text
     
 Description.
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 Some text that becomes the summary
 `;
 
-      const skeleton = { levels: [{ id: "L1" }] };
+      const skeleton = { levels: [{ id: "1" }] };
       const result = parse({
         text: md,
         skeleton,
@@ -126,7 +126,7 @@ Some text that becomes the summary
       const expected = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Put Level's title here",
             summary: "Some text that becomes the summary",
             content: "Some text that becomes the summary",
@@ -142,12 +142,12 @@ Some text that becomes the summary
     
 Description.
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 Some text that becomes the summary and goes beyond the maximum length of 80 so that it gets truncated at the end
 `;
 
-      const skeleton = { levels: [{ id: "L1" }] };
+      const skeleton = { levels: [{ id: "1" }] };
       const result = parse({
         text: md,
         skeleton,
@@ -156,7 +156,7 @@ Some text that becomes the summary and goes beyond the maximum length of 80 so t
       const expected = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Put Level's title here",
             summary: "Some text that becomes the summary",
             content: "Some text that becomes the summary",
@@ -171,14 +171,14 @@ Some text that becomes the summary and goes beyond the maximum length of 80 so t
     
 Description.
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 Some text.
 
 But not including this line.
 `;
 
-      const skeleton = { levels: [{ id: "L1" }] };
+      const skeleton = { levels: [{ id: "1" }] };
       const result = parse({
         text: md,
         skeleton,
@@ -187,7 +187,7 @@ But not including this line.
       const expected = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Put Level's title here",
             summary: "Some text.",
             content: "Some text.\n\nBut not including this line.",
@@ -203,7 +203,7 @@ But not including this line.
 
 Description.
 
-## L1 Put Level's title here
+## 1. Put Level's title here
 
 >
 
@@ -212,7 +212,7 @@ Some text.
 But not including this line.
 `;
 
-      const skeleton = { levels: [{ id: "L1" }] };
+      const skeleton = { levels: [{ id: "1" }] };
       const result = parse({
         text: md,
         skeleton,
@@ -221,7 +221,7 @@ But not including this line.
       const expected = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Put Level's title here",
             summary: "Some text.",
             content: "Some text.\n\nBut not including this line.",
@@ -239,7 +239,7 @@ Description.
 
 Second description line
 
-## L1 Titles
+## 1. Titles
 
 First line
 
@@ -248,7 +248,7 @@ Second line
 Third line
 `;
 
-      const skeleton = { levels: [{ id: "L1" }] };
+      const skeleton = { levels: [{ id: "1" }] };
       const result = parse({
         text: md,
         skeleton,
@@ -260,7 +260,7 @@ Third line
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             summary: "Some text that becomes the summary",
             content: "First line\n\nSecond line\n\nThird line",
           },
@@ -275,21 +275,21 @@ Third line
     
 Description.
 
-## L1 Title
+## 1. Title
 
 First line
 
-### L1S1 Step
+### 1.1
 
 The first step
 `;
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
               },
             ],
           },
@@ -299,7 +299,7 @@ The first step
         text: md,
         skeleton,
         commits: {
-          L1S1Q: ["abcdefg1"],
+          "1.1Q": ["abcdefg1"],
         },
       });
       const expected = {
@@ -308,12 +308,12 @@ The first step
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             summary: "First line",
             content: "First line",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 content: "The first step",
                 setup: {
                   commits: ["abcdefg1"],
@@ -331,21 +331,21 @@ The first step
     
 Description.
 
-## L1 Title
+## 1. Title
 
 First line
 
-### L1S1 Step
+### 1.1 Step
 
 The first step
 `;
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
               },
             ],
           },
@@ -355,7 +355,7 @@ The first step
         text: md,
         skeleton,
         commits: {
-          L1S1Q: ["abcdefg1", "123456789"],
+          "1.1Q": ["abcdefg1", "123456789"],
         },
       });
       const expected = {
@@ -364,12 +364,12 @@ The first step
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             summary: "First line",
             content: "First line",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 content: "The first step",
                 setup: {
                   commits: ["abcdefg1", "123456789"],
@@ -387,18 +387,18 @@ The first step
     
 Description.
 
-## L1 Title
+## 1. Title
 
 First line
 
-### L1S1
+### 1.1
 
 The first step
 `;
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
           },
         ],
       };
@@ -406,7 +406,7 @@ The first step
         text: md,
         skeleton,
         commits: {
-          L1: ["abcdefg1"],
+          "1": ["abcdefg1"],
         },
       });
       const expected = {
@@ -415,7 +415,7 @@ The first step
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             summary: "First line",
             content: "First line",
             setup: {
@@ -432,11 +432,11 @@ The first step
     
 Description.
 
-## L1 Title
+## 1. Title
 
 First line
 
-### L1S1
+### 1.1
 
 The first step
 
@@ -451,10 +451,10 @@ Another line
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
               },
             ],
           },
@@ -464,12 +464,12 @@ Another line
         text: md,
         skeleton,
         commits: {
-          L1: ["abcdefg1"],
-          L1S1Q: ["12345678"],
+          "1": ["abcdefg1"],
+          "1.1Q": ["12345678"],
         },
       });
       const expected = {
-        id: "L1S1",
+        id: "1.1",
         setup: {
           commits: ["12345678"],
         },
@@ -484,21 +484,21 @@ Another line
     
 Description.
 
-## L1 Title
+## 1. Title
 
 First line
 
-### L1S1 Step
+### 1.1 Step
 
 The first step
 `;
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 setup: {
                   commands: ["npm install"],
                   files: ["someFile.js"],
@@ -519,8 +519,8 @@ The first step
         text: md,
         skeleton,
         commits: {
-          L1S1Q: ["abcdefg1", "123456789"],
-          L1S1A: ["1gfedcba", "987654321"],
+          "1.1Q": ["abcdefg1", "123456789"],
+          "1.1A": ["1gfedcba", "987654321"],
         },
       });
       const expected = {
@@ -529,12 +529,12 @@ The first step
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             summary: "First line",
             content: "First line",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 content: "The first step",
                 setup: {
                   commits: ["abcdefg1", "123456789"],
@@ -562,33 +562,33 @@ The first step
     
 Description.
 
-## L1 Title 1
+## 1. Title 1
 
 First level content.
 
-### L1S1
+### 1.1
 
 The first step
 
-### L1S2
+### 1.2
 
 The second step
 
-## L2 Title 2
+## 2. Title 2
 
 Second level content.
 
-### L2S1
+### 2.1
 
 The third step
 `;
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 setup: {
                   commands: ["npm install"],
                   files: ["someFile.js"],
@@ -602,7 +602,7 @@ The third step
                 },
               },
               {
-                id: "L1S2",
+                id: "1.2",
                 setup: {
                   commands: ["npm install"],
                   files: ["someFile.js"],
@@ -618,12 +618,12 @@ The third step
             ],
           },
           {
-            id: "L2",
+            id: "2",
             summary: "Second level content.",
             content: "First level content.",
             steps: [
               {
-                id: "L2S1",
+                id: "2.1",
                 setup: {
                   commands: ["npm install"],
                   files: ["someFile.js"],
@@ -644,12 +644,12 @@ The third step
         text: md,
         skeleton,
         commits: {
-          L1S1Q: ["abcdef1", "123456789"],
-          L1S1A: ["1fedcba", "987654321"],
-          L1S2Q: ["2abcdef"],
-          L1S2A: ["3abcdef"],
-          L2S1Q: ["4abcdef"],
-          L2S1A: ["5abcdef"],
+          "1.1Q": ["abcdef1", "123456789"],
+          "1.1A": ["1fedcba", "987654321"],
+          "1.2Q": ["2abcdef"],
+          "1.2A": ["3abcdef"],
+          "2.1Q": ["4abcdef"],
+          "2.1A": ["5abcdef"],
         },
       });
       const expected = {
@@ -658,13 +658,13 @@ The third step
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Title 1",
             summary: "First level content.",
             content: "First level content.",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 content: "The first step",
                 setup: {
                   commits: ["abcdef1", "123456789"],
@@ -681,7 +681,7 @@ The third step
                 },
               },
               {
-                id: "L1S2",
+                id: "1.2",
                 content: "The second step",
                 setup: {
                   commits: ["2abcdef"],
@@ -700,13 +700,13 @@ The third step
             ],
           },
           {
-            id: "L2",
+            id: "2",
             title: "Title 2",
             summary: "Second level content.",
             content: "Second level content.",
             steps: [
               {
-                id: "L2S1",
+                id: "2.1",
                 content: "The third step",
                 setup: {
                   commits: ["4abcdef"],
@@ -734,11 +734,11 @@ The third step
     
 Description.
 
-## L1 Title 1
+## 1. Title 1
 
 First level content.
 
-### L1S1
+### 1.1
 
 The first step
 
@@ -746,10 +746,10 @@ The first step
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
               },
             ],
           },
@@ -759,7 +759,7 @@ The first step
         text: md,
         skeleton,
         commits: {
-          L1S1Q: ["abcdef1", "123456789"],
+          "1.1Q": ["abcdef1", "123456789"],
         },
       });
       const expected = {
@@ -768,13 +768,13 @@ The first step
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Title 1",
             summary: "First level content.",
             content: "First level content.",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 content: "The first step",
                 setup: {
                   commits: ["abcdef1", "123456789"],
@@ -941,11 +941,11 @@ Description.
     
 Description.
 
-## L1 Title 1
+## 1. Title 1
 
 First level content.
 
-### L1S1
+### 1.1
 
 The first step
 
@@ -958,10 +958,10 @@ The first step
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
               },
             ],
           },
@@ -971,7 +971,7 @@ The first step
         text: md,
         skeleton,
         commits: {
-          L1S1Q: ["abcdef1", "123456789"],
+          "1.1Q": ["abcdef1", "123456789"],
         },
       });
       const expected = {
@@ -980,13 +980,13 @@ The first step
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Title 1",
             summary: "First level content.",
             content: "First level content.",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 content: "The first step",
                 setup: {
                   commits: ["abcdef1", "123456789"],
@@ -1005,11 +1005,11 @@ The first step
     
 Description.
 
-## L1 Title 1
+## 1. Title 1
 
 First level content.
 
-### L1S1
+### 1.1
 
 The first step
 
@@ -1027,10 +1027,10 @@ And spans multiple lines.
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
               },
             ],
           },
@@ -1040,7 +1040,7 @@ And spans multiple lines.
         text: md,
         skeleton,
         commits: {
-          L1S1Q: ["abcdef1", "123456789"],
+          "1.1Q": ["abcdef1", "123456789"],
         },
       });
       const expected = {
@@ -1049,13 +1049,13 @@ And spans multiple lines.
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Title 1",
             summary: "First level content.",
             content: "First level content.",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 content: "The first step",
                 setup: {
                   commits: ["abcdef1", "123456789"],
@@ -1077,11 +1077,11 @@ And spans multiple lines.
     
 Description.
 
-## L1 Title 1
+## 1. Title 1
 
 First level content.
 
-### L1S1
+### 1.1
 
 The first step
 
@@ -1096,20 +1096,20 @@ var a = 1;
 
 And spans multiple lines.
 
-### L1S2
+### 1.2
 
 The second uninterrupted step
 `;
       const skeleton = {
         levels: [
           {
-            id: "L1",
+            id: "1",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
               },
               {
-                id: "L1S2",
+                id: "1.2",
               },
             ],
           },
@@ -1119,9 +1119,9 @@ The second uninterrupted step
         text: md,
         skeleton,
         commits: {
-          L1S1Q: ["abcdef1"],
-          L1S1A: ["123456789"],
-          L1S2Q: ["fedcba1"],
+          "1.1Q": ["abcdef1"],
+          "1.1A": ["123456789"],
+          "1.2Q": ["fedcba1"],
         },
       });
       const expected = {
@@ -1130,13 +1130,13 @@ The second uninterrupted step
         },
         levels: [
           {
-            id: "L1",
+            id: "1",
             title: "Title 1",
             summary: "First level content.",
             content: "First level content.",
             steps: [
               {
-                id: "L1S1",
+                id: "1.1",
                 content: "The first step",
                 setup: {
                   commits: ["abcdef1"],
@@ -1150,7 +1150,7 @@ The second uninterrupted step
                 ],
               },
               {
-                id: "L1S2",
+                id: "1.2",
                 content: "The second uninterrupted step",
                 setup: {
                   commits: ["fedcba1"],
diff --git a/tests/skeleton.test.ts b/tests/skeleton.test.ts
index 4025bc8..57a5f3c 100644
--- a/tests/skeleton.test.ts
+++ b/tests/skeleton.test.ts
@@ -30,7 +30,7 @@ const validJson = {
     {
       steps: [
         {
-          id: "L1S1",
+          id: "1.1",
           setup: {
             files: ["package.json"],
           },
@@ -39,7 +39,7 @@ const validJson = {
           },
         },
         {
-          id: "L1S2",
+          id: "1.2",
           setup: {
             commands: ["npm install"],
           },
@@ -48,7 +48,7 @@ const validJson = {
           },
         },
         {
-          id: "L1S3",
+          id: "1.3",
           setup: {
             files: ["package.json"],
             watchers: ["package.json", "node_modules/some-package"],
@@ -58,7 +58,7 @@ const validJson = {
           },
         },
         {
-          id: "L1S4",
+          id: "1.4",
           setup: {
             commands: [],
             filter: "^Example 2",
@@ -66,7 +66,7 @@ const validJson = {
           },
         },
       ],
-      id: "L1",
+      id: "1",
     },
   ],
 };
@@ -186,7 +186,7 @@ describe("validate skeleton", () => {
     const valid = validateSkeleton(json);
     expect(valid).toBe(false);
   });
-  it("should fial if level is missing id", () => {
+  it("should fail if level is missing id", () => {
     const level1 = { ...validJson.levels[0], id: undefined };
     const json = {
       ...validJson,
diff --git a/tests/tutorial.test.ts b/tests/tutorial.test.ts
index 2f7dde7..f0e809c 100644
--- a/tests/tutorial.test.ts
+++ b/tests/tutorial.test.ts
@@ -39,7 +39,7 @@ describe("validate tutorial", () => {
       },
       levels: [
         {
-          id: "L1",
+          id: "1",
           title: "Level 1",
           summary: "The first level",
           content: "The first level",
diff --git a/typings/tutorial.d.ts b/typings/tutorial.d.ts
index 82b1e2e..2b7769c 100644
--- a/typings/tutorial.d.ts
+++ b/typings/tutorial.d.ts
@@ -25,8 +25,8 @@ export type Level = {
 export type Step = {
   id: string;
   content: string;
-  setup: StepActions;
-  solution: Maybe<StepActions>;
+  setup?: StepActions;
+  solution?: Maybe<StepActions>;
   subtasks?: { [testName: string]: boolean };
   hints?: string[];
 };
@@ -48,7 +48,7 @@ export type TutorialSummary = {
 
 export type StepActions = {
   commands?: string[];
-  commits: string[];
+  commits?: string[];
   files?: string[];
   watchers?: string[];
   filter?: string;