install: detect the test command instead of asking for it
The third setup question wanted an answer about a repo the person may have just cloned, thirty seconds in, before anything had explained why the board needed one. The file that names a project's ecosystem usually names its runner too, so it is read off the project: package.json → npm test, Cargo.toml → cargo test, go.mod → go test ./..., a pyproject/setup.py/tests/ → python3 -m unittest. Nothing recognisable writes BOARD_AGENT_COMMANDS empty rather than the example's Python default, which would be quietly wrong in most repos — and a wrong prefix denies an agent exactly the way an empty one does, so absent beats wrong here. Two details the first attempt got wrong, both now covered: - Detection runs *before* the questions. Placed after them, a Ctrl-D part-way through skipped it and left the example's default. - "Keep what the project already said" reads the existing .env, not the example-seeded defaults dict — which already carries the example's answer, so every first run looked like it had one and detection never ran. The landing page's terminal, README.md and AGENTS.md follow: two questions now, and AGENTS.md documents the detection table.
This commit is contained in:
@@ -155,10 +155,22 @@ the two settings that change what bench *is* — claim-on-move and syncing
|
|||||||
through origin/main — stay invisible to anyone who has not read
|
through origin/main — stay invisible to anyone who has not read
|
||||||
`core/.env.example`. So the first run asks, and writes the file: **solo or
|
`core/.env.example`. So the first run asks, and writes the file: **solo or
|
||||||
team** (team turns `BOARD_COMMIT_MOVES` and `BOARD_SYNC` on together, and
|
team** (team turns `BOARD_COMMIT_MOVES` and `BOARD_SYNC` on together, and
|
||||||
the question says what that costs), **which agent adapter** (enumerated
|
the question says what that costs) and **which agent adapter**
|
||||||
from the adapter directories, so a project's own `local/adapters/` entry
|
(enumerated from the adapter directories, so a project's own
|
||||||
is offered), and **what command runs this project's tests**
|
`local/adapters/` entry is offered).
|
||||||
(`BOARD_AGENT_COMMANDS` — the one a headless agent cannot work around).
|
|
||||||
|
It does not ask what runs the project's tests. That was a third question
|
||||||
|
once, and it wanted an answer about a repo the person may have just
|
||||||
|
cloned, thirty seconds in and before anything had explained why the
|
||||||
|
board needed one. It is detected instead — `package.json` → `npm test`,
|
||||||
|
`Cargo.toml` → `cargo test`, `go.mod` → `go test ./...`, a
|
||||||
|
`pyproject.toml`/`setup.py`/`tests/` → `python3 -m unittest` — and a
|
||||||
|
project matching none of them gets `BOARD_AGENT_COMMANDS` empty, which
|
||||||
|
is honest: an agent then runs no project commands until someone fills it
|
||||||
|
in. A wrong guess costs nothing an empty value would not, since a prefix
|
||||||
|
that matches nothing denies exactly the same way. An existing value is
|
||||||
|
never overwritten, so `--setup` cannot undo a hand-edit.
|
||||||
|
|
||||||
Bare Enter takes the default, Ctrl-D skips the rest, and what lands is
|
Bare Enter takes the default, Ctrl-D skips the rest, and what lands is
|
||||||
`core/.env.example` with the answers substituted into their lines: every
|
`core/.env.example` with the answers substituted into their lines: every
|
||||||
other key, every comment, so the written file is where the project reads
|
other key, every comment, so the written file is where the project reads
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ mkdir .task-manager && curl -L \
|
|||||||
No token, no clone: releases are curated artifacts that never contained
|
No token, no clone: releases are curated artifacts that never contained
|
||||||
bench's own cards or settings, so the board starts empty by construction.
|
bench's own cards or settings, so the board starts empty by construction.
|
||||||
|
|
||||||
The first run asks three questions it cannot answer for you — solo or
|
The first run asks the two questions it cannot answer for you — solo or
|
||||||
team, which agent adapter, what command runs your tests — and writes
|
team, and which agent adapter — and writes `manager/local/.env` from the
|
||||||
`manager/local/.env` from the documented example, so every other setting
|
documented example, so every other setting is discoverable in your own
|
||||||
is discoverable in your own copy. Bare Enter takes the default
|
copy. What runs your tests is read off the project rather than asked
|
||||||
|
(`package.json` → `npm test`, and so on); nothing recognisable leaves
|
||||||
|
`BOARD_AGENT_COMMANDS` empty, and that is the key to set before an agent
|
||||||
|
can run them. Bare Enter takes the default
|
||||||
throughout; with no terminal (CI, a script) it asks nothing and
|
throughout; with no terminal (CI, a script) it asks nothing and
|
||||||
`install.py --setup` asks later.
|
`install.py --setup` asks later.
|
||||||
|
|
||||||
|
|||||||
+57
-17
@@ -124,9 +124,10 @@ def first_boot_clean(dry_run: bool) -> None:
|
|||||||
# ── First-run settings ────────────────────────────────────────────────
|
# ── First-run settings ────────────────────────────────────────────────
|
||||||
#
|
#
|
||||||
# Everything not asked about is written at its documented default, so the
|
# Everything not asked about is written at its documented default, so the
|
||||||
# answers are only the ones no default can be right about: how this
|
# answers are only the ones no default can be right about and nothing
|
||||||
# project works (solo or team), which agent runs its headless jobs, and
|
# can be read off the project: how this project works (solo or team)
|
||||||
# what command runs its tests.
|
# and which agent runs its headless jobs. The test command used to be a
|
||||||
|
# third question; it is detected instead — see TEST_COMMANDS.
|
||||||
|
|
||||||
ENV_EXAMPLE = CORE / ".env.example"
|
ENV_EXAMPLE = CORE / ".env.example"
|
||||||
ENV_FILE = LOCAL / ".env"
|
ENV_FILE = LOCAL / ".env"
|
||||||
@@ -137,10 +138,33 @@ TEAM_NOTE = """\
|
|||||||
merges, and a local main that only ever advances through the board.
|
merges, and a local main that only ever advances through the board.
|
||||||
Solo — today's default — does none of it."""
|
Solo — today's default — does none of it."""
|
||||||
|
|
||||||
COMMANDS_NOTE = """\
|
# What runs this project's tests, read off the project rather than asked.
|
||||||
Headless agents may only run the command prefixes named here, so a test
|
# It was a question once, and it was the wrong one to put to someone
|
||||||
runner missing from the list is a test the work agent cannot run.
|
# thirty seconds into their first run: it wants an answer about a project
|
||||||
Comma-separate several."""
|
# they may have just cloned, before anything has explained why the board
|
||||||
|
# needs it. The file that names a project's ecosystem usually names its
|
||||||
|
# test runner too, so the first match wins and no match writes nothing.
|
||||||
|
#
|
||||||
|
# A wrong guess costs nothing it did not already cost: the prefix simply
|
||||||
|
# never matches, and the agent is denied exactly as it would be with the
|
||||||
|
# key empty. What it must never do is guess something *broader* than the
|
||||||
|
# truth — every entry here is one runner, not a shell.
|
||||||
|
TEST_COMMANDS = [
|
||||||
|
("package.json", "npm test"),
|
||||||
|
("Cargo.toml", "cargo test"),
|
||||||
|
("go.mod", "go test ./..."),
|
||||||
|
("pyproject.toml", "python3 -m unittest"),
|
||||||
|
("setup.py", "python3 -m unittest"),
|
||||||
|
("tests", "python3 -m unittest"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def detect_test_command(root: Path) -> str:
|
||||||
|
"""The project's test runner, or "" when nothing here names one."""
|
||||||
|
for marker, command in TEST_COMMANDS:
|
||||||
|
if (root / marker).exists():
|
||||||
|
return command
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
class _Skipped(Exception):
|
class _Skipped(Exception):
|
||||||
@@ -217,9 +241,25 @@ def _ask(question: str, default: str, note: str = "") -> str:
|
|||||||
return answer or default
|
return answer or default
|
||||||
|
|
||||||
|
|
||||||
def ask_questions(current: dict[str, str], answers: dict[str, str]) -> None:
|
def ask_questions(current: dict[str, str], answers: dict[str, str],
|
||||||
|
held: dict[str, str] | None = None) -> None:
|
||||||
"""Fill `answers` in place — in place because a Ctrl-D part-way through
|
"""Fill `answers` in place — in place because a Ctrl-D part-way through
|
||||||
keeps what was already answered."""
|
keeps what was already answered.
|
||||||
|
|
||||||
|
`current` is the example's values under the existing file's, which is
|
||||||
|
what a question should offer as its default. `held` is only what this
|
||||||
|
project itself has said — empty on a first run — because "keep what is
|
||||||
|
already there" must not mean "keep the example's default".
|
||||||
|
"""
|
||||||
|
held = held or {}
|
||||||
|
# Detected, not asked — and settled before the first question, so a
|
||||||
|
# Ctrl-D part-way through still leaves the project's own runner rather
|
||||||
|
# than the example's Python one. A value this project has already set
|
||||||
|
# wins: --setup must not undo a hand-edit. The example's default is
|
||||||
|
# not such a value, which is why this reads `held` and not `current`.
|
||||||
|
answers["BOARD_AGENT_COMMANDS"] = (
|
||||||
|
held.get("BOARD_AGENT_COMMANDS") or detect_test_command(PROJECT))
|
||||||
|
|
||||||
team = env_on(current.get("BOARD_SYNC", "")) or env_on(
|
team = env_on(current.get("BOARD_SYNC", "")) or env_on(
|
||||||
current.get("BOARD_COMMIT_MOVES", ""))
|
current.get("BOARD_COMMIT_MOVES", ""))
|
||||||
while True:
|
while True:
|
||||||
@@ -246,10 +286,6 @@ def ask_questions(current: dict[str, str], answers: dict[str, str]) -> None:
|
|||||||
print(f" no such adapter here — one of: {', '.join(allowed)}.")
|
print(f" no such adapter here — one of: {', '.join(allowed)}.")
|
||||||
answers["BOARD_AGENT_ADAPTER"] = reply
|
answers["BOARD_AGENT_ADAPTER"] = reply
|
||||||
|
|
||||||
answers["BOARD_AGENT_COMMANDS"] = _ask(
|
|
||||||
"what command runs this project's tests?",
|
|
||||||
current.get("BOARD_AGENT_COMMANDS", "python3 -m unittest"),
|
|
||||||
note=COMMANDS_NOTE)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(dry_run: bool, forced: bool) -> None:
|
def setup(dry_run: bool, forced: bool) -> None:
|
||||||
@@ -261,9 +297,12 @@ def setup(dry_run: bool, forced: bool) -> None:
|
|||||||
return
|
return
|
||||||
verb = "rewrite" if exists else "write"
|
verb = "rewrite" if exists else "write"
|
||||||
if dry_run:
|
if dry_run:
|
||||||
print(f"would ask: solo or team, which agent adapter, the test "
|
found = detect_test_command(PROJECT)
|
||||||
f"command — and {verb} {_rel(ENV_FILE)} from "
|
print(f"would ask: solo or team, which agent adapter — and "
|
||||||
f"{_rel(ENV_EXAMPLE)}.\nDry run — nothing written.\n")
|
f"{verb} {_rel(ENV_FILE)} from {_rel(ENV_EXAMPLE)}, with "
|
||||||
|
f"BOARD_AGENT_COMMANDS="
|
||||||
|
f"{found or '(nothing detected)'}.\n"
|
||||||
|
f"Dry run — nothing written.\n")
|
||||||
return
|
return
|
||||||
if not sys.stdin.isatty():
|
if not sys.stdin.isatty():
|
||||||
if exists:
|
if exists:
|
||||||
@@ -290,7 +329,8 @@ def setup(dry_run: bool, forced: bool) -> None:
|
|||||||
|
|
||||||
answers: dict[str, str] = {}
|
answers: dict[str, str] = {}
|
||||||
try:
|
try:
|
||||||
ask_questions(current, answers)
|
ask_questions(current, answers,
|
||||||
|
held=env_values(base) if exists else {})
|
||||||
except _Skipped:
|
except _Skipped:
|
||||||
print(" skipped — the rest stay at their documented defaults.")
|
print(" skipped — the rest stay at their documented defaults.")
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ $nav
|
|||||||
|
|
||||||
solo or team? <span class="t-accent">[solo]</span>:
|
solo or team? <span class="t-accent">[solo]</span>:
|
||||||
which agent adapter? <span class="t-accent">[claude]</span>:
|
which agent adapter? <span class="t-accent">[claude]</span>:
|
||||||
what command runs this project's tests? <span class="t-accent">[python3 -m unittest]</span>:
|
|
||||||
|
|
||||||
<span class="t-dim"> …</span>
|
<span class="t-dim"> …</span>
|
||||||
|
|
||||||
|
|||||||
@@ -204,17 +204,47 @@ class FirstRunSettings(unittest.TestCase):
|
|||||||
self.env_file.read_text(encoding="utf-8"))
|
self.env_file.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
def test_bare_enter_everywhere_writes_the_shipped_defaults(self):
|
def test_bare_enter_everywhere_writes_the_shipped_defaults(self):
|
||||||
"""Every question defaulted → the file is the example verbatim, so
|
"""Every question defaulted → the file is the example, save the one
|
||||||
the board behaves exactly as it does with no .env at all."""
|
key that is read off the project rather than asked about. A scratch
|
||||||
out = run_install_tty(self.tm, ["", "", ""])
|
host names no test runner, so that key lands empty."""
|
||||||
|
out = run_install_tty(self.tm, ["", ""])
|
||||||
self.assertIn("solo or team?", out)
|
self.assertIn("solo or team?", out)
|
||||||
self.assertIn("which agent adapter?", out)
|
self.assertIn("which agent adapter?", out)
|
||||||
self.assertIn("what command runs this project's tests?", out)
|
self.assertEqual(
|
||||||
self.assertEqual(self.env_file.read_text(encoding="utf-8"),
|
self.env_file.read_text(encoding="utf-8"),
|
||||||
self.example)
|
self.example.replace("BOARD_AGENT_COMMANDS=python3 -m unittest",
|
||||||
|
"BOARD_AGENT_COMMANDS="))
|
||||||
|
|
||||||
|
def test_the_test_command_is_never_asked_for(self):
|
||||||
|
"""It was a third question once. It wanted an answer about a repo
|
||||||
|
the person may have just cloned, before anything had explained why
|
||||||
|
the board needed one."""
|
||||||
|
out = run_install_tty(self.tm, ["", ""])
|
||||||
|
self.assertNotIn("runs this project's tests", out)
|
||||||
|
|
||||||
|
def test_the_test_command_is_read_off_the_project(self):
|
||||||
|
(self.tm.parent / "package.json").write_text("{}\n", encoding="utf-8")
|
||||||
|
run_install_tty(self.tm, ["", ""])
|
||||||
|
self.assertEqual(self.values()["BOARD_AGENT_COMMANDS"], "npm test")
|
||||||
|
|
||||||
|
def test_a_project_naming_no_runner_gets_an_empty_allowlist(self):
|
||||||
|
"""Empty is honest: the agent runs no project commands until
|
||||||
|
someone fills it in. The example's Python default would be quietly
|
||||||
|
wrong in most repos, and wrong is worse than absent here."""
|
||||||
|
run_install_tty(self.tm, ["", ""])
|
||||||
|
self.assertEqual(self.values()["BOARD_AGENT_COMMANDS"], "")
|
||||||
|
|
||||||
|
def test_the_first_marker_wins(self):
|
||||||
|
"""A repo with both a package.json and a tests/ directory is a JS
|
||||||
|
project with tests, not a Python one."""
|
||||||
|
(self.tm.parent / "package.json").write_text("{}\n", encoding="utf-8")
|
||||||
|
(self.tm.parent / "tests").mkdir(exist_ok=True)
|
||||||
|
run_install_tty(self.tm, ["", ""])
|
||||||
|
self.assertEqual(self.values()["BOARD_AGENT_COMMANDS"], "npm test")
|
||||||
|
|
||||||
def test_answers_are_substituted_into_the_whole_example(self):
|
def test_answers_are_substituted_into_the_whole_example(self):
|
||||||
out = run_install_tty(self.tm, ["team", "claude", "npm test"])
|
(self.tm.parent / "package.json").write_text("{}\n", encoding="utf-8")
|
||||||
|
out = run_install_tty(self.tm, ["team", "claude"])
|
||||||
written = self.env_file.read_text(encoding="utf-8")
|
written = self.env_file.read_text(encoding="utf-8")
|
||||||
self.assertEqual(self.values()["BOARD_COMMIT_MOVES"], "1")
|
self.assertEqual(self.values()["BOARD_COMMIT_MOVES"], "1")
|
||||||
self.assertEqual(self.values()["BOARD_SYNC"], "1")
|
self.assertEqual(self.values()["BOARD_SYNC"], "1")
|
||||||
@@ -229,21 +259,25 @@ class FirstRunSettings(unittest.TestCase):
|
|||||||
self.assertIn("Wrote .task-manager/manager/local/.env", out)
|
self.assertIn("Wrote .task-manager/manager/local/.env", out)
|
||||||
|
|
||||||
def test_solo_leaves_both_team_settings_empty(self):
|
def test_solo_leaves_both_team_settings_empty(self):
|
||||||
run_install_tty(self.tm, ["solo", "", ""])
|
run_install_tty(self.tm, ["solo", ""])
|
||||||
self.assertEqual(self.values()["BOARD_COMMIT_MOVES"], "")
|
self.assertEqual(self.values()["BOARD_COMMIT_MOVES"], "")
|
||||||
self.assertEqual(self.values()["BOARD_SYNC"], "")
|
self.assertEqual(self.values()["BOARD_SYNC"], "")
|
||||||
|
|
||||||
def test_an_invalid_answer_is_asked_again(self):
|
def test_an_invalid_answer_is_asked_again(self):
|
||||||
out = run_install_tty(self.tm, ["both", "team", "", ""])
|
out = run_install_tty(self.tm, ["both", "team", ""])
|
||||||
self.assertIn("answer solo or team.", out)
|
self.assertIn("answer solo or team.", out)
|
||||||
self.assertEqual(self.values()["BOARD_SYNC"], "1")
|
self.assertEqual(self.values()["BOARD_SYNC"], "1")
|
||||||
|
|
||||||
def test_ctrl_d_skips_the_rest_and_writes_the_defaults(self):
|
def test_ctrl_d_skips_the_rest_and_writes_the_defaults(self):
|
||||||
|
"""The detected command is settled before the first question, so a
|
||||||
|
Ctrl-D part-way through still leaves the project's own runner —
|
||||||
|
not the example's Python one, which is what a later detection
|
||||||
|
would have been skipped past."""
|
||||||
|
(self.tm.parent / "package.json").write_text("{}\n", encoding="utf-8")
|
||||||
out = run_install_tty(self.tm, ["team", CTRL_D])
|
out = run_install_tty(self.tm, ["team", CTRL_D])
|
||||||
self.assertIn("skipped", out)
|
self.assertIn("skipped", out)
|
||||||
self.assertEqual(self.values()["BOARD_SYNC"], "1")
|
self.assertEqual(self.values()["BOARD_SYNC"], "1")
|
||||||
self.assertEqual(self.values()["BOARD_AGENT_COMMANDS"],
|
self.assertEqual(self.values()["BOARD_AGENT_COMMANDS"], "npm test")
|
||||||
"python3 -m unittest")
|
|
||||||
|
|
||||||
def test_the_adapter_question_enumerates_the_directories(self):
|
def test_the_adapter_question_enumerates_the_directories(self):
|
||||||
"""Adapters are listed from disk, so a project's own local one is
|
"""Adapters are listed from disk, so a project's own local one is
|
||||||
@@ -255,12 +289,12 @@ class FirstRunSettings(unittest.TestCase):
|
|||||||
mine = self.tm / "manager" / "local" / "adapters" / "mine"
|
mine = self.tm / "manager" / "local" / "adapters" / "mine"
|
||||||
mine.mkdir(parents=True)
|
mine.mkdir(parents=True)
|
||||||
(mine / "run").write_text("#!/bin/sh\n", encoding="utf-8")
|
(mine / "run").write_text("#!/bin/sh\n", encoding="utf-8")
|
||||||
out = run_install_tty(self.tm, ["", "mine", ""])
|
out = run_install_tty(self.tm, ["", "mine"])
|
||||||
self.assertIn("here: claude, opencode, mine.", out)
|
self.assertIn("here: claude, opencode, mine.", out)
|
||||||
self.assertEqual(self.values()["BOARD_AGENT_ADAPTER"], "mine")
|
self.assertEqual(self.values()["BOARD_AGENT_ADAPTER"], "mine")
|
||||||
|
|
||||||
def test_an_existing_env_is_never_touched_and_the_run_stays_quiet(self):
|
def test_an_existing_env_is_never_touched_and_the_run_stays_quiet(self):
|
||||||
run_install_tty(self.tm, ["team", "", ""])
|
run_install_tty(self.tm, ["team", ""])
|
||||||
written = self.env_file.read_text(encoding="utf-8")
|
written = self.env_file.read_text(encoding="utf-8")
|
||||||
result = run_install(self.tm)
|
result = run_install(self.tm)
|
||||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||||
@@ -297,13 +331,15 @@ class FirstRunSettings(unittest.TestCase):
|
|||||||
.replace("BOARD_AGENT_COMMANDS=python3 -m unittest",
|
.replace("BOARD_AGENT_COMMANDS=python3 -m unittest",
|
||||||
"BOARD_AGENT_COMMANDS=make test"),
|
"BOARD_AGENT_COMMANDS=make test"),
|
||||||
encoding="utf-8")
|
encoding="utf-8")
|
||||||
out = run_install_tty(self.tm, ["", "", ""], "--setup")
|
out = run_install_tty(self.tm, ["", ""], "--setup")
|
||||||
self.assertIn("[team]", out) # the current file's answer…
|
self.assertIn("[team]", out) # the current file's answer…
|
||||||
self.assertIn("[make test]", out) # …offered as the default
|
|
||||||
self.assertEqual(self.values()["BOARD_SYNC"], "1")
|
self.assertEqual(self.values()["BOARD_SYNC"], "1")
|
||||||
self.assertEqual(self.values()["BOARD_PORT"], "26099")
|
self.assertEqual(self.values()["BOARD_PORT"], "26099")
|
||||||
|
# …and the hand-edited command survives, detection notwithstanding:
|
||||||
|
# an existing answer is never second-guessed by a marker file.
|
||||||
|
self.assertEqual(self.values()["BOARD_AGENT_COMMANDS"], "make test")
|
||||||
|
|
||||||
out = run_install_tty(self.tm, ["solo", "", ""], "--setup")
|
out = run_install_tty(self.tm, ["solo", ""], "--setup")
|
||||||
self.assertEqual(self.values()["BOARD_SYNC"], "")
|
self.assertEqual(self.values()["BOARD_SYNC"], "")
|
||||||
self.assertEqual(self.values()["BOARD_COMMIT_MOVES"], "")
|
self.assertEqual(self.values()["BOARD_COMMIT_MOVES"], "")
|
||||||
self.assertEqual(self.values()["BOARD_PORT"], "26099")
|
self.assertEqual(self.values()["BOARD_PORT"], "26099")
|
||||||
@@ -320,7 +356,7 @@ class FirstRunSettings(unittest.TestCase):
|
|||||||
def test_first_boot_both_clears_the_cards_and_writes_the_env(self):
|
def test_first_boot_both_clears_the_cards_and_writes_the_env(self):
|
||||||
"""The order is load-bearing: .env is one of the two things the
|
"""The order is load-bearing: .env is one of the two things the
|
||||||
first-boot guard reads, so writing it early would skip the clean."""
|
first-boot guard reads, so writing it early would skip the clean."""
|
||||||
out = run_install_tty(self.tm, ["", "", ""])
|
out = run_install_tty(self.tm, ["", ""])
|
||||||
self.assertIn("removed tasks/backlog/00-shipped-card.md", out)
|
self.assertIn("removed tasks/backlog/00-shipped-card.md", out)
|
||||||
self.assertEqual(shipped_files(self.tm), [])
|
self.assertEqual(shipped_files(self.tm), [])
|
||||||
self.assertTrue(self.env_file.is_file())
|
self.assertTrue(self.env_file.is_file())
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ CONFIG = REPO / "manager" / "core" / "config.py"
|
|||||||
TRANSCRIPT = [
|
TRANSCRIPT = [
|
||||||
("solo or team?", "solo or team?", INSTALL),
|
("solo or team?", "solo or team?", INSTALL),
|
||||||
("which agent adapter?", "which agent adapter?", INSTALL),
|
("which agent adapter?", "which agent adapter?", INSTALL),
|
||||||
("what command runs this project's tests?",
|
|
||||||
"what command runs this project's tests?", INSTALL),
|
|
||||||
("python3 -m unittest", "python3 -m unittest", INSTALL),
|
|
||||||
("Task board for", "Task board for", BOARD),
|
("Task board for", "Task board for", BOARD),
|
||||||
("http://127.0.0.1:", "http://127.0.0.1:", BOARD),
|
("http://127.0.0.1:", "http://127.0.0.1:", BOARD),
|
||||||
("26071", "26071", CONFIG),
|
("26071", "26071", CONFIG),
|
||||||
|
|||||||
Reference in New Issue
Block a user