Repository: kurusugawa-computer/annofab-cli (language: python)

Issue:
検査コメントAPIをコメントAPIに置き換える
検査コメントWebAPIは2022-08以降に廃止される予定。

検査コメント全件ファイルに対応する新しいWebAPIができたら、以下のコマンドを対応する。

# 修正するコマンド
* inspection_comment delete
* inspection_comment list
* inspection_comment list_with_json
* visualize_statistics

## 修正方法
* `inspection_comment delete` を `comment delete`に変更する。
    * 検査コメントの削除のインターフェイスは保留コメントの削除と同じなので、`comment`サブコマンドに移動する。`inspection_comment delete`が用意されていないことは、どこかに明記する。
* `inspection_comment list`の出力結果を、`api.get_comments`のレスポンスにする。
* `inspection_comment list_with_json`の出力結果を、`api.get_comments`のレスポンスにする。


Discussion / hints:

Fix patch:
diff --git a/.vscode/cspell.json b/.vscode/cspell.json
index 4e523a2b..72973f6b 100644
--- a/.vscode/cspell.json
+++ b/.vscode/cspell.json
@@ -63,6 +63,7 @@
         "notequals",
         "notna",
         "numpy",
+        "ONHOLD",
         "pformat",
         "pickel",
         "pylint",
diff --git a/annofabcli/__version__.py b/annofabcli/__version__.py
index d41c3fa4..4deffd11 100644
--- a/annofabcli/__version__.py
+++ b/annofabcli/__version__.py
@@ -1,1 +1,1 @@
-__version__ = "1.71.0"
+__version__ = "1.72.0"
diff --git a/annofabcli/comment/delete_comment.py b/annofabcli/comment/delete_comment.py
new file mode 100644
index 00000000..4a30f884
--- /dev/null
+++ b/annofabcli/comment/delete_comment.py
@@ -0,0 +1,290 @@
+import argparse
+import json
+import logging
+import multiprocessing
+import sys
+from typing import Any, Dict, List, Optional, Tuple
+
+import annofabapi
+import annofabapi.utils
+import requests
+from annofabapi.models import ProjectMemberRole, TaskStatus
+
+import annofabcli
+import annofabcli.common.cli
+from annofabcli.common.cli import (
+    COMMAND_LINE_ERROR_STATUS_CODE,
+    AbstractCommandLineInterface,
+    AbstractCommandLineWithConfirmInterface,
+    ArgumentParser,
+    build_annofabapi_resource_and_login,
+)
+from annofabcli.common.facade import AnnofabApiFacade
+
+logger = logging.getLogger(__name__)
+
+
+DeletedCommentsForTask = Dict[str, List[str]]
+"""
+タスク配下の削除対象のコメント
+keyはinput_data_id, value: comment_idのlist
+"""
+
+DeletedComments = Dict[str, DeletedCommentsForTask]
+"""
+削除対象のコメント
+keyはtask_id, value: `DeletedCommentsForTask`
+"""
+
+
+class DeleteCommentMain(AbstractCommandLineWithConfirmInterface):
+    def __init__(self, service: annofabapi.Resource, project_id: str, all_yes: bool = False):
+        self.service = service
+        self.facade = AnnofabApiFacade(service)
+        self.project_id = project_id
+
+        AbstractCommandLineWithConfirmInterface.__init__(self, all_yes)
+
+    def _create_request_body(
+        self, task: Dict[str, Any], input_data_id: str, comment_ids: List[str]
+    ) -> List[Dict[str, Any]]:
+        """batch_update_comments に渡すリクエストボディを作成する。"""
+
+        def _convert(comment_id: str) -> Dict[str, Any]:
+            return {
+                "comment_id": comment_id,
+                "_type": "Delete",
+            }
+
+        old_comment_list, _ = self.service.api.get_comments(
+            self.project_id, task["task_id"], input_data_id, query_params={"v": "2"}
+        )
+        old_comment_ids = {e["comment_id"] for e in old_comment_list}
+        request_body = []
+        for comment_id in comment_ids:
+            if comment_id not in old_comment_ids:
+                logger.warning(
+                    f"task_id={task['task_id']}, input_data_id={input_data_id}: "
+                    f"comment_id='{comment_id}'のコメントは存在しません。",
+                )
+                continue
+            request_body.append(_convert(comment_id))
+        return request_body
+
+    def change_to_working_status(self, project_id: str, task: Dict[str, Any]) -> Dict[str, Any]:
+        """
+        作業中状態に遷移する。必要ならば担当者を自分自身に変更する。
+
+        Args:
+            project_id:
+            task:
+
+        Returns:
+            作業中状態遷移後のタスク
+        """
+
+        task_id = task["task_id"]
+        try:
+            if task["account_id"] != self.service.api.account_id:
+                self.service.wrapper.change_task_operator(project_id, task_id, self.service.api.account_id)
+                logger.debug(f"{task_id}: 担当者を自分自身に変更しました。")
+
+            changed_task = self.service.wrapper.change_task_status_to_working(project_id, task_id)
+            return changed_task
+
+        except requests.HTTPError:
+            logger.warning(f"{task_id}: 担当者の変更、または作業中状態への変更に失敗しました。", exc_info=True)
+            raise
+
+    def _can_delete_comment(
+        self,
+        task: Dict[str, Any],
+    ) -> bool:
+        task_id = task["task_id"]
+        if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
+            logger.warning(
+                f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを削除できません。（task_status='{task['status']}'）"
+            )
+            return False
+        return True
+
+    def delete_comments_for_task(
+        self,
+        task_id: str,
+        comment_ids_for_task: DeletedCommentsForTask,
+        task_index: Optional[int] = None,
+    ) -> int:
+        """
+        タスクに対してコメントを削除します。
+
+        Args:
+            task_id: タスクID
+            comment_ids_for_task: 削除対象のコメントのIDの集合
+            task_index: タスクの連番
+
+        Returns:
+            削除したコメントが所属する入力データ数
+        """
+        logging_prefix = f"{task_index+1} 件目" if task_index is not None else ""
+
+        task = self.service.wrapper.get_task_or_none(self.project_id, task_id)
+        if task is None:
+            logger.warning(f"{logging_prefix} : task_id='{task_id}' のタスクは存在しないので、スキップします。")
+            return 0
+
+        logger.debug(
+            f"{logging_prefix} : task_id = {task['task_id']}, "
+            f"status = {task['status']}, "
+            f"phase = {task['phase']}, "
+        )
+
+        if not self._can_delete_comment(
+            task=task,
+        ):
+            return 0
+
+        if not self.confirm_processing(f"task_id='{task_id}' のタスクに付与されたコメントを削除しますか？"):
+            return 0
+
+        # コメントを削除するには作業中状態にする必要がある
+        changed_task = self.change_to_working_status(self.project_id, task)
+        added_comments_count = 0
+        for input_data_id, comment_ids in comment_ids_for_task.items():
+            if input_data_id not in task["input_data_id_list"]:
+                logger.warning(
+                    f"{logging_prefix} : task_id='{task_id}'のタスクに input_data_id='{input_data_id}'の入力データは存在しません。"
+                )
+                continue
+            try:
+                # コメントを削除
+                request_body = self._create_request_body(
+                    task=changed_task, input_data_id=input_data_id, comment_ids=comment_ids
+                )
+                if len(request_body) > 0:
+                    self.service.api.batch_update_comments(
+                        self.project_id, task_id, input_data_id, request_body=request_body
+                    )
+                    added_comments_count += 1
+                    logger.debug(
+                        f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: "
+                        f"{len(request_body)}件のコメントを削除しました。"
+                    )
+                else:
+                    logger.warning(
+                        f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: " f"削除できるコメントは存在しませんでした。"
+                    )
+
+            except Exception:  # pylint: disable=broad-except
+                logger.warning(
+                    f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: コメントの削除に失敗しました。",
+                    exc_info=True,
+                )
+
+        self.service.wrapper.change_task_status_to_break(self.project_id, task_id)
+        return added_comments_count
+
+    def delete_comments_for_task_wrapper(
+        self,
+        tpl: Tuple[int, Tuple[str, DeletedCommentsForTask]],
+    ) -> int:
+        task_index, (task_id, comment_ids_for_task) = tpl
+        return self.delete_comments_for_task(
+            task_id=task_id, comment_ids_for_task=comment_ids_for_task, task_index=task_index
+        )
+
+    def delete_comments_for_task_list(
+        self,
+        comment_ids_for_task_list: DeletedComments,
+        parallelism: Optional[int] = None,
+    ) -> None:
+        comments_count = sum(len(e) for e in comment_ids_for_task_list.values())
+        logger.info(f"削除対象のコメントを含むタスクの個数: {len(comment_ids_for_task_list)}, 削除対象のコメントを含む入力データ数: {comments_count}")
+
+        if parallelism is not None:
+            with multiprocessing.Pool(parallelism) as pool:
+                result_bool_list = pool.map(
+                    self.delete_comments_for_task_wrapper, enumerate(comment_ids_for_task_list.items())
+                )
+                added_comments_count = sum(e for e in result_bool_list)
+
+        else:
+            # 逐次処理
+            added_comments_count = 0
+            for task_index, (task_id, comment_ids_for_task) in enumerate(comment_ids_for_task_list.items()):
+                try:
+                    result = self.delete_comments_for_task(
+                        task_id=task_id,
+                        comment_ids_for_task=comment_ids_for_task,
+                        task_index=task_index,
+                    )
+                    added_comments_count += result
+                except Exception:  # pylint: disable=broad-except
+                    logger.warning(f"task_id={task_id}: コメントの削除に失敗しました。", exc_info=True)
+                    continue
+
+        logger.info(f"{added_comments_count} / {comments_count} 件の入力データからコメントを削除しました。")
+
+
+class DeleteComment(AbstractCommandLineInterface):
+    COMMON_MESSAGE = "annofabcli comment delete: error:"
+
+    def validate(self, args: argparse.Namespace) -> bool:
+
+        if args.parallelism is not None and not args.yes:
+            print(
+                f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず '--yes' を指定してください。",
+                file=sys.stderr,
+            )
+            return False
+
+        return True
+
+    def main(self):
+        args = self.args
+        if not self.validate(args):
+            sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
+
+        super().validate_project(args.project_id, [ProjectMemberRole.ACCEPTER, ProjectMemberRole.OWNER])
+
+        dict_comments = annofabcli.common.cli.get_json_from_args(args.json)
+        main_obj = DeleteCommentMain(self.service, project_id=args.project_id, all_yes=self.all_yes)
+        main_obj.delete_comments_for_task_list(
+            comment_ids_for_task_list=dict_comments,
+            parallelism=args.parallelism,
+        )
+
+
+def main(args: argparse.Namespace):
+    service = build_annofabapi_resource_and_login(args)
+    facade = AnnofabApiFacade(service)
+    DeleteComment(service, facade, args).main()
+
+
+def parse_args(parser: argparse.ArgumentParser):
+    argument_parser = ArgumentParser(parser)
+
+    argument_parser.add_project_id()
+
+    JSON_SAMPLE = {"task_id1": {"input_data_id1": ["comment_id1", "comment_id2"]}}
+    parser.add_argument(
+        "--json",
+        type=str,
+        required=True,
+        help=("削除するコメントのcomment_idをJSON形式で指定してください。\n\n" f"(ex) ``{json.dumps(JSON_SAMPLE)}``"),
+    )
+
+    parser.add_argument(
+        "--parallelism", type=int, help="使用するプロセス数（並列度）を指定してください。指定する場合は必ず '--yes' を指定してください。指定しない場合は、逐次的に処理します。"
+    )
+
+    parser.set_defaults(subcommand_func=main)
+
+
+def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
+    subcommand_name = "delete"
+    subcommand_help = "コメントを削除します。"
+    description = "コメントを削除します。\n【注意】他人の付けたコメントも削除できてしまいます。"
+
+    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description)
+    parse_args(parser)
+    return parser
diff --git a/annofabcli/comment/list_all_comment.py b/annofabcli/comment/list_all_comment.py
index 02bb8806..f5ee45ac 100644
--- a/annofabcli/comment/list_all_comment.py
+++ b/annofabcli/comment/list_all_comment.py
@@ -8,10 +8,10 @@
 import logging
 import tempfile
 from pathlib import Path
-from typing import Any, Callable, Collection, Optional
+from typing import Any, Collection, Optional
 
 import annofabapi
-from annofabapi.models import Inspection
+from annofabapi.models import CommentType
 
 import annofabcli
 import annofabcli.common.cli
@@ -23,8 +23,6 @@
 
 logger = logging.getLogger(__name__)
 
-FilterInspectionFunc = Callable[[Inspection], bool]
-
 
 class ListAllCommentMain:
     def __init__(self, service: annofabapi.Resource):
@@ -35,6 +33,8 @@ def get_all_comment(
         project_id: str,
         comment_json: Optional[Path],
         task_ids: Optional[Collection[str]],
+        comment_type: Optional[CommentType],
+        exclude_reply: bool,
     ) -> list[dict[str, Any]]:
 
         if comment_json is None:
@@ -54,6 +54,13 @@ def get_all_comment(
             task_id_set = set(task_ids)
             comment_list = [e for e in comment_list if e["task_id"] in task_id_set]
 
+        if comment_type is not None:
+            comment_list = [e for e in comment_list if e["comment_type"] == comment_type.value]
+
+        if exclude_reply:
+            # 返信コメントを除外する
+            comment_list = [e for e in comment_list if e["comment_node"]["_type"] != "Reply"]
+
         visualize = AddProps(self.service, project_id)
         comment_list = [visualize.add_properties_to_comment(e) for e in comment_list]
         return comment_list
@@ -66,11 +73,15 @@ def main(self):
         super().validate_project(project_id, project_member_roles=None)
 
         task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id) if args.task_id is not None else None
+        comment_type = CommentType(args.comment_type) if args.comment_type is not None else None
+
         main_obj = ListAllCommentMain(self.service)
         comment_list = main_obj.get_all_comment(
             project_id=project_id,
             comment_json=args.comment_json,
             task_ids=task_id_list,
+            comment_type=comment_type,
+            exclude_reply=args.exclude_reply,
         )
 
         logger.info(f"コメントの件数: {len(comment_list)}")
@@ -86,6 +97,16 @@ def parse_args(parser: argparse.ArgumentParser):
         help_message=("対象のタスクのtask_idを指定します。 \n" "``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。"),
     )
 
+    parser.add_argument(
+        "--comment_type",
+        choices=[CommentType.INSPECTION.value, CommentType.ONHOLD.value],
+        help=(
+            "コメントの種類で絞り込みます。\n\n"
+            f" * {CommentType.INSPECTION.value}: 検査コメント\n"
+            f" * {CommentType.ONHOLD.value}: 保留コメント\n"
+        ),
+    )
+
     parser.add_argument(
         "--comment_json",
         type=Path,
@@ -93,6 +114,8 @@ def parse_args(parser: argparse.ArgumentParser):
         "JSONファイルは ``$ annofabcli comment download`` コマンドで取得できます。",
     )
 
