Cold Start

Goal

Delete ~/projects/first-site/ and rebuild it from the keyboard in eight commands or fewer. Prove the rebuild with four pieces of real terminal output. ---

Type: interactive-form Unit: 2 — No mouse

Goal

Delete ~/projects/first-site/ and rebuild it from the keyboard in eight commands or fewer. Prove the rebuild with four pieces of real terminal output.


How this works

You really delete the folder from Project 1, and you really put it back. Fill the six fenced blocks in starter/submission.md. The tests find fields by those ## headings — do not rename them.

Linux and WSL prompts end $. A Mac prompt ends %. Neither character is part of the command. Strip it if you paste from the screen.

touch makes an empty file. If you delete index.html without a copy, the <title> line from Project 1 is gone for good. Copy the file out first.

Your tasks

  1. Copy index.html somewhere outside ~/projects/first-site/. This is the first line of your transcript.
  2. Delete the folder~/projects/first-site itself, not just the file. rm needs -r for a directory. Read the path twice.
  3. Rebuild the path with one mkdir -p ~/projects/first-site.
  4. Put index.html back from the copy. An empty touched file has no <title>.
  5. Stay inside eight commands.
  6. Capture pwd from inside the rebuilt folder.
  7. Capture ls -la of that folder — the whole listing.
  8. Capture grep -n "<title>" index.html. Quote the search text.
  9. Write project_1_mouse_actions from Project 1, and look at it next to command_count.

What the scaffolding is for

starter/reference-project-1.md is read-only: it is what you are rebuilding. The six fields have to agree with each other — a pwd in the wrong folder will not match an ls of the right one.

Expected output

commands is eight lines or fewer. pwd_output ends with /projects/first-site. ls_la_output includes a row for index.html. grep_output looks like 1:<title>….

Rules

See rubric.md for how this is scored.

Steps

Your files

Download these onto your machine and fill them in. The brief says which, and what “done” looks like.

reference-project-1.md Download

                    # READ ONLY — do not edit

This is what Project 1 left on your disk. It is the definition of "rebuilt", so that you are checking
your work against a record instead of a memory.

## What exists after Project 1

```
~/projects/first-site/index.html
```

One folder inside one folder inside your home directory, holding one file. `index.html` is plain
text with a `<title>` line in it, which you typed in a text editor.

## How you opened it

You double-clicked `index.html` and the browser's address bar filled in with a `file://` address:

```
file:///home/you/projects/first-site/index.html
```

That address works on your machine and nowhere else. Unit 6 replaces it. For now it is only useful
as a way to check, in a browser, that the file you rebuilt is the file you had.

## How you built it

Three applications: a file manager to make the folders, a text editor to make and fill the file, a
browser to open it. Every step was a mouse action.

## The number you wrote down

Project 1 asked you to count your mouse actions — clicks, double-clicks, drags, and menu selections
— across all three applications. Copy that number into the `project_1_mouse_actions` field of your
submission. It is the number this project is measured against.

If you did not record it, redo Project 1's build in a scratch folder and count it now. Do not
estimate: an invented number makes the comparison worthless, and the rubric is looking for the real
one.

                  

submission.md Download

                    # Project 2 — Cold Start: submission

Fill in the six fenced blocks below. Do not rename, reorder, or delete the `##` headings — the tests
find your fields by those headings, and a renamed heading reads as a missing field.

Leave your answer **inside** the fence. Everything outside the fences is instructions and is ignored.

---

## commands

One command per line, in the order you ran them. No blank lines, no output lines, and no `history`
or `clear` — `history` is how you read the list back, not part of the rebuild.

A leading `$`, a leading `%`, or a leading history number is stripped for you, so pasting straight
out of `history` is fine.

```text
```

---

## command_count

A single whole number: how many command lines are in the block above. Count them; do not estimate.
Eight or fewer.

```text
```

---

## pwd_output

The output of `pwd`, run from inside the rebuilt folder. One line.

```text
```

---

## ls_la_output

The full output of `ls -la`, run in the rebuilt folder. Every line, including the `.` and `..` lines.

```text
```

---

## grep_output

The output of `grep -n "<title>" index.html`, run in the rebuilt folder. Point `grep` at the one
file, not at the folder — pointed at a single file it leaves the `path:` prefix off, and that is the
form this field wants.

```text
```

---

## project_1_mouse_actions

A single whole number: the mouse actions you counted in Project 1, from
`reference-project-1.md`.

```text
```

---

## Before you submit

Read your own two numbers next to each other. `command_count` is one of them and
`project_1_mouse_actions` is the other. That gap is what this project was for.

                  

On your machine, run the tests in this project’s tests/ folder. Each one prints PASS or a FAIL: line that names what is wrong.

How it is graded
CriterionWhat earns itWeight
The rebuild really happened, in the right orderThe commands transcript reads, in this order: a cp that takes index.html out of ~/projects/first-site before anything is destroyed; an rm carrying -r, -R, or --recursive whose argument is the folder ~/projects/first-site itself rather than a file inside it; a mkdir that recreates the path; and a cp or touch that puts index.html back. Full credit requires the rm to appear before the mkdir — a transcript that creates before it destroys did not start from nothing. Deduct if the deletion targets only index.html, if the backup copy is missing or taken after the rm, or if the folder is rebuilt at a path other than ~/projects/first-site.30
The evidence is genuine and self-consistentpwd_output is a single absolute path line ending in /projects/first-site. ls_la_output is a real long listing: one line per entry, each beginning with a ten- or eleven-character mode string, including a . line and a .. line — which only appear because -a was used — and a line whose last field is index.html. grep_output is in the single-file line:text form with a leading line number and no index.html: prefix, and the matched text contains <title>. Full credit requires the three captures to describe the same folder: a pwd from one directory and an ls -la from another is the exact confusion this unit exists to remove. Deduct for invented, hand-typed, or reformatted output, and for a grep_output that is empty because index.html was recreated with touch and never refilled.25
The transcript is economicalcommand_count is 8 or fewer and the path is created by a single mkdir -p, not by a chain of single-level mkdirs. Full credit also requires the transcript to read like it was typed with the keyboard's help rather than in full: no near-duplicate lines that differ only by a typo and its retry, no path typed out and then corrected on the next line, no cd to a directory the shell was already standing in, and no command repeated because its output was not read the first time. Tab completion and the Up arrow leave exactly this signature — long paths spelled right the first time, and no line typed twice. Deduct for redundant cds, for ls runs that serve no proof, and for a transcript so padded that it fits the budget only by leaving out work that must have happened.20
The comparison is made, in honest numbersproject_1_mouse_actions holds the count actually recorded during Project 1 — clicks, drags, double-clicks, and menu selections across the file manager, the text editor, and the browser — not a number invented at submission time to make the comparison look good. command_count equals the number of command lines in the commands field, with nothing dropped to fit the budget and nothing added that was not run. Full credit requires both fields to be positive integers measured in the units they claim: mouse actions on one side, whole commands on the other. Deduct if command_count disagrees with the transcript's own line count, if either field is left as a guess or a range, or if history or clear lines were counted as part of the rebuild.25
Total100
Test cases and grader source
Test Checks Expected Weight
rebuild_commands_present All six fields are filled in, command_count parses as a positive integer of 8 or fewer that matches the transcript, and the transcript contains the four commands a cold-start rebuild requires: a cp taking index.html out before anything is destroyed, an rm, a mkdir -p naming a path ending in projects/first-site, and a cp or touch after the mkdir that puts index.html back. PASS 30
captured_output_shape The three captures are real output from the rebuilt folder: pwd_output is one absolute line ending in /projects/first-site, ls_la_output is a long listing whose lines start with a mode string and which includes ., .. and index.html, and grep_output is in the single-file number: text form containing with no path: prefix. PASS 25
deletion_actually_happened Catches a shortcut: Catches the learner who never deleted the folder and wrote down the commands they would have run. It rejects a transcript with no rm -r naming ~/projects/first-site, one that deletes only index.html rather than the directory, one whose rm appears after the mkdir, and one whose bare mkdir (no -p) is aimed at a directory that Project 1 had left in place, which would have printed mkdir: cannot create directory '.../a': File exists instead of succeeding. PASS 25
transcript_not_padded Catches a shortcut: Catches the learner who pads or trims the transcript rather than reporting what they ran: a command_count that disagrees with the number of command lines in its own commands field, a line that is pasted terminal output rather than a command, history or clear counted as part of the rebuild, and a project_1_mouse_actions that is a range or an estimate rather than the whole number recorded in Project 1. PASS 20

