Skip to content

Commit 092c772

Browse files
committed
fix: update tests for ❓ question marker and resolve CodeRabbit findings
- Update test_status_icon_names_are_stable to assert against QUESTION_MARKER constant instead of hardcoded "?" so it works in both ASCII and emoji terminals - Update test_ask_user_renders_question_and_options to expect ❓ prefix on per-question lines (matches the standardized question marker from #9d65748) - Update test_set_todo_list_description snapshot to match the rewritten set_todo_list.md description (execution-gated, evidence-driven restructuring) - Replace hardcoded allowlist in review.py clean --dry-run with _ALLOWED_NAMES constant from findings_store (DRY, single source of truth) - Remove ignore_errors=True from shutil.rmtree in purge_unknown and purge_stale_projects; surface OSError via logging.warning so callers can observe deletion failures instead of silently ignoring them
1 parent c7de321 commit 092c772

6 files changed

Lines changed: 34 additions & 26 deletions

File tree

packages/pythinker-review/src/pythinker_review/cli/review.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
status_project,
7676
triage_project,
7777
)
78-
from pythinker_review.store.findings_store import FindingsStore
78+
from pythinker_review.store.findings_store import _ALLOWED_NAMES, FindingsStore
7979
from pythinker_review.store.gitignore import ensure_gitignored
8080
from pythinker_review.store.models import SEVERITY_ORDER, Finding, Pass, RunMeta
8181

@@ -379,11 +379,7 @@ def clean(
379379
if not store.state_dir.exists():
380380
typer.echo("nothing to clean (.pythinker-review/ does not exist)")
381381
return
382-
unknown = [
383-
e.name
384-
for e in store.state_dir.iterdir()
385-
if e.name not in {"index.json", "runs", "security-scan"}
386-
]
382+
unknown = [e.name for e in store.state_dir.iterdir() if e.name not in _ALLOWED_NAMES]
387383
if unknown:
388384
typer.echo("would remove: " + ", ".join(sorted(unknown)))
389385
else:

packages/pythinker-review/src/pythinker_review/security_scan/store.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import annotations
88

99
import json
10+
import logging
1011
import os
1112
import secrets
1213
import shutil
@@ -276,7 +277,11 @@ def purge_stale_projects(*, data_root: Path, keep_project_id: str) -> list[str]:
276277
for entry in data_root.iterdir():
277278
if not entry.is_dir() or entry.name == keep_project_id:
278279
continue
279-
shutil.rmtree(entry, ignore_errors=True)
280+
try:
281+
shutil.rmtree(entry)
282+
except OSError:
283+
logging.getLogger(__name__).warning("Failed to remove stale project %s", entry.name)
284+
continue
280285
removed.append(entry.name)
281286
return removed
282287

packages/pythinker-review/src/pythinker_review/store/findings_store.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import json
6+
import logging
67
import os
78
import shutil
89
from pathlib import Path
@@ -104,9 +105,13 @@ def purge_unknown(self) -> list[str]:
104105
removed: list[str] = []
105106
for entry in self.state_dir.iterdir():
106107
if entry.name not in _ALLOWED_NAMES:
107-
if entry.is_dir():
108-
shutil.rmtree(entry, ignore_errors=True)
109-
else:
110-
entry.unlink(missing_ok=True)
108+
try:
109+
if entry.is_dir():
110+
shutil.rmtree(entry)
111+
else:
112+
entry.unlink(missing_ok=True)
113+
except OSError:
114+
logging.getLogger(__name__).warning("Failed to remove unknown entry %s", entry)
115+
continue
111116
removed.append(entry.name)
112117
return removed

tests/tools/test_tool_descriptions.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,29 +140,29 @@ def test_set_todo_list_description(set_todo_list_tool: SetTodoList):
140140
"""Test the description of SetTodoList tool."""
141141
assert set_todo_list_tool.base.description == snapshot(
142142
"""\
143-
Manage your todo list for tracking task progress.
143+
Manage your todo list for tracking task progress during execution.
144144
145-
Todo list is a simple yet powerful tool to help you get things done. You typically want to use this tool when the given task involves multiple subtasks/milestones, or, multiple tasks are given in a single request. This tool can help you to break down the task and track the progress.
145+
**When to set todos (Update mode):**
146+
Set the todo list **only after the user has explicitly agreed on the plan**. The todo list marks the start of execution — it is not a planning scratch-pad. Do not call this tool while exploring, gathering context, presenting options, or waiting for user feedback. The moment the user says "yes", "do it", "go ahead", or otherwise confirms the approach, set the list and begin.
146147
147148
**Usage modes:**
148149
149150
- **Update mode**: Pass `todos` to set the entire todo list. The previous list is replaced.
150151
- **Query mode**: Omit `todos` (or pass null) to retrieve the current todo list without changes.
151-
- **Clear mode**: Pass an empty array `[]` to clear all todos.
152+
- **Clear mode**: Pass an empty array `[]` to clear all todos when work is fully done.
152153
153-
This is the only todo list tool available to you. That said, each time you want to update the todo list, you need to provide the whole list. Make sure to maintain the todo items and their statuses properly.
154+
Once the todo list is set, it is the single source of truth for in-progress work. During execution, update item statuses as you complete work (`pending` → `in_progress` → `done`). Only restructure or replace the list when evidence genuinely changes the scope — not for convenience replanning. When in doubt, surface the new evidence to the user before changing the plan.
154155
155-
Once you finished a subtask/milestone, remember to update the todo list to reflect the progress. Also, you can give yourself a self-encouragement to keep you motivated.
156+
Once you finish a subtask/milestone, update its status before moving to the next item.
156157
157-
Abusing this tool to track too small steps will just waste your time and make your context messy. For example, here are some cases you should not use this tool:
158+
**Do NOT use this tool:**
158159
159-
- When the user just simply ask you a question. E.g. "What language and framework is used in the project?", "What is the best practice for x?"
160-
- When it only takes a few steps/tool calls to complete the task. E.g. "Fix the unit test function 'test_xxx'", "Refactor the function 'xxx' to make it more solid."
161-
- When the user prompt is very specific and the only thing you need to do is brainlessly following the instructions. E.g. "Replace xxx to yyy in the file zzz", "Create a file xxx with content yyy."
160+
- During the planning or exploration phase, before the user has confirmed the approach.
161+
- When the user asks a question or requests a review without agreeing to a concrete plan.
162+
- When the task only takes a few steps/tool calls. E.g. "Fix the unit test function 'test_xxx'".
163+
- When the user prompt is very specific and fully self-contained. E.g. "Replace xxx to yyy in file zzz".
162164
163-
However, do not get stuck in a rut. Be flexible. Sometimes, you may try to use todo list at first, then realize the task is too simple and you can simply stop using it; or, sometimes, you may realize the task is complex after a few steps and then you can start using todo list to break it down.
164-
165-
IMPORTANT: Do not call this tool repeatedly without making real progress on at least one task between calls. If you are unsure about the current state, use Query mode (omit `todos`) to check before updating. If you find yourself unable to advance any task with your available tools, inform the user about what is blocking you instead of replanning. Repeatedly updating the todo list without doing actual work is counterproductive.
165+
**IMPORTANT:** Do not call this tool repeatedly without making real progress between calls. Use Query mode to check current state before updating. If you cannot advance any task, surface the blocker to the user instead of replanning. Repeated todo updates without real work are counterproductive.
166166
"""
167167
)
168168

tests/ui_and_conv/test_shell_design_system.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
shell_style,
1515
status_icon,
1616
)
17+
from pythinker_code.ui.shell.glyphs import QUESTION_MARKER
1718