+    parser.add_argument("--exclude_reply", action="store_true", help="返信コメントを除外します。")
+
     argument_parser.add_format(
         choices=[
             FormatArgument.CSV,
diff --git a/annofabcli/comment/list_comment.py b/annofabcli/comment/list_comment.py
index 20917006..8a7de090 100644
--- a/annofabcli/comment/list_comment.py
+++ b/annofabcli/comment/list_comment.py
@@ -1,10 +1,9 @@
 import argparse
 import logging
-from pathlib import Path
 from typing import List, Optional
 
 import requests
-from annofabapi.models import Comment
+from annofabapi.models import Comment, CommentType
 
 import annofabcli
 import annofabcli.common.cli
@@ -21,7 +20,9 @@ def get_comments(self, project_id: str, task_id: str, input_data_id: str):
         comments, _ = self.service.api.get_comments(project_id, task_id, input_data_id, query_params={"v": "2"})
         return comments
 
-    def list_comments(self, project_id: str, task_id_list: List[str], output_file: Path):
+    def list_comments(
+        self, project_id: str, task_id_list: List[str], *, comment_type: Optional[CommentType], exclude_reply: bool
+    ):
         all_comments: List[Comment] = []
 
         for task_id in task_id_list:
@@ -32,15 +33,23 @@ def list_comments(self, project_id: str, task_id_list: List[str], output_file: P
                     continue
 
                 input_data_id_list = task["input_data_id_list"]
-                logger.info(f"タスク '{task_id}' に紐づくコメントを取得します。input_dataの個数 = {len(input_data_id_list)}")
+                logger.debug(f"タスク '{task_id}' に紐づくコメントを取得します。input_dataの個数 = {len(input_data_id_list)}")
                 for input_data_id in input_data_id_list:
                     comments = self.get_comments(project_id, task_id, input_data_id)
+
+                    if comment_type is not None:
+                        comments = [e for e in comments if e["comment_type"] == comment_type.value]
+
+                    if exclude_reply:
+                        # 返信コメントを除外する
+                        comments = [e for e in comments if e["comment_node"]["_type"] != "Reply"]
+
                     all_comments.extend(comments)
 
             except requests.HTTPError:
                 logger.warning(f"タスク task_id = {task_id} のコメントを取得できませんでした。", exc_info=True)
 
-        logger.info(f"対象タスクに紐付いたコメントをすべて取得しました。output={output_file}")
+        logger.info(f"コメントの件数: {len(all_comments)}")
 
         visualize = AddProps(self.service, project_id)
         all_comments = [visualize.add_properties_to_comment(e) for e in all_comments]
@@ -49,12 +58,9 @@ def list_comments(self, project_id: str, task_id_list: List[str], output_file: P
     def main(self):
         args = self.args
         task_id_list = annofabcli.common.cli.get_list_from_args(args.task_id)
+        comment_type = CommentType(args.comment_type) if args.comment_type is not None else None
 
-        self.list_comments(
-            args.project_id,
-            task_id_list,
-            output_file=args.output,
-        )
+        self.list_comments(args.project_id, task_id_list, comment_type=comment_type, exclude_reply=args.exclude_reply)
 
 
 def main(args: argparse.Namespace):
@@ -72,6 +78,18 @@ def parse_args(parser: argparse.ArgumentParser):
         help_message="対象のタスクのtask_idを指定します。" " ``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。",
     )
 
+    parser.add_argument(
+        "--comment_type",
+        choices=[CommentType.INSPECTION.value, CommentType.ONHOLD.value],
+        help=(
+            "コメントの種類で絞り込みます。\n\n"
+            f" * {CommentType.INSPECTION.value}: 検査コメント\n"
+            f" * {CommentType.ONHOLD.value}: 保留コメント\n"
+        ),
+    )
+
+    parser.add_argument("--exclude_reply", action="store_true", help="返信コメントを除外します。")
+
     argument_parser.add_format(
         choices=[
             FormatArgument.CSV,
diff --git a/annofabcli/comment/put_comment.py b/annofabcli/comment/put_comment.py
new file mode 100644
index 00000000..bb8a21a6
--- /dev/null
+++ b/annofabcli/comment/put_comment.py
@@ -0,0 +1,314 @@
+from __future__ import annotations
+
+import logging
+import multiprocessing
+import uuid
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional, Tuple
+
+import annofabapi
+import annofabapi.utils
+import requests
+from annofabapi.models import CommentType, TaskPhase, TaskStatus
+from dataclasses_json import DataClassJsonMixin
+
+from annofabcli.comment.utils import get_comment_type_name
+from annofabcli.common.cli import AbstractCommandLineWithConfirmInterface
+from annofabcli.common.facade import AnnofabApiFacade
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class AddedComment(DataClassJsonMixin):
+    """
+    追加対象のコメント。
+    """
+
+    comment: str
+    """コメントの中身"""
+
+    data: Optional[Dict[str, Any]]
+    """コメントを付与する位置や区間"""
+
+    annotation_id: Optional[str]
+    """コメントに紐付けるアノテーションID"""
+
+    phrases: Optional[List[str]]
+    """参照している定型指摘ID"""
+
+
+AddedCommentsForTask = Dict[str, List[AddedComment]]
+"""
+タスク配下の追加対象のコメント
+keyはinput_data_id
+"""
+
+AddedComments = Dict[str, AddedCommentsForTask]
+"""
+追加対象のコメント
+keyはtask_id
+"""
+
+
+class PutCommentMain(AbstractCommandLineWithConfirmInterface):
+    def __init__(self, service: annofabapi.Resource, project_id: str, comment_type: CommentType, all_yes: bool = False):
+        self.service = service
+        self.facade = AnnofabApiFacade(service)
+        self.project_id = project_id
+
+        self.comment_type = comment_type
+        self.comment_type_name = get_comment_type_name(comment_type)
+
+        AbstractCommandLineWithConfirmInterface.__init__(self, all_yes)
+
+    def _create_request_body(
+        self, task: Dict[str, Any], input_data_id: str, comments: List[AddedComment]
+    ) -> List[Dict[str, Any]]:
+        """batch_update_comments に渡すリクエストボディを作成する。"""
+
+        def _create_dict_annotation_id() -> Dict[str, str]:
+            content, _ = self.service.api.get_editor_annotation(self.project_id, task["task_id"], input_data_id)
+            details = content["details"]
+            return {e["annotation_id"]: e["label_id"] for e in details}
+
+        dict_annotation_id_label_id = _create_dict_annotation_id()
+
+        def _convert(comment: AddedComment) -> Dict[str, Any]:
+            return {
+                "comment_id": str(uuid.uuid4()),
+                "phase": task["phase"],
+                "phase_stage": task["phase_stage"],
+                "account_id": self.service.api.account_id,
+                "comment_type": self.comment_type.value,
+                "comment": comment.comment,
+                "comment_node": {
+                    "data": comment.data,
+                    "annotation_id": comment.annotation_id,
+                    "label_id": dict_annotation_id_label_id.get(comment.annotation_id)
+                    if comment.annotation_id is not None
+                    else None,
+                    "status": "open",
+                    "_type": "Root",
+                },
+                "phrases": comment.phrases,
+                "_type": "Put",
+            }
+
+        return [_convert(e) for e in comments]
+
+    def change_to_working_status(self, project_id: str, task: Dict[str, Any]) -> Dict[str, Any]:
+        """
+        作業中状態に遷移する。必要ならば担当者を自分自身に変更する。
+
+        Args:
+            project_id:
+            task:
+
+        Returns:
+            作業中状態遷移後のタスク
+        """
+
+        task_id = task["task_id"]
+        try:
+            if task["account_id"] != self.service.api.account_id:
+                self.service.wrapper.change_task_operator(project_id, task_id, self.service.api.account_id)
+                logger.debug(f"{task_id}: 担当者を自分自身に変更しました。")
+
+            changed_task = self.service.wrapper.change_task_status_to_working(project_id, task_id)
+            return changed_task
+
+        except requests.HTTPError:
+            logger.warning(f"{task_id}: 担当者の変更、または作業中状態への変更に失敗しました。", exc_info=True)
+            raise
+
+    def _can_add_comment(
+        self,
+        task: Dict[str, Any],
+    ) -> bool:
+        task_id = task["task_id"]
+
+        if self.comment_type == CommentType.INSPECTION:
+            if task["phase"] == TaskPhase.ANNOTATION.value:
+                logger.warning(f"task_id='{task_id}': 教師付フェーズなので、検査コメントを付与できません。")
+                return False
+
+        if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
+            logger.warning(
+                f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを付与できません。（task_status='{task['status']}'）"
+            )
+            return False
+        return True
+
+    def add_comments_for_task(
+        self,
+        task_id: str,
+        comments_for_task: AddedCommentsForTask,
+        task_index: Optional[int] = None,
+    ) -> int:
+        """
+        タスクにコメントを付与します。
+
+        Args:
+            task_id: タスクID
+            comments_for_task: 1つのタスクに付与するコメントの集合
+            task_index: タスクの連番
+
+        Returns:
+            付与したコメントの数
+        """
+        logging_prefix = f"{task_index+1} 件目" if task_index is not None else ""
+
+        task = self.service.wrapper.get_task_or_none(self.project_id, task_id)
+        if task is None:
+            logger.warning(f"{logging_prefix} : task_id='{task_id}' のタスクは存在しないので、スキップします。")
+            return 0
+
+        logger.debug(
+            f"{logging_prefix} : task_id = {task['task_id']}, "
+            f"status = {task['status']}, "
+            f"phase = {task['phase']}, "
+        )
+
+        if not self._can_add_comment(
+            task=task,
+        ):
+            return 0
+
+        if not self.confirm_processing(f"task_id='{task_id}' のタスクに{self.comment_type_name}を付与しますか？"):
+            return 0
+
+        # コメントを付与するには作業中状態にする必要がある
+        changed_task = self.change_to_working_status(self.project_id, task)
+        added_comments_count = 0
+        for input_data_id, comments in comments_for_task.items():
+            if input_data_id not in task["input_data_id_list"]:
+                logger.warning(
+                    f"{logging_prefix} : task_id='{task_id}'のタスクに input_data_id='{input_data_id}'の入力データは存在しません。"
+                )
+                continue
+            try:
+                # コメントを付与する
+                request_body = self._create_request_body(
+                    task=changed_task, input_data_id=input_data_id, comments=comments
+                )
+                self.service.api.batch_update_comments(
+                    self.project_id, task_id, input_data_id, request_body=request_body
+                )
+                added_comments_count += 1
+                logger.debug(
+                    f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: "
+                    f"{len(comments)}件のコメントを付与しました。"
+                )
+            except Exception:  # pylint: disable=broad-except
+                logger.warning(
+                    f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: コメントの付与に失敗しました。",
+                    exc_info=True,
+                )
+            finally:
+                self.service.wrapper.change_task_status_to_break(self.project_id, task_id)
+                # 担当者が変えている場合は、元に戻す
+                if task["account_id"] != changed_task["account_id"]:
+                    self.service.wrapper.change_task_operator(self.project_id, task_id, task["account_id"])
+                    logger.debug(f"{task_id}: 担当者を元のユーザ( account_id={task['account_id']}）に戻しました。")
+
+        return added_comments_count
+
+    def add_comments_for_task_wrapper(
+        self,
+        tpl: Tuple[int, Tuple[str, AddedCommentsForTask]],
+    ) -> int:
+        task_index, (task_id, comments_for_task) = tpl
+        return self.add_comments_for_task(task_id=task_id, comments_for_task=comments_for_task, task_index=task_index)
+
+    def add_comments_for_task_list(
+        self,
+        comments_for_task_list: AddedComments,
+        parallelism: Optional[int] = None,
+    ) -> None:
+        comments_count = sum(len(e) for e in comments_for_task_list.values())
+        logger.info(
+            f"{self.comment_type_name}を付与するタスク数: {len(comments_for_task_list)}, "
+            f"{self.comment_type_name}を付与する入力データ数: {comments_count}"
+        )
+
+        if parallelism is not None:
+            with multiprocessing.Pool(parallelism) as pool:
+                result_bool_list = pool.map(
+                    self.add_comments_for_task_wrapper, enumerate(comments_for_task_list.items())
+                )
+                added_comments_count = sum(e for e in result_bool_list)
+
+        else:
+            # 逐次処理
+            added_comments_count = 0
+            for task_index, (task_id, comments_for_task) in enumerate(comments_for_task_list.items()):
+                try:
+                    result = self.add_comments_for_task(
+                        task_id=task_id,
+                        comments_for_task=comments_for_task,
+                        task_index=task_index,
+                    )
+                    added_comments_count += result
+                except Exception:  # pylint: disable=broad-except
+                    logger.warning(f"task_id={task_id}: コメントの付与に失敗しました。", exc_info=True)
+                    continue
+
+        logger.info(f"{added_comments_count} / {comments_count} 件の入力データに{self.comment_type_name}を付与しました。")
+
+
+def convert_cli_comments(dict_comments: Dict[str, Any], *, comment_type: CommentType) -> AddedComments:
+    """
+    CLIから受け取ったコメント情報を、データクラスに変換する。
+    """
+
+    @dataclass
+    class AddedInspectionComment(DataClassJsonMixin):
+        """
+        追加対象の検査コメント
+        """
+
+        comment: str
+        """コメントの中身"""
+
+        data: Dict[str, Any]
+        """コメントを付与する位置や区間"""
+
+        annotation_id: Optional[str] = None
+        """コメントに紐付けるアノテーションID"""
+
+        phrases: Optional[List[str]] = None
+        """参照している定型指摘ID"""
+
+    @dataclass
+    class AddedOnholdComment(DataClassJsonMixin):
+        """
+        追加対象の保留コメント
+        """
+
+        comment: str
+        """コメントの中身"""
+
+        annotation_id: Optional[str] = None
+        """コメントに紐付けるアノテーションID"""
+
+    def convert_inspection_comment(comment: dict[str, Any]) -> AddedComment:
+        tmp = AddedInspectionComment.from_dict(comment)
+        return AddedComment(comment=tmp.comment, data=tmp.data, annotation_id=tmp.annotation_id, phrases=tmp.phrases)
+
+    def convert_onhold_comment(comment: dict[str, Any]) -> AddedComment:
+        tmp = AddedOnholdComment.from_dict(comment)
+        return AddedComment(comment=tmp.comment, annotation_id=tmp.annotation_id, data=None, phrases=None)
+
+    if comment_type == CommentType.INSPECTION:
+        func_convert = convert_inspection_comment
+    elif comment_type == CommentType.ONHOLD:
+        func_convert = convert_onhold_comment
+    else:
+        raise RuntimeError(f"{comment_type=}が無効な値です。")
+
+    return {
+        task_id: {input_data_id: [func_convert(e) for e in comments]}
+        for task_id, comments_for_task in dict_comments.items()
+        for input_data_id, comments in comments_for_task.items()
+    }
diff --git a/annofabcli/comment/put_comment_simply.py b/annofabcli/comment/put_comment_simply.py
new file mode 100644
index 00000000..099fff77
--- /dev/null
+++ b/annofabcli/comment/put_comment_simply.py
@@ -0,0 +1,203 @@
+import logging
+import multiprocessing
+import uuid
+from dataclasses import dataclass
+from functools import partial
+from typing import Any, Collection, Dict, List, Optional, Tuple
+
+import annofabapi
+import annofabapi.utils
+from annofabapi.models import CommentType, TaskPhase, TaskStatus
+
+from annofabcli.comment.utils import get_comment_type_name
+from annofabcli.common.cli import AbstractCommandLineWithConfirmInterface
+from annofabcli.common.facade import AnnofabApiFacade
+
+logger = logging.getLogger(__name__)
+
+
+@dataclass
+class AddedSimpleComment:
+    """
+    付与するシンプルな検査コメント
+    """
+
+    comment: str
+    """コメントの中身"""
+
+    data: Optional[Dict[str, Any]]
+    """コメントを付与する位置や区間"""
+
+    phrases: Optional[List[str]] = None
+    """参照している定型指摘ID"""
+
+
+class PutCommentSimplyMain(AbstractCommandLineWithConfirmInterface):
+    def __init__(self, service: annofabapi.Resource, project_id: str, comment_type: CommentType, all_yes: bool = False):
+        self.service = service
+        self.facade = AnnofabApiFacade(service)
+        self.project_id = project_id
+
+        self.comment_type = comment_type
+        self.comment_type_name = get_comment_type_name(comment_type)
+
+        AbstractCommandLineWithConfirmInterface.__init__(self, all_yes)
+
+    def _create_request_body(self, task: Dict[str, Any], comment_info: AddedSimpleComment) -> List[Dict[str, Any]]:
+        """batch_update_comments に渡すリクエストボディを作成する。"""
+
+        def _convert(comment: AddedSimpleComment) -> Dict[str, Any]:
+            return {
+                "comment": comment.comment,
+                "comment_id": str(uuid.uuid4()),
+                "phase": task["phase"],
+                "phase_stage": task["phase_stage"],
+                "comment_type": self.comment_type.value,
+                "account_id": self.service.api.account_id,
+                "comment_node": {"data": comment.data, "status": "open", "_type": "Root"},
+                "phrases": comment.phrases,
+                "_type": "Put",
+            }
+
+        return [_convert(comment_info)]
+
+    def change_to_working_status(self, project_id: str, task: Dict[str, Any]) -> Dict[str, Any]:
+        """
+        作業中状態に遷移する。必要ならば担当者を自分自身に変更する。
+
+        Args:
+            project_id:
+            task:
+
+        Returns:
+            作業中状態遷移後のタスク
+        """
+
+        task_id = task["task_id"]
+
+        if task["account_id"] != self.service.api.account_id:
+            self.service.wrapper.change_task_operator(project_id, task_id, self.service.api.account_id)
+            logger.debug(f"{task_id}: 担当者を自分自身に変更しました。")
+
+        changed_task = self.service.wrapper.change_task_status_to_working(project_id, task_id)
+        return changed_task
+
+    def _can_add_comment(
+        self,
+        task: Dict[str, Any],
+    ) -> bool:
+        task_id = task["task_id"]
+
+        if self.comment_type == CommentType.INSPECTION:
+            if task["phase"] == TaskPhase.ANNOTATION.value:
+                logger.warning(f"task_id='{task_id}': 教師付フェーズなので、検査コメントを付与できません。")
+                return False
+
+        if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
+            logger.warning(
+                f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、コメントを付与できません。（task_status='{task['status']}'）"
+            )
+            return False
+        return True
+
+    def put_comment_for_task(
+        self,
+        task_id: str,
+        comment_info: AddedSimpleComment,
+        task_index: Optional[int] = None,
+    ) -> bool:
+        """
+        タスクにコメントを付与します。
+
+        Args:
+            task_id: タスクID
+            comment_info: コメント情報
+            task_index: タスクの連番
+
+        Returns:
+            付与したコメントの数
+        """
+        logging_prefix = f"{task_index+1} 件目" if task_index is not None else ""
+
+        task = self.service.wrapper.get_task_or_none(self.project_id, task_id)
+        if task is None:
+            logger.warning(f"{logging_prefix} : task_id='{task_id}' のタスクは存在しないので、スキップします。")
+            return False
+
+        logger.debug(
+            f"{logging_prefix} : task_id = {task['task_id']}, "
+            f"status = {task['status']}, "
+            f"phase = {task['phase']}, "
+        )
+
+        if not self._can_add_comment(
+            task=task,
+        ):
+            return False
+
+        if not self.confirm_processing(f"task_id='{task_id}' のタスクに{self.comment_type_name}を付与しますか？"):
+            return False
+
+        # コメントを付与するには作業中状態にする必要があるので、タスクの状態を作業中にする
+        changed_task = self.change_to_working_status(self.project_id, task)
+
+        input_data_id = task["input_data_id_list"][0]
+
+        try:
+            # コメントを付与する
+            request_body = self._create_request_body(task=changed_task, comment_info=comment_info)
+            self.service.api.batch_update_comments(self.project_id, task_id, input_data_id, request_body=request_body)
+            logger.debug(f"{logging_prefix} : task_id={task_id} のタスクにコメントを付与しました。")
+            return True
+        except Exception:  # pylint: disable=broad-except
+            logger.warning(
+                f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: コメントの付与に失敗しました。", exc_info=True
+            )
+            return False
+        finally:
+            self.service.wrapper.change_task_status_to_break(self.project_id, task_id)
+            # 担当者が変えている場合は、元に戻す
+            if task["account_id"] != changed_task["account_id"]:
+                self.service.wrapper.change_task_operator(self.project_id, task_id, task["account_id"])
+                logger.debug(f"{task_id}: 担当者を元のユーザ( account_id={task['account_id']}）に戻しました。")
+
+    def add_comments_for_task_wrapper(self, tpl: Tuple[int, str], comment_info: AddedSimpleComment) -> bool:
+        task_index, task_id = tpl
+        try:
+            return self.put_comment_for_task(task_id=task_id, comment_info=comment_info, task_index=task_index)
+        except Exception:  # pylint: disable=broad-except
+            logger.warning(f"task_id={task_id}: コメントの付与に失敗しました。", exc_info=True)
+            return False
+
+    def put_comment_for_task_list(
+        self,
+        task_ids: Collection[str],
+        comment_info: AddedSimpleComment,
+        parallelism: Optional[int] = None,
+    ) -> None:
+
+        logger.info(f"{len(task_ids)} 件のタスクに{self.comment_type_name}を付与します。")
+
+        if parallelism is not None:
+            func = partial(self.add_comments_for_task_wrapper, comment_info=comment_info)
+            with multiprocessing.Pool(parallelism) as pool:
+                result_bool_list = pool.map(func, enumerate(task_ids))
+                success_count = len([e for e in result_bool_list if e])
+
+        else:
+            # 逐次処理
+            success_count = 0
+            for task_index, task_id in enumerate(task_ids):
+                try:
+                    result = self.put_comment_for_task(
+                        task_id=task_id,
+                        comment_info=comment_info,
+                        task_index=task_index,
+                    )
+                    if result:
+                        success_count += 1
+                except Exception:  # pylint: disable=broad-except
+                    logger.warning(f"task_id={task_id}: {self.comment_type_name}の付与に失敗しました。", exc_info=True)
+                    continue
+
+        logger.info(f"{success_count} / {len(task_ids)} 件のタスクに{self.comment_type_name}を付与しました。")
diff --git a/annofabcli/comment/put_inspection_comment.py b/annofabcli/comment/put_inspection_comment.py
new file mode 100644
index 00000000..e0ad1a46
--- /dev/null
+++ b/annofabcli/comment/put_inspection_comment.py
@@ -0,0 +1,97 @@
+from __future__ import annotations
+
+import argparse
+import json
+import logging
+import sys
+from typing import Optional
+
+from annofabapi.models import CommentType, ProjectMemberRole
+
+import annofabcli
+import annofabcli.common.cli
+from annofabcli.comment.put_comment import PutCommentMain, convert_cli_comments
+from annofabcli.common.cli import (
+    COMMAND_LINE_ERROR_STATUS_CODE,
+    AbstractCommandLineInterface,
+    ArgumentParser,
+    build_annofabapi_resource_and_login,
+)
+from annofabcli.common.facade import AnnofabApiFacade
+
+logger = logging.getLogger(__name__)
+
+
+class PutInspectionComment(AbstractCommandLineInterface):
+    COMMON_MESSAGE = "annofabcli comment put_inspection: error:"
+
+    def validate(self, args: argparse.Namespace) -> bool:
+
+        if args.parallelism is not None and not args.yes:
+            print(
+                f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず '--yes' を指定してください。",
+                file=sys.stderr,
+            )
+            return False
+
+        return True
+
+    def main(self):
+        args = self.args
+        if not self.validate(args):
+            sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
+
+        super().validate_project(args.project_id, [ProjectMemberRole.ACCEPTER, ProjectMemberRole.OWNER])
+
+        dict_comments = annofabcli.common.cli.get_json_from_args(args.json)
+        comments_for_task_list = convert_cli_comments(dict_comments, comment_type=CommentType.INSPECTION)
+
+        main_obj = PutCommentMain(
+            self.service, project_id=args.project_id, comment_type=CommentType.INSPECTION, all_yes=self.all_yes
+        )
+        main_obj.add_comments_for_task_list(
+            comments_for_task_list=comments_for_task_list,
+            parallelism=args.parallelism,
+        )
+
+
+def main(args: argparse.Namespace):
+    service = build_annofabapi_resource_and_login(args)
+    facade = AnnofabApiFacade(service)
+    PutInspectionComment(service, facade, args).main()
+
+
+def parse_args(parser: argparse.ArgumentParser):
+    argument_parser = ArgumentParser(parser)
+
+    argument_parser.add_project_id()
+
+    SAMPLE_JSON = {
+        "task1": {"input_data1": [{"comment": "type属性が間違っています。", "data": {"x": 10, "y": 20, "_type": "Point"}}]}
+    }
+    parser.add_argument(
+        "--json",
+        type=str,
+        help=(
+            "付与する検査コメントの内容をJSON形式で指定してください。"
+            "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n"
+            f"(ex)  ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"
+        ),
+    )
+
+    parser.add_argument(
+        "--parallelism", type=int, help="使用するプロセス数（並列度）を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。"
+    )
+
+    parser.set_defaults(subcommand_func=main)
+
+
+def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
+    subcommand_name = "put_inspection"
+    subcommand_help = "検査コメントを付与します"
+    description = "検査コメントを付与します。"
+    epilog = "チェッカーロールまたはオーナロールを持つユーザで実行してください。"
+
+    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description, epilog=epilog)
+    parse_args(parser)
+    return parser
diff --git a/annofabcli/comment/put_inspection_comment_simply.py b/annofabcli/comment/put_inspection_comment_simply.py
new file mode 100644
index 00000000..ab117c6a
--- /dev/null
+++ b/annofabcli/comment/put_inspection_comment_simply.py
@@ -0,0 +1,145 @@
+import argparse
+import logging
+import sys
+from typing import Optional
+
+from annofabapi.models import CommentType, InputDataType, ProjectMemberRole
+
+import annofabcli
+import annofabcli.common.cli
+from annofabcli.comment.put_comment_simply import AddedSimpleComment, PutCommentSimplyMain
+from annofabcli.common.cli import (
+    COMMAND_LINE_ERROR_STATUS_CODE,
+    AbstractCommandLineInterface,
+    ArgumentParser,
+    build_annofabapi_resource_and_login,
+    get_list_from_args,
+)
+from annofabcli.common.enums import CustomProjectType
+from annofabcli.common.facade import AnnofabApiFacade
+
+logger = logging.getLogger(__name__)
+
+
+class PutInspectionCommentSimply(AbstractCommandLineInterface):
+
+    COMMON_MESSAGE = "annofabcli comment put_inspection_simply: error:"
+
+    def validate(self, args: argparse.Namespace) -> bool:
+        if args.parallelism is not None and not args.yes:
+            print(
+                f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず '--yes' を指定してください。",
+                file=sys.stderr,
+            )
+            return False
+
+        return True
+
+    def main(self):
+        args = self.args
+        if not self.validate(args):
+            sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
+
+        super().validate_project(args.project_id, [ProjectMemberRole.ACCEPTER, ProjectMemberRole.OWNER])
+
+        comment_data = annofabcli.common.cli.get_json_from_args(args.comment_data)
+        custom_project_type = (
+            CustomProjectType(args.custom_project_type) if args.custom_project_type is not None else None
+        )
+
+        project, _ = self.service.api.get_project(args.project_id)
+        if comment_data is None:
+            if project["input_data_type"] == InputDataType.IMAGE.value:
+                comment_data = {"x": 0, "y": 0, "_type": "Point"}
+            elif project["input_data_type"] == InputDataType.MOVIE.value:
+                # 注意：少なくとも0.1秒以上の区間にしないと、Annofab上で検査コメントを確認できない
+                comment_data = {"start": 0, "end": 100, "_type": "Time"}
+            elif project["input_data_type"] == InputDataType.CUSTOM.value:
+                if custom_project_type == CustomProjectType.THREE_DIMENSION_POINT_CLOUD:
+                    comment_data = {
+                        "data": '{"kind": "CUBOID", "shape": {"dimensions": {"width": 1.0, "height": 1.0, "depth": 1.0}, "location": {"x": 0.0, "y": 0.0, "z": 0.0}, "rotation": {"x": 0.0, "y": 0.0, "z": 0.0}, "direction": {"front": {"x": 1.0, "y": 0.0, "z": 0.0}, "up": {"x": 0.0, "y": 0.0, "z": 1.0}}}, "version": "2"}',  # noqa: E501
+                        "_type": "Custom",
+                    }
+                else:
+                    print(
+                        f"{self.COMMON_MESSAGE}: カスタムプロジェクトに検査コメントを付与する場合は、'--comment_data' または '--custom_project_type'を指定してください。",  # noqa: E501
+                        file=sys.stderr,
+                    )
+                    sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
+
+        task_id_list = get_list_from_args(args.task_id)
+        phrase_id_list = get_list_from_args(args.phrase_id)
+        main_obj = PutCommentSimplyMain(
+            self.service, project_id=args.project_id, comment_type=CommentType.INSPECTION, all_yes=self.all_yes
+        )
+        main_obj.put_comment_for_task_list(
+            task_ids=task_id_list,
+            comment_info=AddedSimpleComment(comment=args.comment, data=comment_data, phrases=phrase_id_list),
+            parallelism=args.parallelism,
+        )
+
+
+def main(args: argparse.Namespace):
+    service = build_annofabapi_resource_and_login(args)
+    facade = AnnofabApiFacade(service)
+    PutInspectionCommentSimply(service, facade, args).main()
+
+
+def parse_args(parser: argparse.ArgumentParser):
+    argument_parser = ArgumentParser(parser)
+
+    argument_parser.add_project_id()
+
+    parser.add_argument(
+        "-t",
+        "--task_id",
+        type=str,
+        nargs="+",
+        required=True,
+        help=("検査コメントを付与するタスクのtask_idを指定してください。\n" "``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。"),
+    )
+
+    parser.add_argument(
+        "--comment",
+        type=str,
+        required=True,
+        help="付与する検査コメントのメッセージを指定します。",
+    )
+
+    parser.add_argument("--phrase_id", type=str, nargs="+", help="定型指摘コメントのIDを指定してください。")
+
+    parser.add_argument(
+        "--comment_data",
+        type=str,
+        help="検査コメントを付与する位置や区間をJSON形式で指定します。\n"
+        "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n"
+        "デフォルトの検査コメントの種類と位置は以下の通りです。\n"
+        "\n"
+        " * 画像プロジェクト：点。先頭画像の左上\n"
+        " * 動画プロジェクト：区間。動画の先頭\n"
+        " * カスタムプロジェクト(3dpc)：辺が1の立方体。原点\n",
+    )
+
+    parser.add_argument(
+        "--custom_project_type",
+        type=str,
+        choices=[e.value for e in CustomProjectType],
+        help="[BETA] カスタムプロジェクトの種類を指定します。カスタムプロジェクトに対して、検査コメントの位置を指定しない場合は必須です。\n"
+        "※ Annofabの情報だけでは'3dpc'プロジェクトかどうかが分からないため、指定する必要があります。",
+    )
+
+    parser.add_argument(
+        "--parallelism", type=int, help="使用するプロセス数（並列度）を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。"
+    )
+
+    parser.set_defaults(subcommand_func=main)
+
+
+def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
+    subcommand_name = "put_inspection_simply"
+    subcommand_help = "``comment put_inspection`` コマンドよりも、簡単に検査コメントを付与します。"
+    epilog = "チェッカーロールまたはオーナロールを持つユーザで実行してください。"
+
+    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, epilog=epilog)
+    parse_args(parser)
+    return parser
diff --git a/annofabcli/comment/put_onhold_comment.py b/annofabcli/comment/put_onhold_comment.py
new file mode 100644
index 00000000..20b0279b
--- /dev/null
+++ b/annofabcli/comment/put_onhold_comment.py
@@ -0,0 +1,93 @@
+import argparse
+import json
+import logging
+import sys
+from typing import Optional
+
+from annofabapi.models import CommentType
+
+import annofabcli
+import annofabcli.common.cli
+from annofabcli.comment.put_comment import PutCommentMain, convert_cli_comments
+from annofabcli.common.cli import (
+    COMMAND_LINE_ERROR_STATUS_CODE,
+    AbstractCommandLineInterface,
+    ArgumentParser,
+    build_annofabapi_resource_and_login,
+)
+from annofabcli.common.facade import AnnofabApiFacade
+
+logger = logging.getLogger(__name__)
+
+
+class PutInspectionComment(AbstractCommandLineInterface):
+    COMMON_MESSAGE = "annofabcli comment put_onhold: error:"
+
+    def validate(self, args: argparse.Namespace) -> bool:
+
+        if args.parallelism is not None and not args.yes:
+            print(
+                f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず '--yes' を指定してください。",
+                file=sys.stderr,
+            )
+            return False
+
+        return True
+
+    def main(self):
+        args = self.args
+        if not self.validate(args):
+            sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
+
+        super().validate_project(args.project_id)
+
+        dict_comments = annofabcli.common.cli.get_json_from_args(args.json)
+        comments_for_task_list = convert_cli_comments(
+            dict_comments,
+            comment_type=CommentType.ONHOLD,
+        )
+        main_obj = PutCommentMain(
+            self.service, project_id=args.project_id, comment_type=CommentType.ONHOLD, all_yes=self.all_yes
+        )
+        main_obj.add_comments_for_task_list(
+            comments_for_task_list=comments_for_task_list,
+            parallelism=args.parallelism,
+        )
+
+
+def main(args: argparse.Namespace):
+    service = build_annofabapi_resource_and_login(args)
+    facade = AnnofabApiFacade(service)
+    PutInspectionComment(service, facade, args).main()
+
+
+def parse_args(parser: argparse.ArgumentParser):
+    argument_parser = ArgumentParser(parser)
+
+    argument_parser.add_project_id()
+
+    SAMPLE_JSON = {"task1": {"input_data1": [{"comment": "type属性が間違っています。"}]}}
+    parser.add_argument(
+        "--json",
+        type=str,
+        help=(
+            "付与する保留コメントの情報をJSON形式で指定してください。"
+            "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n\n"
+            f"(ex)  ``{json.dumps(SAMPLE_JSON, ensure_ascii=False)}``"
+        ),
+    )
+
+    parser.add_argument(
+        "--parallelism", type=int, help="使用するプロセス数（並列度）を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。"
+    )
+
+    parser.set_defaults(subcommand_func=main)
+
+
+def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
+    subcommand_name = "put_onhold"
+    subcommand_help = "保留コメントを付与します"
+
+    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help)
+    parse_args(parser)
+    return parser
diff --git a/annofabcli/comment/put_onhold_comment_simply.py b/annofabcli/comment/put_onhold_comment_simply.py
new file mode 100644
index 00000000..d041d67f
--- /dev/null
+++ b/annofabcli/comment/put_onhold_comment_simply.py
@@ -0,0 +1,95 @@
+import argparse
+import logging
+import sys
+from typing import Optional
+
+from annofabapi.models import CommentType
+
+import annofabcli
+import annofabcli.common.cli
+from annofabcli.comment.put_comment_simply import AddedSimpleComment, PutCommentSimplyMain
+from annofabcli.common.cli import (
+    COMMAND_LINE_ERROR_STATUS_CODE,
+    AbstractCommandLineInterface,
+    ArgumentParser,
+    build_annofabapi_resource_and_login,
+    get_list_from_args,
+)
+from annofabcli.common.facade import AnnofabApiFacade
+
+logger = logging.getLogger(__name__)
+
+
+class PutOnholdCommentSimply(AbstractCommandLineInterface):
+
+    COMMON_MESSAGE = "annofabcli comment put_onhold_simply: error:"
+
+    def validate(self, args: argparse.Namespace) -> bool:
+        if args.parallelism is not None and not args.yes:
+            print(
+                f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず '--yes' を指定してください。",
+                file=sys.stderr,
+            )
+            return False
+
+        return True
+
+    def main(self):
+        args = self.args
+        if not self.validate(args):
+            sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
+
+        super().validate_project(args.project_id)
+
+        task_id_list = get_list_from_args(args.task_id)
+        main_obj = PutCommentSimplyMain(
+            self.service, project_id=args.project_id, comment_type=CommentType.ONHOLD, all_yes=self.all_yes
+        )
+        main_obj.put_comment_for_task_list(
+            task_ids=task_id_list,
+            comment_info=AddedSimpleComment(comment=args.comment, data=None, phrases=None),
+            parallelism=args.parallelism,
+        )
+
+
+def main(args: argparse.Namespace):
+    service = build_annofabapi_resource_and_login(args)
+    facade = AnnofabApiFacade(service)
+    PutOnholdCommentSimply(service, facade, args).main()
+
+
+def parse_args(parser: argparse.ArgumentParser):
+    argument_parser = ArgumentParser(parser)
+
+    argument_parser.add_project_id()
+
+    parser.add_argument(
+        "-t",
+        "--task_id",
+        type=str,
+        nargs="+",
+        required=True,
+        help=("コメントを付与するタスクのtask_idを指定してください。\n" "``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。"),
+    )
+
+    parser.add_argument(
+        "--comment",
+        type=str,
+        required=True,
+        help="コメントのメッセージを指定します。",
+    )
+
+    parser.add_argument(
+        "--parallelism", type=int, help="使用するプロセス数（並列度）を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。"
+    )
+
+    parser.set_defaults(subcommand_func=main)
+
+
+def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
+    subcommand_name = "put_onhold_simply"
+    subcommand_help = "``comment put_onhold`` コマンドよりも、簡単に保留コメントを付与します。"
+
+    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help)
+    parse_args(parser)
+    return parser
diff --git a/annofabcli/comment/subcommand_comment.py b/annofabcli/comment/subcommand_comment.py
index e3099004..294ee824 100644
--- a/annofabcli/comment/subcommand_comment.py
+++ b/annofabcli/comment/subcommand_comment.py
@@ -2,9 +2,14 @@
 from typing import Optional
 
 import annofabcli
+import annofabcli.comment.delete_comment
 import annofabcli.comment.download_comment_json
 import annofabcli.comment.list_all_comment
 import annofabcli.comment.list_comment
+import annofabcli.comment.put_inspection_comment
+import annofabcli.comment.put_inspection_comment_simply
+import annofabcli.comment.put_onhold_comment
+import annofabcli.comment.put_onhold_comment_simply
 import annofabcli.common.cli
 
 
@@ -13,9 +18,14 @@ def parse_args(parser: argparse.ArgumentParser):
     subparsers = parser.add_subparsers(dest="subcommand_name")
 
     # サブコマンドの定義
+    annofabcli.comment.delete_comment.add_parser(subparsers)
     annofabcli.comment.download_comment_json.add_parser(subparsers)
     annofabcli.comment.list_comment.add_parser(subparsers)
     annofabcli.comment.list_all_comment.add_parser(subparsers)
+    annofabcli.comment.put_inspection_comment.add_parser(subparsers)
+    annofabcli.comment.put_inspection_comment_simply.add_parser(subparsers)
+    annofabcli.comment.put_onhold_comment.add_parser(subparsers)
+    annofabcli.comment.put_onhold_comment_simply.add_parser(subparsers)
 
 
 def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
diff --git a/annofabcli/comment/utils.py b/annofabcli/comment/utils.py
new file mode 100644
index 00000000..5c2af149
--- /dev/null
+++ b/annofabcli/comment/utils.py
@@ -0,0 +1,10 @@
+from annofabapi.models import CommentType
+
+
+def get_comment_type_name(comment_type) -> str:
+    if comment_type == CommentType.INSPECTION:
+        return "検査コメント"
+    elif comment_type == CommentType.ONHOLD:
+        return "保留コメント"
+    else:
+        raise RuntimeError(f"{comment_type=}は無効な値です。")
diff --git a/annofabcli/common/cli.py b/annofabcli/common/cli.py
index 06b7a274..60d2d203 100644
--- a/annofabcli/common/cli.py
+++ b/annofabcli/common/cli.py
@@ -427,7 +427,7 @@ def add_csv_format(self, help_message: Optional[str] = None):
         if help_message is None:
             help_message = (
                 "CSVのフォーマットをJSON形式で指定します。 ``--format`` が ``csv`` でないときは、このオプションは無視されます。\n"
-                " ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n"
+                "``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n"
                 "指定した値は ``pandas.DataFrame.to_csv`` の引数として渡されます。"
             )
 
diff --git a/annofabcli/common/enums.py b/annofabcli/common/enums.py
index a8443e2e..1cca5a6e 100644
--- a/annofabcli/common/enums.py
+++ b/annofabcli/common/enums.py
@@ -41,3 +41,10 @@ class FormatArgument(Enum):
 
     #: project_idの一覧
     PROJECT_ID_LIST = "project_id_list"
+
+
+class CustomProjectType(Enum):
+    """カスタムプロジェクトの場合、検査コメントのフォーマットが分からないため、カスタムプロジェクトの種類をannofabcliで定義する。"""
+
+    THREE_DIMENSION_POINT_CLOUD = "3dpc"
+    """3DPCプロジェクト"""
diff --git a/annofabcli/filesystem/draw_annotation.py b/annofabcli/filesystem/draw_annotation.py
index 0c39b1ba..58fd38b2 100644
--- a/annofabcli/filesystem/draw_annotation.py
+++ b/annofabcli/filesystem/draw_annotation.py
@@ -275,8 +275,8 @@ def draw_annotation_all(
                 f"アノテーションJSON={parser.json_file_path}"
             )
             success_count += 1
-        except Exception as e:  # pylint: disable=broad-except
-            logger.warning(f"{parser.json_file_path} のアノテーションの描画に失敗しました。", e)
+        except Exception:  # pylint: disable=broad-except
+            logger.warning(f"{parser.json_file_path} のアノテーションの描画に失敗しました。", exc_info=True)
 
     logger.info(f"{success_count} / {total_count} 件、アノテーションを描画しました。")
 
diff --git a/annofabcli/inspection_comment/delete_inspection_comments.py b/annofabcli/inspection_comment/delete_inspection_comments.py
index e8591cce..8bf79fb9 100644
--- a/annofabcli/inspection_comment/delete_inspection_comments.py
+++ b/annofabcli/inspection_comment/delete_inspection_comments.py
@@ -1,267 +1,21 @@
 import argparse
 import json
 import logging
-import multiprocessing
-import sys
-from typing import Any, Dict, List, Optional, Tuple
-
-import annofabapi
-import annofabapi.utils
-import requests
-from annofabapi.models import ProjectMemberRole, TaskPhase, TaskStatus
+from typing import Optional
 
 import annofabcli
 import annofabcli.common.cli
-from annofabcli.common.cli import (
-    COMMAND_LINE_ERROR_STATUS_CODE,
-    AbstractCommandLineInterface,
-    AbstractCommandLineWithConfirmInterface,
-    ArgumentParser,
-    build_annofabapi_resource_and_login,
-)
+from annofabcli.comment.delete_comment import DeleteComment
+from annofabcli.common.cli import ArgumentParser, build_annofabapi_resource_and_login
 from annofabcli.common.facade import AnnofabApiFacade
 
 logger = logging.getLogger(__name__)
 
 
-DeletedCommentsForTask = Dict[str, List[str]]
-"""
-タスク配下の削除対象の検査コメント
-keyはinput_data_id, value: input_data_idのlist
-"""
-
-DeletedInspectionIds = Dict[str, DeletedCommentsForTask]
-"""
-削除対象の検査コメント
-keyはtask_id
-"""
-
-
-class DeleteInspectionCommentsMain(AbstractCommandLineWithConfirmInterface):
-    def __init__(self, service: annofabapi.Resource, project_id: str, all_yes: bool = False):
-        self.service = service
-        self.facade = AnnofabApiFacade(service)
-        self.project_id = project_id
-
-        AbstractCommandLineWithConfirmInterface.__init__(self, all_yes)
-
-    def _create_request_body(
-        self, task: Dict[str, Any], input_data_id: str, comment_ids: List[str]
-    ) -> List[Dict[str, Any]]:
-        """batch_update_inspections に渡すリクエストボディを作成する。"""
-
-        def _convert(inspection_id: str) -> Dict[str, Any]:
-            return {
-                "comment_id": inspection_id,
-                "_type": "Delete",
-            }
-
-        old_comment_list, _ = self.service.api.get_comments(
-            self.project_id, task["task_id"], input_data_id, query_params={"v": "2"}
-        )
-        old_comment_ids = {e["comment_id"] for e in old_comment_list if e["comment_type"] == "inspection"}
-        request_body = []
-        for comment_id in comment_ids:
-            if comment_id not in old_comment_ids:
-                logger.warning(
-                    f"task_id={task['task_id']}, input_data_id={input_data_id}: "
-                    f"comment_id='{comment_id}'の検査コメントは存在しません。",
-                )
-                continue
-            request_body.append(_convert(comment_id))
-        return request_body
-
-    def change_to_working_status(self, project_id: str, task: Dict[str, Any]) -> Dict[str, Any]:
-        """
-        作業中状態に遷移する。必要ならば担当者を自分自身に変更する。
-
-        Args:
-            project_id:
-            task:
-
-        Returns:
-            作業中状態遷移後のタスク
-        """
-
-        task_id = task["task_id"]
-        try:
-            if task["account_id"] != self.service.api.account_id:
-                self.service.wrapper.change_task_operator(project_id, task_id, self.service.api.account_id)
-                logger.debug(f"{task_id}: 担当者を自分自身に変更しました。")
-
-            changed_task = self.service.wrapper.change_task_status_to_working(project_id, task_id)
-            return changed_task
-
-        except requests.HTTPError:
-            logger.warning(f"{task_id}: 担当者の変更、または作業中状態への変更に失敗しました。", exc_info=True)
-            raise
-
-    @staticmethod
-    def _can_delete_comment(
-        task: Dict[str, Any],
-    ) -> bool:
-        task_id = task["task_id"]
-        if task["phase"] == TaskPhase.ANNOTATION.value:
-            logger.warning(f"task_id='{task_id}': 教師付フェーズなので、検査コメントを削除できません。")
-            return False
-
-        if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
-            logger.warning(
-                f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、検査コメントを削除できません。（task_status='{task['status']}'）"
-            )
-            return False
-        return True
-
-    def delete_comments_for_task(
-        self,
-        task_id: str,
-        inspection_ids_for_task: DeletedCommentsForTask,
-        task_index: Optional[int] = None,
-    ) -> int:
-        """
-        タスクに対して検査コメントを削除します。
-
-        Args:
-            task_id: タスクID
-            inspection_ids_for_task: 削除対象の検査コメントのIDの集合
-            task_index: タスクの連番
-
-        Returns:
-            削除した検査コメントが所属する入力データ数
-        """
-        logging_prefix = f"{task_index+1} 件目" if task_index is not None else ""
-
-        task = self.service.wrapper.get_task_or_none(self.project_id, task_id)
-        if task is None:
-            logger.warning(f"{logging_prefix} : task_id='{task_id}' のタスクは存在しないので、スキップします。")
-            return 0
-
-        logger.debug(
-            f"{logging_prefix} : task_id = {task['task_id']}, "
-            f"status = {task['status']}, "
-            f"phase = {task['phase']}, "
-        )
-
-        if not self._can_delete_comment(
-            task=task,
-        ):
-            return 0
-
-        if not self.confirm_processing(f"task_id='{task_id}' のタスクに付与された検査コメントを削除しますか？"):
-            return 0
-
-        # 検査コメントを削除するには作業中状態にする必要がある
-        changed_task = self.change_to_working_status(self.project_id, task)
-        added_comments_count = 0
-        for input_data_id, inspection_ids in inspection_ids_for_task.items():
-            if input_data_id not in task["input_data_id_list"]:
-                logger.warning(
-                    f"{logging_prefix} : task_id='{task_id}'のタスクに input_data_id='{input_data_id}'の入力データは存在しません。"
-                )
-                continue
-            try:
-                # 検査コメントを削除
-                request_body = self._create_request_body(
-                    task=changed_task, input_data_id=input_data_id, comment_ids=inspection_ids
-                )
-                if len(request_body) > 0:
-                    self.service.api.batch_update_comments(
-                        self.project_id, task_id, input_data_id, request_body=request_body
-                    )
-                    added_comments_count += 1
-                    logger.debug(
-                        f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: "
-                        f"{len(request_body)}件の検査コメントを削除しました。"
-                    )
-                else:
-                    logger.warning(
-                        f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: "
-                        f"削除できる検査コメントは存在しませんでした。"
-                    )
-
-            except Exception as e:  # pylint: disable=broad-except
-                logger.warning(
-                    f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: 検査コメントの削除に失敗しました。", e
-                )
-
-        self.service.wrapper.change_task_status_to_break(self.project_id, task_id)
-        return added_comments_count
-
-    def delete_comments_for_task_wrapper(
-        self,
-        tpl: Tuple[int, Tuple[str, DeletedCommentsForTask]],
-    ) -> int:
-        task_index, (task_id, inspection_ids_for_task) = tpl
-        return self.delete_comments_for_task(
-            task_id=task_id, inspection_ids_for_task=inspection_ids_for_task, task_index=task_index
-        )
-
-    def delete_comments_for_task_list(
-        self,
-        inspection_ids_for_task_list: DeletedInspectionIds,
-        parallelism: Optional[int] = None,
-    ) -> None:
-        comments_count = sum(len(e) for e in inspection_ids_for_task_list.values())
-        logger.info(f"検査コメントを削除するタスク数: {len(inspection_ids_for_task_list)}, 検査コメントを削除する入力データ数: {comments_count}")
-
-        if parallelism is not None:
-            with multiprocessing.Pool(parallelism) as pool:
-                result_bool_list = pool.map(
-                    self.delete_comments_for_task_wrapper, enumerate(inspection_ids_for_task_list.items())
-                )
-                added_comments_count = sum(e for e in result_bool_list)
-
-        else:
-            # 逐次処理
-            added_comments_count = 0
-            for task_index, (task_id, inspection_ids_for_task) in enumerate(inspection_ids_for_task_list.items()):
-                try:
-                    result = self.delete_comments_for_task(
-                        task_id=task_id,
-                        inspection_ids_for_task=inspection_ids_for_task,
-                        task_index=task_index,
-                    )
-                    added_comments_count += result
-                except Exception as e:  # pylint: disable=broad-except
-                    logger.warning(f"task_id={task_id}: 検査コメントの削除に失敗しました。", e)
-                    continue
-
-        logger.info(f"{added_comments_count} / {comments_count} 件の入力データから検査コメントを削除しました。")
-
-
-class DeleteInspectionComments(AbstractCommandLineInterface):
-    @staticmethod
-    def validate(args: argparse.Namespace) -> bool:
-        COMMON_MESSAGE = "annofabcli inspection_comment delete: error:"
-
-        if args.parallelism is not None and not args.yes:
-            print(
-                f"{COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず ``--yes`` を指定してください。",
-                file=sys.stderr,
-            )
-            return False
-
-        return True
-
-    def main(self):
-        args = self.args
-        if not self.validate(args):
-            sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
-
-        super().validate_project(args.project_id, [ProjectMemberRole.ACCEPTER, ProjectMemberRole.OWNER])
-
-        dict_comments = annofabcli.common.cli.get_json_from_args(args.json)
-        main_obj = DeleteInspectionCommentsMain(self.service, project_id=args.project_id, all_yes=self.all_yes)
-        main_obj.delete_comments_for_task_list(
-            inspection_ids_for_task_list=dict_comments,
-            parallelism=args.parallelism,
-        )
-
-
 def main(args: argparse.Namespace):
     service = build_annofabapi_resource_and_login(args)
     facade = AnnofabApiFacade(service)
-    DeleteInspectionComments(service, facade, args).main()
+    DeleteComment(service, facade, args).main()
 
 
 def parse_args(parser: argparse.ArgumentParser):
@@ -285,10 +39,12 @@ def parse_args(parser: argparse.ArgumentParser):
 
 def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
     subcommand_name = "delete"
-    subcommand_help = "検査コメントを削除します。"
-    description = "検査コメントを削除します。\n【注意】他人の検査コメントや他のフェーズで付与された検査コメントを削除できてしまいます。"
-    epilog = "チェッカーロールまたはオーナロールを持つユーザで実行してください。"
+    subcommand_help = "[DEPRECATED] 検査コメントを削除します。"
+    description = (
+        "[DEPRECATED] 検査コメントを削除します。\n【注意】他人の検査コメントや他のフェーズで付与された検査コメントを削除できてしまいます。"
+        "2022/12/01以降に廃止する予定です。替わりに ``annofabcli comment delete`` コマンドを利用してください。"
+    )
 
-    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description, epilog=epilog)
+    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description)
     parse_args(parser)
     return parser
