Repository navigation
Description
Problem or Motivation
The stage job accepts a plan whose ## Criteria section holds an acceptance criterion that the issue does not hold. The plan for #3320 in PR #3333 shows it. Issue #3320 has no Acceptance criteria heading in the part that a person wrote. Its only list of criteria sits in the devops-roadmap-refine block. The prompt src/devops_cli/ai/tasks/roadmap_stage_change.md says to give one entry for each item of the person-written list and none for the list in the refine block, and an issue with no such list gets an empty list.
The plan copied the five requirements of the refine block into ## Criteria under groups 1, 1, 2, 2 and 3. criterion_problems in src/devops_cli/roadmap/stage_criteria.py refuses only a missing criterion and a group number over the group count. It accepts an entry that no person wrote. The implement job reads this section, so it verifies steps against criteria that the model invented, and it hides that the person gave no acceptance criteria.
Proposed Solution & Architectural Design
- In
criterion_problems, refuse an entry whose text is not one ofacceptance_criteria(issue_body). The function already cuts off the refine block, so the check reads only the person-written part. - Add a test in
tests/roadmap/test_roadmap_stage_criteria.pyfor a body with no person-written heading and a reply whosecriteriaholds an entry: the check names the entry. - Add a test for a body whose only list is in the refine block: the check accepts an empty
criterialist and refuses a copied entry. - State in
src/devops_cli/ai/tasks/roadmap_stage_change.mdthat a plan whose issue has no person-written list gives an emptycriterialist, so the returned plan tells the model the same rule.
Target Subsystem
src/devops_cli/roadmap/stage_criteria.py (criterion_problems), src/devops_cli/ai/tasks/roadmap_stage_change.md, tests/roadmap/test_roadmap_stage_criteria.py.
Existing Tool Considered
No open, maintained tool covers the need. The rule compares a reply with the text of one issue of this repository, so it lives in criterion_problems. The change deletes no internal code: acceptance_criteria and criterion_problems stay and gain one comparison and one problem text.
Source: #3333
Proposed design
Problem statement
The stage criteria check accepts an entry in a plan's ## Criteria section even when the issue has no person-written criterion with that text. The plan for #3320 copied criteria from the refine block. The implement job then verifies steps against criteria that the model invented. The prompt already states the empty-list rule, and the check does not enforce it. The change makes the check refuse any entry that is not one of the person-written criteria.
Acceptance criteria
Requirement: Refuse an entry that no person wrote
criterion_problems SHALL return a problem for each criteria entry that is not a person-written criterion of the issue.
Scenario: An entry copies a refine-block criterion
- WHEN the issue body has no person-written
Acceptance criteriaheading and the reply holds one criterion from the refine block - THEN
criterion_problemsreturns one problem that quotes the copied criterion.
Requirement: Accept an empty list when no person wrote a criterion
criterion_problems MUST return no problem for an empty criteria list when the issue has no person-written acceptance criteria.
Scenario: An issue has no person-written list
- WHEN the issue body has no
Acceptance criteriaheading outside the refine block and the reply has an emptycriterialist - THEN
criterion_problemsreturns an empty problem list.
Requirement: Refuse a copied refine-block entry
criterion_problems SHALL refuse a criteria entry copied from the refine block when that block is the only acceptance criteria list.
Scenario: The refine block is the only list
- WHEN the issue body has an
Acceptance criteriaheading only inside the refine block and the reply copies one entry - THEN
criterion_problemsreturns a problem that names the copied entry.
Requirement: Test the two issue bodies
The stage criteria test module MUST cover a body with no person-written heading and a body with only a refine-block list.
Scenario: The two issue bodies have tests
- WHEN the stage criteria test module runs
- THEN the tests for the no-heading body and the refine-only body pass.
Requirement: State the empty-list rule in the stage prompt
The stage change prompt SHALL state that an issue with no person-written acceptance criteria list gets an empty criteria list.
Scenario: The prompt states the empty-list rule
- WHEN a test reads
src/devops_cli/ai/tasks/roadmap_stage_change.md - THEN the file contains the rule that an issue with no person-written list gets an empty
criterialist.
Existing Tool Considered
No alternatives check has run. No open, maintained tool covers the need. The rule compares a reply with the text of one issue in this repository, and it belongs in criterion_problems. The change adds one comparison and one problem text to existing code. The written cost is about 3k tokens to build, keep and fix the code, and under 1 hour.
Key questions
- Does
acceptance_criteriaread only the person-written part of the issue body?: Yes.acceptance_criteriacalls_without_refine_blockand reads only the text before and after the refine block. A body with noAcceptance criteriaheading outside that block returns an empty list. [sources: src/devops_cli/roadmap/stage_criteria.py] - Should
criterion_problemsrefuse an entry that no person wrote?: Yes. The check must enforce the prompt rule. An entry that is not inacceptance_criteria(issue_body)gets a problem. (best practice) [sources: src/devops_cli/roadmap/stage_criteria.py, src/devops_cli/ai/tasks/roadmap_stage_change.md]- Chosen: Reject unknown entries. The stage prompt says to copy only person-written criteria. The check must enforce that rule.
- Rejected: Accept unknown entries. The implement job then verifies invented criteria and hides that the person gave no criteria.
- Rejected: Reject only refine-block text. The check cannot tell every invented text from refine-block text. It must use the person-written list as the only source.
- How should the check compare an entry with a person-written criterion?: Use
_one_lineon the entry and compare it with the set fromacceptance_criteria. The comparison is exact after whitespace normalization. (best practice) [sources: src/devops_cli/roadmap/stage_criteria.py]- Chosen: Normalized exact match. The existing check normalizes spacing with
_one_line, and the prompt requires copied text. - Rejected: Raw text match. Whitespace changes would refuse a valid copy.
- Rejected: Substring match. A partial text could match a different criterion and weaken the check.
- Chosen: Normalized exact match. The existing check normalizes spacing with
- Where should the new problem text live?: Add a new message under
MESSAGES.roadmapand use it incriterion_problems. The message names the entry and says that it is not a person-written criterion. (best practice) [sources: src/devops_cli/roadmap/stage_criteria.py]- Chosen: New localized message. The module already reads problem texts from
MESSAGES.roadmap, and tests assert the exact text. - Rejected: Inline string. It breaks the existing message pattern and makes the text harder to keep.
- Rejected: Reuse the group message. That message names a group count, not an unknown criterion.
- Chosen: New localized message. The module already reads problem texts from
- What should the stage prompt state for an issue with no person-written list?: The prompt already says that an issue with no such list gets an empty
criterialist. Keep that sentence and add a test that reads it. No prompt edit is needed. (best practice) [sources: src/devops_cli/ai/tasks/roadmap_stage_change.md]- Chosen: Keep the existing empty-list rule. The prompt already has the rule, and a test can check it.
- Rejected: Add a second statement. It would duplicate the rule and add no behavior.
- Rejected: Remove the rule. The model then may copy refine-block entries.
- Which tests should cover the new rule?: Add two tests in
tests/roadmap/test_roadmap_stage_criteria.py. One test uses a body with no person-written heading and an entry. The other uses a body whose only list is in the refine block, accepts an empty list and refuses a copied entry. (best practice) [sources: tests/roadmap/test_roadmap_stage_criteria.py, src/devops_cli/roadmap/stage_criteria.py]- Chosen: Two focused tests. They match the two failure cases in the issue and assert the problem text.
- Rejected: One broad test. It would hide which case failed.
- Rejected: No new test. The check would have no regression guard.
Fit
Fits in one pull request.
Suspected block
None.
Dependencies
None.
Alternatives check of 2026-11-06: none.
Tool: none found.
Activity
devops-cli-service commentedon Nov 4, 2026
Status set to Ready at 46fc42e (see proposed design in issue body).
[-]fix(roadmap): the stage criteria check refuses an entry the issue does not hold[/-][+]fix(roadmap): Refuse criteria entries not in the issue body[/+]dan-petty commentedon Nov 6, 2026
The job shortened the title of this item. The summary had 65 characters, and the limit is 60. The type and the scope stay.
fix(roadmap): the stage criteria check refuses an entry the issue does not holdfix(roadmap): Refuse criteria entries not in the issue bodydevops-cli-service commentedon Nov 6, 2026
Alternatives check: none
Tool: none found
Lacks:
criteriareply against the person-written acceptance criteria of one issue in this repository; the rule belongs incriterion_problemsnext toacceptance_criteria.devops-roadmap-refineblock, so criteria copied from that block are refused and an issue with no person-written list accepts an emptycriterialist — a repo-specific reading of one issue body, not a general-purpose function.acceptance_criteria(issue_body), one new problem text underMESSAGES.roadmap, and two focused tests intests/roadmap/test_roadmap_stage_criteria.py.criterialist) is already stated insrc/devops_cli/ai/tasks/roadmap_stage_change.md; only a test that reads it is added.Searches run:
Checked on 2026-11-06. A person decides what this means for the item. The check changes no label and closes no issue.
devops-cli-service commentedon Nov 7, 2026
Staged: branch
fix/3391-refuse-criteria-entries-not-in-the, draft pull request #4069. The pull request plans the item and closes no issue.The task groups, with the tokens of the inputs of each step, for a budget of 64000 tokens:
A model review of the plan found these points. They stop no draft, and a person judges them:
false_premiseat feasibility: The plan says 3411 is the only golden case with acriterialist and that the new rule refuses no recorded plan, but 3174's plan.json also holds acriterialist (empty), and the replay's_path_gatecallscheck_proposalwithoutissue_body, so the new rule would refuse 3411's entry there. Evidence: ""criteria": []"task_orderat group 1: Group 1 adds the unknown-entry rule tocriterion_problems, which the replay'spathsgate also calls with no issue body; the existing replay test that asserts a mapped plan is not refused fails until group 2 passesplan.issue_body. Group 1 therefore needs group 2's result, and the groups should be reordered or merged. Evidence: "assert not replay_case(_with_criteria(good, both)).refused"false_premiseat feasibility: The plan says the known defect of case 3174 is a placeholder group at thecontentgate, but the case file pins that defect to no gate; the plan's reasoning about the replay's pinned counts rests on a wrong fact. Evidence: ""gate": "none""The runs of the planner and the review made 237 model request(s), and used 16299016 input tokens and 227273 output tokens. The tool calls, in order:
read_file({"path":"src/devops_cli/roadmap/stage/stage_criteria.py"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_criteria.py"})search_code({"text":"stage_problem_criterion"})search_code({"text":"an issue with no such list gets an empty list"})read_file({"path":"src/devops_cli/lang/en/messages.py","start_line":3595,"end_line":3640})read_file({"path":"src/devops_cli/ai/tasks/roadmap_stage_change.md","start_line":38,"end_line":60})search_code({"text":"criterion_problems("})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py"})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":975,"end_line":1010})read_file({"path":"src/devops_cli/roadmap/stage/stage.py","start_line":1140,"end_line":1180})read_spec({"capability":"roadmap-stage","requirement":"An unmapped criterion is refused"})read_spec({"capability":"roadmap-stage","requirement":"Criteria mapping in the stage reply"})search_code({"text":"roadmap_stage_change.md"})search_code({"text":"test_roadmap_stage_prompt_rules"})list_files({"directory":"tests/roadmap/stage"})search_code({"text":"STAGE_EVAL_TRIGGER_PATHS"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_prompt_rules.py"})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":160,"end_line":185})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":860,"end_line":915})read_file({"end_line":200,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":180})read_file({"end_line":1000,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":911})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_text_lines.py","start_line":120,"end_line":150})search_code({"text":"stage_problem_criterion_unknown"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_criterion_dropped.py"})list_files({"directory":"tests/fixtures/roadmap_eval/stage"})search_code({"text":"\"criteria\""})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/plan.json","start_line":60,"end_line":90})search_code({"text":"Acceptance criteria"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/issue.txt"})read_file({"end_line":300,"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":255})search_code({"text":"def _overlap_problems"})read_issue({"number":3989})read_file({"end_line":1175,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":1120})search_code({"text":"3347"})search_code({"text":"_TOKEN_COST"})read_file({"end_line":120,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":100})search_code({"text":"leaves one item out"})search_code({"text":"one item out"})search_code({"text":"devops ci test tests/"})read_file({"path":"src/devops_cli/roadmap/stage/stage_criteria.py"})list_files({"directory":"tests/roadmap/stage"})search_code({"text":"stage_problem_criterion"})search_code({"text":"An issue with no such list gets an empty list"})read_file({"path":"src/devops_cli/lang/en/messages.py","start_line":3605,"end_line":3630})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_prompt_rules.py","start_line":1,"end_line":80})search_code({"text":"criteria"})search_code({"text":"--replay"})search_code({"text":"criterion_problems"})search_code({"text":"criteria gate"})read_spec({"capability":"roadmap-stage","requirement":"An unmapped criterion is refused"})search_code({"text":"Criteria mapping in the stage reply"})list_files({"directory":"tests/fixtures/roadmap_eval/stage"})read_spec({"capability":"roadmap-stage","requirement":"Criteria mapping in the stage reply"})search_code({"text":"\"criteria\""})search_code({"text":"## Acceptance criteria"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/plan.json","start_line":60,"end_line":90})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/issue.txt","start_line":1,"end_line":40})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_split.py","start_line":30,"end_line":110})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_criteria.py"})search_code({"text":"criterion_problems("})search_code({"text":"First criterion."})read_file({"end_line":150,"path":"tests/roadmap/stage/test_roadmap_stage_text_lines.py","start_line":115})read_file({"end_line":60,"path":"tests/roadmap/implement/test_roadmap_implement_resplit.py","start_line":30})search_code({"text":"STAGE_EVAL_TRIGGER_PATHS"})search_code({"text":"criterion_unknown"})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":160,"end_line":200})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":865,"end_line":915})search_code({"text":"def _quotes_probe"})read_file({"end_line":1010,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":915})search_code({"text":"criteria"})search_code({"text":"def _plan_citation_problems"})read_file({"end_line":866,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":700})search_code({"text":"_LINE_CITATION"})read_file({"end_line":160,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":130})search_code({"text":"GroupKind.BEHAVIOR"})read_file({"end_line":1300,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":1240})read_file({"end_line":470,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":370})read_spec({"capability":"roadmap-stage"})list_files({"directory":"src/devops_cli/roadmap/stage"})read_spec({"capability":"roadmap-stage","requirement":"An unmapped criterion is refused"})read_spec({"capability":"roadmap-stage","requirement":"Criteria mapping in the stage reply"})search_code({"text":"An issue with no such list gets an empty list"})search_code({"text":"stage_problem_criterion"})search_code({"text":"ci test"})list_files({"directory":"tests/fixtures/roadmap_eval/stage"})search_code({"text":"\"criteria\""})search_code({"text":"criteria:"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/plan.json","start_line":60,"end_line":100})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/issue.txt"})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":960,"end_line":1030})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":70,"end_line":120})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":240,"end_line":340})search_code({"text":"ACCEPTANCE_BODY"})search_code({"text":"criterion_problems"})search_code({"text":"criteria=["})read_file({"end_line":180,"path":"tests/roadmap/implement/test_roadmap_implement_resplit.py","start_line":130})read_file({"end_line":320,"path":"tests/roadmap/implement/test_roadmap_implement_resplit.py","start_line":270})search_code({"text":"criterion_problems("})read_file({"path":"src/devops_cli/roadmap/implement/implement_run.py","start_line":840,"end_line":950})search_code({"text":"First criterion."})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_text_lines.py","start_line":100,"end_line":160})read_file({"end_line":90,"path":"tests/roadmap/implement/test_roadmap_implement_resplit.py","start_line":25})read_file({"end_line":2320,"path":"tests/roadmap/stage/test_roadmap_stage_plan.py","start_line":2180})search_code({"text":"roadmap eval stage"})read_file({"path":"src/devops_cli/lang/en/messages.py","start_line":3600,"end_line":3640})search_code({"text":"STAGE_EVAL_TRIGGER_PATHS"})search_code({"text":"def eval_replay_problems"})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":160,"end_line":195})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":865,"end_line":900})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":900,"end_line":930})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":760,"end_line":870})search_code({"text":"def _self_acceptance_problems"})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":660,"end_line":762})search_code({"text":"_PLAN_CHECK_FILE"})list_files({"directory":"src/devops_cli/lang"})list_files({"directory":"tests/roadmap/stage"})search_code({"text":"criteria"})search_code({"text":"An issue with no such list"})search_code({"text":"criteria"})read_file({"end_line":110,"path":"tests/roadmap/stage/test_roadmap_stage_split.py","start_line":30})read_spec({"capability":"roadmap-stage","requirement":"A plan that adds a text rule carries its self-acceptance test"})read_spec({"capability":"roadmap-stage","requirement":"A plan that changes no plan check needs no self-acceptance test"})search_code({"text":"stage_problem_criterion_group"})search_code({"text":"stage_problem_criterion_unknown"})read_spec({"capability":"roadmap-stage","requirement":"The replay judges the criteria gate on plans with a criteria list"})read_spec({"capability":"roadmap-stage","requirement":"Criteria section in tasks.md"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/issue.txt"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/case.json"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":120,"end_line":230})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/case.json"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":230,"end_line":340})search_code({"text":"3411"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":1,"end_line":120})search_code({"text":"replay_case("})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_criterion_dropped.py"})search_code({"text":"what-changes"})search_code({"text":"The stage check refuses a What Changes item"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_command.py","start_line":70,"end_line":120})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_run.py","start_line":150,"end_line":200})search_code({"text":"class RoadmapMessages"})list_files({"directory":"tests/lang"})read_file({"path":"tests/lang/test_language_catalogs.py"})find_issues({"terms":"3989"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_render.py"})read_file({"path":"docs/roadmap/plan-check.md","start_line":1,"end_line":140})read_spec({"capability":"roadmap-stage","requirement":"The stage context lists an overlapping pull request"})read_spec({"capability":"roadmap-stage","requirement":"The gate refuses an empty Dependencies part on an overlap"})read_issue({"number":3989})read_spec({"capability":"roadmap-stage","requirement":"A closed plan is no dependency"})search_code({"text":"_ISSUE_DEPENDENCY"})read_spec({"capability":"roadmap-plan-check"})read_spec({"capability":"roadmap-plan-check","requirement":"Plan-check reports an open issue dependency"})read_spec({"capability":"roadmap-plan-check","requirement":"The plan reader reads the dependencies of a plan"})read_spec({"capability":"roadmap-stage","requirement":"The stage prompt states the Dependencies rule"})search_code({"text":"dependencies' field"})search_code({"text":"'dependencies':"})read_spec({"capability":"roadmap-stage","requirement":"A task group renders the tasks of its kind"})read_spec({"capability":"roadmap-stage","requirement":"A task group has a kind"})git_log({"path":"src/devops_cli/roadmap/stage/stage_criteria.py"})git_log({"path":"src/devops_cli/roadmap/stage_criteria.py"})search_code({"text":"StageCriterion("})read_file({"path":"src/devops_cli/roadmap/stage/stage_criteria.py"})search_code({"text":"_path_gate"})list_files({"directory":"tests/fixtures/roadmap_eval/stage"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":100,"end_line":220})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/plan.json"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/plan.json"})search_code({"text":"def check_proposal"})search_code({"text":"criterion_problems("})search_code({"text":"\"criteria\": ["})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":955,"end_line":1060})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":1,"end_line":100})read_file({"end_line":400,"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":216})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/issue.txt"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/issue.txt"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/case.json"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_criteria.py"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":1,"end_line":120})read_spec({"capability":"roadmap-stage","requirement":"An unmapped criterion is refused"})read_spec({"capability":"roadmap-stage","requirement":"The replay judges the criteria gate on plans with a criteria list"})search_code({"text":"stage_problem_criterion"})read_file({"path":"src/devops_cli/lang/en/messages.py","start_line":3600,"end_line":3630})search_code({"text":"An issue with no such list"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_plan.py","start_line":280,"end_line":340})search_code({"text":"eval stage"})callers({"symbol":"check_gates"})search_code({"text":"check_proposal("})read_file({"path":"src/devops_cli/roadmap/plan_recheck.py","start_line":260,"end_line":330})read_file({"path":"src/devops_cli/roadmap/plan_check.py","start_line":340,"end_line":400})read_file({"path":"src/devops_cli/roadmap/implement/implement.py","start_line":385,"end_line":425})search_code({"text":"test_roadmap_stage_prompt_rules"})list_files({"directory":"tests/roadmap/stage"})read_file({"end_line":150,"path":"tests/roadmap/stage/test_roadmap_stage_text_lines.py","start_line":115})read_file({"end_line":80,"path":"tests/roadmap/stage/test_roadmap_stage_criterion_dropped.py","start_line":1})search_code({"text":"def make_stage_proposal"})read_file({"end_line":120,"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":120})read_file({"end_line":200,"path":"tests/support/builders.py","start_line":140})read_file({"path":"src/devops_cli/roadmap/stage/stage_criteria.py"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":150,"end_line":330})read_spec({"capability":"roadmap-stage"})read_spec({"capability":"roadmap-stage","requirement":"An unmapped criterion is refused"})read_spec({"capability":"roadmap-stage","requirement":"The replay judges the criteria gate on plans with a criteria list"})search_code({"text":"def check_proposal"})search_code({"text":"criterion_problems("})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":930,"end_line":1050})search_code({"text":"def make_stage_proposal"})search_code({"text":"stage_problem_criterion"})read_file({"path":"src/devops_cli/lang/en/messages.py","start_line":3600,"end_line":3640})read_file({"path":"tests/support/builders.py","start_line":130,"end_line":220})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":100,"end_line":250})list_files({"directory":"tests/fixtures/roadmap_eval/stage"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/plan.json"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/case.json"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/issue.txt"})search_code({"text":"\"criteria\""})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":246,"end_line":340})read_file({"path":"src/devops_cli/ai/tasks/roadmap_stage_change.md","start_line":30,"end_line":70})git_log({"path":"src/devops_cli/roadmap/stage/stage_criteria.py"})read_file({"path":"tests/invariants/test_roadmap_workflow_doc.py"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_text_lines.py","start_line":120,"end_line":160})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/case.json"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/issue.txt"})search_code({"text":"acceptance_criteria("})search_code({"text":"stage_problem_criterion_unknown"})read_file({"path":"src/devops_cli/roadmap/stage/stage.py","start_line":1120,"end_line":1185})list_files({"directory":"src/devops_cli/roadmap"})find_issues({"terms":"roadmap stage change prompt criteria"})read_issue({"number":3989})read_issue({"number":3830})search_code({"text":"score before and after"})search_code({"text":"eval stage --replay"})search_code({"text":"STAGE_EVAL_TRIGGER_PATHS"})search_code({"text":"def eval_replay_problems"})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":160,"end_line":195})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":860,"end_line":905})search_code({"text":"\"criterion\":"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_plan.py","start_line":2170,"end_line":2330})read_file({"path":"tests/roadmap/test_roadmap_plan_check.py","start_line":300,"end_line":380})read_file({"path":"tests/roadmap/test_roadmap_plan_recheck.py","start_line":180,"end_line":250})search_code({"text":"check_proposal("})read_file({"path":"src/devops_cli/roadmap/plan_check.py","start_line":350,"end_line":410})read_file({"path":"src/devops_cli/roadmap/implement/implement.py","start_line":380,"end_line":430})read_file({"path":"tests/roadmap/test_roadmap_plan_check.py","start_line":1,"end_line":140})search_code({"text":"_gate_problems("})read_file({"path":"src/devops_cli/roadmap/plan_check.py","start_line":1,"end_line":140})read_file({"path":"src/devops_cli/roadmap/plan_check.py","start_line":470,"end_line":550})read_spec({"capability":"roadmap-stage","requirement":"Criteria mapping in the stage reply"})read_spec({"capability":"roadmap-stage","requirement":"The prompt asks feasibility to quote the probes"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/plan.json","start_line":55,"end_line":75})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_task_verification.py","start_line":200,"end_line":245})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_text_lines.py","start_line":190,"end_line":225})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":340,"end_line":460})search_code({"text":"person wrote"})search_code({"text":"not a person-written"})list_files({"directory":"openspec/changes/3320-a-plan-that-deletes-a-guard"})read_file({"path":"openspec/changes/3320-a-plan-that-deletes-a-guard/tasks.md"})search_code({"text":"criteria"})search_code({"text":"Criteria"})search_code({"text":"Acceptance criteria"})search_code({"text":"criteria"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_prompt_rules.py","start_line":100,"end_line":200})search_code({"text":"def registry_table"})search_code({"text":"def registry_problems"})read_file({"path":"src/devops_cli/roadmap/stage/stage_registry.py","start_line":80,"end_line":120})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":1057,"end_line":1095})search_code({"text":"def recheck_plans"})read_file({"path":"src/devops_cli/roadmap/stage/stage_criteria.py"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":155,"end_line":280})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py"})search_code({"text":"_with_criteria"})search_code({"text":"\"gate\": \"none\""})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":270,"end_line":340})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/case.json"})read_file({"end_line":340,"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":278})search_code({"text":"\"criteria\":"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/case.json"})search_code({"text":"check_proposal("})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/issue.txt"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/plan.json","start_line":60,"end_line":85})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/issue.txt"})read_file({"end_line":2240,"path":"tests/roadmap/stage/test_roadmap_stage_plan.py","start_line":2170})search_code({"text":"criterion_problems"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_criteria.py"})search_code({"text":"stage_problem_criterion"})search_code({"text":"An issue with no such list gets an empty list"})read_file({"path":"src/devops_cli/lang/en/messages.py","start_line":3605,"end_line":3630})search_code({"text":"roadmap_stage_change.md"})search_code({"text":"criteria"})read_file({"path":"src/devops_cli/roadmap/implement/implement.py","start_line":392,"end_line":420})read_file({"path":"src/devops_cli/roadmap/plan_check.py","start_line":368,"end_line":400})list_files({"directory":"tests/roadmap/stage"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_prompt_rules.py","end_line":80})search_code({"text":"criteria"})read_spec({"capability":"roadmap-stage","requirement":"An unmapped criterion is refused"})read_spec({"capability":"roadmap-stage","requirement":"The replay judges the criteria gate on plans with a criteria list"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":196,"end_line":270})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":200,"end_line":400})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":1,"end_line":80})search_code({"text":"_path_gate"})search_code({"text":"def check_proposal"})read_file({"path":"src/devops_cli/roadmap/stage/stage_eval_gates.py","start_line":120,"end_line":200})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":1000,"end_line":1070})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_gates.py","start_line":140,"end_line":420})read_spec({"capability":"roadmap-stage"})read_spec({"capability":"roadmap-stage","requirement":"An unmapped criterion is refused"})read_spec({"capability":"roadmap-stage","requirement":"The replay judges the criteria gate on plans with a criteria list"})search_code({"text":"Acceptance criteria"})search_code({"text":"criteria"})search_code({"text":"An issue with no such list gets an empty list"})search_code({"text":"stage_problem_criterion"})search_code({"text":"ci test"})search_code({"text":"criterion_problems("})search_code({"text":"issue_body="})read_file({"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":940,"end_line":1005})read_file({"end_line":400,"path":"src/devops_cli/roadmap/plan_check.py","start_line":330})read_file({"end_line":320,"path":"src/devops_cli/roadmap/plan_recheck.py","start_line":270})search_code({"text":"\"criteria\""})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/plan.json","start_line":55,"end_line":90})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/issue.txt"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/case.json"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3174-a-plan-with-a-placeholder-group/issue.txt"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_eval_command.py","start_line":60,"end_line":140})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_text_lines.py","start_line":120,"end_line":160})read_file({"path":"docs/ROADMAP_WORKFLOW.md","start_line":300,"end_line":330})search_code({"text":"3174"})search_code({"text":"3411"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3411-a-what-changes-item-that-names/case.json"})git_log({"path":"src/devops_cli/ai/tasks/roadmap_stage_change.md"})read_issue({"number":3989})search_code({"text":"Criteria mapping in the stage reply"})read_spec({"capability":"roadmap-stage","requirement":"Criteria mapping in the stage reply"})search_code({"text":"stage prompt states"})search_code({"text":"def make_stage_proposal"})read_file({"path":"src/devops_cli/roadmap/plan_recheck.py","start_line":1,"end_line":80})search_code({"text":"open_items"})search_code({"text":"_gate_problems("})read_file({"end_line":545,"path":"src/devops_cli/roadmap/plan_check.py","start_line":470})read_file({"end_line":240,"path":"tests/support/builders.py","start_line":120})read_file({"end_line":2240,"path":"tests/roadmap/stage/test_roadmap_stage_plan.py","start_line":2180})read_file({"end_line":580,"path":"tests/roadmap/test_roadmap_plan_check.py","start_line":520})search_code({"text":"class GroupKind"})read_file({"end_line":370,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":335})search_code({"text":"criteria"})file_symbols({"path":"tests/roadmap/stage/test_roadmap_stage_prompt_rules.py"})read_file({"end_line":540,"path":"tests/roadmap/stage/test_roadmap_stage_prompt_rules.py","start_line":425})list_files({"directory":"tests/lang"})list_files({"directory":"tests/roadmap/stage"})read_file({"path":"tests/roadmap/stage/test_roadmap_stage_criterion_dropped.py"})list_files({"directory":"tests/fixtures/roadmap_eval/stage"})read_file({"path":"tests/fixtures/roadmap_eval/stage/3410-the-eval-field-group-is-dropped/plan.json"})search_code({"text":"\"criteria\""})read_file({"end_line":110,"path":"tests/roadmap/stage/test_roadmap_stage_split.py","start_line":1})read_file({"end_line":1180,"path":"src/devops_cli/roadmap/stage/stage.py","start_line":1100})search_code({"text":"It works"})search_code({"text":"dependencies"})read_file({"end_line":470,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":370})read_file({"end_line":560,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":470})search_code({"text":"def _overlap_problems"})read_file({"end_line":1160,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":1120})search_code({"text":"def content_problems"})read_file({"end_line":940,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":860})search_code({"text":"def _self_acceptance_problems"})read_file({"end_line":790,"path":"src/devops_cli/roadmap/stage/stage_change.py","start_line":660})Tool calls: callers=1 (empty=0), file_symbols=1 (empty=0), find_issues=2 (empty=0), git_log=4 (empty=0), list_files=18 (empty=0), read_file=171 (empty=0), read_issue=5 (empty=0), read_spec=33 (empty=0), search_code=130 (empty=6)
The stage job read the open pull requests and their files in 1 GraphQL request(s).
docs(openspec): plan #3391 Refuse criteria entries not in the issue body
docs(openspec): refine plan #3391 tasks and dependencies
docs(openspec): plan #3391 Refuse criteria entries not in the issue b…
devops-cli-service commentedon Nov 7, 2026
Stopped at archive.
Failed check:
uv run devops openspec archive 3391-refuse-criteria-entries-not-in-the.Failure class:
test.Action taken: none.
Action left:
repair: give the failure to a fix step.pytest on base: tests/roadmap/stage/test_roadmap_stage_eval_gates.pypytest on base: tests/roadmap/stage/test_roadmap_stage_criteria.pyuv run devops openspec archive 3391-refuse-criteria-entries-not-in-theReason:
Last message of the agent (a claim that no check verified): none.
The item is Blocked, and the job will not run it. Fix the cause. Then comment
implement: retry. The Service clears the stop on its own when a new runner image rolls out, but only if a retry can fix the class of the stop and the limits ofroadmap.implement.digest_clears_per_classandroadmap.implement.digest_clears_per_itemallow it.The push of the commits that passed failed too.
git push --force-with-lease=fix/3391-refuse-criteria-entries-not-in-the:57efbfb9ea5afbcf9daa29f01000da369e8ca3dd origin fix/3391-refuse-criteria-entries-not-in-thefailed: To https://github.com/dan-petty/devops-cli.git ! [rejected] fix/3391-refuse-criteria-entries-not-in-the -> fix/3391-refuse-criteria-entries-not-in-the (stale info) error: failed to push some refs to 'https://github.com/dan-petty/devops-cli.git'devops-cli-service commentedon Nov 7, 2026
The runner Job devops-cli-runner-20261107-201633-cba7 ended: succeeded.
Image digest: sha256:9a1af5a95b44197c2568d011d53c1b9a84748cf35f9721e302fe56d860d9ae06
The last lines of its log: