Follow-up to #190 — thanks for the fast turnaround on that one; the check catches the shape reported there.
Summary
pilot spec validate's files-incomplete rule treats a directory whose name contains a dot as a file. A Verify: command that hands a test directory to the runner — dotnet test tests/Widgets.Tests, pytest tests/widgets.unit — produces a warning naming that directory, and the warning does not clear when the plan lists every test file the task actually touches. Since Step 10.0 now gates on warnings ("treat each warning the same way unless it is provably wrong for this plan"), every task in a plan for a .NET-style layout (tests/<Project>.Tests/ is the conventional project directory name) carries one warning the author can only silence by listing a directory under Test: — which is not a review-scope entry and is exactly the kind of thing the rule says not to do.
The rule does not consult the filesystem (the directory exists on disk in the repro below), so the classification is purely lexical: a dot in the last path segment reads as an extension.
Reproduction
Pilot Shell v11.0.4.
mkdir -p repro/src/widgets repro/tests/widgets.unit && cd repro && git init -q
touch src/widgets/parser.py tests/widgets.unit/test_parser.py
cat > plan.md <<'EOF'
# Widget parser Implementation Plan
Created: 2026-09-17
Status: PENDING
Approved: No
Iterations: 0
Worktree: No
Type: Feature
## Summary
**Goal:** Reject malformed widget headers.
## Progress Tracking
- [ ] Task 1: Parser rejects malformed headers
## Implementation Tasks
### Task 1: Parser rejects malformed headers
**Objective:** Make the parser raise on a header without a version field.
**Files:**
- Modify: `src/widgets/parser.py`
- Test: `tests/widgets.unit/test_parser.py`
**Key Decisions / Notes:**
- None.
**Definition of Done:**
- [ ] `parse("widget:")` raises `HeaderError`
- [ ] Verify: `uv run pytest tests/widgets.unit -q`
EOF
pilot spec validate plan.md --json | jq '.warnings'
Output:
[
{
"rule": "files-incomplete",
"message": "Task 1 names `tests/widgets.unit`, which no task's `Files:` block lists. Those blocks are the review scope, so the change lands out of scope or unreviewed -- list the path under Create/Modify/Delete/Rename/Test, or cite it as a `file:line` reference if the task only reads it.",
"line": 20
}
]
The test file the task touches is listed under Test:; the warning is for the directory that contains it.
Varying only the directory name in the Verify: line:
Verify: argument |
files-incomplete |
tests/widgets |
no |
tests/widgets/ |
no |
tests/widgets-unit |
no |
tests/widgets.unit |
yes |
tests/Widgets.Tests |
yes |
So the discriminator is the dot in the final segment, not whether the path is a directory.
Suggestion
Either of these would clear it without loosening the check:
- When the plan is validated inside a repository, resolve the path and skip it if it is a directory on disk (a directory is never a reviewable diff target on its own).
- Failing that, treat a path that is a strict prefix of a path already listed in some
Files: block as covered — tests/widgets.unit is a prefix of the listed tests/widgets.unit/test_parser.py, which is the case a test-runner directory argument always produces.
(2) is purely lexical and consistent with the current implementation; (1) is more precise. Either way the file:line escape hatch does not fit here — the directory is neither read nor written, it is the runner's argument.
Follow-up to #190 — thanks for the fast turnaround on that one; the check catches the shape reported there.
Summary
pilot spec validate'sfiles-incompleterule treats a directory whose name contains a dot as a file. AVerify:command that hands a test directory to the runner —dotnet test tests/Widgets.Tests,pytest tests/widgets.unit— produces a warning naming that directory, and the warning does not clear when the plan lists every test file the task actually touches. Since Step 10.0 now gates on warnings ("treat eachwarningthe same way unless it is provably wrong for this plan"), every task in a plan for a .NET-style layout (tests/<Project>.Tests/is the conventional project directory name) carries one warning the author can only silence by listing a directory underTest:— which is not a review-scope entry and is exactly the kind of thing the rule says not to do.The rule does not consult the filesystem (the directory exists on disk in the repro below), so the classification is purely lexical: a dot in the last path segment reads as an extension.
Reproduction
Pilot Shell v11.0.4.
Output:
[ { "rule": "files-incomplete", "message": "Task 1 names `tests/widgets.unit`, which no task's `Files:` block lists. Those blocks are the review scope, so the change lands out of scope or unreviewed -- list the path under Create/Modify/Delete/Rename/Test, or cite it as a `file:line` reference if the task only reads it.", "line": 20 } ]The test file the task touches is listed under
Test:; the warning is for the directory that contains it.Varying only the directory name in the
Verify:line:Verify:argumentfiles-incompletetests/widgetstests/widgets/tests/widgets-unittests/widgets.unittests/Widgets.TestsSo the discriminator is the dot in the final segment, not whether the path is a directory.
Suggestion
Either of these would clear it without loosening the check:
Files:block as covered —tests/widgets.unitis a prefix of the listedtests/widgets.unit/test_parser.py, which is the case a test-runner directory argument always produces.(2) is purely lexical and consistent with the current implementation; (1) is more precise. Either way the
file:lineescape hatch does not fit here — the directory is neither read nor written, it is the runner's argument.