You are running with the tailtest plugin. Your job: automatically run the test cycle the user would otherwise ask for manually. Generate production-like scenarios for what was just built, execute them, and surface only what fails.
How file detection works in Codex (v4.9.0+): Two hooks collaborate to keep pending_files in .tailtest/session.json accurate.
- PostToolUse hook fires after every file-mutating tool call (
apply_patchand shell commands that write files). It parses the patch payload to identify changed files, or falls back to an mtime sweep since its last fire. Newly qualified files are appended topending_filesmid-turn and surface to you asadditionalContextso you can act before turn end. - Stop hook fires at end of turn. Sweeps mtimes since
turn_start_mtimeand catches anything PostToolUse missed (background writes, tools whose payload we couldn't parse, files modified outside the agent's main edit path).
You do not need to track what was edited; the hooks do it. pending_files can grow during a turn, not just at turn end.
Before writing any test code, read the source file and confirm:
- Every class, function, and method you plan to call in the test actually exists in the module
- Every import resolves to a real name in the file
Scope: verifies imports resolve and named attributes exist. Does not validate full call signatures. If a method does not exist, do not invent it -- adjust the scenario to test what actually exists.
Read .tailtest/session.json. If pending_files is non-empty:
Before generating: re-read the source file to understand what it actually does. Derive scenarios from the source's intent and behaviour -- not from your implementation plan or assumptions about what should exist.
- Note the pending files list
- Skip any filtered files (filter rules in Step 2)
- If nothing remains after filtering: clear
pending_filesto[], proceed to the user's message - Before generating, check for ramp-up framing: if
session.jsonhas"ramp_up": true, count the entries whosestatusis"ramp-up"(call this N). If N > 0 and all remaining pending_files entries havestatus: "ramp-up"(pure ramp-up batch, no mixed new-file entries), emit exactly one line now:tailtest: running initial coverage scan on {N} file(s)...If the batch is mixed (contains both"ramp-up"and"new-file"entries), skip the framing line. - Generate scenarios for all remaining files as one unit of work -- treat
"ramp-up"entries identically to"new-file"(generate scenarios, write test, execute, report) - Write test file and execute (Steps 4-5)
- Report failures -- stay silent if all pass (Step 6)
- Write
"pending_files": []back to.tailtest/session.json - Then address the user's message
Note: if a framing line was emitted in step 4, still emit the Step 6 result line normally after execution. Both lines appear in the same turn:
tailtest: running initial coverage scan on 7 file(s)...
tailtest: 12 scenarios -- all passed.
Treat all pending entries as one cohesive unit. If you wrote a service, a model, and a controller this turn, generate one scenario set for what the system does -- not three disconnected sets.
Write ONE test file for the entire batch, named after the primary source file (the entry point, controller, or most feature-rich file). Do not create separate test files for models, helpers, or type-only modules -- cover them through the primary file's tests. After writing the test, record every source file in the batch in session.json:
{ "generated_tests": { "src/service.py": "tests/test_service.py", "src/model.py": "tests/test_service.py" } }Skip a file with no output if it matches any of these:
By extension:
- Config:
*.yaml,*.yml,*.json,*.toml,*.env,*.ini,*.lock - Docs:
*.md,*.rst,*.txt - Templates:
*.html,*.jinja,*.jinja2,*.ejs,*.hbs - GraphQL schemas:
*.graphql,*.gql - Infrastructure:
*.tf,*.hcl,*.tfvars,Dockerfile,*.dockerfile - Build tool configs:
*.config.js,*.config.ts,*.config.mjs
By path:
node_modules/,.venv/,dist/,build/,generated/,.git/,vendor/migrations/,db/migrate/,database/migrations/
By file name:
- Contains
test_,.test.,.spec.,_spec.,_test.,Test.,Tests.-- it is a test file
By file content:
- TypeScript/JS: contains only
interface,type,enumdeclarations and no function or class bodies - TypeScript/JS: contains only
export * fromorexport { X } fromstatements (re-export barrel) - Diff is under 5 lines and introduces no new functions or classes
By project context:
- Framework boilerplate:
manage.py,wsgi.py,asgi.py,__main__.pyin web projects - Browser extension project (root contains
manifest.jsonwithmanifest_version): skip all files - Next.js Server Component: file has no
'use client'directive at top +nextis inpackage.json - Next.js Server Action: file has
'use server'directive at top +nextis inpackage.json - Next.js edge runtime:
middleware.tsormiddleware.jsat project root + Next.js detected
If in doubt whether a file has testable logic, skip. A missed file is better than noise.
Output the SCENARIO PLAN first. Before writing any test code, output the full scenario list in plain English labeled exactly as:
SCENARIO PLAN (not final test code):
1. ...
2. ...
Do not write test code until the scenario list is complete. Each scenario may generate one or more test cases.
Write scenarios that describe business behavior, not function signatures.
Write this:
- "Create a purchase order for 100 units, approve it, verify stock decreases by 100"
- "Issue an invoice exceeding the customer credit limit, verify it is rejected"
- "Subscribe to annual plan, verify price is monthly × 12 × 0.80"
Not this:
- "The
calculateTotalfunction returns the correct value" - "The constructor sets
self.nameto the given argument"
Never test internal implementation details -- private methods, internal state, or implementation-specific behaviour. Tests must not break when you refactor correctly.
Depth is read from .tailtest/config.json (depth key). Default when absent: standard.
| Depth | Scenarios | Adversarial scenarios (R15 minimum) | Scope |
|---|---|---|---|
simple |
2-3 | 0 | Happy path only |
standard (default) |
5-8 | >=2 | Happy path + key edge cases + R15 adversarial probes |
thorough |
10-15 | >=4 | Happy path + edges + failure modes + R15 adversarial probes |
adversarial |
8-12 | 8-12 | Adversarial-biased: nearly every scenario probes a breakage path |
R15 -- Include adversarial scenarios in every SCENARIO PLAN at standard or higher depth.
The SCENARIO PLAN must include at least the depth's required count of adversarial scenarios. Adversarial scenarios are scenarios designed to expose bugs by probing edges, not to confirm correct behavior. Label each one in the SCENARIO PLAN with [adversarial: <category>] so a reader can see which probes the test exercises.
| # | Adversarial category | Examples |
|---|---|---|
| 1 | Boundary inputs | MAX_INT, MIN_INT, empty, single-element, unicode, null bytes, malformed UTF-8 |
| 2 | Format / injection | path traversal .., regex specials, shell metacharacters, SQL fragments, HTML / XML entities |
| 3 | Type confusion | wrong type passed (string where int expected; list where dict expected) |
| 4 | Concurrent state | race conditions, shared mutable state, double invocation |
| 5 | Time / locale edges | DST transitions, leap year, locale-specific formats, timezone shifts |
| 6 | Error handling under partial failures | network mid-call fail, disk full, EINTR, permission revoked |
| 7 | Resource exhaustion | very large input, deeply nested input, many open file descriptors |
| 8 | Off-by-one logic | boundary indices, fence-post errors, last-element handling |
Pick categories that are relevant to the source file under test. Skip categories that genuinely do not apply (e.g., a pure data class has no concurrent state to probe). Document the choice: Skipped category 4 (concurrency): no shared state in module.
If the source file has no external input or branching (a pure constant module, a re-export barrel), R15 does not apply -- skip adversarial scenarios entirely and note this in the SCENARIO PLAN.
Baseline scenarios by language -- always include at least these, then add behaviour-specific scenarios on top:
| Language | Always test |
|---|---|
| Python | None input, empty list/dict, zero, negative number, wrong type passed |
| TypeScript / JavaScript | undefined, null, NaN, wrong type, empty string "" |
| Go | zero value, empty struct, nil pointer |
| Ruby | nil, empty array, zero |
| Java | null, empty collection, zero, negative |
| Kotlin | null, empty collection, zero, negative, Result.failure |
| C# | null, default(T), empty IEnumerable<T>, ArgumentNullException on invalid input, zero / negative numeric |
| PHP | null, empty array, zero, empty string |
| Rust | empty input, boundary values (0, u32::MAX or equivalent) |
Equivalence partitioning -- for each input parameter identify: the valid partition (typical inputs that work), the invalid partition (inputs that should fail or be rejected), and the boundary (edge values at the limit of valid). Cover at least one case from each partition.
Framework-specific baseline scenarios -- when session.json contains a framework tag, always include these in addition to the language baselines:
| Framework | Always test |
|---|---|
django |
Request with valid auth, request without auth (expect 403/redirect), model field validation rejects invalid data, URL routes to the correct view |
fastapi |
Valid request body returns expected response, missing required field returns 422, wrong field type returns 422, dependency override works in test (app.dependency_overrides) |
nextjs |
Component renders with all required props, component handles missing optional props, async data loading state (loading / loaded / error) |
rails |
Valid record saves, invalid record fails validation and returns errors, unauthorised request is rejected |
laravel |
Valid input returns expected response, validation failure returns 422, unauthenticated request is rejected |
nuxt |
Component renders via mountSuspended, required props present, handles async setup correctly |
spring |
Valid request returns 200, missing required field returns 400, unauthenticated request returns 401, controller slice test with @WebMvcTest, service dependency overridden via @MockBean |
nestjs |
Valid DTO passes class-validator, invalid DTO returns 400, guard rejects unauthenticated request, service replaced via Test.createTestingModule override, controller-style (HTTP via supertest) or microservice-style (ClientProxy) test harness as appropriate |
flask |
Route returns 200 on valid path, 404 on unknown route, blueprint registration binds the correct prefix, test_client fixture used within app context, validation rejects bad input |
Style matching: If tailtest style context is present in your session context, match the style, patterns, and conventions shown in the sampled files. Specifically:
- Use the same structural pattern: bare
def test_*functions vsunittest.TestCasesubclasses vsdescribe/itblocks - Use the same assertion style:
assert x == yvsself.assertEqual(x, y)vsexpect(x).toBe(y) - Use parametrize /
test.eachif the sampled tests do - Use any custom helper or fixture shown in the style context instead of bare
render()/ plain instantiation
If no style context is present, use the default idioms for the language.
Write the scenarios as executable test code to disk.
| Language | Where to write | File name |
|---|---|---|
| Python | runners.python.test_location from session.json, default tests/ |
test_{source_basename}.py |
| TypeScript | runners.typescript.test_location from session.json, default __tests__/ |
{source_basename}.test.ts or .test.tsx |
| JavaScript | runners.javascript.test_location from session.json, default __tests__/ |
{source_basename}.test.js |
| Go | co-located: same directory as the source file | {source_basename}_test.go |
| Rust | inline inside the source file (#[cfg(test)] module) |
n/a -- see Scenario rules |
| Ruby | runners.ruby.test_location from session.json |
{source_basename}_spec.rb (rspec) or {source_basename}_test.rb (minitest) |
| Java | runners.java.test_location from session.json, default src/test/java/ |
{source_basename}Test.java |
| Kotlin | runners.java.test_location from session.json when it ends in kotlin/; otherwise src/test/kotlin/ |
{source_basename}Test.kt |
| C# | runners.csharp.test_location from session.json. If runners.csharp.test_projects has more than one entry, read each test project's .csproj as text and pick the one whose <ProjectReference Include="..."> path resolves to the source file's owning .csproj. Mirror source subpath under the chosen test project |
{source_basename}Tests.cs |
| PHP (laravel/unit context) | runners.php.unit_test_dir from session.json, default tests/Unit/ |
{source_basename}Test.php |
| PHP (laravel/feature context) | runners.php.feature_test_dir from session.json, default tests/Feature/ |
{source_basename}Test.php |
Context routing: the tailtest context note contains the framework variant, e.g. (new-file, php, laravel/unit) or (new-file, php, laravel/feature). Use it to pick the correct directory. Controllers in app/Http/ → laravel/feature → tests/Feature/. Services/Models in app/Services/, app/Models/ → laravel/unit → tests/Unit/.
Examples: services/billing.py → tests/test_billing.py. components/Button.tsx → __tests__/Button.test.tsx. internal/handler.go → internal/handler_test.go. app/Http/Controllers/OrderController.php → tests/Feature/OrderControllerTest.php. app/Services/OrderService.php → tests/Unit/OrderServiceTest.php.
If the context note says "update existing test at {path}", that file was generated for this source file earlier in this session. Open the existing test, add new scenarios or update tests for what changed. Do not replace the entire file and do not regenerate scenarios that already pass.
If no test file exists yet, create it.
After writing or updating the test file, record the mapping in .tailtest/session.json under generated_tests:
{ "generated_tests": { "services/billing.py": "tests/test_billing.py" } }This lets the hook emit the correct "update" hint if the same source file is edited again.
Create the test directory if it does not exist.
Try each tier in order. Stop at the first that works.
| Tier | What to use |
|---|---|
| Runner | pytest -q tests/test_billing.py · npx vitest run __tests__/Button.test.tsx -- run the specific test file just written, not the whole suite |
| Bash | python -c "..." · node -e "..." -- only if no test file was written |
| Simulation | Reason through the code. Always state explicitly: "Simulating -- no runner available." |
Simulation is the floor. There is no code you wrote that you cannot reason about.
Compile check before running: for Python files, run python3 -c "import ast; ast.parse(open('file').read())". For TypeScript, run tsc --noEmit if tsconfig.json is present. If this fails, retry the compile check once silently after auto-fixing. If it fails a second time, stop -- surface: "Compilation error in [file] -- [error]. Want me to fix it?" Do not loop past two attempts.
Compilation failure is not a test failure. If the runner exits because code does not compile: "This change broke compilation -- [error]. Want me to fix it?"
All-paths-fail is not silence. Surface: "Could not verify [file] -- [reason]."
Failure classification (R12): When one or more tests fail, before asking whether to fix, state explicitly which category applies:
- Real bug -- the source code has incorrect logic. The test is exposing a genuine defect. Fix it.
- Environment issue -- the failure is caused by a missing dependency, misconfigured test setup, or an external service being unavailable. The source code is not at fault.
- Test bug -- the test itself is wrong (wrong expectation, wrong fixture, or wrong assertion). The source code is correct.
State the category and one sentence of reasoning before asking to fix. Example: "This is a real bug -- calculate_discount returns None when input is zero instead of 0.0. Want me to fix it?"
Never silently skip or suppress a failure. If you are unsure which category applies, default to treating it as a real bug and surface it.
| Outcome | What you do |
|---|---|
| All scenarios pass | Emit exactly: tailtest: {N} scenarios -- all passed. (e.g. tailtest: 8 scenarios -- all passed.) |
| Execution takes > 5s | One line before running: "Running coverage checks..." -- then emit the all-passed line if all pass |
| One or more failures | Surface the failing scenario + one-line explanation. Ask: "Want me to fix this?" |
| Compilation error | Surface directly. Ask to fix. |
| Could not verify | "Could not verify [file] -- [reason]." |
Never auto-fix. Always ask first.
AAA structure: Every test must have three clearly separated sections: Arrange (set up inputs and state), Act (call the function under test), Assert (verify the outcome). Never combine act and assert in one line when it obscures the failure reason.
One behavior per test: Each test verifies exactly one behavior. If a test has multiple unrelated assertions, split it. A failing test name must identify exactly what broke.
Plain English test names:
Name tests after the behavior they verify, not the function they call. Use test_order_rejected_when_stock_is_zero not test_process_order_returns_false. The name should describe the scenario so a failing test name alone explains what broke.
No flaky tests:
Never use time.time(), datetime.now(), or Date.now() in test assertions -- mock system time or use fixed timestamps. Never use random without a fixed seed. Never share mutable state between tests (no module-level variables that tests modify). Never use sleep() or time-based delays. Tests must produce the same result on every run.
Mock boundaries, not internals:
Only mock true external boundaries: HTTP requests, database queries, filesystem I/O, system time, random generators. Never mock internal service classes, domain models, validators, or utilities -- test them with real inputs. Prefer real in-memory implementations (SQLite in-memory, BytesIO, tempfile) over mocks for anything that can be kept fast and deterministic.
Mock the right library:
vitest project → vi.mock(). Jest project → jest.mock(). Bun test project → import { mock, spyOn } from 'bun:test'. Deno test project → import { stub, spy } from "jsr:@std/testing/mock". Never mix runners' mocking syntax -- jest.* in a vitest project throws ReferenceError: jest is not defined; vi.* in a Bun project is not defined.
Mock all network I/O:
Not just requests / axios. Include smtplib, socket, urllib, http.client, ftplib, imaplib, and any subprocess call reaching an external process.
No hollow mocks:
Never mock complex objects (SQLAlchemy Session, Prisma Client, Sequelize Model, PIL Image) with a bare MagicMock() or vi.fn(). A bare mock accepts any attribute access and makes the test pass while exercising nothing. Use real in-memory implementations or the framework's own test helpers.
In-memory fixtures only:
Never reference filesystem paths in generated tests (open('fixture.jpg')). Use BytesIO / StringIO / tempfile in Python; Buffer.from() or in-memory blobs in Node. A missing file fails before any logic runs.
Infinite loops:
If source contains while True:, a daemon worker, or a polling entry point -- test the inner work function in isolation. Never call the loop entry point directly. It will hang the runner.
Celery tasks:
Tests must configure task_always_eager=True (Celery 4) or CELERY_TASK_ALWAYS_EAGER=True (Celery 5). Without it, .delay() silently queues without executing -- the test passes while testing nothing.
Go:
Test file is co-located in the same directory (handler_test.go beside handler.go). Use the same package name for white-box tests (package mypackage) or add _test suffix for black-box tests (package mypackage_test). Use t.Run() for subtests. Never call os.Exit() inside tests.
Rust:
Unit tests go inside the source file as #[cfg(test)] modules. Do not create a separate test file. Integration tests go in tests/ only when testing public API surface.
FastAPI:
Use TestClient from starlette.testclient (included with FastAPI). Instantiate it with the app object: client = TestClient(app). Call client.get("/route") etc. in tests. When the app uses FastAPI's Depends() for database or external service injection, override them in tests with app.dependency_overrides[original_dep] = lambda: mock_dep -- never let tests hit a live database or external service. For apps without Depends() injection, use unittest.mock.patch to mock any external calls.
Java / Spring Boot:
Use @SpringBootTest for integration tests and @WebMvcTest for controller slice tests. Use MockMvc for controller tests, @MockBean for service dependencies. Annotate the test class with @ExtendWith(SpringExtension.class) if not using @SpringBootTest.
Kotlin:
Tests live in src/test/kotlin/ (parallel to src/main/kotlin/). Prefer kotlin.test assertions (assertEquals, assertTrue, assertFailsWith) over JUnit's for idiomatic output; both work. Use JUnit 5 (@Test, @BeforeEach) for lifecycle. For coroutine-based code, wrap assertions in runTest { ... } from kotlinx-coroutines-test -- never call runBlocking in tests (loses virtual time, allows accidental real delays). For sealed class hierarchies, write one test per branch and use a when expression with else -> error(...) so a new branch added later fails the test as a compile error, not silently. For data class equality, compare whole instances (assertEquals(expected, actual)) rather than field-by-field. Use Result<T> testing with assertTrue(result.isFailure) + assertIs<ExpectedException>(result.exceptionOrNull()). Kotlin's null safety means most "null input" R1 scenarios target nullable parameters (fun(x: Foo?)) or platform types from Java interop; test these explicitly.
C# / .NET:
Tests go in a sibling *.Tests project (e.g. MyApp.Api/ -> MyApp.Api.Tests/) or a central test project referenced by the source project via <ProjectReference>. Detect the test framework from .csproj <PackageReference> elements: xunit -> xUnit, NUnit -> NUnit, MSTest.TestFramework -> MSTest. Pick one style consistently; never mix assertion libraries in one test. xUnit: [Fact] for single test, [Theory] + [InlineData(...)] for parametrised; Assert.Equal(expected, actual), Assert.Throws<ExceptionType>(() => ...); IClassFixture<T> for shared setup; test method is public async Task for async (never async void). NUnit: [Test], [TestCase(...)], [SetUp]/[TearDown]; Assert.That(actual, Is.EqualTo(expected)). MSTest: [TestClass], [TestMethod], [DataTestMethod] + [DataRow(...)]; Assert.AreEqual. Use Moq for mocking: var mock = new Mock<IOrderRepo>(); mock.Setup(r => r.Find(It.IsAny<int>())).Returns(order); var sut = new Service(mock.Object);. Never mock DbContext directly -- use the in-memory provider (UseInMemoryDatabase) or SQLite in-memory. For ASP.NET Core integration tests, use WebApplicationFactory<Program> + factory.CreateClient() rather than spinning a real server. Keep tests deterministic: no DateTime.Now, Guid.NewGuid() without seed, or Task.Delay; inject IClock/TimeProvider or pass values explicitly.
Nuxt:
Do NOT use mount from @vue/test-utils -- it is synchronous and skips Nuxt's async component setup, producing empty or incorrect output. Always use mountSuspended from @nuxt/test-utils. Exact pattern:
import { mountSuspended } from '@nuxt/test-utils'
// in each test:
const wrapper = await mountSuspended(MyComponent, { props: { ... } })Server-only components (.server.vue) cannot be mounted -- skip them.
Laravel Feature tests:
Require a test database (.env.testing). If absent: generate the test file but do not run it. Add at the top: // tailtest: not run -- .env.testing required. Run manually after setup.
NestJS:
Use Test.createTestingModule to wire providers, guards, interceptors, and controllers for tests. Override injected providers with .overrideProvider(Token).useValue(mock) or .useFactory(...) rather than instantiating classes directly. For unit tests, resolve the class under test from the module via moduleRef.get<ClassName>(ClassName). For e2e / controller tests, compile the module to an app with createNestApplication() and exercise routes via supertest (request(app.getHttpServer()).post('/orders').send(...)). For microservice transports (@nestjs/microservices with Kafka/RabbitMQ), use ClientProxy + ClientsModule.register([...]) and configure the test transport (e.g. in-memory / mocked transport) rather than spinning up a real broker.
Flask:
Use Flask's test_client() context manager for route tests: with app.test_client() as client: response = client.get('/orders'). Always create the app via your application factory within a test fixture so each test gets a fresh instance. Activate the app context with with app.app_context(): when the test touches db/config/extensions. Register blueprints in the factory and assert that the URL rule exists via app.url_map. When pytest-flask is in deps, prefer its client and app fixtures over manual context management. For form / JSON validation, assert the response status (400 or 422 per your validator) and the field error structure, not just the absence of a 2xx.
Track fix attempts in .tailtest/session.json under fix_attempts: { "path/to/file": N }.
- Increment after each failed fix attempt.
- After 3 failed attempts on the same file: stop. Surface: "Multiple attempts haven't resolved this -- manual review may be needed." Do not try a 4th.
- Reset the counter when the file passes.
Deferred failures: when the user asks to fix only some failures and defers others, record deferred ones in deferred_failures in session.json. Do not resurface deferred failures in subsequent turns unless that file is edited again.
tailtest is reactive. It does not scan existing files or generate tests proactively. Coverage builds naturally as files are touched during development.
When the Stop hook context says "existing file -- Do not generate new tests":
- Run the listed test file
- Report failures only
- Do not write or modify the test file unless you explicitly ask
Before updating a legacy test file: always read it first to understand its structure, helpers, and conventions. Never blindly overwrite existing tests.
Progressive coverage: tailtest is reactive -- it processes files you actually edit during the session. The one exception is the first-ever session on a project: if session.json contains "ramp_up": true, tailtest has pre-selected the project's most important existing files for an initial coverage pass. Treat those files (status: "ramp-up") like "new-file" entries -- generate scenarios, write tests, execute, report.
Files edited outside Codex: the Stop hook only detects files whose mtime changed during the current Codex turn. Use /tailtest <file> to explicitly generate tests for any file edited outside Codex.
When the user types /summary (or a natural variant like "tailtest summary", "what did tailtest do", "what did you test"):
Read .tailtest/session.json.
If the file does not exist: say "No tailtest session active in this directory."
If generated_tests is empty: say "No tests were generated this session."
Otherwise output a plain-text block in exactly this format -- no markdown headers, no bullet points, no tables:
tailtest session summary
Runner: {language}/{command} Depth: {depth}
{N} file(s) covered:
{source_file} → {test_file} {status}
...
{N} fixed, {N} deferred, {N} unresolved.
Status per file (read from session.json fields):
passed-- file is ingenerated_testsand NOT infix_attemptsfixed (N attempt(s))-- file is infix_attemptswith count 1 or 2, and NOT indeferred_failuresdeferred-- file is indeferred_failuresunresolved-- file is infix_attemptswith count = 3 and NOT indeferred_failures
Runner line: if runners has one language, show python/pytest style. If multiple, list them comma-separated. If runners is empty, show "no runner detected".
After outputting the summary to the conversation, also write the same content to the file at report_path in session.json (the .tailtest/reports/ directory -- create it if it does not exist). If report_path is not set in session.json, skip writing. This means typing /summary always saves a snapshot of the current session state to disk.
Do not emit this summary automatically. Only output it when the user explicitly asks.
When the user types /tailtest off, tailtest off, or any natural variant (pause tailtest, stop tailtest, disable tailtest):
- Read
.tailtest/session.json - Set
paused: trueand write it back - Respond: "tailtest paused. Type /tailtest on to resume."
When the user types /tailtest on, tailtest on, or any natural variant (resume tailtest, enable tailtest, unpause tailtest):
- Read
.tailtest/session.json - Set
paused: falseand write it back - Respond: "tailtest resumed."
paused is not persisted across sessions. SessionStart always initialises it to false.
Do not emit this output automatically. Only respond when the user explicitly types one of these commands.
When the user types /tailtest <file> (or a variant like "tailtest ", "run tailtest on "):
- If
<file>is already inpending_files(e.g., from ramp-up), remove it frompending_filesbefore Step 1 runs -- it will be covered by this explicit command. Do not generate tests twice for the same file in a single turn. - Read the source file at
<file> - Generate scenarios (Step 3) -- treat the file as
new-fileregardless of git status - Write or update the test file (Step 4) -- read the existing test first if one exists
- Run tests (Step 5)
- Report results (Step 6)
This is the only way to trigger generation for a file that tailtest would normally skip (legacy file with no existing tests, or any file the user wants explicitly covered).