Skip to content

Commit e9b25b9

Browse files
Copilotnotfolder
andcommitted
Address code review feedback for TASK_STOP_SPECIFICATION.md
Co-authored-by: notfolder <20558197+notfolder@users.noreply.github.com>
1 parent 1bb068c commit e9b25b9

1 file changed

Lines changed: 162 additions & 34 deletions

File tree

TASK_STOP_SPECIFICATION.md

Lines changed: 162 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -263,15 +263,43 @@ Merge Request もIssueと同様に `assignees` または `assignee` フィール
263263

264264
### 5.2 停止時のコメント
265265

266-
タスク停止時に以下のコメントをissue/MR/PRに投稿します:
266+
タスク停止時に以下のコメントをissue/MR/PRに投稿します。
267+
処理モードに応じて異なるテンプレートを使用します。
267268

269+
**Planningモードの場合:**
268270
```markdown
269271
## ⛔ タスク停止
270272

271273
コーディングエージェントのアサインが解除されたため、タスクを停止しました。
272274

273275
**停止時刻:** {ISO 8601形式のタイムスタンプ}
274276
**処理状況:** {実行済みアクション数}/{全アクション数} 完了
277+
**フェーズ:** {現在のフェーズ(planning/execution/reflection/revision)}
278+
279+
タスクを再開する場合は、コーディングエージェントを再度アサインし、
280+
`coding agent` ラベルを付与してください。
281+
```
282+
283+
**Context Storageモード(Planning無効)の場合:**
284+
```markdown
285+
## ⛔ タスク停止
286+
287+
コーディングエージェントのアサインが解除されたため、タスクを停止しました。
288+
289+
**停止時刻:** {ISO 8601形式のタイムスタンプ}
290+
**LLM対話回数:** {実行済みのLLM対話回数}
291+
292+
タスクを再開する場合は、コーディングエージェントを再度アサインし、
293+
`coding agent` ラベルを付与してください。
294+
```
295+
296+
**レガシーモードの場合:**
297+
```markdown
298+
## ⛔ タスク停止
299+
300+
コーディングエージェントのアサインが解除されたため、タスクを停止しました。
301+
302+
**停止時刻:** {ISO 8601形式のタイムスタンプ}
275303

276304
タスクを再開する場合は、コーディングエージェントを再度アサインし、
277305
`coding agent` ラベルを付与してください。
@@ -345,41 +373,68 @@ TaskStopManager
345373
def get_assignees(self) -> list[str]:
346374
"""タスクにアサインされているユーザー名のリストを取得する.
347375
376+
この処理は外部API(GitHub/GitLab)を呼び出すため、
377+
ネットワークエラーやAPIエラーが発生する可能性があります。
378+
348379
Returns:
349380
アサインされているユーザー名のリスト
381+
382+
Raises:
383+
Exception: API呼び出しに失敗した場合
384+
385+
Note:
386+
呼び出し元でエラーハンドリングを行い、
387+
エラー時はタスクを停止せずに処理を継続することを推奨
350388
351389
"""
352390
```
353391

354392
**GitHub Issue 実装例:**
355393
```python
356394
def get_assignees(self) -> list[str]:
357-
"""Issueにアサインされているユーザー名のリストを取得する."""
358-
# 最新の情報を取得
359-
issue = self.mcp_client.call_tool(
360-
"get_issue",
361-
{"owner": self.issue["owner"], "repo": self.issue["repo"], "issue_number": self.issue["number"]},
362-
)
363-
return [assignee.get("login", "") for assignee in issue.get("assignees", [])]
395+
"""Issueにアサインされているユーザー名のリストを取得する.
396+
397+
Raises:
398+
Exception: API呼び出しに失敗した場合
399+
"""
400+
try:
401+
# 最新の情報を取得
402+
issue = self.mcp_client.call_tool(
403+
"get_issue",
404+
{"owner": self.issue["owner"], "repo": self.issue["repo"], "issue_number": self.issue["number"]},
405+
)
406+
return [assignee.get("login", "") for assignee in issue.get("assignees", [])]
407+
except Exception as e:
408+
self.logger.exception("アサイン情報の取得に失敗: %s", e)
409+
raise # 呼び出し元でエラーハンドリングを行う
364410
```
365411

366412
**GitLab Issue 実装例:**
367413
```python
368414
def get_assignees(self) -> list[str]:
369-
"""Issueにアサインされているユーザー名のリストを取得する."""
370-
# 最新の情報を取得
371-
issue = self.mcp_client.call_tool(
372-
"get_issue",
373-
{"project_id": str(self.project_id), "issue_iid": self.issue_iid},
374-
)
375-
assignees = []
376-
# assignees配列があれば使用
377-
if issue.get("assignees"):
378-
assignees = [a.get("username", "") for a in issue.get("assignees", [])]
379-
# assignee単体フィールドがあれば追加
380-
elif issue.get("assignee"):
381-
assignees = [issue["assignee"].get("username", "")]
382-
return assignees
415+
"""Issueにアサインされているユーザー名のリストを取得する.
416+
417+
Raises:
418+
Exception: API呼び出しに失敗した場合
419+
"""
420+
try:
421+
# 最新の情報を取得
422+
issue = self.mcp_client.call_tool(
423+
"get_issue",
424+
{"project_id": str(self.project_id), "issue_iid": self.issue_iid},
425+
)
426+
assignees = []
427+
# assignees配列が存在し、かつ空でない場合
428+
assignees_list = issue.get("assignees", [])
429+
if assignees_list and len(assignees_list) > 0:
430+
assignees = [a.get("username", "") for a in assignees_list]
431+
# assignees配列がない、または空の場合、assignee単体フィールドを確認
432+
elif issue.get("assignee"):
433+
assignees = [issue["assignee"].get("username", "")]
434+
return assignees
435+
except Exception as e:
436+
self.logger.exception("アサイン情報の取得に失敗: %s", e)
437+
raise # 呼び出し元でエラーハンドリングを行う
383438
```
384439

