This document explains how the levels and stages system works in Git Mastery and how to add new levels.
The level system is organized into:
Each level is defined using the following structure:
export interface LevelType {
id: number;
name: string;
description: string;
objectives: string[];
hints: string[];
requirements: LevelRequirement[];
requirementLogic?: "any" | "all";
completedRequirements?: string[];
story?: StoryContext;
resetGitRepo?: boolean;
initialState?: LevelInitialState;
}
The LevelRequirement defines what the user needs to do to complete the level:
export type LevelRequirement = {
id?: string;
command: string;
requiresArgs?: string[];
description: string;
successMessage?: string;
};
Levels are created using the helper functions in src/level/LevelCreator.ts:
// In src/level/stages/my-stage.ts
import {
createLevel,
createRequirement,
createStory,
createInitialState,
createFileStructure,
createGitState,
} from "../LevelCreator";
const myStageLevel1 = createLevel({
id: 1,
name: "mystage.level1.name", // Translation key
description: "mystage.level1.description", // Translation key
objectives: ["mystage.level1.objective1"], // Translation keys
hints: ["mystage.level1.hint1", "mystage.level1.hint2"], // Translation keys
requirements: [
createRequirement({
command: "git command", // The command that completes this level
requiresArgs: ["optional", "required", "args"],
description: "mystage.level1.requirement1.description",
successMessage: "mystage.level1.requirement1.success",
}),
],
story: createStory({
title: "mystage.level1.story.title",
narrative: "mystage.level1.story.narrative",
realWorldContext: "mystage.level1.story.realWorldContext",
taskIntroduction: "mystage.level1.story.taskIntroduction",
}),
initialState: createInitialState({
files: [createFileStructure("/path/to/file.ext", "file content")],
git: createGitState({
initialized: true, // Start with git initialized
currentBranch: "main",
branches: ["main", "feature"],
commits: [
{
message: "Initial commit",
files: ["/path/to/file.ext"],
},
],
}),
}),
});
export const myStageLevels = {
1: myStageLevel1,
// Add more levels here
};
src/level/index.ts:import { myStageLevels } from "./stages/my-stage";
export const allStages = {
// ...existing stages,
MyStage: createStage({
id: "mystage",
name: "mystage.name",
description: "mystage.description",
icon: "🔍",
levels: myStageLevels,
}),
};
Requirements define what a user must do to complete a level. The main ways to define requirements are:
Command Match: User must execute a specific command
createRequirement({
command: "git init",
description: "Initialize a git repository",
});
Command with Arguments: User must execute a command with specific arguments
createRequirement({
command: "git switch",
requiresArgs: ["-c"], // Required argument
description: "Create a new branch",
});
Special Case - Any Argument: User must provide any argument
createRequirement({
command: "git add",
requiresArgs: ["any"], // Any argument will satisfy this
description: "Add a file to staging",
});
The initial state controls how the environment is set up when the level starts:
Files: Create initial files in the file system
files: [
createFileStructure("/README.md", "# Project\n\nThis is a README file"),
createFileStructure("/src/index.js", 'console.log("Hello world");'),
];
Git State: Set up Git repository state
git: createGitState({
initialized: true,
currentBranch: "main",
branches: ["main", "feature"],
commits: [
{
message: "Initial commit",
files: ["/README.md"],
},
{
message: "Add feature",
files: ["/src/feature.js"],
branch: "feature", // Switch to this branch for this commit
},
],
fileChanges: [
{
path: "/README.md",
content: "Updated content",
status: "modified",
},
],
});
All user-facing strings should use translation keys rather than hardcoded strings. Add the corresponding translations to:
src/translations/en/levels.ts for Englishsrc/translations/de/levels.ts for GermanFollow this naming convention for keys:
stageid.level#.elementtype.elementname
Example:
intro.level1.hint1: "Use the git init command"
To test your level: