Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-11-04
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Design

## Decisions

- **Put the rule in `content_problems` in `src/devops_cli/roadmap/stage_change.py`, beside the placeholder and message-file rules.**: `check_proposal` calls `content_problems`, and the replay of `stage_eval_gates` calls `check_proposal`, so one rule set judges the job and the replay.
- **Compare the work text that each task states, and mirror the order of `_task_texts`: the test task, the implementation task and the documentation task of a group.**: The number of a task in `tasks.md` comes from that order, so the problem names the numbers that the file shows.
- **Normalize each work text with `" ".join(text.split()).casefold()` before the comparison.**: The model can change spacing or case without changing the work, and the check must still refuse it.
- **Keep the `Verification:` line, the `process` check and the fixed `uv run devops docs generate --sync-readme` suffix out of the comparison.**: Two tasks may share a command and stay different tasks, and a documentation task repeats a work text only when its paragraph is the same.
- **Format the group phrase from the catalog, with one entry for one group and one for two groups, and one entry for the problem itself.**: The single-group case reads as `group 2` and the two-group case as `groups 2 and 4`, and no user-facing English sits in the code.
- **Add the tests to `tests/roadmap/test_roadmap_stage_content.py`.**: That module already tests the content rules of `check_proposal`, so the new rule belongs with them.

## Rejected alternatives

- **Parse the rendered `tasks.md` and compare its task lines.**: `_task_texts` in `src/devops_cli/roadmap/stage_change.py` already renders the tasks from the group fields, so a second parse can drift from the renderer.
- **Compare the whole rendered task line, `Verification:` line and docs command included.**: The `_task_texts` code appends the fixed `REGENERATE_DOCUMENTS` command and the `verify` command to a task, so equal work would look different and a duplicate would pass.
- **Refuse two tasks that share a verification command.**: The acceptance criteria of #3456 require the check to accept group 3 task 3.1 and task 3.2 that differ in work text and share a `Verification:` command.
- **Add a fuzzy match of task text.**: A fuzzy match refuses distinct tasks, and `src/devops_cli/roadmap/stage_change.py` compares text with no such dependency today.
- **Hard-code the problem text in `stage_change.py`.**: `src/devops_cli/lang/en/messages.py` holds every `stage_problem_*` entry that `content_problems` formats, so the text belongs there.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Proposal

## Why

