papycli 0.15.2__tar.gz → 0.16.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. papycli-0.16.0/.github/workflows/copilot-review.yml +43 -0
  2. papycli-0.16.0/.release-please-manifest.json +3 -0
  3. {papycli-0.15.2 → papycli-0.16.0}/CHANGELOG.md +23 -0
  4. {papycli-0.15.2 → papycli-0.16.0}/PKG-INFO +11 -5
  5. {papycli-0.15.2 → papycli-0.16.0}/README.ja.md +9 -4
  6. {papycli-0.15.2 → papycli-0.16.0}/README.md +9 -4
  7. papycli-0.16.0/docs/superpowers/plans/2026-04-01-config-add-upgrade.md +354 -0
  8. papycli-0.16.0/docs/superpowers/plans/2026-04-02-dotenv-autoload.md +263 -0
  9. papycli-0.16.0/docs/superpowers/specs/2026-04-01-config-add-upgrade-design.md +66 -0
  10. papycli-0.16.0/docs/superpowers/specs/2026-04-02-dotenv-autoload-design.md +74 -0
  11. {papycli-0.15.2 → papycli-0.16.0}/pyproject.toml +3 -2
  12. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/config.py +19 -4
  13. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/main.py +74 -5
  14. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_main.py +227 -1
  15. {papycli-0.15.2 → papycli-0.16.0}/uv.lock +229 -1
  16. papycli-0.15.2/.github/workflows/copilot-review.yml +0 -69
  17. papycli-0.15.2/.release-please-manifest.json +0 -3
  18. {papycli-0.15.2 → papycli-0.16.0}/.github/copilot-instructions.md +0 -0
  19. {papycli-0.15.2 → papycli-0.16.0}/.github/workflows/docs.yml +0 -0
  20. {papycli-0.15.2 → papycli-0.16.0}/.github/workflows/release-please.yml +0 -0
  21. {papycli-0.15.2 → papycli-0.16.0}/.gitignore +0 -0
  22. {papycli-0.15.2 → papycli-0.16.0}/.pre-commit-config.yaml +0 -0
  23. {papycli-0.15.2 → papycli-0.16.0}/.python-version +0 -0
  24. {papycli-0.15.2 → papycli-0.16.0}/CLAUDE.md +0 -0
  25. {papycli-0.15.2 → papycli-0.16.0}/LICENSE +0 -0
  26. {papycli-0.15.2 → papycli-0.16.0}/design/2026-03-27-completion-static-script.md +0 -0
  27. {papycli-0.15.2 → papycli-0.16.0}/design/2026-03-28-claude-md-simplification-plan.md +0 -0
  28. {papycli-0.15.2 → papycli-0.16.0}/design/2026-03-28-claude-md-simplification.md +0 -0
  29. {papycli-0.15.2 → papycli-0.16.0/docs}/design_doc.md +0 -0
  30. {papycli-0.15.2 → papycli-0.16.0}/docs/development.md +0 -0
  31. {papycli-0.15.2 → papycli-0.16.0}/docs/index.md +0 -0
  32. {papycli-0.15.2 → papycli-0.16.0}/docs/installation.md +0 -0
  33. {papycli-0.15.2 → papycli-0.16.0}/docs/ja/development.md +0 -0
  34. {papycli-0.15.2 → papycli-0.16.0}/docs/ja/index.md +0 -0
  35. {papycli-0.15.2 → papycli-0.16.0}/docs/ja/installation.md +0 -0
  36. {papycli-0.15.2 → papycli-0.16.0}/docs/ja/plugins.md +0 -0
  37. {papycli-0.15.2 → papycli-0.16.0}/docs/ja/quickstart.md +0 -0
  38. {papycli-0.15.2 → papycli-0.16.0}/docs/ja/reference.md +0 -0
  39. {papycli-0.15.2 → papycli-0.16.0}/docs/plugins.md +0 -0
  40. {papycli-0.15.2 → papycli-0.16.0}/docs/quickstart.md +0 -0
  41. {papycli-0.15.2 → papycli-0.16.0}/docs/reference.md +0 -0
  42. {papycli-0.15.2 → papycli-0.16.0}/examples/petstore/docker-compose.yml +0 -0
  43. {papycli-0.15.2 → papycli-0.16.0}/examples/petstore/petstore-oas3.json +0 -0
  44. {papycli-0.15.2 → papycli-0.16.0}/examples/request_filter/README.md +0 -0
  45. {papycli-0.15.2 → papycli-0.16.0}/examples/request_filter/pyproject.toml +0 -0
  46. {papycli-0.15.2 → papycli-0.16.0}/examples/request_filter/src/papycli_debug_filter/__init__.py +0 -0
  47. {papycli-0.15.2 → papycli-0.16.0}/examples/response_filter/README.md +0 -0
  48. {papycli-0.15.2 → papycli-0.16.0}/examples/response_filter/pyproject.toml +0 -0
  49. {papycli-0.15.2 → papycli-0.16.0}/examples/response_filter/src/papycli_debug_response_filter/__init__.py +0 -0
  50. {papycli-0.15.2 → papycli-0.16.0}/mkdocs.yml +0 -0
  51. {papycli-0.15.2 → papycli-0.16.0}/release-please-config.json +0 -0
  52. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/__init__.py +0 -0
  53. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/api_call.py +0 -0
  54. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/checker.py +0 -0
  55. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/completion.py +0 -0
  56. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/filters.py +0 -0
  57. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/i18n.py +0 -0
  58. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/init_cmd.py +0 -0
  59. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/response_checker.py +0 -0
  60. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/spec_loader.py +0 -0
  61. {papycli-0.15.2 → papycli-0.16.0}/src/papycli/summary.py +0 -0
  62. {papycli-0.15.2 → papycli-0.16.0}/tests/integration/__init__.py +0 -0
  63. {papycli-0.15.2 → papycli-0.16.0}/tests/integration/conftest.py +0 -0
  64. {papycli-0.15.2 → papycli-0.16.0}/tests/integration/test_integration.py +0 -0
  65. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/conftest.py +0 -0
  66. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_api_call.py +0 -0
  67. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_checker.py +0 -0
  68. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_completion.py +0 -0
  69. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_config.py +0 -0
  70. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_filters.py +0 -0
  71. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_i18n.py +0 -0
  72. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_init_cmd.py +0 -0
  73. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_response_checker.py +0 -0
  74. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_spec_loader.py +0 -0
  75. {papycli-0.15.2 → papycli-0.16.0}/tests/unittest/test_summary.py +0 -0