diff --git a/annofabcli/inspection_comment/download_inspection_comment_json.py b/annofabcli/inspection_comment/download_inspection_comment_json.py
index 18a525c9..f65b4123 100644
--- a/annofabcli/inspection_comment/download_inspection_comment_json.py
+++ b/annofabcli/inspection_comment/download_inspection_comment_json.py
@@ -52,11 +52,19 @@ def parse_args(parser: argparse.ArgumentParser):
 
 def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
     subcommand_name = "download"
-    subcommand_help = "検査コメント全件ファイルをダウンロードします。"
+    subcommand_help = "[DEPRECATED] 検査コメント全件ファイルをダウンロードします。"
+    description = (
+        "[DEPRECATED] 検査コメント全件ファイルをダウンロードします。"
+        "2022/12/01以降に廃止する予定です。替わりに ``annofabcli comment download`` コマンドを利用してください。"
+    )
     epilog = "オーナロールまたはアノテーションユーザロールを持つユーザで実行してください。"
 
     parser = annofabcli.common.cli.add_parser(
-        subparsers, subcommand_name, subcommand_help, description=subcommand_help, epilog=epilog
+        subparsers,
+        subcommand_name,
+        subcommand_help,
+        description=description,
+        epilog=epilog,
     )
     parse_args(parser)
     return parser
diff --git a/annofabcli/inspection_comment/list_all_inspection_comment.py b/annofabcli/inspection_comment/list_all_inspection_comment.py
index 4be4dcf6..bbfac541 100644
--- a/annofabcli/inspection_comment/list_all_inspection_comment.py
+++ b/annofabcli/inspection_comment/list_all_inspection_comment.py
@@ -159,8 +159,11 @@ def main(args):
 def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
     subcommand_name = "list_all"
 
-    subcommand_help = "すべての検査コメントの一覧を出力します。"
-    description = "すべての検査コメントの一覧を出力します。\n出力される検査コメントは、コマンドを実行した日の02:00(JST)頃の状態です。"
+    subcommand_help = "[DEPRECATED] すべての検査コメントの一覧を出力します。"
+    description = (
+        "[DEPRECATED] すべての検査コメントの一覧を出力します。\n出力される検査コメントは、コマンドを実行した日の02:00(JST)頃の状態です。"
+        "2022/12/01以降に廃止する予定です。替わりに ``annofabcli comment list_all`` コマンドを利用してください。"
+    )
 
     parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description)
     parse_args(parser)
diff --git a/annofabcli/inspection_comment/list_inspections.py b/annofabcli/inspection_comment/list_inspections.py
index 5ec774a3..759652d5 100644
--- a/annofabcli/inspection_comment/list_inspections.py
+++ b/annofabcli/inspection_comment/list_inspections.py
@@ -212,10 +212,10 @@ def main(args):
 def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
     subcommand_name = "list"
 