The plan check of `devops roadmap stage` tests the form, the paths, the criteria and the content of a plan, but it never compares two tasks of that plan (#3456). The plan of #3417 held task 3.1 and task 3.2 that added the same paragraph to `docs/roadmap/implement.md`, and every check accepted it. A step runs one task group with a model and a token budget, so a duplicate task can make the model repeat an edit, spend the group on no change, or stop the group when the second edit writes no file.

## What Changes

- `check_proposal` refuses two tasks whose normalized work text is the same, and names both task numbers and the group or groups.
- The duplicate check reads the work text of a task only: `test`, `implementation` and `documentation`. A shared `Verification:` line, a `process` check and the fixed `uv run devops docs generate --sync-readme` suffix never make a duplicate.
- The work text of each task is collapsed to one line and casefolded before it is compared.
- A new message in `src/devops_cli/lang/en/messages.py` carries the duplicate-task problem, with one phrase for a group and one for two groups.
- `tests/roadmap/test_roadmap_stage_content.py` gains a test for a duplicate in one group, a test for a duplicate across two groups, a test for two tasks that share a verification command, a test for a documentation task that repeats a work text, and a test for work text that differs only in spacing and case.
- The change carries a spec delta on `roadmap-stage`, because the plan check is behavior that a consumer observes.
- The change fits one pull request.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `roadmap-stage`: Two tasks of a plan state different work

## Impact

- Code: `src/devops_cli/roadmap/stage_change.py`, `src/devops_cli/lang/en/messages.py`
- Tests: `tests/roadmap/test_roadmap_stage_content.py`

## Feasibility

- The probes confirm the premise: `The path `src/devops_cli/roadmap/stage_change.py` is a file of the staging commit.` and `The path `tests/roadmap/test_roadmap_stage_content.py` is a file of the staging commit.` The code of `src/devops_cli/roadmap/stage_change.py` in the context holds `def check_proposal(` and `def _task_texts(group: StageGroup) -> list[str]:`, so the rule has a place to live and the task order has one renderer.
- The probes confirm the message catalog: `The path `src/devops_cli/lang/en/messages.py` is a file of the staging commit.` That file holds the `stage_problem_*` entries that `content_problems` formats, so the new problem joins them.
- The probes confirm that the task text is already parsed and rendered: `The path `src/devops_cli/roadmap/step_files.py` is a file of the staging commit.` and `_task_texts` renders the tasks of a group from `test`, `implementation`, `documentation` and `process`, so the check needs no parser.
- First check of the real system: `uv run devops ci test tests/roadmap/test_roadmap_stage_content.py` on the branch tip runs the module as it stands and shows the existing content tests pass, before any test of this change is written.
- Second check of the real system: after the new test is written and before the rule, the same command `uv run devops ci test tests/roadmap/test_roadmap_stage_content.py` fails on the new duplicate-task test and passes on the rest.
- Third check: `uv run devops ci test tests/roadmap/` runs the roadmap stage and replay modules that call `check_proposal`, so a new problem on a plan that no fixture duplicates shows up there.
- Fourth check: `uv run devops ci` on the pull request runs the static checks, the type check and the full suite over the new rule.

## Existing Tool Considered

`TaskGroup.tasks` in `src/devops_cli/roadmap/step_files.py` and the renderer `_task_texts` in `src/devops_cli/roadmap/stage_change.py` already hold and order the task text of a group, so the check compares fields that a parser gives it and adds no parser. The cost is about 500 tokens to build and test, and about 15 minutes elapsed.

## Dependencies

None.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## ADDED Requirements

### Requirement: Two tasks of a plan state different work
`check_proposal` SHALL refuse two tasks whose normalized work text is the same, and SHALL name both task numbers and the group or groups of those tasks.

#### Scenario: Duplicate task in one group
- **WHEN** group 2 holds task 2.1 and task 2.3 with the same normalized work text
- **THEN** `check_proposal` returns a problem that names task 2.1, task 2.3 and group 2

#### Scenario: Duplicate task across two groups
- **WHEN** group 2 task 2.1 and group 4 task 4.2 hold the same normalized work text
- **THEN** `check_proposal` returns a problem that names task 2.1, task 4.2, group 2 and group 4

#### Scenario: Two tasks share a verification command
- **WHEN** group 3 task 3.1 and task 3.2 hold different work text and the same `Verification:` command
- **THEN** `check_proposal` returns no duplicate-task problem for task 3.1 and task 3.2

#### Scenario: A documentation task repeats an implementation task
- **WHEN** an `other` group 3 holds task 3.1 that adds a paragraph and task 3.2 that adds the same paragraph with the docs command
- **THEN** `check_proposal` returns a duplicate-task problem that names task 3.1 and task 3.2

#### Scenario: Work text that differs only in spacing and case
- **WHEN** group 1 task 1.1 and task 1.2 hold the same words with different runs of spaces and different case
- **THEN** `check_proposal` returns a duplicate-task problem for task 1.1 and task 1.2
14 changes: 14 additions & 0 deletions openspec/changes/3456-the-plan-check-refuses-two-tasks/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Tasks

## 1. Refuse two plan tasks that state the same work

Reads: `src/devops_cli/roadmap/stage_change.py`, `src/devops_cli/roadmap/step_files.py`, `tests/roadmap/test_roadmap_stage_content.py`, `tests/support/roadmap_stage.py`. Changes: `src/devops_cli/roadmap/stage_change.py`, `src/devops_cli/lang/en/messages.py`, `tests/roadmap/test_roadmap_stage_content.py`.

- [ ] 1.1 Add to `tests/roadmap/test_roadmap_stage_content.py` a test for a duplicate in one group, a test for a duplicate across two groups, a test for two tasks that share a `Verification:` command, a test for a documentation task that repeats a work text, and a test for work text that differs only in spacing and case; each test builds a `StageProposal` and calls `check_proposal`
- Verification: `uv run devops ci test tests/roadmap/test_roadmap_stage_content.py` fails on the base
- [ ] 1.2 Add `stage_problem_duplicate_task`, `stage_group_one` and `stage_group_two` to the roadmap messages of `src/devops_cli/lang/en/messages.py`. Add a helper in `src/devops_cli/roadmap/stage_change.py` that returns the work text of each task of a group in the order of `_task_texts`: the test, the implementation and the documentation. Normalize each text with `" ".join(text.split()).casefold()`, compare every task of the plan with every later task, and append the formatted problem to `content_problems`. Name both task numbers and the group or groups, and update the docstring of `content_problems`
- Verification: `uv run devops ci test tests/roadmap/test_roadmap_stage_content.py` passes

## Workflow follow-up

- Pending a person: review the diff in tuicr (tuicr -r <base>..HEAD).
Loading