1819

1920
def _plain(renderable, *, width: int = 80) -> str:
@@ -37,8 +38,8 @@ def test_status_icon_names_are_stable():
3738
assert status_icon("denied").plain == "×"
3839
assert status_icon("interrupted").plain == "■"
3940
assert status_icon("waiting").plain == "○"
40-
assert status_icon("question").plain == "?"
41-
assert status_icon("approval").plain == "?"
41+
assert status_icon("question").plain == QUESTION_MARKER
42+
assert status_icon("approval").plain == QUESTION_MARKER
4243

4344

4445
def test_running_and_failed_status_icons_use_expected_tones():

tests/ui_and_conv/test_tui_card_tool_renderers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
render_diff,
1919
render_plain,
2020
)
21+
from pythinker_code.ui.shell.glyphs import QUESTION_MARKER
2122
from pythinker_code.ui.shell.tool_renderers import (
2223
ToolResultPayload,
2324
clear_tool_renderers,
@@ -881,7 +882,7 @@ def test_ask_user_renders_question_and_options():
881882
},
882883
)
883884
assert "● Ask 1 question" in rendered
884-
assert "● Ask 1 question\n\n Which auth method?" in rendered
885+
assert f"● Ask 1 question\n\n{QUESTION_MARKER} Which auth method?" in rendered
885886
assert "OAuth" in rendered
886887
assert "API key" in rendered
887888

0 commit comments

Comments
 (0)