tests/test_1_rebuild_commands_present.py

                        #!/usr/bin/env python3
"""Test 1 of 4 - rebuild_commands_present (weight 30).

Every field is filled in, the budget parses, and the four commands that make a cold-start
rebuild are all present in the transcript: the backup copy, the recursive delete, the one-command
`mkdir -p`, and the command that puts index.html back.

Prints PASS, or FAIL: <reason>.

Usage: python3 test_1_rebuild_commands_present.py [path/to/submission.md]
"""

import pathlib
import re
import sys

DEFAULT = pathlib.Path(__file__).resolve().parents[1] / "starter" / "submission.md"
FIELDS = ("commands", "command_count", "pwd_output", "ls_la_output",
          "grep_output", "project_1_mouse_actions")
PROMPT = re.compile(r"^\s*(?:\d+\s+)?(?:[$%]\s+)?")
HEADING = re.compile(r"^##\s+([a-z0-9_]+)\s*$")


def fail(message):
    print("FAIL: " + message)
    raise SystemExit(0)


def read_fields():
    path = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT
    if not path.is_file():
        fail("no submission file at %s" % path)
    found, current, in_fence = {}, None, False
    for line in path.read_text(encoding="utf-8").splitlines():
        if line.lstrip().startswith("```"):
            in_fence = not in_fence
            continue
        if not in_fence:
            match = HEADING.match(line)
            if match:
                current = match.group(1)
                found.setdefault(current, [])
            continue
        if current is not None:
            found[current].append(line)
    return {name: "\n".join(found.get(name, [])).strip() for name in FIELDS}


def command_lines(raw):
    out = []
    for line in raw.splitlines():
        stripped = PROMPT.sub("", line).strip()
        if stripped:
            out.append(stripped)
    return out


def words(line):
    return line.replace('"', " ").replace("'", " ").split()


def has_flag(tokens, shorts, longs):
    for token in tokens[1:]:
        if token in longs:
            return True
        if token.startswith("--") or not token.startswith("-") or len(token) < 2:
            continue
        if any(short in token[1:] for short in shorts):
            return True
    return False


def arguments(tokens):
    return [t.rstrip("/") for t in tokens[1:] if not t.startswith("-")]


def positive_int(raw, field):
    if not re.fullmatch(r"\d+", raw):
        fail("%s must be a single whole number, got %r" % (field, raw))
    value = int(raw)
    if value < 1:
        fail("%s must be a positive integer, got %d" % (field, value))
    return value


def main():
    fields = read_fields()

    for name in FIELDS:
        if not fields[name]:
            fail("field %r is empty - fill in every fenced block in submission.md" % name)

    count = positive_int(fields["command_count"], "command_count")
    if count > 8:
        fail("command_count is %d - the budget is 8 commands or fewer" % count)
    positive_int(fields["project_1_mouse_actions"], "project_1_mouse_actions")

    lines = command_lines(fields["commands"])
    if not lines:
        fail("the commands field has no command lines in it")

    rm_idx = None
    mkdir_idx = None
    backup_idx = None
    restore_idx = None

    for i, line in enumerate(lines):
        tokens = words(line)
        if not tokens:
            continue
        name, args = tokens[0], arguments(tokens)
        if name == "rm" and rm_idx is None:
            rm_idx = i
        if name == "mkdir" and mkdir_idx is None:
            if any(a.endswith("projects/first-site") for a in args) \
                    and has_flag(tokens, ("p",), ("--parents",)):
                mkdir_idx = i
        if name == "cp" and backup_idx is None and (rm_idx is None or i < rm_idx):
            if any(a.endswith("index.html") or "first-site" in a for a in args):
                backup_idx = i
        if name in ("cp", "touch") and mkdir_idx is not None and i > mkdir_idx:
            if restore_idx is None and any(a.endswith("index.html") for a in args):
                restore_idx = i

    if backup_idx is None:
        fail("no cp line taking index.html out of ~/projects/first-site before the first rm - "
             "without a copy there is no way to get the <title> line back")
    if rm_idx is None:
        fail("no rm line in the transcript - the folder was never deleted")
    if mkdir_idx is None:
        fail("no `mkdir -p` line naming a path ending in projects/first-site")
    if restore_idx is None:
        fail("no cp or touch line naming index.html after the mkdir - the folder was rebuilt empty")

    if count != len(lines):
        fail("command_count is %d but the commands field holds %d command lines"
             % (count, len(lines)))

    print("PASS")