-    subcommand_help = "検査コメント一覧を出力します。"
+    subcommand_help = "[DEPRECATED] 検査コメント一覧を出力します。"
 
-    description = "検査コメント一覧を出力します。"
+    description = "[DEPRECATED] 検査コメント一覧を出力します。\n2022/12/01以降に廃止する予定です。替わりに ``annofabcli comment list`` コマンドを利用してください。"
 
-    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description)
+    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description=description)
     parse_args(parser)
     return parser
diff --git a/annofabcli/inspection_comment/put_inspection_comments.py b/annofabcli/inspection_comment/put_inspection_comments.py
index bf6878a2..ebb794ef 100644
--- a/annofabcli/inspection_comment/put_inspection_comments.py
+++ b/annofabcli/inspection_comment/put_inspection_comments.py
@@ -1,338 +1,40 @@
 import argparse
 import logging
-import multiprocessing
-import sys
-import uuid
-from dataclasses import dataclass
-from typing import Any, Dict, List, Optional, Tuple
-
-import annofabapi
-import annofabapi.utils
-import requests
-from annofabapi.models import ProjectMemberRole, TaskPhase, TaskStatus
-from dataclasses_json import DataClassJsonMixin
+from typing import Optional
 
 import annofabcli
 import annofabcli.common.cli
-from annofabcli.common.cli import (
-    COMMAND_LINE_ERROR_STATUS_CODE,
-    AbstractCommandLineInterface,
-    AbstractCommandLineWithConfirmInterface,
-    ArgumentParser,
-    build_annofabapi_resource_and_login,
-)
+from annofabcli.comment.put_inspection_comment import PutInspectionComment
+from annofabcli.comment.put_inspection_comment import parse_args as parse_args_in_put_inspection_comment
+from annofabcli.common.cli import build_annofabapi_resource_and_login
 from annofabcli.common.facade import AnnofabApiFacade
 
 logger = logging.getLogger(__name__)
 
 
-@dataclass
-class AddedComment(DataClassJsonMixin):
-    """
-    追加対象の検査コメント
-    """
-
-    comment: str
-    """検査コメントの中身"""
-
-    data: Dict[str, Any]
-    """検査コメントを付与する位置や区間"""
-
-    annotation_id: Optional[str] = None
-    """検査コメントに紐付ける"""
-
-    phrases: Optional[List[str]] = None
-    """参照している定型指摘ID"""
-
-
-AddedCommentsForTask = Dict[str, List[AddedComment]]
-"""
-タスク配下の追加対象の検査コメント
-keyはinput_data_id
-"""
-
-AddedComments = Dict[str, AddedCommentsForTask]
-"""
-追加対象の検査コメント
-keyはtask_id
-"""
-
-
-class AddInspectionCommentsMain(AbstractCommandLineWithConfirmInterface):
-    def __init__(self, service: annofabapi.Resource, project_id: str, all_yes: bool = False):
-        self.service = service
-        self.facade = AnnofabApiFacade(service)
-        self.project_id = project_id
-
-        AbstractCommandLineWithConfirmInterface.__init__(self, all_yes)
-
-    def _create_request_body(
-        self, task: Dict[str, Any], input_data_id: str, comments: List[AddedComment]
-    ) -> List[Dict[str, Any]]:
-        """batch_update_comments に渡すリクエストボディを作成する。"""
-
-        def _create_dict_annotation_id() -> Dict[str, str]:
-            content, _ = self.service.api.get_editor_annotation(self.project_id, task["task_id"], input_data_id)
-            details = content["details"]
-            return {e["annotation_id"]: e["label_id"] for e in details}
-
-        dict_annotation_id_label_id = _create_dict_annotation_id()
-
-        def _convert(comment: AddedComment) -> Dict[str, Any]:
-            return {
-                "comment_id": str(uuid.uuid4()),
-                "phase": task["phase"],
-                "phase_stage": task["phase_stage"],
-                "account_id": self.service.api.account_id,
-                "comment_type": "inspection",
-                "comment": comment.comment,
-                "comment_node": {
-                    "data": comment.data,
-                    "annotation_id": comment.annotation_id,
-                    "label_id": dict_annotation_id_label_id.get(comment.annotation_id)
-                    if comment.annotation_id is not None
-                    else None,
-                    "status": "open",
-                    "_type": "Root",
-                },
-                "phrases": comment.phrases,
-                "_type": "Put",
-            }
-
-        return [_convert(e) for e in comments]
-
-    def change_to_working_status(self, project_id: str, task: Dict[str, Any]) -> Dict[str, Any]:
-        """
-        作業中状態に遷移する。必要ならば担当者を自分自身に変更する。
-
-        Args:
-            project_id:
-            task:
-
-        Returns:
-            作業中状態遷移後のタスク
-        """
-
-        task_id = task["task_id"]
-        try:
-            if task["account_id"] != self.service.api.account_id:
-                self.service.wrapper.change_task_operator(project_id, task_id, self.service.api.account_id)
-                logger.debug(f"{task_id}: 担当者を自分自身に変更しました。")
-
-            changed_task = self.service.wrapper.change_task_status_to_working(project_id, task_id)
-            return changed_task
-
-        except requests.HTTPError:
-            logger.warning(f"{task_id}: 担当者の変更、または作業中状態への変更に失敗しました。", exc_info=True)
-            raise
-
-    @staticmethod
-    def _can_add_comment(
-        task: Dict[str, Any],
-    ) -> bool:
-        task_id = task["task_id"]
-        if task["phase"] == TaskPhase.ANNOTATION.value:
-            logger.warning(f"task_id='{task_id}': 教師付フェーズなので、検査コメントを付与できません。")
-            return False
-
-        if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
-            logger.warning(
-                f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、検査コメントを付与できません。（task_status='{task['status']}'）"
-            )
-            return False
-        return True
-
-    def add_comments_for_task(
-        self,
-        task_id: str,
-        comments_for_task: AddedCommentsForTask,
-        task_index: Optional[int] = None,
-    ) -> int:
-        """
-        タスクに検査コメントを付与します。
-
-        Args:
-            task_id: タスクID
-            comments_for_task: 1つのタスクに付与する検査コメントの集合
-            task_index: タスクの連番
-
-        Returns:
-            付与した検査コメントの数
-        """
-        logging_prefix = f"{task_index+1} 件目" if task_index is not None else ""
-
-        task = self.service.wrapper.get_task_or_none(self.project_id, task_id)
-        if task is None:
-            logger.warning(f"{logging_prefix} : task_id='{task_id}' のタスクは存在しないので、スキップします。")
-            return 0
-
-        logger.debug(
-            f"{logging_prefix} : task_id = {task['task_id']}, "
-            f"status = {task['status']}, "
-            f"phase = {task['phase']}, "
-        )
-
-        if not self._can_add_comment(
-            task=task,
-        ):
-            return 0
-
-        if not self.confirm_processing(f"task_id='{task_id}' のタスクに検査コメントを付与しますか？"):
-            return 0
-
-        # 検査コメントを付与するには作業中状態にする必要がある
-        changed_task = self.change_to_working_status(self.project_id, task)
-        added_comments_count = 0
-        for input_data_id, comments in comments_for_task.items():
-            if input_data_id not in task["input_data_id_list"]:
-                logger.warning(
-                    f"{logging_prefix} : task_id='{task_id}'のタスクに input_data_id='{input_data_id}'の入力データは存在しません。"
-                )
-                continue
-            try:
-                # 検査コメントを付与する
-                request_body = self._create_request_body(
-                    task=changed_task, input_data_id=input_data_id, comments=comments
-                )
-                self.service.api.batch_update_comments(
-                    self.project_id, task_id, input_data_id, request_body=request_body
-                )
-                added_comments_count += 1
-                logger.debug(
-                    f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: "
-                    f"{len(comments)}件の検査コメントを付与しました。"
-                )
-            except Exception:  # pylint: disable=broad-except
-                logger.warning(
-                    f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: 検査コメントの付与に失敗しました。",
-                    exc_info=True,
-                )
-            finally:
-                self.service.wrapper.change_task_status_to_break(self.project_id, task_id)
-                # 担当者が変えている場合は、元に戻す
-                if task["account_id"] != changed_task["account_id"]:
-                    self.service.wrapper.change_task_operator(self.project_id, task_id, task["account_id"])
-                    logger.debug(f"{task_id}: 担当者を元のユーザ( account_id={task['account_id']}）に戻しました。")
-
-        return added_comments_count
-
-    def add_comments_for_task_wrapper(
-        self,
-        tpl: Tuple[int, Tuple[str, AddedCommentsForTask]],
-    ) -> int:
-        task_index, (task_id, comments_for_task) = tpl
-        return self.add_comments_for_task(task_id=task_id, comments_for_task=comments_for_task, task_index=task_index)
-
-    def add_comments_for_task_list(
-        self,
-        comments_for_task_list: AddedComments,
-        parallelism: Optional[int] = None,
-    ) -> None:
-        comments_count = sum(len(e) for e in comments_for_task_list.values())
-        logger.info(f"検査コメントを付与するタスク数: {len(comments_for_task_list)}, 検査コメント付与する入力データ数: {comments_count}")
-
-        if parallelism is not None:
-            with multiprocessing.Pool(parallelism) as pool:
-                result_bool_list = pool.map(
-                    self.add_comments_for_task_wrapper, enumerate(comments_for_task_list.items())
-                )
-                added_comments_count = sum(e for e in result_bool_list)
-
-        else:
-            # 逐次処理
-            added_comments_count = 0
-            for task_index, (task_id, comments_for_task) in enumerate(comments_for_task_list.items()):
-                try:
-                    result = self.add_comments_for_task(
-                        task_id=task_id,
-                        comments_for_task=comments_for_task,
-                        task_index=task_index,
-                    )
-                    added_comments_count += result
-                except Exception as e:  # pylint: disable=broad-except
-                    logger.warning(f"task_id={task_id}: 検査コメントの付与に失敗しました。", e)
-                    continue
-
-        logger.info(f"{added_comments_count} / {comments_count} 件の入力データに検査コメントを付与しました。")
-
-
-class PutInspectionComments(AbstractCommandLineInterface):
-    @staticmethod
-    def validate(args: argparse.Namespace) -> bool:
-        COMMON_MESSAGE = "annofabcli inspection_comment put: error:"
-
-        if args.parallelism is not None and not args.yes:
-            print(
-                f"{COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず ``--yes`` を指定してください。",
-                file=sys.stderr,
-            )
-            return False
-
-        return True
-
-    @staticmethod
-    def _convert_comments(dict_comments: Dict[str, Any]) -> AddedComments:
-        def _covert_comments_for_task(comments_for_task):
-            return {
-                input_data_id: AddedComment.schema().load(comments, many=True)
-                for input_data_id, comments in comments_for_task.items()
-            }
-
-        return {
-            task_id: _covert_comments_for_task(comments_for_task)
-            for task_id, comments_for_task in dict_comments.items()
-        }
-
-    def main(self):
-        args = self.args
-        if not self.validate(args):
-            sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
-
-        super().validate_project(args.project_id, [ProjectMemberRole.ACCEPTER, ProjectMemberRole.OWNER])
-
-        dict_comments = annofabcli.common.cli.get_json_from_args(args.json)
-        comments_for_task_list = self._convert_comments(dict_comments)
-        main_obj = AddInspectionCommentsMain(self.service, project_id=args.project_id, all_yes=self.all_yes)
-        main_obj.add_comments_for_task_list(
-            comments_for_task_list=comments_for_task_list,
-            parallelism=args.parallelism,
-        )
-
-
 def main(args: argparse.Namespace):
     service = build_annofabapi_resource_and_login(args)
     facade = AnnofabApiFacade(service)
-    PutInspectionComments(service, facade, args).main()
+    obj = PutInspectionComment(service, facade, args)
+    obj.COMMON_MESSAGE = "annofabcli inspection_comment put: error:"
+    obj.main()
 
 
 def parse_args(parser: argparse.ArgumentParser):
-    argument_parser = ArgumentParser(parser)
-
-    argument_parser.add_project_id()
-
-    parser.add_argument(
-        "--json",
-        type=str,
-        help=(
-            "付与する検査コメントをJSON形式で指定してください。"
-            "JSONのスキーマは https://annofab-cli.readthedocs.io/ja/latest/command_reference/inspection_comment/put.html "
-            "に記載されています。"
-        ),
-    )
-
-    parser.add_argument(
-        "--parallelism", type=int, help="使用するプロセス数（並列度）を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。"
-    )
-
-    parser.set_defaults(subcommand_func=main)
+    parse_args_in_put_inspection_comment(parser)
 
 
 def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
     subcommand_name = "put"
