From c93f95ee4dd13c15066b5e16dbe76bce09d26f4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:06:24 +0000 Subject: [PATCH 1/3] Initial plan From 037bbbd7a6f72e6c1f7213a7a2751c039d544805 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:08:38 +0000 Subject: [PATCH 2/3] fix: avoid redundant kanban file rewrite after atomic update Co-authored-by: cft0808 <41196455+cft0808@users.noreply.github.com> --- scripts/kanban_update.py | 19 +++++++++---------- tests/test_kanban.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/scripts/kanban_update.py b/scripts/kanban_update.py index 0566cac..3dd55b9 100644 --- a/scripts/kanban_update.py +++ b/scripts/kanban_update.py @@ -32,7 +32,7 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S') # 文件锁 —— 防止多 Agent 同时读写 tasks_source.json -from file_lock import atomic_json_read, atomic_json_update, atomic_json_write # noqa: E402 +from file_lock import atomic_json_read, atomic_json_update # noqa: E402 STATE_ORG_MAP = { 'Taizi': '太子', 'Zhongshu': '中书省', 'Menxia': '门下省', 'Assigned': '尚书省', @@ -66,8 +66,7 @@ def load(): return atomic_json_read(TASKS_FILE, []) -def save(tasks): - atomic_json_write(TASKS_FILE, tasks) +def trigger_refresh(): # 异步触发刷新,不阻塞调用方 try: subprocess.Popen(['python3', str(REFRESH_SCRIPT)], @@ -201,7 +200,7 @@ def modifier(tasks): }) return tasks atomic_json_update(TASKS_FILE, modifier, []) - save(load()) # trigger refresh + trigger_refresh() log.info(f'✅ 创建 {task_id} | {title[:30]} | state={state}') @@ -222,7 +221,7 @@ def modifier(tasks): t['updatedAt'] = now_iso() return tasks atomic_json_update(TASKS_FILE, modifier, []) - save(load()) # trigger refresh + trigger_refresh() log.info(f'✅ {task_id} 状态更新: {old_state[0]} → {new_state}') @@ -240,7 +239,7 @@ def modifier(tasks): t['updatedAt'] = now_iso() return tasks atomic_json_update(TASKS_FILE, modifier, []) - save(load()) # trigger refresh + trigger_refresh() log.info(f'✅ {task_id} 流转记录: {from_dept} → {to_dept}') @@ -261,7 +260,7 @@ def modifier(tasks): t['updatedAt'] = now_iso() return tasks atomic_json_update(TASKS_FILE, modifier, []) - save(load()) # trigger refresh + trigger_refresh() log.info(f'✅ {task_id} 已完成') @@ -277,7 +276,7 @@ def modifier(tasks): t['updatedAt'] = now_iso() return tasks atomic_json_update(TASKS_FILE, modifier, []) - save(load()) # trigger refresh + trigger_refresh() log.warning(f'⚠️ {task_id} 已阻塞: {reason}') @@ -366,7 +365,7 @@ def modifier(tasks): total_cnt[0] = len(t.get('todos', [])) return tasks atomic_json_update(TASKS_FILE, modifier, []) - save(load()) # trigger refresh + trigger_refresh() res_info = '' if tokens or cost or elapsed: res_info = f' [res: {tokens}tok/${cost:.4f}/{elapsed}s]' @@ -406,7 +405,7 @@ def modifier(tasks): result_info[1] = len(t['todos']) return tasks atomic_json_update(TASKS_FILE, modifier, []) - save(load()) # trigger refresh + trigger_refresh() log.info(f'✅ {task_id} todo [{result_info[0]}/{result_info[1]}]: {todo_id} → {status}') _CMD_MIN_ARGS = { diff --git a/tests/test_kanban.py b/tests/test_kanban.py index b137292..2f13bd6 100644 --- a/tests/test_kanban.py +++ b/tests/test_kanban.py @@ -61,3 +61,24 @@ def test_block_and_unblock(tmp_path): assert tasks[0]['block'] == '等待依赖' finally: kb.TASKS_FILE = original + + +def test_state_update_does_not_reload_file_again(tmp_path, monkeypatch): + """state 更新后不应再次 load/save 同一文件。""" + tasks_file = tmp_path / 'tasks_source.json' + tasks_file.write_text(json.dumps([ + {'id': 'T-3', 'title': 'perf', 'state': 'Inbox'} + ])) + + original = kb.TASKS_FILE + kb.TASKS_FILE = tasks_file + popen_calls = [] + monkeypatch.setattr(kb.subprocess, 'Popen', lambda *args, **kwargs: popen_calls.append((args, kwargs))) + monkeypatch.setattr(kb, 'atomic_json_read', lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError('unexpected reload'))) + try: + kb.cmd_state('T-3', 'Doing') + tasks = json.loads(tasks_file.read_text()) + assert tasks[0]['state'] == 'Doing' + assert len(popen_calls) == 1 + finally: + kb.TASKS_FILE = original From 76c873694e874b2f76b83db86efe9750d24975a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:10:23 +0000 Subject: [PATCH 3/3] test: guard against redundant kanban reload after atomic update Co-authored-by: cft0808 <41196455+cft0808@users.noreply.github.com> --- scripts/kanban_update.py | 1 + tests/test_kanban.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/kanban_update.py b/scripts/kanban_update.py index 3dd55b9..1d36492 100644 --- a/scripts/kanban_update.py +++ b/scripts/kanban_update.py @@ -67,6 +67,7 @@ def load(): return atomic_json_read(TASKS_FILE, []) def trigger_refresh(): + """异步触发 REFRESH_SCRIPT 更新 live_status,避免阻塞主更新流程。""" # 异步触发刷新,不阻塞调用方 try: subprocess.Popen(['python3', str(REFRESH_SCRIPT)], diff --git a/tests/test_kanban.py b/tests/test_kanban.py index 2f13bd6..4b097fe 100644 --- a/tests/test_kanban.py +++ b/tests/test_kanban.py @@ -73,8 +73,10 @@ def test_state_update_does_not_reload_file_again(tmp_path, monkeypatch): original = kb.TASKS_FILE kb.TASKS_FILE = tasks_file popen_calls = [] + def fail_on_reload(*_args, **_kwargs): + raise AssertionError('unexpected reload') monkeypatch.setattr(kb.subprocess, 'Popen', lambda *args, **kwargs: popen_calls.append((args, kwargs))) - monkeypatch.setattr(kb, 'atomic_json_read', lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError('unexpected reload'))) + monkeypatch.setattr(kb, 'atomic_json_read', fail_on_reload) try: kb.cmd_state('T-3', 'Doing') tasks = json.loads(tasks_file.read_text())