main()

                      

tests/test_2_captured_output_shape.py

                        #!/usr/bin/env python3
"""Test 2 of 4 - captured_output_shape (weight 25).

The three captures are real terminal output from the rebuilt folder: pwd is one absolute path
ending in /projects/first-site, ls -la is a long listing containing `.`, `..` and index.html, and
grep_output is in the single-file `line:text` form with the <title> match in it.

Prints PASS, or FAIL: <reason>.

Usage: python3 test_2_captured_output_shape.py [path/to/submission.md]
"""

import pathlib
import re
import sys

DEFAULT = pathlib.Path(__file__).resolve().parents[1] / "starter" / "submission.md"
FIELDS = ("commands", "command_count", "pwd_output", "ls_la_output",
          "grep_output", "project_1_mouse_actions")
PROMPT = re.compile(r"^\s*(?:\d+\s+)?(?:[$%]\s+)?")
HEADING = re.compile(r"^##\s+([a-z0-9_]+)\s*$")
MODE = re.compile(r"^[-dlbcps][rwxsStT-]{9}[.+@]?$")
GREP_LINE = re.compile(r"^\s*(\d+):")


def fail(message):
    print("FAIL: " + message)
    raise SystemExit(0)


def read_fields():
    path = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT
    if not path.is_file():
        fail("no submission file at %s" % path)
    found, current, in_fence = {}, None, False
    for line in path.read_text(encoding="utf-8").splitlines():
        if line.lstrip().startswith("```"):
            in_fence = not in_fence
            continue
        if not in_fence:
            match = HEADING.match(line)
            if match:
                current = match.group(1)
                found.setdefault(current, [])
            continue
        if current is not None:
            found[current].append(line)
    return {name: "\n".join(found.get(name, [])).strip() for name in FIELDS}


def output_lines(raw):
    return [PROMPT.sub("", line).rstrip() for line in raw.splitlines() if line.strip()]


def main():
    fields = read_fields()

    # --- pwd_output ---
    pwd = output_lines(fields["pwd_output"])
    if not pwd:
        fail("pwd_output is empty - run pwd inside the rebuilt folder and paste the line")
    if len(pwd) > 1:
        fail("pwd_output has %d lines - pwd prints exactly one" % len(pwd))
    here = pwd[0].strip().rstrip("/")
    if not here.startswith("/"):
        fail("pwd_output %r is not an absolute path - pwd always prints one starting with /" % here)
    if not here.endswith("/projects/first-site"):
        fail("pwd_output ends at %r - you were not standing in the rebuilt folder when you ran pwd"
             % here)

    # --- ls_la_output ---
    listing = output_lines(fields["ls_la_output"])
    if not listing:
        fail("ls_la_output is empty - run ls -la in the rebuilt folder and paste every line")
    last_fields, mode_ok = set(), False
    for line in listing:
        parts = line.split()
        if not parts:
            continue
        last_fields.add(parts[-1])
        if MODE.match(parts[0]):
            mode_ok = True
    if not mode_ok:
        fail("no line of ls_la_output starts with a mode string like -rw-r--r-- or drwxr-xr-x - "
             "this is not `ls -la` output")
    if "." not in last_fields:
        fail("ls_la_output has no `.` entry - that line only appears with -a, so this is `ls -l`")
    if ".." not in last_fields:
        fail("ls_la_output has no `..` entry - paste the whole listing, not the lines you picked")
    if "index.html" not in last_fields:
        fail("ls_la_output has no index.html entry - the file did not land in the rebuilt folder")

    # --- grep_output ---
    grep = output_lines(fields["grep_output"])
    if not grep:
        fail("grep_output is empty - grep found no <title> line, so index.html has no contents; "
             "touch creates the file empty, so restore it from the copy you took")
    for line in grep:
        if line.lstrip().startswith("index.html:"):
            fail("grep_output line %r carries a path: prefix - you searched a directory; point "
                 "grep at the single file index.html" % line.strip())
    matched = [line for line in grep if GREP_LINE.match(line) and "<title>" in line]
    if not matched:
        fail("no line of grep_output is in `<number>: ...<title>...` form - "
             "run grep -n \"<title>\" index.html and paste what it prints")

    print("PASS")