-    subcommand_help = "検査コメントを付与します。"
-    description = "検査コメントを付与します。"
+    subcommand_help = "[DEPRECATED] 検査コメントを付与します。"
+    description = (
+        "[DEPRECATED] 検査コメントを付与します。\n2022/12/01以降に廃止する予定です。替わりに ``annofabcli comment put_inspection`` コマンドを利用してください。"
+    )
+
     epilog = "チェッカーロールまたはオーナロールを持つユーザで実行してください。"
 
-    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, description, epilog=epilog)
+    parser = annofabcli.common.cli.add_parser(
+        subparsers, subcommand_name, subcommand_help, description=description, epilog=epilog
+    )
     parse_args(parser)
     return parser
diff --git a/annofabcli/inspection_comment/put_inspection_comments_simply.py b/annofabcli/inspection_comment/put_inspection_comments_simply.py
index dfccfb1c..9128ca57 100644
--- a/annofabcli/inspection_comment/put_inspection_comments_simply.py
+++ b/annofabcli/inspection_comment/put_inspection_comments_simply.py
@@ -1,314 +1,41 @@
 import argparse
 import logging
-import multiprocessing
-import sys
-import uuid
-from dataclasses import dataclass
-from functools import partial
-from typing import Any, Collection, Dict, List, Optional, Tuple
-
-import annofabapi
-import annofabapi.utils
-from annofabapi.models import InputDataType, ProjectMemberRole, TaskPhase, TaskStatus
-from dataclasses_json import DataClassJsonMixin
+from typing import Optional
 
 import annofabcli
 import annofabcli.common.cli
-from annofabcli.common.cli import (
-    COMMAND_LINE_ERROR_STATUS_CODE,
-    AbstractCommandLineInterface,
-    AbstractCommandLineWithConfirmInterface,
-    ArgumentParser,
-    build_annofabapi_resource_and_login,
-    get_list_from_args,
-)
+from annofabcli.comment.put_inspection_comment_simply import PutInspectionCommentSimply
+from annofabcli.comment.put_inspection_comment_simply import parse_args as parse_args_with_put_inspection_comment_simply
+from annofabcli.common.cli import build_annofabapi_resource_and_login
 from annofabcli.common.facade import AnnofabApiFacade
 
 logger = logging.getLogger(__name__)
 
 
-@dataclass
-class AddedSimpleComment(DataClassJsonMixin):
-    """
-    付与するシンプルな検査コメント
-    """
-
-    comment: str
-    """検査コメントの中身"""
-
-    data: Dict[str, Any]
-    """検査コメントを付与する位置や区間"""
-
-    phrases: Optional[List[str]] = None
-    """参照している定型指摘ID"""
-
-
-class PutInspectionCommentsSimplyMain(AbstractCommandLineWithConfirmInterface):
-    def __init__(self, service: annofabapi.Resource, project_id: str, all_yes: bool = False):
-        self.service = service
-        self.facade = AnnofabApiFacade(service)
-        self.project_id = project_id
-
-        AbstractCommandLineWithConfirmInterface.__init__(self, all_yes)
-
-    def _create_request_body(self, task: Dict[str, Any], comment_info: AddedSimpleComment) -> List[Dict[str, Any]]:
-        """batch_update_comments に渡すリクエストボディを作成する。"""
-
-        def _convert(comment: AddedSimpleComment) -> Dict[str, Any]:
-            return {
-                "comment": comment.comment,
-                "comment_id": str(uuid.uuid4()),
-                "phase": task["phase"],
-                "phase_stage": task["phase_stage"],
-                "comment_type": "inspection",
-                "account_id": self.service.api.account_id,
-                "comment_node": {"data": comment.data, "status": "open", "_type": "Root"},
-                "phrases": comment.phrases,
-                "_type": "Put",
-            }
-
-        return [_convert(comment_info)]
-
-    def change_to_working_status(self, project_id: str, task: Dict[str, Any]) -> Dict[str, Any]:
-        """
-        作業中状態に遷移する。必要ならば担当者を自分自身に変更する。
-
-        Args:
-            project_id:
-            task:
-
-        Returns:
-            作業中状態遷移後のタスク
-        """
-
-        task_id = task["task_id"]
-
-        if task["account_id"] != self.service.api.account_id:
-            self.service.wrapper.change_task_operator(project_id, task_id, self.service.api.account_id)
-            logger.debug(f"{task_id}: 担当者を自分自身に変更しました。")
-
-        changed_task = self.service.wrapper.change_task_status_to_working(project_id, task_id)
-        return changed_task
-
-    @staticmethod
-    def _can_add_comment(
-        task: Dict[str, Any],
-    ) -> bool:
-        task_id = task["task_id"]
-        if task["phase"] == TaskPhase.ANNOTATION.value:
-            logger.warning(f"task_id='{task_id}': 教師付フェーズなので、検査コメントを付与できません。")
-            return False
-
-        if task["status"] not in [TaskStatus.NOT_STARTED.value, TaskStatus.WORKING.value, TaskStatus.BREAK.value]:
-            logger.warning(
-                f"task_id='{task_id}' : タスクの状態が未着手,作業中,休憩中 以外の状態なので、検査コメントを付与できません。（task_status='{task['status']}'）"
-            )
-            return False
-        return True
-
-    def put_inspection_comment_for_task(
-        self,
-        task_id: str,
-        comment_info: AddedSimpleComment,
-        task_index: Optional[int] = None,
-    ) -> bool:
-        """
-        タスクに検査コメントを付与します。
-
-        Args:
-            task_id: タスクID
-            comment_info: コメント情報
-            task_index: タスクの連番
-
-        Returns:
-            付与した検査コメントの数
-        """
-        logging_prefix = f"{task_index+1} 件目" if task_index is not None else ""
-
-        task = self.service.wrapper.get_task_or_none(self.project_id, task_id)
-        if task is None:
-            logger.warning(f"{logging_prefix} : task_id='{task_id}' のタスクは存在しないので、スキップします。")
-            return False
-
-        logger.debug(
-            f"{logging_prefix} : task_id = {task['task_id']}, "
-            f"status = {task['status']}, "
-            f"phase = {task['phase']}, "
-        )
-
-        if not self._can_add_comment(
-            task=task,
-        ):
-            return False
-
-        if not self.confirm_processing(f"task_id='{task_id}' のタスクに検査コメントを付与しますか？"):
-            return False
-
-        # 検査コメントを付与するには作業中状態にする必要があるので、タスクの状態を作業中にする
-        changed_task = self.change_to_working_status(self.project_id, task)
-
-        input_data_id = task["input_data_id_list"][0]
-
-        try:
-            # 検査コメントを付与する
-            request_body = self._create_request_body(task=changed_task, comment_info=comment_info)
-            self.service.api.batch_update_comments(self.project_id, task_id, input_data_id, request_body=request_body)
-            logger.debug(f"{logging_prefix} : task_id={task_id} のタスクに検査コメントを付与しました。")
-            return True
-        except Exception:  # pylint: disable=broad-except
-            logger.warning(
-                f"{logging_prefix} : task_id={task_id}, input_data_id={input_data_id}: 検査コメントの付与に失敗しました。", exc_info=True
-            )
-            return False
-        finally:
-            self.service.wrapper.change_task_status_to_break(self.project_id, task_id)
-            # 担当者が変えている場合は、元に戻す
-            if task["account_id"] != changed_task["account_id"]:
-                self.service.wrapper.change_task_operator(self.project_id, task_id, task["account_id"])
-                logger.debug(f"{task_id}: 担当者を元のユーザ( account_id={task['account_id']}）に戻しました。")
-
-    def add_comments_for_task_wrapper(self, tpl: Tuple[int, str], comment_info: AddedSimpleComment) -> bool:
-        task_index, task_id = tpl
-        try:
-            return self.put_inspection_comment_for_task(
-                task_id=task_id, comment_info=comment_info, task_index=task_index
-            )
-        except Exception:  # pylint: disable=broad-except
-            logger.warning(f"task_id={task_id}: 検査コメントの付与に失敗しました。", exc_info=True)
-            return False
-
-    def put_inspection_comment_for_task_list(
-        self,
-        task_ids: Collection[str],
-        comment_info: AddedSimpleComment,
-        parallelism: Optional[int] = None,
-    ) -> None:
-
-        logger.info(f"{len(task_ids)} 件のタスクに検査コメントを付与します。")
-
-        if parallelism is not None:
-            func = partial(self.add_comments_for_task_wrapper, comment_info=comment_info)
-            with multiprocessing.Pool(parallelism) as pool:
-                result_bool_list = pool.map(func, enumerate(task_ids))
-                success_count = len([e for e in result_bool_list if e])
-
-        else:
-            # 逐次処理
-            success_count = 0
-            for task_index, task_id in enumerate(task_ids):
-                try:
-                    result = self.put_inspection_comment_for_task(
-                        task_id=task_id,
-                        comment_info=comment_info,
-                        task_index=task_index,
-                    )
-                    if result:
-                        success_count += 1
-                except Exception:  # pylint: disable=broad-except
-                    logger.warning(f"task_id={task_id}: 検査コメントの付与に失敗しました。", exc_info=True)
-                    continue
-
-        logger.info(f"{success_count} / {len(task_ids)} 件のタスクに検査コメントを付与しました。")
-
-
-class PutInspectionCommentsSimply(AbstractCommandLineInterface):
-
-    COMMON_MESSAGE = "annofabcli inspection_comment put_simply: error:"
-
-    def validate(self, args: argparse.Namespace) -> bool:
-        if args.parallelism is not None and not args.yes:
-            print(
-                f"{self.COMMON_MESSAGE} argument --parallelism: '--parallelism'を指定するときは、必ず ``--yes`` を指定してください。",
-                file=sys.stderr,
-            )
-            return False
-
-        return True
-
-    def main(self):
-        args = self.args
-        if not self.validate(args):
-            sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
-
-        super().validate_project(args.project_id, [ProjectMemberRole.ACCEPTER, ProjectMemberRole.OWNER])
-
-        comment_data = annofabcli.common.cli.get_json_from_args(args.comment_data)
-        project, _ = self.service.api.get_project(args.project_id)
-        if comment_data is None:
-            if project["input_data_type"] == InputDataType.IMAGE.value:
-                comment_data = {"x": 0, "y": 0, "_type": "Point"}
-            elif project["input_data_type"] == InputDataType.MOVIE.value:
-                # 注意：少なくとも0.1秒以上の区間にしないと、Annofab上で検査コメントを確認できない
-                comment_data = {"start": 0, "end": 100, "_type": "Time"}
-            elif project["input_data_type"] == InputDataType.CUSTOM.value:
-                # customプロジェクト
-                print(
-                    f"{self.COMMON_MESSAGE} argument --comment_data: カスタムプロジェクトに検査コメントを付与する場合は必須です。",
-                    file=sys.stderr,
-                )
-                sys.exit(COMMAND_LINE_ERROR_STATUS_CODE)
-
-        task_id_list = get_list_from_args(args.task_id)
-        phrase_id_list = get_list_from_args(args.phrase_id)
-        main_obj = PutInspectionCommentsSimplyMain(self.service, project_id=args.project_id, all_yes=self.all_yes)
-        main_obj.put_inspection_comment_for_task_list(
-            task_ids=task_id_list,
-            comment_info=AddedSimpleComment(comment=args.comment, data=comment_data, phrases=phrase_id_list),
-            parallelism=args.parallelism,
-        )
-
-
 def main(args: argparse.Namespace):
     service = build_annofabapi_resource_and_login(args)
     facade = AnnofabApiFacade(service)
-    PutInspectionCommentsSimply(service, facade, args).main()
+    obj = PutInspectionCommentSimply(service, facade, args)
+    obj.COMMON_MESSAGE = "annofabcli inspection_comment put_simply: error:"
+    obj.main()
 
 
 def parse_args(parser: argparse.ArgumentParser):
-    argument_parser = ArgumentParser(parser)
-
-    argument_parser.add_project_id()
-
-    parser.add_argument(
-        "-t",
-        "--task_id",
-        type=str,
-        nargs="+",
-        required=True,
-        help=("検査コメントを付与するタスクのtask_idを指定してください。\n" "``file://`` を先頭に付けると、task_idの一覧が記載されたファイルを指定できます。"),
-    )
-
-    parser.add_argument(
-        "--comment",
-        type=str,
-        required=True,
-        help="付与する検査コメントのメッセージを指定します。",
-    )
-
-    parser.add_argument("--phrase_id", type=str, nargs="+", help="定型指摘コメントのIDを指定してください。")
-
-    parser.add_argument(
-        "--comment_data",
-        type=str,
-        help="検査コメントを付与する位置や区間をJSON形式で指定します。\n"
-        "指定方法は https://annofab-cli.readthedocs.io/ja/latest/command_reference/inspection_comment/put_simply.html を参照してください。\n"  # noqa: E501
-        " ``file://`` を先頭に付けると、JSON形式のファイルを指定できます。\n"
-        "デフォルトでは画像プロジェクトならば画像の左上(x=0,y=0)、動画プロジェクトなら動画の先頭（start=0, end=100)に付与します。"
-        "カスタムプロジェクトに検査コメントを付与する場合は必須です。",
-    )
-
-    parser.add_argument(
-        "--parallelism", type=int, help="使用するプロセス数（並列度）を指定してください。指定する場合は必ず ``--yes`` を指定してください。指定しない場合は、逐次的に処理します。"
-    )
-
-    parser.set_defaults(subcommand_func=main)
+    parse_args_with_put_inspection_comment_simply(parser)
 
 
 def add_parser(subparsers: Optional[argparse._SubParsersAction] = None):
     subcommand_name = "put_simply"
-    subcommand_help = "``inspection_comment put`` コマンドよりも、簡単に検査コメントを付与します。"
+    subcommand_help = "[DEPRECATED] ``inspection_comment put`` コマンドよりも、簡単に検査コメントを付与します。"
+    description = (
+        "[DEPRECATED] ``inspection_comment put`` コマンドよりも、簡単に検査コメントを付与します。\n"
+        "2022/12/01以降に廃止する予定です。替わりに ``annofabcli comment put_inspection_simply`` コマンドを利用してください。"
+    )
+
     epilog = "チェッカーロールまたはオーナロールを持つユーザで実行してください。"
 
-    parser = annofabcli.common.cli.add_parser(subparsers, subcommand_name, subcommand_help, epilog=epilog)
+    parser = annofabcli.common.cli.add_parser(
+        subparsers, subcommand_name, subcommand_help, description=description, epilog=epilog
+    )
     parse_args(parser)
     return parser
diff --git a/annofabcli/task/reject_tasks.py b/annofabcli/task/reject_tasks.py
index 5a588a13..ea44945b 100644
--- a/annofabcli/task/reject_tasks.py
+++ b/annofabcli/task/reject_tasks.py
@@ -5,7 +5,6 @@
 import multiprocessing
 import sys
 import uuid
-from enum import Enum
 from functools import partial
 from typing import Any, Optional
 
@@ -24,19 +23,13 @@
     ArgumentParser,
     build_annofabapi_resource_and_login,
 )
+from annofabcli.common.enums import CustomProjectType
 from annofabcli.common.facade import AnnofabApiFacade, TaskQuery, match_task_with_query
 from annofabcli.common.utils import add_dryrun_prefix
 
 logger = logging.getLogger(__name__)
 
 
-class CustomProjectType(Enum):
-    """カスタムプロジェクトの場合、検査コメントのフォーマットが分からないため、カスタムプロジェクトの種類をannofabcliで定義する。"""
-
-    THREE_DIMENSION_POINT_CLOUD = "3dpc"
-    """3DPCプロジェクト"""
-
-
 class RejectTasksMain(AbstractCommandLineWithConfirmInterface):
     def __init__(self, service: annofabapi.Resource, *, comment_data: Optional[dict[str, Any]], all_yes: bool = False):
         self.service = service
@@ -475,8 +468,8 @@ def parse_args(parser: argparse.ArgumentParser):
         "--custom_project_type",
         type=str,
         choices=[e.value for e in CustomProjectType],
-        help="[BETA] カスタムプロジェクトの種類を指定します。カスタムプロジェクトに対して、適当な箇所に検査コメントを付与する場合は必須です。\n"
-        "※ Annofabの情報だけでは'3dpc'プロジェクトかどうかが分からないため、ユーザーに指定させています。",
+        help="[BETA] カスタムプロジェクトの種類を指定します。カスタムプロジェクトに対して、検査コメントの位置を指定しない場合は必須です。\n"
+        "※ Annofabの情報だけでは'3dpc'プロジェクトかどうかが分からないため、指定する必要があります。",
     )
 
     # 差し戻したタスクの担当者の割当に関して