385440
### 6.3 TaskHandler への組み込み
@@ -396,22 +451,82 @@ def _handle_with_context_storage(self, task: Task, task_config: dict[str, Any])
396451

397452
# ... 既存の初期化処理 ...
398453

454+
# Check interval counter
455+
check_counter = 0
456+
check_interval = task_config.get("task_stop", {}).get("check_interval", 1)
457+
399458
while count < max_count:
400-
# Check for pause signal (既存)
401-
if pause_manager.check_pause_signal():
402-
# ... 一時停止処理 ...
403-
return
459+
check_counter += 1
404460

405-
# Check assignee status (新規追加)
406-
if stop_manager.enabled and not stop_manager.check_assignee_status(task):
407-
self.logger.info("アサイン解除を検出、タスクを停止します")
408-
stop_manager.stop_task(task, task.uuid, "アサインが解除されました")
409-
return # finish()を呼ばずに終了
461+
# 優先順位の取得(デフォルトは pause_first)
462+
priority = task_config.get("task_stop", {}).get("priority", "pause_first")
463+
464+
if priority == "pause_first":
465+
# Check for pause signal first (既存)
466+
if pause_manager.check_pause_signal():
467+
# ... 一時停止処理 ...
468+
return
469+
470+
# Then check assignee status (新規追加)
471+
# check_interval に基づいてチェック頻度を制御
472+
if check_interval > 0 and check_counter % check_interval == 0:
473+
if stop_manager.enabled and stop_manager.should_check_now():
474+
if not stop_manager.check_assignee_status(task):
475+
self.logger.info("アサイン解除を検出、タスクを停止します")
476+
stop_manager.stop_task(task, task.uuid, "アサインが解除されました")
477+
return # finish()を呼ばずに終了
478+
else: # stop_first
479+
# Check assignee status first
480+
if check_interval > 0 and check_counter % check_interval == 0:
481+
if stop_manager.enabled and stop_manager.should_check_now():
482+
if not stop_manager.check_assignee_status(task):
483+
self.logger.info("アサイン解除を検出、タスクを停止します")
484+
stop_manager.stop_task(task, task.uuid, "アサインが解除されました")
485+
return
486+
487+
# Then check for pause signal
488+
if pause_manager.check_pause_signal():
489+
# ... 一時停止処理 ...
490+
return
410491

411492
# ... 既存の処理ループ ...
412493
```
413494

414-
### 6.4 PlanningCoordinator への組み込み
495+
### 6.4 TaskStopManager の should_check_now メソッド
496+
497+
`min_check_interval_seconds` 設定に基づき、APIレート制限を考慮したチェックを実装します。
498+
499+
```python
500+
class TaskStopManager:
501+
def __init__(self, config: dict[str, Any]) -> None:
502+
# ... 既存の初期化処理 ...
503+
task_stop_config = config.get("task_stop", {})
504+
self.min_check_interval_seconds = task_stop_config.get("min_check_interval_seconds", 30)
505+
self._last_check_time: float | None = None
506+
507+
def should_check_now(self) -> bool:
508+
"""min_check_interval_seconds を考慮して、チェックを実行すべきかを判定.
509+
510+
Returns:
511+
True: チェックを実行すべき
512+
False: 前回のチェックから十分な時間が経過していない
513+
"""
514+
import time
515+
current_time = time.time()
516+
517+
if self._last_check_time is None:
518+
self._last_check_time = current_time
519+
return True
520+
521+
elapsed = current_time - self._last_check_time
522+
if elapsed >= self.min_check_interval_seconds:
523+
self._last_check_time = current_time
524+
return True
525+
526+
return False
527+
```
528+
529+
### 6.5 PlanningCoordinator への組み込み
415530

416531
`PlanningCoordinator` クラスにも同様にアサインチェックを追加します。
417532

@@ -640,17 +755,30 @@ API呼び出しがタイムアウトした場合:
640755
**シナリオS9: アサイン解除と一時停止シグナルの同時発生**
641756
1. タスク処理を開始
642757
2. 同じタイミングでアサイン解除と `pause_signal` ファイル作成
643-
3. 一時停止が優先されることを確認(または設定で優先順位を変更可能)
758+
3. 設定に応じた優先順位で処理されることを確認
759+
760+
**優先順位の実装:**
761+
セクション6.3「TaskHandler への組み込み」で説明したように、`priority` 設定に基づいて処理順序を制御します。
644762

645763
**設定例:**
646764
```yaml
647765
task_stop:
648766
# 一時停止シグナルとの優先順位
649-
# "pause_first": 一時停止を優先
767+
# "pause_first": 一時停止を優先(デフォルト)
768+
# - 一時停止シグナルが先にチェックされ、状態が保存される
769+
# - 後で再開可能
650770
# "stop_first": 停止を優先
771+
# - アサイン解除が先にチェックされ、タスクが終了する
772+
# - 再開不可(新規タスクとして再実行が必要)
651773
priority: "pause_first"
652774
```
653775
776+
**シナリオS10: 優先順位 stop_first での同時発生**
777+
1. `priority: "stop_first"` を設定
778+
2. タスク処理を開始
779+
3. 同じタイミングでアサイン解除と `pause_signal` ファイル作成
780+
4. 停止が優先されることを確認(一時停止ではなく終了)
781+
654782
## 10. 運用ガイドライン
655783

656784
### 10.1 タスク停止の実行方法

0 commit comments

Comments
 (0)