main()

                      

tests/test_3_deletion_actually_happened.py

                        #!/usr/bin/env python3
"""Test 3 of 4 - deletion_actually_happened (weight 25). ADVERSARIAL.

Catches the learner who never actually deleted anything and submitted the commands they would
have run. Three shortcuts, all of which produce a plausible-looking transcript:

  (a) There is no rm line at all - the Project 1 folder was still on disk the whole time.
  (b) The rm deletes only index.html, not the folder, so `mkdir` had nothing to recreate.
  (c) The rm appears after the mkdir, so the transcript rebuilds a folder it has not destroyed yet.

It also catches the transcript that could not have run as written: a bare `mkdir` (no -p) aimed at
~/projects/first-site with no earlier rm of that folder would have hit the directory left over from
Project 1 and printed `mkdir: cannot create directory '.../a': File exists`.

Prints PASS, or FAIL: <reason>.

Usage: python3 test_3_deletion_actually_happened.py [path/to/submission.md]
"""

import pathlib
import re
import sys

DEFAULT = pathlib.Path(__file__).resolve().parents[1] / "starter" / "submission.md"
FIELDS = ("commands", "command_count", "pwd_output", "ls_la_output",
          "grep_output", "project_1_mouse_actions")
PROMPT = re.compile(r"^\s*(?:\d+\s+)?(?:[$%]\s+)?")
HEADING = re.compile(r"^##\s+([a-z0-9_]+)\s*$")


def fail(message):
    print("FAIL: " + message)
    raise SystemExit(0)


def read_fields():
    path = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT
    if not path.is_file():
        fail("no submission file at %s" % path)
    found, current, in_fence = {}, None, False
    for line in path.read_text(encoding="utf-8").splitlines():
        if line.lstrip().startswith("```"):
            in_fence = not in_fence
            continue
        if not in_fence:
            match = HEADING.match(line)
            if match:
                current = match.group(1)
                found.setdefault(current, [])
            continue
        if current is not None:
            found[current].append(line)
    return {name: "\n".join(found.get(name, [])).strip() for name in FIELDS}


def command_lines(raw):
    out = []
    for line in raw.splitlines():
        stripped = PROMPT.sub("", line).strip()
        if stripped:
            out.append(stripped)
    return out


def words(line):
    return line.replace('"', " ").replace("'", " ").split()


def has_flag(tokens, shorts, longs):
    for token in tokens[1:]:
        if token in longs:
            return True
        if token.startswith("--") or not token.startswith("-") or len(token) < 2:
            continue
        if any(short in token[1:] for short in shorts):
            return True
    return False


def arguments(tokens):
    return [t.rstrip("/") for t in tokens[1:] if not t.startswith("-")]


def is_the_folder(arg):
    return arg.endswith("projects/first-site") or arg == "first-site"


def main():
    fields = read_fields()
    lines = command_lines(fields["commands"])
    if not lines:
        fail("the commands field is empty - nothing to check")

    mkdir_idx, mkdir_has_p = None, False
    folder_rm, file_only_rm = [], []

    for i, line in enumerate(lines):
        tokens = words(line)
        if not tokens:
            continue
        name, args = tokens[0], arguments(tokens)
        if name == "mkdir" and mkdir_idx is None and any(is_the_folder(a) for a in args):
            mkdir_idx = i
            mkdir_has_p = has_flag(tokens, ("p",), ("--parents",))
        if name == "rm":
            if any(is_the_folder(a) for a in args) and has_flag(tokens, ("r", "R"), ("--recursive",)):
                folder_rm.append(i)
            elif args:
                file_only_rm.append(i)

    if mkdir_idx is None:
        fail("no mkdir line names a path ending in projects/first-site - the folder was never "
             "rebuilt, so nothing here is a cold start")

    if not folder_rm:
        if not mkdir_has_p:
            fail("line %d is a bare `mkdir` aimed at projects/first-site with no earlier rm of "
                 "that folder - the Project 1 directory was still there, so this command would "
                 "have printed `mkdir: cannot create directory '.../a': File exists` rather than "
                 "succeeding" % (mkdir_idx + 1))
        if file_only_rm:
            fail("line %d deletes a file but no line deletes the folder ~/projects/first-site "
                 "itself - rm refuses directories by default, so a folder deletion needs -r"
                 % (file_only_rm[0] + 1))
        fail("no `rm -r` line names ~/projects/first-site - the folder was never deleted, so the "
             "rebuild started from the folder that was already there")

    before = [i for i in folder_rm if i < mkdir_idx]
    if not before:
        fail("the rm of ~/projects/first-site is at line %d but the mkdir is at line %d - this "
             "transcript rebuilds the folder before destroying it, which is not a cold start"
             % (folder_rm[0] + 1, mkdir_idx + 1))

    print("PASS")