diff --git a/docs/command_reference/comment/delete.rst b/docs/command_reference/comment/delete.rst
new file mode 100644
index 00000000..1c8ff40e
--- /dev/null
+++ b/docs/command_reference/comment/delete.rst
@@ -0,0 +1,66 @@
+==========================================
+comment delete
+==========================================
+
+Description
+=================================
+コメントを削除します。
+
+.. code-block::
+    
+    他人の付けたコメントや他のフェーズで付与されたコメントも削除できてしまいます。
+
+
+Examples
+=================================
+
+基本的な使い方
+--------------------------
+
+``--json`` に削除するコメントのcomment_idをJSON形式で指定してください。
+
+.. code-block::
+    :caption: inspection_id.json
+
+    {
+        "task1":{
+            "input_data1": ["comment_id1","comment_id2"],
+            "input_data2":[...]
+        },
+        "task2": {
+            "input_data3":[...]
+        }
+    }
+
+
+
+.. code-block::
+
+    $ annofabcli comment delete --project_id prj1 --json file://inspection_id.json
+
+
+
+.. note::
+    
+    過去のフェーズ・ステージで付与されたコメントは削除できません。
+
+
+並列処理
+----------------------------------------------
+
+以下のコマンドは、並列数4で実行します。
+
+.. code-block::
+
+    $  annofabcli comment delete --project_id prj1 --json file://inspection_id.json \
+    --parallelism 4 --yes
+
+Usage Details
+=================================
+
+.. argparse::
+   :ref: annofabcli.comment.delete_comment.add_parser
+   :prog: annofabcli comment delete
+   :nosubcommands:
+   :nodefaultconst:
+
diff --git a/docs/command_reference/comment/index.rst b/docs/command_reference/comment/index.rst
index d7443479..7111e504 100644
--- a/docs/command_reference/comment/index.rst
+++ b/docs/command_reference/comment/index.rst
@@ -15,9 +15,14 @@ Available Commands
    :maxdepth: 1
    :titlesonly:
 
+   delete
    download
    list
    list_all
+   put_inspection
+   put_inspection_simply
+   put_onhold
+   put_onhold_simply
 
 Usage Details
 =================================
diff --git a/docs/command_reference/comment/list.rst b/docs/command_reference/comment/list.rst
index 0d972b0a..60663194 100644
--- a/docs/command_reference/comment/list.rst
+++ b/docs/command_reference/comment/list.rst
@@ -22,6 +22,28 @@ Examples
     $ annofabcli comment list --project_id prj1 --task_id task1 task2
 
 
+``--comment_type`` を指定すると、コメントの種類で絞り込めます。
+
+.. code-block::
+
+    # 検査コメントを出力
+    $ annofabcli comment list --project_id prj1 --task_id task1 task2 \
+     --comment_type inspection
+
+    # 保留コメントを出力
+    $ annofabcli comment list --project_id prj1 --task_id task1 task2 \
+     --comment_type onhold
+
+
+デフォルトでは返信コメントも出力されます。返信コメントを除去する場合は、 ``--exclude_reply`` を指定してください。
+
+.. code-block::
+
+    $ annofabcli comment list --project_id prj1 --task_id task1 task2 \
+     --exclude_reply
+
+
+
 
 出力結果
 =================================
@@ -109,4 +131,3 @@ Usage Details
     :nosubcommands:
     :nodefaultconst:
 
-
diff --git a/docs/command_reference/comment/list_all.rst b/docs/command_reference/comment/list_all.rst
index e5e596e1..d5553328 100644
--- a/docs/command_reference/comment/list_all.rst
+++ b/docs/command_reference/comment/list_all.rst
@@ -56,3 +56,4 @@ Usage Details
 
 
 
+
diff --git a/docs/command_reference/comment/put_inspection.rst b/docs/command_reference/comment/put_inspection.rst
new file mode 100644
index 00000000..0cd07a80
--- /dev/null
+++ b/docs/command_reference/comment/put_inspection.rst
@@ -0,0 +1,128 @@
+==========================================
+comment put_inspection
+==========================================
+
+Description
+=================================
+検査コメントを付与します。
+
+
+Examples
+=================================
+
+基本的な使い方
+--------------------------
+
+``--json`` に検査コメントの内容をJSON形式で指定すると、検査コメントを付与できます。
+
+.. code-block:: json
+    :caption: comment.json
+
+    {
+        "task1":{
+            "input_data1": [
+                {
+                    "comment": "type属性が間違っています。",
+                    "data": {
+                        "x":10,
+                        "y":20,
+                        "_type": "Point"
+                    }
+                },
+                {
+                    "comment": "枠がズレています。 #A1",
+                    "data": {
+                        "coordinates":[
+                            {"x":20, "y":20}, {"x":30, "y":30}
+                        ],
+                        "_type": "Polyline"
+                    },
+                    "annotation_id": "foo",
+                    "phrases": ["A1"]
+                }
+            ],
+            "input_data2":[]
+        },
+        "task2": {
+            "input_data3":[]
+        }
+    }
+
+
+* 1階層目のキーはtask_id, 2階層目のキーはinput_data_idです。
+* 検査コメントのプロパティとして指定できるキーは以下の通りです。
+
+  * ``comment``：検査コメントの内容。必須。
+  * ``data``：検査コメントの位置や区間。必須。詳細は後述を参照してください。
+  * ``annotation_id``：検査コメントに紐づくアノテーションのannotation_id
+  * ``phrases``：参照する定型指摘のIDの配列
+
+
+.. code-block::
+
+    $ annofabcli comment put_inspection --project_id prj1 --json file://comment.json
+
+
+``data`` プロパティに渡す値の例
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+.. code-block:: json
+    :caption: 画像プロジェクト：(x=0,y=0)の位置に点
+
+    {
+        "x":0,
+        "y":0,
+        "_type": "Point"
+    }
+
+
+.. code-block:: json
+    :caption: 画像プロジェクト：(x=0,y=0),(x=100,y=100)のポリライン
+
+    {
+        "coordinates": [{"x":0, "y":0}, {"x":100, "y":100}],
+        "_type": "Polyline"
+    }
+
+
+.. code-block:: json
+    :caption: 動画プロジェクト：0〜100ミリ秒の区間
+
+    {
+        "start":0,
+        "end":100,
+        "_type": "Time"
+    }
+
+
+.. code-block:: json
+    :caption: カスタムプロジェクト（3dpc editor）：原点付近に辺が1の立方体
+
+    {
+        "data": "{\"kind\": \"CUBOID\", \"shape\": {\"dimensions\": {\"width\": 1.0, \"height\": 1.0, \"depth\": 1.0}, \"location\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0}, \"rotation\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0}, \"direction\": {\"front\": {\"x\": 1.0, \"y\": 0.0, \"z\": 0.0}, \"up\": {\"x\": 0.0, \"y\": 0.0, \"z\": 1.0}}}, \"version\": \"2\"}",
+        "_type": "Custom"    
+    }
+
+
+
+
+
+並列処理
+----------------------------------------------
+
+以下のコマンドは、並列数4で実行します。
+
+.. code-block::
+
+    $  annofabcli comment put_inspection --project_id prj1 --json file://comment.json \
+    --parallelism 4 --yes
+
+Usage Details
+=================================
+
+.. argparse::
+   :ref: annofabcli.comment.put_inspection_comment.add_parser
+   :prog: annofabcli comment put_inspection
+   :nosubcommands:
+   :nodefaultconst:
diff --git a/docs/command_reference/comment/put_inspection_simply.rst b/docs/command_reference/comment/put_inspection_simply.rst
new file mode 100644
index 00000000..fb5896f8
--- /dev/null
+++ b/docs/command_reference/comment/put_inspection_simply.rst
@@ -0,0 +1,119 @@
+==========================================
+comment put_inspection_simply
+==========================================
+
+Description
+=================================
+``comment put_inspection`` コマンドよりも、簡単に検査コメントを付与できます。
+
+
+Examples
+=================================
+
+基本的な使い方
+--------------------------
+
+指定したタスクに、検査コメントを付与できます。
+``--comment`` に検査コメントの内容を指定してください。 
+タスク内の先頭の入力データに、検査コメントが付与されます。
+
+.. code-block::
+
+    $ annofabcli comment put_inspection_simply --project_id prj1 --task_id task1 task2 \
+     --comment "枠がズレています。"
+
+
+``--phrase_id`` に定型指摘IDも指定できます。
+
+
+.. code-block::
+
+    $ annofabcli comment put_inspection_simply --project_id prj1 --task_id task1 task2 \
+     --comment "枠がズレています。 #ID1" --phrase_id ID1
+
+
+
+検査コメントの位置や区間を指定する
+--------------------------------------
+``--comment_data`` に、検査コメントの位置や区間をJSON形式で指定することができます。
+
+
+以下は、 ``--comment_data`` に渡すJSON文字列のサンプルです。
+
+.. code-block:: json
+    :caption: 画像プロジェクト：(x=0,y=0)の位置に点
+
+    {
+        "x":0,
+        "y":0,
+        "_type": "Point"
+    }
+
+
+.. code-block:: json
+    :caption: 画像プロジェクト：(x=0,y=0),(x=100,y=100)のポリライン
+
+    {
+        "coordinates": [{"x":0, "y":0}, {"x":100, "y":100}],
+        "_type": "Polyline"
+    }
+
+
+
+.. code-block:: json
+    :caption: 動画プロジェクト：0〜100ミリ秒の区間
+
+    {
+        "start":0,
+        "end":100,
+        "_type": "Time"
+    }
+
+
+.. code-block:: json
+    :caption: カスタムプロジェクト（3dpc editor）：原点付近に辺が1の立方体
+
+    {
+        "data": "{\"kind\": \"CUBOID\", \"shape\": {\"dimensions\": {\"width\": 1.0, \"height\": 1.0, \"depth\": 1.0}, \"location\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0}, \"rotation\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0}, \"direction\": {\"front\": {\"x\": 1.0, \"y\": 0.0, \"z\": 0.0}, \"up\": {\"x\": 0.0, \"y\": 0.0, \"z\": 1.0}}}, \"version\": \"2\"}",
+        "_type": "Custom"    
+    }
+
+
+
+``--comment_data`` を指定しない場合は、以下の検査コメントが付与されます。
+
+* 画像プロジェクト： 点。先頭画像の左上に位置する。
+* 動画プロジェクト： 区間。動画の先頭に位置する。
+* カスタムプロジェクト（3dpc）： 辺が1の立方体。先頭フレームの原点に位置する。
+
+ただし、カスタムプロジェクトの場合は ``--csutom_project_type`` が必須です。
+
+
+.. code-block::
+
+    $ annofabcli comment put_inspection_simply --project_id prj1 --task_id task1 \
+    --comment "weather属性を見直してください。" \
+    --custom_project_type 3dpc
+
+
+
+
+並列処理
+----------------------------------------------
+
+以下のコマンドは、並列数4で実行します。
+
+.. code-block::
+
+    $  annofabcli comment put_inspection_simply --project_id prj1 --task_id t1 t2 t3 t4 \
+    --parallelism 4 --yes
+
+
+Usage Details
+=================================
+
+.. argparse::
+   :ref: annofabcli.comment.put_inspection_comment_simply.add_parser
+   :prog: annofabcli comment put_inspection_simply
+   :nosubcommands:
+   :nodefaultconst:
diff --git a/docs/command_reference/comment/put_onhold.rst b/docs/command_reference/comment/put_onhold.rst
new file mode 100644
index 00000000..83ca9278
--- /dev/null
+++ b/docs/command_reference/comment/put_onhold.rst
@@ -0,0 +1,72 @@
+==========================================
+comment put_onhold
+==========================================
+
+Description
+=================================
+保留コメントを付与します。
+
+
+Examples
+=================================
+
+基本的な使い方
+--------------------------
+
+``--json`` に保留コメントの内容をJSON形式で指定すると、保留コメントを付与できます。
+
+.. code-block:: json
+    :caption: comment.json
+
+    {
+        "task1":{
+            "input_data1": [
+                {
+                    "comment": "type属性が間違っています。",
+                },
+                {
+                    "comment": "枠がズレています。",
+                    "annotation_id": "foo",
+                }
+            ],
+            "input_data2":[]
+        },
+        "task2": {
+            "input_data3":[]
+        }
+    }
+
+
+* 1階層目のキーはtask_id, 2階層目のキーはinput_data_idです。
+* 保留コメントのプロパティとして指定できるキーは以下の通りです。
+
+  * ``comment``：コメントの内容。必須。
+  * ``annotation_id``：コメントに紐づくアノテーションのannotation_id
+
+
+.. code-block::
+
+    $ annofabcli comment put_onhold --project_id prj1 --json file://comment.json
+
+
+
+
+
+並列処理
+----------------------------------------------
+
+以下のコマンドは、並列数4で実行します。
+
+.. code-block::
+
+    $  annofabcli comment put_onhold --project_id prj1 --json file://comment.json \
+    --parallelism 4 --yes
+
+Usage Details
+=================================
+
+.. argparse::
+   :ref: annofabcli.comment.put_onhold_comment.add_parser
+   :prog: annofabcli comment put_onhold
+   :nosubcommands:
+   :nodefaultconst:
diff --git a/docs/command_reference/comment/put_onhold_simply.rst b/docs/command_reference/comment/put_onhold_simply.rst
new file mode 100644
index 00000000..15d7b5c6
--- /dev/null
+++ b/docs/command_reference/comment/put_onhold_simply.rst
@@ -0,0 +1,45 @@
+==========================================
+comment put_onhold_simply
+==========================================
+
+Description
+=================================
+``comment put_onhold`` コマンドよりも、簡単に保留コメントを付与できます。
+
+
+Examples
+=================================
+
+基本的な使い方
+--------------------------
+
+指定したタスクに、保留コメントを付与できます。
+``--comment`` にコメントの内容を指定してください。 
+タスク内の先頭の入力データに、保留コメントが付与されます。
+
+.. code-block::
+
+    $ annofabcli comment put_onhold_simply --project_id prj1 --task_id task1 task2 \
+     --comment "枠がズレています。"
+
+
+
+並列処理
+----------------------------------------------
+
+以下のコマンドは、並列数4で実行します。
+
+.. code-block::
+
+    $  annofabcli comment put_onhold_simply --project_id prj1 --task_id t1 t2 t3 t4 \
+    --parallelism 4 --yes
+
+
+Usage Details
+=================================
+
+.. argparse::
+   :ref: annofabcli.comment.put_onhold_comment_simply.add_parser
+   :prog: annofabcli comment put_onhold_simply
+   :nosubcommands:
+   :nodefaultconst:
diff --git a/docs/command_reference/inspection_comment/delete.rst b/docs/command_reference/inspection_comment/delete.rst
index a9791dce..15efd638 100644
--- a/docs/command_reference/inspection_comment/delete.rst
+++ b/docs/command_reference/inspection_comment/delete.rst
@@ -7,44 +7,22 @@ Description
 検査コメントを削除します。
 
 
+.. warning::
 
-Examples
-=================================
-
-基本的な使い方
---------------------------
-
-``--json`` に削除する検査コメントのinspection_idをJSON形式で指定してください。
-
-.. code-block::
-    :caption: inspection_id.json
+    非推奨なコマンドです。2022/12/01以降に廃止する予定です。
+    替わりに `annofabcli comment delete <../comment/delete.html>`_ コマンドを使用してください。
 
-    {
-        "task1":{
-            "input_data1": ["inspection1","inspection2"],
-            "input_data2":[...]
-        },
-        "task2": {
-            "input_data3":[...]
-        }
-    }
 
 
 
-.. code-block::
-
-    $ annofabcli inspection_comment delete --project_id prj1 --json file://inspection_id.json
-
+Examples
+=================================
 
-並列処理
-----------------------------------------------
 
-以下のコマンドは、並列数4で実行します。
+`annofabcli comment delete <../comment/delete.html>`_ コマンドを参照ください。
+コマンドライン引数の使い方は同じです。
 
-.. code-block::
 
-    $  annofabcli inspection_comment delete --project_id prj1 --json file://inspection_id.json \
-    --parallelism 4 --yes
 
 Usage Details
 =================================
diff --git a/docs/command_reference/inspection_comment/download.rst b/docs/command_reference/inspection_comment/download.rst
index dd8a2d5f..e4b4e447 100644
--- a/docs/command_reference/inspection_comment/download.rst
+++ b/docs/command_reference/inspection_comment/download.rst
@@ -10,7 +10,7 @@ Description
 .. warning::
 
     非推奨なコマンドです。2022-12-01以降に廃止する予定です。
-    替わりに ``annofabcli comment download`` コマンドを利用してください。
+    替わりに `annofabcli comment download <../comment/download.html>`_ コマンドを利用してください。
 
 
 Examples