@@ -0,0 +1,43 @@
1
+ name: Copilot Review
2
+
3
+ on:
4
+ pull_request:
5
+ types:
6
+ # opened は除外する。PR 作成時はリポジトリ設定により Copilot レビューが
7
+ # 自動実行されるため、ここでリクエストすると二重実行になる。
8
+ - synchronize
9
+
10
+ # このワークフローを動作させるには、以下の設定が必要:
11
+ # 1. Copilot コードレビューがリポジトリで有効になっていること
12
+ # (Settings → Copilot → Code review)
13
+ # 2. Copilot が有効なユーザーの Personal Access Token (classic, repo スコープ) を
14
+ # リポジトリシークレット GH_PAT に設定すること
15
+ # (GITHUB_TOKEN は Copilot 権限を持たないため動作しない)
16
+ permissions: {}
17
+
18
+ # 同一 PR への連続プッシュで多重実行されないよう PR 番号単位で直列化する。
19
+ # cancel-in-progress: true にすることで古い実行をキャンセルし最新のみを実行する。
20
+ concurrency:
21
+ group: copilot-review-${{ github.event.pull_request.number }}
22
+ cancel-in-progress: true
23
+
24
+ jobs:
25
+ request-copilot-review:
26
+ name: Request Copilot review
27
+ runs-on: ubuntu-latest
28
+ # fork からの PR ではシークレットが利用できないためスキップする。
29
+ if: github.event.pull_request.head.repo.full_name == github.repository
30
+ steps:
31
+ - name: Checkout repository
32
+ uses: actions/checkout@v4
33
+ with:
34
+ fetch-depth: 1
35
+
36
+ - name: Request review from Copilot
37
+ env:
38
+ GH_TOKEN: ${{ secrets.GH_PAT }}
39
+ run: |
40
+ # すでにレビューリクエスト済みの場合は一度削除してから再リクエストする。
41
+ # 削除→再追加することで、新しいコミットがプッシュされたときも Copilot が再レビューを行う。
42
+ gh pr edit ${{ github.event.pull_request.number }} --remove-reviewer @copilot 2>/dev/null || true
43
+ gh pr edit ${{ github.event.pull_request.number }} --add-reviewer @copilot
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.16.0"
3
+ }
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.16.0](https://github.com/tmonj1/papycli/compare/v0.15.2...v0.16.0) (2026-04-02)
4
+
5
+
6
+ ### Features
7
+
8
+ * .env ファイルの自動読み込み ([#167](https://github.com/tmonj1/papycli/issues/167)) ([950f9a9](https://github.com/tmonj1/papycli/commit/950f9a9c2af862e2d1e8545d3ae9b922a4b8c62a))
9
+ * config add に --upgrade オプションを追加する ([#162](https://github.com/tmonj1/papycli/issues/162)) ([cec6586](https://github.com/tmonj1/papycli/commit/cec6586ca5be6b0c21038c606b7ead2bdd641077))
10
+ * implement config add upgrade option ([0e3134f](https://github.com/tmonj1/papycli/commit/0e3134f4e6d6761e5c6aa38a6985c744e47b869e))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **ci:** add actions/checkout step before gh CLI calls in copilot-review workflow ([ae6aaa3](https://github.com/tmonj1/papycli/commit/ae6aaa3874cb405301fd799d9f32c86a08c6bb9f))
16
+ * **ci:** add checkout step to copilot-review workflow ([#166](https://github.com/tmonj1/papycli/issues/166)) ([ae6aaa3](https://github.com/tmonj1/papycli/commit/ae6aaa3874cb405301fd799d9f32c86a08c6bb9f))
17
+ * **ci:** replace direct API calls with gh pr edit --add-reviewer [@copilot](https://github.com/copilot) ([2ba29d9](https://github.com/tmonj1/papycli/commit/2ba29d92319496ab0a9bc4998fb0f86ea501e9ed))
18
+ * **ci:** trigger Copilot re-review on push via gh pr edit --add-reviewer [@copilot](https://github.com/copilot) ([#164](https://github.com/tmonj1/papycli/issues/164)) ([2ba29d9](https://github.com/tmonj1/papycli/commit/2ba29d92319496ab0a9bc4998fb0f86ea501e9ed))
19
+
20
+
21
+ ### Documentation
22
+
23
+ * add design spec for config add --upgrade option ([b55938e](https://github.com/tmonj1/papycli/commit/b55938eb98c16aa482858d96c5dce689b138f6e8))
24
+ * add implementation plan for config add --upgrade option ([8af1594](https://github.com/tmonj1/papycli/commit/8af15946b1ea65e3bed4b21da8de8649a1bd292e))
25
+
3
26
  ## [0.15.2](https://github.com/tmonj1/papycli/compare/v0.15.1...v0.15.2) (2026-04-01)
4
27
 
5
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: papycli
3
- Version: 0.15.2
3
+ Version: 0.16.0
4
4
  Summary: A CLI tool to call REST APIs defined in OpenAPI 3.0 specs
5
5
  Project-URL: Homepage, https://github.com/tmonj1/papycli
6
6
  Project-URL: Repository, https://github.com/tmonj1/papycli
@@ -21,6 +21,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
21
21
  Classifier: Topic :: Utilities
22
22
  Requires-Python: >=3.12
23
23
  Requires-Dist: click>=8.1
24
+ Requires-Dist: python-dotenv>=1.0
24
25
  Requires-Dist: pyyaml>=6.0
25
26
  Requires-Dist: requests>=2.32
26
27
  Requires-Dist: rich>=13.0
@@ -281,10 +282,15 @@ Options:
281
282
  --help / -h Show help
282
283
 
283
284
  Environment variables:
284
- PAPYCLI_CONF_DIR Path to the config directory (default: ~/.papycli)
285
- PAPYCLI_CUSTOM_HEADER Custom HTTP headers applied to every request.
286
- Separate multiple headers with newlines:
287
- export PAPYCLI_CUSTOM_HEADER=$'Authorization: Bearer token\nX-Tenant: acme'
285
+ PAPYCLI_CONF_DIR Path to the config directory (default: ~/.papycli)
286
+ PAPYCLI_CUSTOM_HEADER Custom HTTP headers applied to every request.
287
+ Separate multiple headers with newlines:
288
+ export PAPYCLI_CUSTOM_HEADER=$'Authorization: Bearer token\nX-Tenant: acme'
289
+ PAPYCLI_DISABLE_DOTENV Set to 1 to disable automatic .env file loading.
290
+ By default papycli loads .env from the current directory
291
+ and $PAPYCLI_CONF_DIR on startup. Disable this when running
292
+ in untrusted directories to prevent unintended env injection:
293
+ export PAPYCLI_DISABLE_DOTENV=1
288
294
  ```
289
295
 
290
296
  ---
@@ -252,10 +252,15 @@ papycli <method> <resource> [options]
252
252
  --help / -h 使い方を表示する
253
253
 
254
254
  環境変数:
255
- PAPYCLI_CONF_DIR 設定ディレクトリのパス(デフォルト: ~/.papycli)
256
- PAPYCLI_CUSTOM_HEADER すべてのリクエストに適用するカスタム HTTP ヘッダー
257
- 複数のヘッダーは改行で区切る:
258
- export PAPYCLI_CUSTOM_HEADER=$'Authorization: Bearer token\nX-Tenant: acme'
255
+ PAPYCLI_CONF_DIR 設定ディレクトリのパス(デフォルト: ~/.papycli)
256
+ PAPYCLI_CUSTOM_HEADER すべてのリクエストに適用するカスタム HTTP ヘッダー
257
+ 複数のヘッダーは改行で区切る:
258
+ export PAPYCLI_CUSTOM_HEADER=$'Authorization: Bearer token\nX-Tenant: acme'
259
+ PAPYCLI_DISABLE_DOTENV 1 に設定すると .env ファイルの自動読み込みを無効化する。
260
+ デフォルトでは起動時にカレントディレクトリと
261
+ $PAPYCLI_CONF_DIR の .env を読み込む。信頼できない
262
+ ディレクトリで実行する際は無効化を推奨する:
263
+ export PAPYCLI_DISABLE_DOTENV=1
259
264
  ```
260
265
 
261
266
  ---
@@ -253,10 +253,15 @@ Options:
253
253
  --help / -h Show help
254
254
 
255
255
  Environment variables:
256
- PAPYCLI_CONF_DIR Path to the config directory (default: ~/.papycli)
257
- PAPYCLI_CUSTOM_HEADER Custom HTTP headers applied to every request.
258
- Separate multiple headers with newlines:
259
- export PAPYCLI_CUSTOM_HEADER=$'Authorization: Bearer token\nX-Tenant: acme'
256
+ PAPYCLI_CONF_DIR Path to the config directory (default: ~/.papycli)
257
+ PAPYCLI_CUSTOM_HEADER Custom HTTP headers applied to every request.
258
+ Separate multiple headers with newlines:
259
+ export PAPYCLI_CUSTOM_HEADER=$'Authorization: Bearer token\nX-Tenant: acme'
260
+ PAPYCLI_DISABLE_DOTENV Set to 1 to disable automatic .env file loading.
261
+ By default papycli loads .env from the current directory
262
+ and $PAPYCLI_CONF_DIR on startup. Disable this when running
263
+ in untrusted directories to prevent unintended env injection:
264
+ export PAPYCLI_DISABLE_DOTENV=1
260
265
  ```
261
266
 
262
267
  ---
@@ -0,0 +1,354 @@
1
+ # config add --upgrade Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** `papycli config add` を新規登録専用にし、既存 API の更新には `--upgrade` フラグを必須とする。
6
+
7
+ **Architecture:** `src/papycli/main.py` の `cmd_config_add` にのみ変更を加える。`--upgrade` フラグ追加・既存 API チェック・出力メッセージの分岐のすべてをこの関数内で完結させる。`init_cmd.py` は変更しない。
8
+
9
+ **Tech Stack:** Python 3.12, click, pytest, Click CliRunner (テスト)
10
+
11
+ ---
12
+
13
+ ## ファイルマップ
14
+
15
+ | ファイル | 変更種別 | 内容 |
16
+ |---|---|---|
17
+ | `src/papycli/main.py` | 修正 | `cmd_config_add` に `--upgrade` フラグと既存 API チェックを追加 |
18
+ | `tests/unittest/test_main.py` | 修正 | `--upgrade` 関連テストを追加 |
19
+
20
+ ---
21
+
22
+ ### Task 1: 既存 API への `config add` がエラーになるテストを書く
23
+
24
+ **Files:**
25
+ - Modify: `tests/unittest/test_main.py`
26
+
27
+ - [ ] **Step 1: 失敗するテストを書く**
28
+
29
+ `tests/unittest/test_main.py` の `# papycli config add` セクション末尾に以下を追加する:
30
+
31
+ ```python
32
+ def test_cmd_add_already_registered_errors(
33
+ tmp_path: Path, minimal_spec_file: Path, monkeypatch: pytest.MonkeyPatch
34
+ ) -> None:
35
+ """config add を同じ API 名で2回実行するとエラーになる。"""
36
+ monkeypatch.setenv("PAPYCLI_CONF_DIR", str(tmp_path))
37
+ runner = CliRunner()
38
+ runner.invoke(cli, ["config", "add", str(minimal_spec_file)])
39
+ result = runner.invoke(cli, ["config", "add", str(minimal_spec_file)])
40
+ assert result.exit_code != 0
41
+ assert "already registered" in result.output
42
+ assert "--upgrade" in result.output
43
+ ```
44
+
45
+ - [ ] **Step 2: テストが失敗することを確認する**
46
+
47
+ ```
48
+ uv run pytest tests/unittest/test_main.py::test_cmd_add_already_registered_errors -v
49
+ ```
50
+
51
+ Expected: FAIL(現状は2回目も exit 0 で成功してしまう)
52
+
53
+ ---
54
+
55
+ ### Task 2: `config add` に既存 API チェックを実装する
56
+
57
+ **Files:**
58
+ - Modify: `src/papycli/main.py:85-119`
59
+
60
+ - [ ] **Step 1: `--upgrade` フラグと既存チェックを追加する**
61
+
62
+ `main.py` の `cmd_config_add` コマンド定義を以下のように変更する:
63
+
64
+ ```python
65
+ @cmd_config.command(
66
+ "add",
67
+ help=h(
68
+ "Register an API from an OpenAPI spec file.",
69
+ "OpenAPI spec ファイルから API を登録する。",
70
+ ),
71
+ )
72
+ @click.argument("spec_file", metavar="SPEC_FILE", type=click.Path(exists=True, dir_okay=False))
73
+ @click.option(
74
+ "--upgrade", "upgrade", is_flag=True,
75
+ help=h(
76
+ "Update an existing registered API with a new spec.",
77
+ "既存の登録済み API を新しい spec で更新する。",
78
+ ),
79
+ )
80
+ def cmd_config_add(spec_file: str, upgrade: bool) -> None:
81
+ spec_path = Path(spec_file)
82
+ conf_dir = get_conf_dir()
83
+
84
+ if spec_path.stem in ("default", "aliases"):
85
+ click.echo(
86
+ f"Error: '{spec_path.stem}' is a reserved name and cannot be used as an API name.",
87
+ err=True,
88
+ )
89
+ sys.exit(1)
90
+
91
+ conf = load_conf(conf_dir)
92
+ api_name = spec_path.stem
93
+ already_registered = api_name in conf and isinstance(conf[api_name], dict)
94
+
95
+ if not upgrade and already_registered:
96
+ click.echo(
97
+ f"Error: API '{api_name}' is already registered. Use --upgrade to update it.",
98
+ err=True,
99
+ )
100
+ sys.exit(1)
101
+
102
+ try:
103
+ api_name, base_url = init_api(spec_path, conf_dir)
104
+ except Exception as e:
105
+ click.echo(f"Error: {e}", err=True)
106
+ sys.exit(1)
107
+
108
+ register_initialized_api(conf, api_name, spec_path, base_url)
109
+ save_conf(conf, conf_dir)
110
+
111
+ if upgrade and already_registered:
112
+ click.echo(f"Updated API '{api_name}'")
113
+ else:
114
+ click.echo(f"Registered API '{api_name}'")
115
+ if base_url:
116
+ click.echo(f" Base URL : {base_url}")
117
+ else:
118
+ click.echo(" Base URL : (not set — edit papycli.conf to add url)")
119
+ click.echo(f" Conf dir : {conf_dir}")
120
+ ```
121
+
122
+ - [ ] **Step 2: Task 1 のテストが通ることを確認する**
123
+
124
+ ```
125
+ uv run pytest tests/unittest/test_main.py::test_cmd_add_already_registered_errors -v
126
+ ```
127
+
128
+ Expected: PASS
129
+
130
+ - [ ] **Step 3: 既存テストが壊れていないことを確認する**
131
+
132
+ ```
133
+ uv run pytest tests/unittest/test_main.py -v
134
+ ```
135
+
136
+ Expected: すべて PASS
137
+
138
+ ---
139
+
140
+ ### Task 3: `--upgrade` で既存 API を更新するテストを書いて通す
141
+
142
+ **Files:**
143
+ - Modify: `tests/unittest/test_main.py`
144
+
145
+ - [ ] **Step 1: テストを書く**
146
+
147
+ `test_cmd_add_already_registered_errors` の直後に以下を追加する:
148
+
149
+ ```python
150
+ def test_cmd_add_upgrade_updates_existing(
151
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
152
+ ) -> None:
153
+ """--upgrade で既存 API の spec・apidef・URL が更新される。"""
154
+ monkeypatch.setenv("PAPYCLI_CONF_DIR", str(tmp_path))
155
+ runner = CliRunner()
156
+
157
+ # 旧 spec で登録
158
+ old_spec: dict[str, Any] = {
159
+ "openapi": "3.0.2",
160
+ "servers": [{"url": "http://old.example.com/api"}],
161
+ "paths": {
162
+ "/items": {"get": {"parameters": []}},
163
+ },
164
+ }
165
+ spec_file = tmp_path / "myapi.json"
166
+ spec_file.write_text(json.dumps(old_spec), encoding="utf-8")
167
+ runner.invoke(cli, ["config", "add", str(spec_file)])
168
+
169
+ # 新 spec で --upgrade
170
+ new_spec: dict[str, Any] = {
171
+ "openapi": "3.0.2",
172
+ "servers": [{"url": "http://new.example.com/api"}],
173
+ "paths": {
174
+ "/items": {"get": {"parameters": []}},
175
+ "/users": {"get": {"parameters": []}},
176
+ },
177
+ }
178
+ spec_file.write_text(json.dumps(new_spec), encoding="utf-8")
179
+ result = runner.invoke(cli, ["config", "add", "--upgrade", str(spec_file)])
180
+
181
+ assert result.exit_code == 0
182
+ assert "Updated API 'myapi'" in result.output
183
+ assert "http://new.example.com/api" in result.output
184
+
185
+ conf = json.loads((tmp_path / "papycli.conf").read_text(encoding="utf-8"))
186
+ assert conf["myapi"]["url"] == "http://new.example.com/api"
187
+
188
+ apidef = json.loads((tmp_path / "apis" / "myapi.json").read_text(encoding="utf-8"))
189
+ assert "/users" in apidef
190
+
191
+
192
+ def test_cmd_add_upgrade_on_new_api_registers(
193
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
194
+ ) -> None:
195
+ """--upgrade で未登録 API を指定すると新規登録として扱われる。"""
196
+ monkeypatch.setenv("PAPYCLI_CONF_DIR", str(tmp_path))
197
+ spec: dict[str, Any] = {
198
+ "openapi": "3.0.2",
199
+ "servers": [{"url": "http://localhost:9000/api"}],
200
+ "paths": {"/items": {"get": {"parameters": []}}},
201
+ }
202
+ spec_file = tmp_path / "myapi.json"
203
+ spec_file.write_text(json.dumps(spec), encoding="utf-8")
204
+
205
+ runner = CliRunner()
206
+ result = runner.invoke(cli, ["config", "add", "--upgrade", str(spec_file)])
207
+
208
+ assert result.exit_code == 0
209
+ assert "Registered API 'myapi'" in result.output
210
+ conf = json.loads((tmp_path / "papycli.conf").read_text(encoding="utf-8"))
211
+ assert conf["default"] == "myapi"
212
+ ```
213
+
214
+ - [ ] **Step 2: テストが通ることを確認する**
215
+
216
+ ```
217
+ uv run pytest tests/unittest/test_main.py::test_cmd_add_upgrade_updates_existing tests/unittest/test_main.py::test_cmd_add_upgrade_on_new_api_registers -v
218
+ ```
219
+
220
+ Expected: 両方 PASS
221
+
222
+ ---
223
+
224
+ ### Task 4: Lint・型チェックを通してコミットする
225
+
226
+ **Files:**
227
+ - No new files
228
+
229
+ - [ ] **Step 1: ruff チェック**
230
+
231
+ ```
232
+ uv run ruff check src/papycli/main.py tests/unittest/test_main.py
233
+ ```
234
+
235
+ Expected: 警告・エラーなし
236
+
237
+ - [ ] **Step 2: ruff フォーマット**
238
+
239
+ ```
240
+ uv run ruff format src/papycli/main.py tests/unittest/test_main.py
241
+ ```
242
+
243
+ - [ ] **Step 3: mypy チェック**
244
+
245
+ ```
246
+ uv run mypy src/
247
+ ```
248
+
249
+ Expected: エラーなし
250
+
251
+ - [ ] **Step 4: テストスイート全体を実行する**
252
+
253
+ ```
254
+ uv run pytest tests/unittest/ -v
255
+ ```
256
+
257
+ Expected: すべて PASS
258
+
259
+ - [ ] **Step 5: コミットする**
260
+
261
+ ```bash
262
+ git add src/papycli/main.py tests/unittest/test_main.py
263
+ git commit -m "feat: add --upgrade option to config add command"
264
+ ```
265
+
266
+ ---
267
+
268
+ ### Task 5: GitHub issue を作成してトピックブランチを切る
269
+
270
+ > このタスクは実装前に行う(issue → branch の順)。Task 1 の前に実施すること。
271
+
272
+ - [ ] **Step 1: issue を作成する**
273
+
274
+ ```bash
275
+ gh issue create \
276
+ --title "feat: config add に --upgrade オプションを追加する" \
277
+ --label "feature" \
278
+ --body "$(cat <<'EOF'
279
+ ## 概要
280
+
281
+ \`papycli config add\` コマンドに \`--upgrade\` オプションを追加する。
282
+
283
+ ## 動作
284
+
285
+ - \`config add <spec>\`: 新規登録専用。既存 API 名があればエラー。
286
+ - \`config add --upgrade <spec>\`: 既存 API の spec・apidef・URL を更新する。未登録の場合は新規登録。
287
+
288
+ ## 参考
289
+
290
+ 設計書: \`docs/superpowers/specs/2026-04-01-config-add-upgrade-design.md\`
291
+ EOF
292
+ )"
293
+ ```
294
+
295
+ - [ ] **Step 2: issue 番号を確認してトピックブランチを作成する**
296
+
297
+ (issue 番号を `<N>` に置き換えて実行する)
298
+
299
+ ```bash
300
+ git checkout main
301
+ git pull
302
+ git checkout -b feature/config-add-upgrade-<N>
303
+ ```
304
+
305
+ ---
306
+
307
+ ### Task 6: PR を作成する
308
+
309
+ - [ ] **Step 1: push する**
310
+
311
+ ```bash
312
+ git push -u origin feature/config-add-upgrade-<N>
313
+ ```
314
+
315
+ - [ ] **Step 2: PR を作成する**
316
+
317
+ (issue 番号を `<N>` に置き換えて実行する)
318
+
319
+ ```bash
320
+ gh pr create \
321
+ --title "feat: config add に --upgrade オプションを追加する" \
322
+ --body "$(cat <<'EOF'
323
+ ## Summary
324
+
325
+ - \`papycli config add\` を新規登録専用に変更し、既存 API への再登録はエラーにする
326
+ - \`--upgrade\` フラグを追加し、既存 API の spec・apidef・base URL を更新できるようにする
327
+ - \`--upgrade\` で未登録 API を指定した場合は新規登録として扱う
328
+
329
+ Closes #<N>
330
+
331
+ ## Test plan
332
+
333
+ - [ ] \`config add\` で既存 API 名を指定するとエラー終了・"already registered" メッセージが出る
334
+ - [ ] \`config add --upgrade\` で既存 API を更新すると conf と apidef が書き換わる
335
+ - [ ] \`config add --upgrade\` で未登録 API を指定すると新規登録される
336
+ - [ ] \`uv run pytest tests/unittest/\` がすべて PASS する
337
+
338
+ 🤖 Generated with [Claude Code](https://claude.com/claude-code)
339
+ EOF
340
+ )"
341
+ ```
342
+
343
+ ---
344
+
345
+ ## 実行順序
346
+
347
+ Tasks は以下の順で実行する:
348
+
349
+ 1. **Task 5**(issue 作成・ブランチ作成)
350
+ 2. **Task 1**(失敗テスト追加)
351
+ 3. **Task 2**(実装)
352
+ 4. **Task 3**(upgrade テスト追加・確認)
353
+ 5. **Task 4**(lint・型チェック・コミット)
354
+ 6. **Task 6**(PR 作成)