main()

                      

tests/test_4_transcript_not_padded.py

                        #!/usr/bin/env python3
"""Test 4 of 4 - transcript_not_padded (weight 20). ADVERSARIAL.

Catches the learner who pads or trims the transcript instead of reporting what they ran. Three
shortcuts:

  (a) command_count does not match the number of command lines in the learner's own commands
      field - a count trimmed to fit the eight-command budget, or padded to look like more work.
  (b) A line in commands is terminal output rather than a command: it does not begin with a word
      that could be a command name.
  (c) `history` or `clear` is counted as part of the rebuild. history is how the transcript is read
      back; it is not work the rebuild required.

Prints PASS, or FAIL: <reason>.

Usage: python3 test_4_transcript_not_padded.py [path/to/submission.md]
"""

import pathlib
import re
import sys

DEFAULT = pathlib.Path(__file__).resolve().parents[1] / "starter" / "submission.md"
FIELDS = ("commands", "command_count", "pwd_output", "ls_la_output",
          "grep_output", "project_1_mouse_actions")
PROMPT = re.compile(r"^\s*(?:\d+\s+)?(?:[$%]\s+)?")
HEADING = re.compile(r"^##\s+([a-z0-9_]+)\s*$")
COMMAND_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_.\-]*$")
NOT_WORK = ("history", "clear")


def fail(message):
    print("FAIL: " + message)
    raise SystemExit(0)


def read_fields():
    path = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT
    if not path.is_file():
        fail("no submission file at %s" % path)
    found, current, in_fence = {}, None, False
    for line in path.read_text(encoding="utf-8").splitlines():
        if line.lstrip().startswith("```"):
            in_fence = not in_fence
            continue
        if not in_fence:
            match = HEADING.match(line)
            if match:
                current = match.group(1)
                found.setdefault(current, [])
            continue
        if current is not None:
            found[current].append(line)
    return {name: "\n".join(found.get(name, [])).strip() for name in FIELDS}


def command_lines(raw):
    out = []
    for line in raw.splitlines():
        stripped = PROMPT.sub("", line).strip()
        if stripped:
            out.append(stripped)
    return out


def main():
    fields = read_fields()

    lines = command_lines(fields["commands"])
    if not lines:
        fail("the commands field is empty - paste the transcript, one command per line")

    for i, line in enumerate(lines):
        first = line.split()[0]
        if not COMMAND_NAME.match(first):
            fail("line %d of commands starts with %r, which is not a command name - "
                 "this line is output, not something you ran: %r" % (i + 1, first, line))
        if first in NOT_WORK:
            fail("line %d of commands is %r - that is not part of the rebuild; remove it and "
                 "recount" % (i + 1, first))

    raw_count = fields["command_count"]
    if not re.fullmatch(r"\d+", raw_count):
        fail("command_count must be a single whole number, got %r" % raw_count)
    declared = int(raw_count)

    if declared != len(lines):
        fail("command_count says %d but the commands field holds %d command lines - "
             "the count has to be the transcript's own line count" % (declared, len(lines)))

    raw_mouse = fields["project_1_mouse_actions"]
    if not re.fullmatch(r"\d+", raw_mouse):
        fail("project_1_mouse_actions must be a single whole number, got %r - it is the count you "
             "recorded in Project 1, not a range or an estimate" % raw_mouse)
    if int(raw_mouse) < 1:
        fail("project_1_mouse_actions is %s - Project 1 took more than zero mouse actions"
             % raw_mouse)

    print("PASS")


main()

                      

Erase saved progress?

This erases all quiz scores, reading progress, project checklists, and your name on the certificate. It cannot be undone, and it affects only this course in this browser.