diff --git a/docs/command_reference/inspection_comment/list.rst b/docs/command_reference/inspection_comment/list.rst
index 0e8c13cc..070a6488 100644
--- a/docs/command_reference/inspection_comment/list.rst
+++ b/docs/command_reference/inspection_comment/list.rst
@@ -7,6 +7,11 @@ Description
 検査コメントの一覧を出力します。
 
 
+.. warning::
+
+    非推奨なコマンドです。2022/12/01以降に廃止する予定です。
+    替わりに `annofabcli comment list <../comment/list.html>`_ コマンドを使用してください。
+
 
 Examples
 =================================
diff --git a/docs/command_reference/inspection_comment/list_all.rst b/docs/command_reference/inspection_comment/list_all.rst
index cffd4e4c..7ec40548 100644
--- a/docs/command_reference/inspection_comment/list_all.rst
+++ b/docs/command_reference/inspection_comment/list_all.rst
@@ -70,3 +70,4 @@ Usage Details
 See also
 =================================
 * `annofabcli inspection_comment list <../inspection_comment/list.html>`_
+
diff --git a/docs/command_reference/inspection_comment/put.rst b/docs/command_reference/inspection_comment/put.rst
index 6e71f4cc..75e44a94 100644
--- a/docs/command_reference/inspection_comment/put.rst
+++ b/docs/command_reference/inspection_comment/put.rst
@@ -7,117 +7,18 @@ Description
 検査コメントを付与します。
 
 
+.. warning::
 
-Examples
-=================================
-
-基本的な使い方
---------------------------
-
-``--json`` に検査コメントの内容をJSON形式で指定してください。
-
-.. code-block:: json
-    :caption: comment.json
-
-    {
-        "task1":{
-            "input_data1": [
-                {
-                    "comment": "type属性が間違っています。",
-                    "data": {
-                        "x":10,
-                        "y":20,
-                        "_type": "Point"
-                    }
-                },
-                {
-                    "comment": "枠がズレています。 #A1",
-                    "data": {
-                        "coordinates":[
-                            {"x":20, "y":20}, {"x":30, "y":30}
-                        ],
-                        "_type": "Polyline"
-                    },
-                    "annotation_id": "foo",
-                    "phrases": ["A1"]
-                }
-            ],
-            "input_data2":[]
-        },
-        "task2": {
-            "input_data3":[]
-        }
-    }
-
-
-* 1階層目のキーはtask_id, 2階層目のキーはinput_data_idです。
-* 検査コメントのプロパティとして指定できるキーは以下の通りです。
-
-  * ``comment``：検査コメントの内容。必須。
-  * ``data``：検査コメントの位置や区間。必須。詳細は後述を参照してください。
-  * ``annotation_id``：検査コメントに紐づくアノテーションのannotation_id
-  * ``phrases``：参照する定型指摘のIDの配列
-
-
-.. code-block::
-
-    $ annofabcli inspection_comment put --project_id prj1 --json file://comment.json
-
-
-``data`` プロパティに渡す値の例
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-
-.. code-block:: json
-    :caption: 画像プロジェクト：(x=0,y=0)の位置に点
+    非推奨なコマンドです。2022/12/01以降に廃止する予定です。
+    替わりに `annofabcli comment put_inspection <../comment/put_inspection.html>`_ コマンドを使用してください。
 
-    {
-        "x":0,
-        "y":0,
-        "_type": "Point"
-    }
 
+Examples
+=================================
 
-.. code-block:: json
-    :caption: 画像プロジェクト：(x=0,y=0),(x=100,y=100)のポリライン
-
-    {
-        "coordinates": [{"x":0, "y":0}, {"x":100, "y":100}],
-        "_type": "Polyline"
-    }
-
-
-.. code-block:: json
-    :caption: 動画プロジェクト：0〜100ミリ秒の区間
-
-    {
-        "start":0,
-        "end":100,
-        "_type": "Time"
-    }
-
-
-.. code-block:: json
-    :caption: カスタムプロジェクト（3dpc editor）：原点付近に辺が1の立方体
-
-    {
-        "data": "{\"kind\": \"CUBOID\", \"shape\": {\"dimensions\": {\"width\": 1.0, \"height\": 1.0, \"depth\": 1.0}, \"location\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0}, \"rotation\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0}, \"direction\": {\"front\": {\"x\": 1.0, \"y\": 0.0, \"z\": 0.0}, \"up\": {\"x\": 0.0, \"y\": 0.0, \"z\": 1.0}}}, \"version\": \"2\"}",
-        "_type": "Custom"    
-    }
-
-
-
-
-
-並列処理
-----------------------------------------------
-
-以下のコマンドは、並列数4で実行します。
-
-.. code-block::
+`annofabcli comment put_inspection <../comment/put_inspection.html>`_ コマンドを参照ください。
+コマンドライン引数の使い方は同じです。
 
-    $  annofabcli inspection_comment put --project_id prj1 --json file://comment.json \
-    --parallelism 4 --yes
 
 Usage Details
 =================================
diff --git a/docs/command_reference/inspection_comment/put_simply.rst b/docs/command_reference/inspection_comment/put_simply.rst
index f2240b50..2d9048e2 100644
--- a/docs/command_reference/inspection_comment/put_simply.rst
+++ b/docs/command_reference/inspection_comment/put_simply.rst
@@ -4,108 +4,22 @@ inspection_comment put_simply
 
 Description
 =================================
-``inspection_comment put`` コマンドよりも、簡単に検査コメントを付与します。
+``inspection_comment put`` コマンドよりも、簡単に検査コメントを付与できます。
 
 
-Examples
-=================================
-
-基本的な使い方
---------------------------
-
-指定したタスクに、検査コメントを付与できます。``--comment`` に検査コメントの内容を指定してください。 
-タスク内の先頭の入力データに、検査コメントが付与されます。
-
-.. code-block::
-
-    $ annofabcli inspection_comment put_simply --project_id prj1 --task_id task1 task2 \
-     --comment "枠がズレています。"
-
-
-``--phrase_id`` に定型指摘IDも指定できます。
-
-
-.. code-block::
-
-    $ annofabcli inspection_comment put_simply --project_id prj1 --task_id task1 task2 \
-     --comment "枠がズレています。 #ID1" --phrase_id ID1
-
-
-
-検査コメントの位置や区間を指定する
---------------------------------------
-``--comment_data`` に、検査コメントの位置や区間をJSON形式で指定することができます。
-``--comment_data`` に渡す形式は、https://annofab.com/docs/api/#operation/batchUpdateInspections APIのリクエストボディ ``data`` を参照してください。
-
-
-以下は、 ``--comment_data`` に渡すJSON文字列のサンプルです。
-
-.. code-block:: json
-    :caption: 画像プロジェクト：(x=0,y=0)の位置に点
-
-    {
-        "x":0,
-        "y":0,
-        "_type": "Point"
-    }
-
-
-.. code-block:: json
-    :caption: 画像プロジェクト：(x=0,y=0),(x=100,y=100)のポリライン
-
-    {
-        "coordinates": [{"x":0, "y":0}, {"x":100, "y":100}],
-        "_type": "Polyline"
-    }
-
+.. warning::
 
+    非推奨なコマンドです。2022/12/01以降に廃止する予定です。
+    替わりに `annofabcli comment put_inspection_simply <../comment/put_inspection_simply.html>`_ コマンドを使用してください。
 
-.. code-block:: json
-    :caption: 動画プロジェクト：0〜100ミリ秒の区間
 
-    {
-        "start":0,
-        "end":100,
-        "_type": "Time"
-    }
-
-
-.. code-block:: json
-    :caption: カスタムプロジェクト（3dpc editor）：原点付近に辺が1の立方体
-
-    {
-        "data": "{\"kind\": \"CUBOID\", \"shape\": {\"dimensions\": {\"width\": 1.0, \"height\": 1.0, \"depth\": 1.0}, \"location\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0}, \"rotation\": {\"x\": 0.0, \"y\": 0.0, \"z\": 0.0}, \"direction\": {\"front\": {\"x\": 1.0, \"y\": 0.0, \"z\": 0.0}, \"up\": {\"x\": 0.0, \"y\": 0.0, \"z\": 1.0}}}, \"version\": \"2\"}",
-        "_type": "Custom"    
-    }
-
-
-``--comment_data`` を指定しない場合は、以下の値になります。
-
-* 画像プロジェクト： ``{"x":0, "y":0, "_type": "Point"}``
-* 動画プロジェクト： ``{"start":0, "end":100, "_type": "Time"}``
-
-カスタムプロジェクトの場合は、検査コメントの位置を決められないので、 ``--comment_data`` は必須です。
-
-
-
-
-
-
-
-
-
-
-
-
-並列処理
-----------------------------------------------
+Examples
+=================================
 
-以下のコマンドは、並列数4で実行します。
 
-.. code-block::
+`annofabcli comment put_inspection_simply <../comment/put_inspection_simply.html>`_ コマンドを参照ください。
+コマンドライン引数の使い方は同じです。
 
-    $  annofabcli inspection_comment put_simply --project_id prj1 --task_id t1 t2 t3 t4 \
-    --parallelism 4 --yes
 
 
 Usage Details
@@ -113,6 +27,6 @@ Usage Details
 
 .. argparse::
    :ref: annofabcli.inspection_comment.put_inspection_comments_simply.add_parser
-   :prog: annofabcli inspection_comment put
+   :prog: annofabcli inspection_comment put_simply
    :nosubcommands:
    :nodefaultconst:
diff --git a/pyproject.toml b/pyproject.toml
index 442570da..f795cf4f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "annofabcli"
-version = "1.71.0"
+version = "1.72.0"
 description = "Utility Command Line Interface for AnnoFab"
 authors = ["yuji38kwmt"]
 license = "MIT"


Test patch:
diff --git a/tests/test_comment.py b/tests/test_comment.py
index c3fe4ec1..0cfba4b8 100644
--- a/tests/test_comment.py
+++ b/tests/test_comment.py
@@ -1,7 +1,12 @@
 import configparser
+import datetime
+import json
 import os
 from pathlib import Path
 
+import annofabapi
+import pytest
+
 from annofabcli.__main__ import main
 
 out_dir = Path("./tests/out/comment")
@@ -16,28 +21,190 @@
 project_id = annofab_config["project_id"]
 
 
+input_data_id = annofab_config["input_data_id"]
+service = annofabapi.build()
+
+
 class TestCommandLine:
-    def test_download(self):
+    @pytest.fixture
+    def target_task(self):
+        """シナリオテスト用のタスクを作成する
+
+        Yields:
+            作成したタスク
+        """
+        # タスクの作成
+        new_task_id = f"test-{str(int(datetime.datetime.now().timestamp()))}"
+        task, _ = service.api.put_task(project_id, new_task_id, request_body={"input_data_id_list": [input_data_id]})
+
+        yield task
+        # タスクの削除
+        service.api.delete_task(project_id, new_task_id)
+
+    def test_scenario_onhold_comment(self, target_task):
+        """
+        保留コメントに関するシナリオテスト。
+        """
+
+        project, _ = service.api.get_project(project_id)
+        assert project["input_data_type"] == "image", "画像プロジェクトでないと、検査コメントコマンドのテストは実行できません。"
+
+        task_id = target_task["task_id"]
+        input_data_id = target_task["input_data_id_list"][0]
+
+        # put_onhold_simply
         main(
             [
                 "comment",
-                "download",
+                "put_onhold_simply",
                 "--project_id",
                 project_id,
+                "--task_id",
+                task_id,
+                "--comment",
+                "test with 'put_onhold_simply'",
+                "--yes",
+            ]
+        )
+
+        # put_onhold
+        dict_comments = {
+            task_id: {
+                input_data_id: [
+                    {
+                        "comment": "test with 'put_onhold'",
+                    }
+                ]
+            }
+        }
+        main(["comment", "put_onhold", "--project_id", project_id, "--json", json.dumps(dict_comments), "--yes"])
+
+        # 保留コメントが登録されたかを確認
+        comment_list, _ = service.api.get_comments(project_id, task_id, input_data_id, query_params={"v": 2})
+        onhold_comment_list = [e for e in comment_list if e["comment_type"] == "onhold"]
+        assert len(onhold_comment_list) == 2
+
+        # list command
+        main(
+            [
+                "comment",
+                "list",
+                "--project_id",
+                project_id,
+                "--task_id",
+                task_id,
+                "--comment_type",
+                "onhold",
                 "--output",
-                str(out_dir / "download.json"),
+                str(out_dir / "list-onhold-out.csv"),
             ]
         )
 
-    def test_list(self):
+        # delete command
+        dict_comments = {task_id: {input_data_id: [e["comment_id"] for e in onhold_comment_list]}}
+        main(["comment", "delete", "--project_id", project_id, "--json", json.dumps(dict_comments), "--yes"])
+
+        # コメントが削除されているかを確認する
+        comment_list, _ = service.api.get_comments(project_id, task_id, input_data_id, query_params={"v": 2})
+        onhold_comment_list = [e for e in comment_list if e["comment_type"] == "onhold"]
+        assert len(onhold_comment_list) == 0
+
+    def test_scenario_inspection_comment(self, target_task):
+        """
+        検査コメントに関するシナリオテスト。
+        """
+        project, _ = service.api.get_project(project_id)
+        assert project["input_data_type"] == "image", "画像プロジェクトでないと、検査コメントコマンドのテストは実行できません。"
+
+        task_id = target_task["task_id"]
+        input_data_id = target_task["input_data_id_list"][0]
+
+        # 検査/受入フェーズにして、検査コメントを付与できるようにする
+        main(
+            [
+                "task",
+                "complete",
+                "--project_id",
+                project_id,
+                "--task_id",
+                task_id,
+                "--phase",
+                "annotation",
+                "--yes",
+            ]
+        )
+
+        task, _ = service.api.get_task(project_id, task_id)
+        # アノテーション仕様に全体ラベルがあって属性が必須だと、タスクを次の状態に遷移させられないので、ここで確認する
+        assert task["phase"] == {"inspection", "acceptance"}
+        # 抜き取り検査率/受入率が設定されていると、完了状態になっている可能性があるので確認する
+        assert task["status"] == "not_started"
+
+        # put_inspection_simply
         main(
             [
                 "comment",
+                "put_inspection_simply",
+                "--project_id",
+                project_id,
+                "--task_id",
+                task_id,
+                "--comment",
+                "test with 'put_inspection_simply'",
+                "--yes",
+            ]
+        )
+
+        # put_inspection command
+        dict_comments = {
+            task_id: {
+                input_data_id: [
+                    {
+                        "comment": "test with 'put_inspection'",
+                        "data": {"x": 10, "y": 20, "_type": "Point"},
+                    }
+                ]
+            }
+        }
+        main(["comment", "put_inspection", "--project_id", project_id, "--json", json.dumps(dict_comments), "--yes"])
+
+        # 検査コメントが登録されたことの確認
+        comment_list, _ = service.api.get_comments(project_id, task_id, input_data_id, query_params={"v": 2})
+        inspection_comment_list = [e for e in comment_list if e["comment_type"] == "inspection"]
+        assert len(inspection_comment_list) == 2
+
+        # list command
+        main(
+            [
+                "inspection_comment",
                 "list",
                 "--project_id",
                 project_id,
+                "--task_id",
+                task_id,
+                "--comment_type",
+                "inspection" "--output",
+                str(out_dir / "list-inspection-out.csv"),
+            ]
+        )
+
+        # delete command
+        dict_comments = {task_id: {input_data_id: [e["comment_id"] for e in inspection_comment_list]}}
+        main(["comment", "delete", "--project_id", project_id, "--json", json.dumps(dict_comments), "--yes"])
+
+        comment_list, _ = service.api.get_comments(project_id, task_id, input_data_id, query_params={"v": 2})
+        inspection_comment_list = [e for e in comment_list if e["comment_type"] == "inspection"]
+        assert len(inspection_comment_list) == 0
+
+    def test_download(self):
+        main(
+            [
+                "comment",
+                "download",
+                "--project_id",
+                project_id,
                 "--output",
-                str(out_dir / "list-comment.csv"),
+                str(out_dir / "download.json"),
             ]
         )
 
diff --git a/tests/test_inspection_comment.py b/tests/test_inspection_comment.py
index 7a5e2426..b2dca29f 100644
--- a/tests/test_inspection_comment.py
+++ b/tests/test_inspection_comment.py
@@ -50,6 +50,12 @@ def target_task(self):
             ]
         )
 
+        task, _ = service.api.get_task(project_id, new_task_id)
+        # アノテーション仕様に全体ラベルがあって属性が必須だと、タスクを次の状態に遷移させられないので、ここで確認する
+        assert task["phase"] in {"inspection", "acceptance"}
+        # 抜き取り検査率/受入率が設定されていると、完了状態になっている可能性があるので確認する
+        assert task["status"] == "not_started"
+
         yield task
         # タスクの削除
         service.api.delete_task(project_id, new_task_id)