feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567) - #584

Merged
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc
Aug 5, 2026
Merged

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)#584
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0026 の実装。マイルストーン「ドラフト縮小 N/7」のうち #563 / #564 / #565 / #566 / #567 を依存順に実装する。

#562 について: 選定順の変更(決定 3)は本ブランチでも実装したが、並行して #585 が main へ先にマージされた。main を取り込んだ際に、本ブランチの API(rank_repos / evaluate_default_selection / build_candidates)を採用しつつ、main 側にしか無かった継続期間の時刻成分の扱いを移植して統合済み(4d5aab1)。そのため mapper.py には #562 相当の差分も残っている。

変更内容

#563 出力を project 明細へ縮小しプレースホルダ生成を廃止(決定 1)

破壊的変更: GET /api/agent/resume-draft/result のレスポンスから experiences / qualifications を削除し、projects に置き換えた。

  • PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE とチーム構成の固定値生成を削除。GitHub から得られない値(会社・事業内容・役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
  • build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ変更。career_summary / self_pr は projects から独立した候補として返す
  • PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む experience / client は空(プレースホルダを入れない)で、projects が 0 件なら空箱すら作らない

#564 候補一覧 API と選択付きドラフト生成(決定 2)

破壊的変更: POST /api/agent/resume-draft/runrepo_full_names(1 件以上・上限 5 件)が必須になった。

  • GET /api/agent/resume-draft/candidates を追加。入口フィルタを通ったリポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)とデフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
  • LLM の説明文生成を採用分のみに限定(コストが選択数に比例 / P1)。出力スキーマの enum も採用分で縛る
  • PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、正本を schemas/agent.pyRESUME_DRAFT_SELECTION_LIMIT へ移した
  • 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は router で 422。タスク payload 側でも二重ガード
  • 非同期タスク構造・冪等ガード(ADR-0020)は変更なし

#565 リポジトリ候補の選択 UI(決定 2 / 3)

  • 候補一覧のチェックボックス選択。シグナル表示とデフォルト非選択の理由バッジを出す
  • デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
  • 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
  • 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求でエラートーストを出さないため)

#566 ドラフト注入を「置換」から「追加」へ(決定 5)

  • applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、appendResumeDraftProjects を追加。ユーザーが指定した experience / client の projects へ追加するだけで、既存の職歴は書き換えない
  • 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が 1 件も無い場合のみ空の experience / client を 1 件ずつ作る
  • 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
  • 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
  • career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
  • 追加のみになったため上書き確認ダイアログを廃止

#567 rules / docs への同期

  • .claude/rules/backend/agent.mdresume_draft 節を新設計へ更新(出力単位・プレースホルダ廃止・候補提示 + 人間の採用・LLM は採用分のみを不変条件として明記)
  • .claude/rules/backend/architecture.md の構成図を更新
  • docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの契約を記載
  • ADR-0026 の関連リンクに実装状況と、「ADR では固定しない」とした値の正本(mapper.py / schemas/agent.py)への参照表を追記

検証

make ci 相当をすべて green(backend 670 tests / web 383 tests / E2E 39 件)。lint・codegen-drift・lint-tdd・lint-adr-index も通過。

TDD 対象(mapper.py / utils/ / hooks/)は red の失敗出力を確認してから実装している。

レビュー対応

CodeRabbit の指摘 6 件(Actionable 4 + Nitpick 2)は全件を実コードで検証し、c01a7ba で対応済み。うち 2 件は実バグだった(再連携後に候補が古いまま残る/取引先ゼロの職歴へ追加できてしまう)。

ADR-0026 決定 3 / #562。
- select_repos の主キーを「最終 push 日時」から「継続期間 × 実装量」へ変更し、
直近性はタイブレークへ降格した。最終 tie-breaker は full_name 辞書順のまま
維持し、同一入力から常に同一の並びが出る完全順序を不変条件とした
- 候補を落とさず順位付けだけを行う rank_repos を新設し、select_repos はその
上位を PROJECT_LIMIT で打ち切る薄いラッパーにした(ADR-0026 決定 2 の
「機械は候補を落とさない」を #564 の候補一覧 API で使えるようにするため)
- デフォルト非選択の判定 evaluate_default_selection を追加した。継続期間が
閾値未満・topics に学習用途語を含む場合に理由コードを返す。判定結果は
デフォルト選択状態と理由表示にのみ影響し、候補一覧からは除外しない
- スコア式・重み・閾値・topics 正規化規則の正本を mapper.py の定数に置いた
(ADR に数値を複製しない)。float の丸めで同点判定がブレないよう整数演算に限定
- mutmut の only_mutate に mapper.py を追加した
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 1 / #563。
BREAKING CHANGE: GET /api/agent/resume-draft/result のレスポンスから
experiences / qualifications を削除し、projects(プロジェクト明細のリスト)に
置き換えた。web のフォーム注入は #566 で追随する。
- PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE と
チーム構成の固定値生成を削除した。GitHub から得られない値(会社・事業内容・
役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
- build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ
変更した。career_summary / self_pr は projects から独立した候補として返す
- PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む
experience / client は空(会社名・顧客名はプレースホルダを入れない)とし、
projects が 0 件なら空箱すら作らない
- agent_resume_draft.md に「役割・担当工程・チーム規模を書かない」を明記した
- make codegen-types の生成差分(web/src/api/generated.ts)を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / #564。
BREAKING CHANGE: POST /api/agent/resume-draft/run に repo_full_names(1 件以上・
上限 5 件)が必須になった。web の導線は #565 で追随する。
- GET /api/agent/resume-draft/candidates を追加した。入口フィルタを通った
リポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)と
デフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
- ドラフト生成は採用リポジトリの指定を必須にし、LLM の説明文生成を採用分のみに
限定した(コストが選択数に比例する / P1)。出力スキーマの enum も採用分で縛る
- PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、
正本を schemas/agent.py の RESUME_DRAFT_SELECTION_LIMIT へ移した(mapper が
import する。schemas → services の循環 import を避けるため)
- 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は
router で 422 にして捏造リポの構造混入を入口で止める。タスク payload 側でも
採用リポジトリ欠落・未知リポジトリを NonRetryableError で二重ガードする
- 非同期タスク構造・冪等ガード(ADR-0020)は変更していない
- make codegen-types の生成差分を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / 3 / 5 / #565#566#563 のレスポンス契約変更(experiences → projects)に web を追随させる変更で、
両 issue を分けると web の型チェックが通らない期間ができるため 1 コミットにまとめた。
#565 リポジトリ候補の選択 UI:
- GET /resume-draft/candidates を叩く useResumeDraftCandidates と、候補一覧
コンポーネント ResumeDraftCandidateList を追加した。シグナル(継続期間・実装量・
技術スタック・IaC 有無)を表示し、デフォルト非選択には理由バッジを出す
- デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
- 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
- 選択 0 件では生成ボタンを押せないようにした
- 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求で
エラートーストを出さないため。E2E で検知した)
#566 ドラフト注入の「置換」→「追加」:
- applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、
appendResumeDraftProjects を追加した。ユーザーが指定した experience /
client の projects へ追加するだけで、既存の職歴は書き換えない
- 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が
1 件も無い場合のみ空の experience / client を 1 件ずつ作る
- 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
- 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
- career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
- 追加のみになったため上書き確認ダイアログを廃止した
- ResumeFormSource から ResumeDraftResultResponse を外した(保存済み経歴書専用へ)
- 文言はすべて constants/messages.ts に追加した
E2E(resume-draft-apply / github-link)を新しい導線に合わせて更新し、全 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#567。
- .claude/rules/backend/agent.md の resume_draft 節を新設計へ更新した。
出力単位(experience → project)・プレースホルダ生成の廃止・選定が
「候補提示 + 人間の採用」であること・LLM は採用分のみ実行することを
「崩してはいけない不変条件」として明記した
- .claude/rules/backend/architecture.md の resume_draft/ 構成図を更新した
(mapper.py の責務追記・run_task.py の追加)
- docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの
契約(返す単位・バリデーション・409/422 の条件)を記載した
- ADR-0026 の関連リンクに実装状況を追記し、「ADR では固定しない」とした
スコア式・閾値・語彙・上限の正本(mapper.py / schemas/agent.py)への
参照表を足した(数値は複製せず drift を防ぐ)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The resume draft workflow now supports repository candidate discovery, user-selected generation, project-only results, PDF compatibility conversion, and targeted project injection into existing career records.

Changes

Resume draft backend

Layer / File(s)Summary
Project output and repository-selection contracts
.claude/rules/backend/*, backend/app/schemas/agent.py, backend/app/services/agent/resume_draft/mapper.py, backend/app/prompts/agent_resume_draft.md
The backend ranks repositories, exposes candidate metadata, validates selections, and returns project-centric drafts without fabricated employment data.
Validated generation and asynchronous execution
backend/app/routers/agent.py, backend/app/services/agent/resume_draft/*, backend/tests/*resume_draft*, docs/api.md
The API validates selected repositories, passes them to the worker, handles unknown repositories, and converts stored project payloads for PDF rendering.

Frontend selection and injection

Layer / File(s)Summary
Candidate selection and generation controls
web/src/api/*, web/src/components/github-link/*, web/src/hooks/useResumeDraftCandidates.ts, web/src/hooks/useResumeDraftPdf.ts, web/src/utils/resumeDraftCandidates.ts
The dashboard loads candidates, enforces the selection limit, displays selection reasons, and starts generation with selected repository names.
Targeted project injection into career forms
web/src/components/forms/*, web/src/utils/resumeImport.ts, web/src/formMappers.ts, web/e2e/resume-draft-apply.spec.ts
The injection dialog appends projects to a selected experience and client without overwriting existing data. Career summary and self-PR candidates can be applied separately.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • yusuke0610/devforge#463 — Extends the earlier resume-draft implementation across the mapper, service, API, frontend flow, and tests.
  • yusuke0610/devforge#469 — Extends the earlier asynchronous resume-draft flow with selected-repository handling.
  • yusuke0610/devforge#568 — Directly implements the ADR-0026 project-only workflow and append-based injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedDocstring coverage is 93.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passedタイトルは、ドラフトをプロジェクト明細へ変更し、リポジトリ候補の選択制を導入する主要な変更を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-implementation-order-syt0rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド web フロントエンド (web) test テスト追加・修正 agent DevForge Agent feature 新機能 labels Aug 5, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/tests/test_resume_draft_mapper.py (1)

289-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the boundary dates with date arithmetic.

The test formats the day number directly from MIN_DURATION_DAYS. If MIN_DURATION_DAYS becomes 31 or larger, the string becomes an invalid date such as 2026-01-32. _parse_date then returns None, duration_days returns 0, and the test fails for a date-format reason instead of the boundary it verifies. Derive the pushed date from a timedelta so the test stays valid for any threshold value.

♻️ Proposed refactor
 def test_default_selection_duration_threshold_boundary() -> None:
"""閾値ちょうどは選択、1 日足りなければ非選択(境界の固定)。"""
+ created = date(2026, 1, 1)
exactly = _repo(
- "o/exact", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{1 + MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/exact", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS)).isoformat()}T00:00:00Z",
)
one_short = _repo(
- "o/short", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/short", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS - 1)).isoformat()}T00:00:00Z",
)

Add the import:

-from datetime import date+from datetime import date, timedelta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/tests/test_resume_draft_mapper.py` around lines 289 - 300, Update
test_default_selection_duration_threshold_boundary to construct both pushed
timestamps using date arithmetic with timedelta from the fixed created date,
rather than interpolating day numbers from MIN_DURATION_DAYS. Add the required
timedelta import and preserve the exact-threshold and one-day-short assertions.
backend/app/services/agent/resume_draft/mapper.py (1)

157-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused select_repos and update test callers.

select_repos is only called by tests; production code now uses build_candidates or select_requested_repos. Drop the public function and replace the build_skeleton(select_repos(...)) test inputs with rank_repos(source)[:PROJECT_LIMIT].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/agent/resume_draft/mapper.py` around lines 157 - 173,
Remove the unused public select_repos function and update all test callers that
invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/backend/architecture.md:
- Around line 64-69: Update the task-handler registration documentation and
related registry comment to list both TaskType.GITHUB_LINK and
TaskType.RESUME_DRAFT, reflecting the registered ResumeDraftHandler. Replace the
stale GITHUB_LINK-only wording in dispatch_service.py and the architecture
documentation while preserving the existing descriptions.
In `@web/src/components/forms/ResumeDraftInjectDialog.tsx`:
- Around line 73-116: Update the experience-selection and append flow around
ResumeDraftInjectDialog so an experience with no clients cannot be submitted
with the default clientIndex of 0. Disable the append action and show an
explicit unavailable-target message, or otherwise filter those experiences
before selection; preserve valid appending for experiences with at least one
client and ensure appendResumeDraftProjects is never called with an invalid
client target.
In `@web/src/components/github-link/GitHubLinkDashboard.tsx`:
- Around line 74-77: Update the candidate-loading flow around
useResumeDraftCandidates and the runLink/polling lifecycle so candidates are
refreshed when the latest GitHub link result changes, including when result
remains non-null across successful links. Ensure previous-analysis candidates
are loaded during polling as needed, then trigger a reload using a
result-derived key or equivalent change signal once the new result arrives.
In `@web/src/formMappers.ts`:
- Around line 12-21: Update ResumeFormSource in web/src/formMappers.ts (lines
12-21) to omit id along with created_at and updated_at, allowing id-less
pre-save payloads. In web/src/formMappers.test.ts (lines 39-60), remove the id
property and the broad unknown cast so the test validates the intended type
directly.
---
Nitpick comments:
In `@backend/app/services/agent/resume_draft/mapper.py`:
- Around line 157-173: Remove the unused public select_repos function and update
all test callers that invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
In `@backend/tests/test_resume_draft_mapper.py`:
- Around line 289-300: Update test_default_selection_duration_threshold_boundary
to construct both pushed timestamps using date arithmetic with timedelta from
the fixed created date, rather than interpolating day numbers from
MIN_DURATION_DAYS. Add the required timedelta import and preserve the
exact-threshold and one-day-short assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b9761d-a28f-4e43-bf55-391e5d87cab8

📥 Commits

Reviewing files that changed from the base of the PR and between 01ff6f3 and 7f371c5.

📒 Files selected for processing (40)
  • .claude/rules/backend/agent.md
  • .claude/rules/backend/architecture.md
  • backend/app/messages.json
  • backend/app/prompts/agent_resume_draft.md
  • backend/app/routers/agent.py
  • backend/app/schemas/agent.py
  • backend/app/services/agent/resume_draft/draft_service.py
  • backend/app/services/agent/resume_draft/mapper.py
  • backend/app/services/agent/resume_draft/run_task.py
  • backend/pyproject.toml
  • backend/tests/test_resume_draft_api.py
  • backend/tests/test_resume_draft_mapper.py
  • backend/tests/test_resume_draft_service.py
  • backend/tests/test_worker/test_resume_draft.py
  • docs/adr/0026-resume-draft-project-scope.md
  • docs/api.md
  • web/e2e/github-link.spec.ts
  • web/e2e/resume-draft-apply.spec.ts
  • web/src/api/agent.ts
  • web/src/api/generated.ts
  • web/src/api/paths.ts
  • web/src/api/types.ts
  • web/src/components/forms/CareerResumeForm.tsx
  • web/src/components/forms/ResumeDraftInjectDialog.module.css
  • web/src/components/forms/ResumeDraftInjectDialog.tsx
  • web/src/components/github-link/GitHubLinkDashboard.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.module.css
  • web/src/components/github-link/ResumeDraftCandidateList.test.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.tsx
  • web/src/constants/messages.ts
  • web/src/formMappers.test.ts
  • web/src/formMappers.ts
  • web/src/hooks/useResumeDraftCandidates.test.ts
  • web/src/hooks/useResumeDraftCandidates.ts
  • web/src/hooks/useResumeDraftPdf.test.ts
  • web/src/hooks/useResumeDraftPdf.ts
  • web/src/utils/resumeDraftCandidates.test.ts
  • web/src/utils/resumeDraftCandidates.ts
  • web/src/utils/resumeImport.test.ts
  • web/src/utils/resumeImport.ts

Comment thread.claude/rules/backend/architecture.md
Comment threadweb/src/components/forms/ResumeDraftInjectDialog.tsx
Comment threadweb/src/components/github-link/GitHubLinkDashboard.tsx
Comment threadweb/src/formMappers.ts Outdated
PR #584 のレビュー指摘 6 件(Actionable 4 + Nitpick 2)を実コードで検証し、
いずれも再現したため修正した。
- 再連携で候補が古いままになる不具合を修正した(Functional Correctness)。
useAsyncTaskPage は transitionToPolling で result をクリアしないため、
連携済みユーザーが再連携しても Boolean(result) は true→true で変化せず、
候補の再取得が走らなかった。連携結果の分析時刻(analyzed_at)を鍵にして
取り直すようにし、旧挙動で落ちる回帰テストを追加した
- 追加先の職歴に取引先が 1 件も無い場合、追加ボタンが押せて
DRAFT_TARGET_OUT_OF_RANGE を投げていた。ボタンを無効化して理由を表示する
(追加先はユーザーが明示指定する契約のため、機械が取引先を作らない)
- ResumeFormSource から id を除外した。マッパーは id を参照しないのに必須
だったため、型の説明と実際が食い違い、テストがキャストで隠していた。
キャストを外して型どおり検証されるようにした
- 非同期タスクが GITHUB_LINK の 1 種類のみという陳腐化した記述を修正した
(dispatch_service.py / architecture.md 2 箇所)。RESUME_DRAFT は
ADR-0020 で ResumeDraftHandler として登録済み。ハンドラ構成図にも追記
- 本番から呼ばれなくなった select_repos を削除し、テストの呼び出しを
rank_repos(source)[:PROJECT_LIMIT] へ置き換えた(ミューテーション対象
ファイルの dead code を残さない)
- 継続期間の境界テストを timedelta による日付演算へ変更した
(MIN_DURATION_DAYS が 31 以上になると不正な日付文字列になり、境界とは
無関係な理由で落ちるため)
backend 668 tests / web 383 tests / E2E 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#585(PR #581 相当)が main に squash マージされ、同じ #562 の実装が両側に
別コミットとして入ったためコンフリクトしていた。
- mapper.py / test_resume_draft_mapper.py: 本ブランチ側を採用した。main 側の
select_repos / evaluate_noise は本ブランチの rank_repos /
evaluate_default_selection / build_candidates に置き換わっており、候補一覧 API・
選択 UI がこちらの API に依存しているため
- ただし main 側にしか無かった修正(継続期間の時刻成分切り捨て / PR #581 の
CodeRabbit 指摘)は本ブランチの duration_days へ移植した。_parse_datetime を
追加して UTC の aware datetime で減算する。日粒度でよい「参画中」判定は
_parse_date のまま残す
- 移植分のテスト 2 件(閾値直下 29 日 1 分の境界 / タイムゾーン混在表記)を
本ブランチの evaluate_default_selection の契約に合わせて追加した
- pyproject.toml: only_mutate の同一エントリで、コメント文言のみ本ブランチ側を採用
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaqnCny4wsD6AkBkJeTEEV
@yusuke0610yusuke0610 changed the title feat(resume-draft): 選定順を「継続期間 × 実装量」へ変更しノイズ判定を付与feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)Aug 5, 2026
@yusuke0610
yusuke0610 merged commit 3ff5d83 into mainAug 5, 2026
24 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentDevForge AgentbackendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正webフロントエンド (web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yusuke0610@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567) - #584

Merged
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc
Aug 5, 2026
Merged

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)#584
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0026 の実装。マイルストーン「ドラフト縮小 N/7」のうち #563 / #564 / #565 / #566 / #567 を依存順に実装する。

#562 について: 選定順の変更(決定 3)は本ブランチでも実装したが、並行して #585 が main へ先にマージされた。main を取り込んだ際に、本ブランチの API(rank_repos / evaluate_default_selection / build_candidates)を採用しつつ、main 側にしか無かった継続期間の時刻成分の扱いを移植して統合済み(4d5aab1)。そのため mapper.py には #562 相当の差分も残っている。

変更内容

#563 出力を project 明細へ縮小しプレースホルダ生成を廃止(決定 1)

破壊的変更: GET /api/agent/resume-draft/result のレスポンスから experiences / qualifications を削除し、projects に置き換えた。

  • PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE とチーム構成の固定値生成を削除。GitHub から得られない値(会社・事業内容・役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
  • build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ変更。career_summary / self_pr は projects から独立した候補として返す
  • PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む experience / client は空(プレースホルダを入れない)で、projects が 0 件なら空箱すら作らない

#564 候補一覧 API と選択付きドラフト生成(決定 2)

破壊的変更: POST /api/agent/resume-draft/runrepo_full_names(1 件以上・上限 5 件)が必須になった。

  • GET /api/agent/resume-draft/candidates を追加。入口フィルタを通ったリポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)とデフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
  • LLM の説明文生成を採用分のみに限定(コストが選択数に比例 / P1)。出力スキーマの enum も採用分で縛る
  • PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、正本を schemas/agent.pyRESUME_DRAFT_SELECTION_LIMIT へ移した
  • 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は router で 422。タスク payload 側でも二重ガード
  • 非同期タスク構造・冪等ガード(ADR-0020)は変更なし

#565 リポジトリ候補の選択 UI(決定 2 / 3)

  • 候補一覧のチェックボックス選択。シグナル表示とデフォルト非選択の理由バッジを出す
  • デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
  • 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
  • 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求でエラートーストを出さないため)

#566 ドラフト注入を「置換」から「追加」へ(決定 5)

  • applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、appendResumeDraftProjects を追加。ユーザーが指定した experience / client の projects へ追加するだけで、既存の職歴は書き換えない
  • 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が 1 件も無い場合のみ空の experience / client を 1 件ずつ作る
  • 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
  • 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
  • career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
  • 追加のみになったため上書き確認ダイアログを廃止

#567 rules / docs への同期

  • .claude/rules/backend/agent.mdresume_draft 節を新設計へ更新(出力単位・プレースホルダ廃止・候補提示 + 人間の採用・LLM は採用分のみを不変条件として明記)
  • .claude/rules/backend/architecture.md の構成図を更新
  • docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの契約を記載
  • ADR-0026 の関連リンクに実装状況と、「ADR では固定しない」とした値の正本(mapper.py / schemas/agent.py)への参照表を追記

検証

make ci 相当をすべて green(backend 670 tests / web 383 tests / E2E 39 件)。lint・codegen-drift・lint-tdd・lint-adr-index も通過。

TDD 対象(mapper.py / utils/ / hooks/)は red の失敗出力を確認してから実装している。

レビュー対応

CodeRabbit の指摘 6 件(Actionable 4 + Nitpick 2)は全件を実コードで検証し、c01a7ba で対応済み。うち 2 件は実バグだった(再連携後に候補が古いまま残る/取引先ゼロの職歴へ追加できてしまう)。

ADR-0026 決定 3 / #562。
- select_repos の主キーを「最終 push 日時」から「継続期間 × 実装量」へ変更し、
直近性はタイブレークへ降格した。最終 tie-breaker は full_name 辞書順のまま
維持し、同一入力から常に同一の並びが出る完全順序を不変条件とした
- 候補を落とさず順位付けだけを行う rank_repos を新設し、select_repos はその
上位を PROJECT_LIMIT で打ち切る薄いラッパーにした(ADR-0026 決定 2 の
「機械は候補を落とさない」を #564 の候補一覧 API で使えるようにするため)
- デフォルト非選択の判定 evaluate_default_selection を追加した。継続期間が
閾値未満・topics に学習用途語を含む場合に理由コードを返す。判定結果は
デフォルト選択状態と理由表示にのみ影響し、候補一覧からは除外しない
- スコア式・重み・閾値・topics 正規化規則の正本を mapper.py の定数に置いた
(ADR に数値を複製しない)。float の丸めで同点判定がブレないよう整数演算に限定
- mutmut の only_mutate に mapper.py を追加した
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 1 / #563。
BREAKING CHANGE: GET /api/agent/resume-draft/result のレスポンスから
experiences / qualifications を削除し、projects(プロジェクト明細のリスト)に
置き換えた。web のフォーム注入は #566 で追随する。
- PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE と
チーム構成の固定値生成を削除した。GitHub から得られない値(会社・事業内容・
役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
- build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ
変更した。career_summary / self_pr は projects から独立した候補として返す
- PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む
experience / client は空(会社名・顧客名はプレースホルダを入れない)とし、
projects が 0 件なら空箱すら作らない
- agent_resume_draft.md に「役割・担当工程・チーム規模を書かない」を明記した
- make codegen-types の生成差分(web/src/api/generated.ts)を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / #564。
BREAKING CHANGE: POST /api/agent/resume-draft/run に repo_full_names(1 件以上・
上限 5 件)が必須になった。web の導線は #565 で追随する。
- GET /api/agent/resume-draft/candidates を追加した。入口フィルタを通った
リポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)と
デフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
- ドラフト生成は採用リポジトリの指定を必須にし、LLM の説明文生成を採用分のみに
限定した(コストが選択数に比例する / P1)。出力スキーマの enum も採用分で縛る
- PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、
正本を schemas/agent.py の RESUME_DRAFT_SELECTION_LIMIT へ移した(mapper が
import する。schemas → services の循環 import を避けるため)
- 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は
router で 422 にして捏造リポの構造混入を入口で止める。タスク payload 側でも
採用リポジトリ欠落・未知リポジトリを NonRetryableError で二重ガードする
- 非同期タスク構造・冪等ガード(ADR-0020)は変更していない
- make codegen-types の生成差分を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / 3 / 5 / #565#566#563 のレスポンス契約変更(experiences → projects)に web を追随させる変更で、
両 issue を分けると web の型チェックが通らない期間ができるため 1 コミットにまとめた。
#565 リポジトリ候補の選択 UI:
- GET /resume-draft/candidates を叩く useResumeDraftCandidates と、候補一覧
コンポーネント ResumeDraftCandidateList を追加した。シグナル(継続期間・実装量・
技術スタック・IaC 有無)を表示し、デフォルト非選択には理由バッジを出す
- デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
- 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
- 選択 0 件では生成ボタンを押せないようにした
- 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求で
エラートーストを出さないため。E2E で検知した)
#566 ドラフト注入の「置換」→「追加」:
- applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、
appendResumeDraftProjects を追加した。ユーザーが指定した experience /
client の projects へ追加するだけで、既存の職歴は書き換えない
- 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が
1 件も無い場合のみ空の experience / client を 1 件ずつ作る
- 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
- 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
- career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
- 追加のみになったため上書き確認ダイアログを廃止した
- ResumeFormSource から ResumeDraftResultResponse を外した(保存済み経歴書専用へ)
- 文言はすべて constants/messages.ts に追加した
E2E(resume-draft-apply / github-link)を新しい導線に合わせて更新し、全 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#567。
- .claude/rules/backend/agent.md の resume_draft 節を新設計へ更新した。
出力単位(experience → project)・プレースホルダ生成の廃止・選定が
「候補提示 + 人間の採用」であること・LLM は採用分のみ実行することを
「崩してはいけない不変条件」として明記した
- .claude/rules/backend/architecture.md の resume_draft/ 構成図を更新した
(mapper.py の責務追記・run_task.py の追加)
- docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの
契約(返す単位・バリデーション・409/422 の条件)を記載した
- ADR-0026 の関連リンクに実装状況を追記し、「ADR では固定しない」とした
スコア式・閾値・語彙・上限の正本(mapper.py / schemas/agent.py)への
参照表を足した(数値は複製せず drift を防ぐ)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The resume draft workflow now supports repository candidate discovery, user-selected generation, project-only results, PDF compatibility conversion, and targeted project injection into existing career records.

Changes

Resume draft backend

Layer / File(s)Summary
Project output and repository-selection contracts
.claude/rules/backend/*, backend/app/schemas/agent.py, backend/app/services/agent/resume_draft/mapper.py, backend/app/prompts/agent_resume_draft.md
The backend ranks repositories, exposes candidate metadata, validates selections, and returns project-centric drafts without fabricated employment data.
Validated generation and asynchronous execution
backend/app/routers/agent.py, backend/app/services/agent/resume_draft/*, backend/tests/*resume_draft*, docs/api.md
The API validates selected repositories, passes them to the worker, handles unknown repositories, and converts stored project payloads for PDF rendering.

Frontend selection and injection

Layer / File(s)Summary
Candidate selection and generation controls
web/src/api/*, web/src/components/github-link/*, web/src/hooks/useResumeDraftCandidates.ts, web/src/hooks/useResumeDraftPdf.ts, web/src/utils/resumeDraftCandidates.ts
The dashboard loads candidates, enforces the selection limit, displays selection reasons, and starts generation with selected repository names.
Targeted project injection into career forms
web/src/components/forms/*, web/src/utils/resumeImport.ts, web/src/formMappers.ts, web/e2e/resume-draft-apply.spec.ts
The injection dialog appends projects to a selected experience and client without overwriting existing data. Career summary and self-PR candidates can be applied separately.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • yusuke0610/devforge#463 — Extends the earlier resume-draft implementation across the mapper, service, API, frontend flow, and tests.
  • yusuke0610/devforge#469 — Extends the earlier asynchronous resume-draft flow with selected-repository handling.
  • yusuke0610/devforge#568 — Directly implements the ADR-0026 project-only workflow and append-based injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedDocstring coverage is 93.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passedタイトルは、ドラフトをプロジェクト明細へ変更し、リポジトリ候補の選択制を導入する主要な変更を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-implementation-order-syt0rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド web フロントエンド (web) test テスト追加・修正 agent DevForge Agent feature 新機能 labels Aug 5, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/tests/test_resume_draft_mapper.py (1)

289-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the boundary dates with date arithmetic.

The test formats the day number directly from MIN_DURATION_DAYS. If MIN_DURATION_DAYS becomes 31 or larger, the string becomes an invalid date such as 2026-01-32. _parse_date then returns None, duration_days returns 0, and the test fails for a date-format reason instead of the boundary it verifies. Derive the pushed date from a timedelta so the test stays valid for any threshold value.

♻️ Proposed refactor
 def test_default_selection_duration_threshold_boundary() -> None:
"""閾値ちょうどは選択、1 日足りなければ非選択(境界の固定)。"""
+ created = date(2026, 1, 1)
exactly = _repo(
- "o/exact", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{1 + MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/exact", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS)).isoformat()}T00:00:00Z",
)
one_short = _repo(
- "o/short", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/short", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS - 1)).isoformat()}T00:00:00Z",
)

Add the import:

-from datetime import date+from datetime import date, timedelta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/tests/test_resume_draft_mapper.py` around lines 289 - 300, Update
test_default_selection_duration_threshold_boundary to construct both pushed
timestamps using date arithmetic with timedelta from the fixed created date,
rather than interpolating day numbers from MIN_DURATION_DAYS. Add the required
timedelta import and preserve the exact-threshold and one-day-short assertions.
backend/app/services/agent/resume_draft/mapper.py (1)

157-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused select_repos and update test callers.

select_repos is only called by tests; production code now uses build_candidates or select_requested_repos. Drop the public function and replace the build_skeleton(select_repos(...)) test inputs with rank_repos(source)[:PROJECT_LIMIT].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/agent/resume_draft/mapper.py` around lines 157 - 173,
Remove the unused public select_repos function and update all test callers that
invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/backend/architecture.md:
- Around line 64-69: Update the task-handler registration documentation and
related registry comment to list both TaskType.GITHUB_LINK and
TaskType.RESUME_DRAFT, reflecting the registered ResumeDraftHandler. Replace the
stale GITHUB_LINK-only wording in dispatch_service.py and the architecture
documentation while preserving the existing descriptions.
In `@web/src/components/forms/ResumeDraftInjectDialog.tsx`:
- Around line 73-116: Update the experience-selection and append flow around
ResumeDraftInjectDialog so an experience with no clients cannot be submitted
with the default clientIndex of 0. Disable the append action and show an
explicit unavailable-target message, or otherwise filter those experiences
before selection; preserve valid appending for experiences with at least one
client and ensure appendResumeDraftProjects is never called with an invalid
client target.
In `@web/src/components/github-link/GitHubLinkDashboard.tsx`:
- Around line 74-77: Update the candidate-loading flow around
useResumeDraftCandidates and the runLink/polling lifecycle so candidates are
refreshed when the latest GitHub link result changes, including when result
remains non-null across successful links. Ensure previous-analysis candidates
are loaded during polling as needed, then trigger a reload using a
result-derived key or equivalent change signal once the new result arrives.
In `@web/src/formMappers.ts`:
- Around line 12-21: Update ResumeFormSource in web/src/formMappers.ts (lines
12-21) to omit id along with created_at and updated_at, allowing id-less
pre-save payloads. In web/src/formMappers.test.ts (lines 39-60), remove the id
property and the broad unknown cast so the test validates the intended type
directly.
---
Nitpick comments:
In `@backend/app/services/agent/resume_draft/mapper.py`:
- Around line 157-173: Remove the unused public select_repos function and update
all test callers that invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
In `@backend/tests/test_resume_draft_mapper.py`:
- Around line 289-300: Update test_default_selection_duration_threshold_boundary
to construct both pushed timestamps using date arithmetic with timedelta from
the fixed created date, rather than interpolating day numbers from
MIN_DURATION_DAYS. Add the required timedelta import and preserve the
exact-threshold and one-day-short assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b9761d-a28f-4e43-bf55-391e5d87cab8

📥 Commits

Reviewing files that changed from the base of the PR and between 01ff6f3 and 7f371c5.

📒 Files selected for processing (40)
  • .claude/rules/backend/agent.md
  • .claude/rules/backend/architecture.md
  • backend/app/messages.json
  • backend/app/prompts/agent_resume_draft.md
  • backend/app/routers/agent.py
  • backend/app/schemas/agent.py
  • backend/app/services/agent/resume_draft/draft_service.py
  • backend/app/services/agent/resume_draft/mapper.py
  • backend/app/services/agent/resume_draft/run_task.py
  • backend/pyproject.toml
  • backend/tests/test_resume_draft_api.py
  • backend/tests/test_resume_draft_mapper.py
  • backend/tests/test_resume_draft_service.py
  • backend/tests/test_worker/test_resume_draft.py
  • docs/adr/0026-resume-draft-project-scope.md
  • docs/api.md
  • web/e2e/github-link.spec.ts
  • web/e2e/resume-draft-apply.spec.ts
  • web/src/api/agent.ts
  • web/src/api/generated.ts
  • web/src/api/paths.ts
  • web/src/api/types.ts
  • web/src/components/forms/CareerResumeForm.tsx
  • web/src/components/forms/ResumeDraftInjectDialog.module.css
  • web/src/components/forms/ResumeDraftInjectDialog.tsx
  • web/src/components/github-link/GitHubLinkDashboard.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.module.css
  • web/src/components/github-link/ResumeDraftCandidateList.test.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.tsx
  • web/src/constants/messages.ts
  • web/src/formMappers.test.ts
  • web/src/formMappers.ts
  • web/src/hooks/useResumeDraftCandidates.test.ts
  • web/src/hooks/useResumeDraftCandidates.ts
  • web/src/hooks/useResumeDraftPdf.test.ts
  • web/src/hooks/useResumeDraftPdf.ts
  • web/src/utils/resumeDraftCandidates.test.ts
  • web/src/utils/resumeDraftCandidates.ts
  • web/src/utils/resumeImport.test.ts
  • web/src/utils/resumeImport.ts

Comment thread.claude/rules/backend/architecture.md
Comment threadweb/src/components/forms/ResumeDraftInjectDialog.tsx
Comment threadweb/src/components/github-link/GitHubLinkDashboard.tsx
Comment threadweb/src/formMappers.ts Outdated
PR #584 のレビュー指摘 6 件(Actionable 4 + Nitpick 2)を実コードで検証し、
いずれも再現したため修正した。
- 再連携で候補が古いままになる不具合を修正した(Functional Correctness)。
useAsyncTaskPage は transitionToPolling で result をクリアしないため、
連携済みユーザーが再連携しても Boolean(result) は true→true で変化せず、
候補の再取得が走らなかった。連携結果の分析時刻(analyzed_at)を鍵にして
取り直すようにし、旧挙動で落ちる回帰テストを追加した
- 追加先の職歴に取引先が 1 件も無い場合、追加ボタンが押せて
DRAFT_TARGET_OUT_OF_RANGE を投げていた。ボタンを無効化して理由を表示する
(追加先はユーザーが明示指定する契約のため、機械が取引先を作らない)
- ResumeFormSource から id を除外した。マッパーは id を参照しないのに必須
だったため、型の説明と実際が食い違い、テストがキャストで隠していた。
キャストを外して型どおり検証されるようにした
- 非同期タスクが GITHUB_LINK の 1 種類のみという陳腐化した記述を修正した
(dispatch_service.py / architecture.md 2 箇所)。RESUME_DRAFT は
ADR-0020 で ResumeDraftHandler として登録済み。ハンドラ構成図にも追記
- 本番から呼ばれなくなった select_repos を削除し、テストの呼び出しを
rank_repos(source)[:PROJECT_LIMIT] へ置き換えた(ミューテーション対象
ファイルの dead code を残さない)
- 継続期間の境界テストを timedelta による日付演算へ変更した
(MIN_DURATION_DAYS が 31 以上になると不正な日付文字列になり、境界とは
無関係な理由で落ちるため)
backend 668 tests / web 383 tests / E2E 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#585(PR #581 相当)が main に squash マージされ、同じ #562 の実装が両側に
別コミットとして入ったためコンフリクトしていた。
- mapper.py / test_resume_draft_mapper.py: 本ブランチ側を採用した。main 側の
select_repos / evaluate_noise は本ブランチの rank_repos /
evaluate_default_selection / build_candidates に置き換わっており、候補一覧 API・
選択 UI がこちらの API に依存しているため
- ただし main 側にしか無かった修正(継続期間の時刻成分切り捨て / PR #581 の
CodeRabbit 指摘)は本ブランチの duration_days へ移植した。_parse_datetime を
追加して UTC の aware datetime で減算する。日粒度でよい「参画中」判定は
_parse_date のまま残す
- 移植分のテスト 2 件(閾値直下 29 日 1 分の境界 / タイムゾーン混在表記)を
本ブランチの evaluate_default_selection の契約に合わせて追加した
- pyproject.toml: only_mutate の同一エントリで、コメント文言のみ本ブランチ側を採用
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaqnCny4wsD6AkBkJeTEEV
@yusuke0610yusuke0610 changed the title feat(resume-draft): 選定順を「継続期間 × 実装量」へ変更しノイズ判定を付与feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)Aug 5, 2026
@yusuke0610
yusuke0610 merged commit 3ff5d83 into mainAug 5, 2026
24 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentDevForge AgentbackendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正webフロントエンド (web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yusuke0610@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567) - #584

Merged
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc
Aug 5, 2026
Merged

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)#584
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0026 の実装。マイルストーン「ドラフト縮小 N/7」のうち #563 / #564 / #565 / #566 / #567 を依存順に実装する。

#562 について: 選定順の変更(決定 3)は本ブランチでも実装したが、並行して #585 が main へ先にマージされた。main を取り込んだ際に、本ブランチの API(rank_repos / evaluate_default_selection / build_candidates)を採用しつつ、main 側にしか無かった継続期間の時刻成分の扱いを移植して統合済み(4d5aab1)。そのため mapper.py には #562 相当の差分も残っている。

変更内容

#563 出力を project 明細へ縮小しプレースホルダ生成を廃止(決定 1)

破壊的変更: GET /api/agent/resume-draft/result のレスポンスから experiences / qualifications を削除し、projects に置き換えた。

  • PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE とチーム構成の固定値生成を削除。GitHub から得られない値(会社・事業内容・役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
  • build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ変更。career_summary / self_pr は projects から独立した候補として返す
  • PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む experience / client は空(プレースホルダを入れない)で、projects が 0 件なら空箱すら作らない

#564 候補一覧 API と選択付きドラフト生成(決定 2)

破壊的変更: POST /api/agent/resume-draft/runrepo_full_names(1 件以上・上限 5 件)が必須になった。

  • GET /api/agent/resume-draft/candidates を追加。入口フィルタを通ったリポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)とデフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
  • LLM の説明文生成を採用分のみに限定(コストが選択数に比例 / P1)。出力スキーマの enum も採用分で縛る
  • PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、正本を schemas/agent.pyRESUME_DRAFT_SELECTION_LIMIT へ移した
  • 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は router で 422。タスク payload 側でも二重ガード
  • 非同期タスク構造・冪等ガード(ADR-0020)は変更なし

#565 リポジトリ候補の選択 UI(決定 2 / 3)

  • 候補一覧のチェックボックス選択。シグナル表示とデフォルト非選択の理由バッジを出す
  • デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
  • 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
  • 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求でエラートーストを出さないため)

#566 ドラフト注入を「置換」から「追加」へ(決定 5)

  • applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、appendResumeDraftProjects を追加。ユーザーが指定した experience / client の projects へ追加するだけで、既存の職歴は書き換えない
  • 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が 1 件も無い場合のみ空の experience / client を 1 件ずつ作る
  • 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
  • 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
  • career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
  • 追加のみになったため上書き確認ダイアログを廃止

#567 rules / docs への同期

  • .claude/rules/backend/agent.mdresume_draft 節を新設計へ更新(出力単位・プレースホルダ廃止・候補提示 + 人間の採用・LLM は採用分のみを不変条件として明記)
  • .claude/rules/backend/architecture.md の構成図を更新
  • docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの契約を記載
  • ADR-0026 の関連リンクに実装状況と、「ADR では固定しない」とした値の正本(mapper.py / schemas/agent.py)への参照表を追記

検証

make ci 相当をすべて green(backend 670 tests / web 383 tests / E2E 39 件)。lint・codegen-drift・lint-tdd・lint-adr-index も通過。

TDD 対象(mapper.py / utils/ / hooks/)は red の失敗出力を確認してから実装している。

レビュー対応

CodeRabbit の指摘 6 件(Actionable 4 + Nitpick 2)は全件を実コードで検証し、c01a7ba で対応済み。うち 2 件は実バグだった(再連携後に候補が古いまま残る/取引先ゼロの職歴へ追加できてしまう)。

ADR-0026 決定 3 / #562。
- select_repos の主キーを「最終 push 日時」から「継続期間 × 実装量」へ変更し、
直近性はタイブレークへ降格した。最終 tie-breaker は full_name 辞書順のまま
維持し、同一入力から常に同一の並びが出る完全順序を不変条件とした
- 候補を落とさず順位付けだけを行う rank_repos を新設し、select_repos はその
上位を PROJECT_LIMIT で打ち切る薄いラッパーにした(ADR-0026 決定 2 の
「機械は候補を落とさない」を #564 の候補一覧 API で使えるようにするため)
- デフォルト非選択の判定 evaluate_default_selection を追加した。継続期間が
閾値未満・topics に学習用途語を含む場合に理由コードを返す。判定結果は
デフォルト選択状態と理由表示にのみ影響し、候補一覧からは除外しない
- スコア式・重み・閾値・topics 正規化規則の正本を mapper.py の定数に置いた
(ADR に数値を複製しない)。float の丸めで同点判定がブレないよう整数演算に限定
- mutmut の only_mutate に mapper.py を追加した
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 1 / #563。
BREAKING CHANGE: GET /api/agent/resume-draft/result のレスポンスから
experiences / qualifications を削除し、projects(プロジェクト明細のリスト)に
置き換えた。web のフォーム注入は #566 で追随する。
- PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE と
チーム構成の固定値生成を削除した。GitHub から得られない値(会社・事業内容・
役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
- build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ
変更した。career_summary / self_pr は projects から独立した候補として返す
- PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む
experience / client は空(会社名・顧客名はプレースホルダを入れない)とし、
projects が 0 件なら空箱すら作らない
- agent_resume_draft.md に「役割・担当工程・チーム規模を書かない」を明記した
- make codegen-types の生成差分(web/src/api/generated.ts)を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / #564。
BREAKING CHANGE: POST /api/agent/resume-draft/run に repo_full_names(1 件以上・
上限 5 件)が必須になった。web の導線は #565 で追随する。
- GET /api/agent/resume-draft/candidates を追加した。入口フィルタを通った
リポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)と
デフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
- ドラフト生成は採用リポジトリの指定を必須にし、LLM の説明文生成を採用分のみに
限定した(コストが選択数に比例する / P1)。出力スキーマの enum も採用分で縛る
- PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、
正本を schemas/agent.py の RESUME_DRAFT_SELECTION_LIMIT へ移した(mapper が
import する。schemas → services の循環 import を避けるため)
- 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は
router で 422 にして捏造リポの構造混入を入口で止める。タスク payload 側でも
採用リポジトリ欠落・未知リポジトリを NonRetryableError で二重ガードする
- 非同期タスク構造・冪等ガード(ADR-0020)は変更していない
- make codegen-types の生成差分を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / 3 / 5 / #565#566#563 のレスポンス契約変更(experiences → projects)に web を追随させる変更で、
両 issue を分けると web の型チェックが通らない期間ができるため 1 コミットにまとめた。
#565 リポジトリ候補の選択 UI:
- GET /resume-draft/candidates を叩く useResumeDraftCandidates と、候補一覧
コンポーネント ResumeDraftCandidateList を追加した。シグナル(継続期間・実装量・
技術スタック・IaC 有無)を表示し、デフォルト非選択には理由バッジを出す
- デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
- 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
- 選択 0 件では生成ボタンを押せないようにした
- 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求で
エラートーストを出さないため。E2E で検知した)
#566 ドラフト注入の「置換」→「追加」:
- applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、
appendResumeDraftProjects を追加した。ユーザーが指定した experience /
client の projects へ追加するだけで、既存の職歴は書き換えない
- 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が
1 件も無い場合のみ空の experience / client を 1 件ずつ作る
- 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
- 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
- career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
- 追加のみになったため上書き確認ダイアログを廃止した
- ResumeFormSource から ResumeDraftResultResponse を外した(保存済み経歴書専用へ)
- 文言はすべて constants/messages.ts に追加した
E2E(resume-draft-apply / github-link)を新しい導線に合わせて更新し、全 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#567。
- .claude/rules/backend/agent.md の resume_draft 節を新設計へ更新した。
出力単位(experience → project)・プレースホルダ生成の廃止・選定が
「候補提示 + 人間の採用」であること・LLM は採用分のみ実行することを
「崩してはいけない不変条件」として明記した
- .claude/rules/backend/architecture.md の resume_draft/ 構成図を更新した
(mapper.py の責務追記・run_task.py の追加)
- docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの
契約(返す単位・バリデーション・409/422 の条件)を記載した
- ADR-0026 の関連リンクに実装状況を追記し、「ADR では固定しない」とした
スコア式・閾値・語彙・上限の正本(mapper.py / schemas/agent.py)への
参照表を足した(数値は複製せず drift を防ぐ)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The resume draft workflow now supports repository candidate discovery, user-selected generation, project-only results, PDF compatibility conversion, and targeted project injection into existing career records.

Changes

Resume draft backend

Layer / File(s)Summary
Project output and repository-selection contracts
.claude/rules/backend/*, backend/app/schemas/agent.py, backend/app/services/agent/resume_draft/mapper.py, backend/app/prompts/agent_resume_draft.md
The backend ranks repositories, exposes candidate metadata, validates selections, and returns project-centric drafts without fabricated employment data.
Validated generation and asynchronous execution
backend/app/routers/agent.py, backend/app/services/agent/resume_draft/*, backend/tests/*resume_draft*, docs/api.md
The API validates selected repositories, passes them to the worker, handles unknown repositories, and converts stored project payloads for PDF rendering.

Frontend selection and injection

Layer / File(s)Summary
Candidate selection and generation controls
web/src/api/*, web/src/components/github-link/*, web/src/hooks/useResumeDraftCandidates.ts, web/src/hooks/useResumeDraftPdf.ts, web/src/utils/resumeDraftCandidates.ts
The dashboard loads candidates, enforces the selection limit, displays selection reasons, and starts generation with selected repository names.
Targeted project injection into career forms
web/src/components/forms/*, web/src/utils/resumeImport.ts, web/src/formMappers.ts, web/e2e/resume-draft-apply.spec.ts
The injection dialog appends projects to a selected experience and client without overwriting existing data. Career summary and self-PR candidates can be applied separately.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • yusuke0610/devforge#463 — Extends the earlier resume-draft implementation across the mapper, service, API, frontend flow, and tests.
  • yusuke0610/devforge#469 — Extends the earlier asynchronous resume-draft flow with selected-repository handling.
  • yusuke0610/devforge#568 — Directly implements the ADR-0026 project-only workflow and append-based injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedDocstring coverage is 93.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passedタイトルは、ドラフトをプロジェクト明細へ変更し、リポジトリ候補の選択制を導入する主要な変更を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-implementation-order-syt0rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド web フロントエンド (web) test テスト追加・修正 agent DevForge Agent feature 新機能 labels Aug 5, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/tests/test_resume_draft_mapper.py (1)

289-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the boundary dates with date arithmetic.

The test formats the day number directly from MIN_DURATION_DAYS. If MIN_DURATION_DAYS becomes 31 or larger, the string becomes an invalid date such as 2026-01-32. _parse_date then returns None, duration_days returns 0, and the test fails for a date-format reason instead of the boundary it verifies. Derive the pushed date from a timedelta so the test stays valid for any threshold value.

♻️ Proposed refactor
 def test_default_selection_duration_threshold_boundary() -> None:
"""閾値ちょうどは選択、1 日足りなければ非選択(境界の固定)。"""
+ created = date(2026, 1, 1)
exactly = _repo(
- "o/exact", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{1 + MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/exact", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS)).isoformat()}T00:00:00Z",
)
one_short = _repo(
- "o/short", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/short", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS - 1)).isoformat()}T00:00:00Z",
)

Add the import:

-from datetime import date+from datetime import date, timedelta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/tests/test_resume_draft_mapper.py` around lines 289 - 300, Update
test_default_selection_duration_threshold_boundary to construct both pushed
timestamps using date arithmetic with timedelta from the fixed created date,
rather than interpolating day numbers from MIN_DURATION_DAYS. Add the required
timedelta import and preserve the exact-threshold and one-day-short assertions.
backend/app/services/agent/resume_draft/mapper.py (1)

157-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused select_repos and update test callers.

select_repos is only called by tests; production code now uses build_candidates or select_requested_repos. Drop the public function and replace the build_skeleton(select_repos(...)) test inputs with rank_repos(source)[:PROJECT_LIMIT].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/agent/resume_draft/mapper.py` around lines 157 - 173,
Remove the unused public select_repos function and update all test callers that
invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/backend/architecture.md:
- Around line 64-69: Update the task-handler registration documentation and
related registry comment to list both TaskType.GITHUB_LINK and
TaskType.RESUME_DRAFT, reflecting the registered ResumeDraftHandler. Replace the
stale GITHUB_LINK-only wording in dispatch_service.py and the architecture
documentation while preserving the existing descriptions.
In `@web/src/components/forms/ResumeDraftInjectDialog.tsx`:
- Around line 73-116: Update the experience-selection and append flow around
ResumeDraftInjectDialog so an experience with no clients cannot be submitted
with the default clientIndex of 0. Disable the append action and show an
explicit unavailable-target message, or otherwise filter those experiences
before selection; preserve valid appending for experiences with at least one
client and ensure appendResumeDraftProjects is never called with an invalid
client target.
In `@web/src/components/github-link/GitHubLinkDashboard.tsx`:
- Around line 74-77: Update the candidate-loading flow around
useResumeDraftCandidates and the runLink/polling lifecycle so candidates are
refreshed when the latest GitHub link result changes, including when result
remains non-null across successful links. Ensure previous-analysis candidates
are loaded during polling as needed, then trigger a reload using a
result-derived key or equivalent change signal once the new result arrives.
In `@web/src/formMappers.ts`:
- Around line 12-21: Update ResumeFormSource in web/src/formMappers.ts (lines
12-21) to omit id along with created_at and updated_at, allowing id-less
pre-save payloads. In web/src/formMappers.test.ts (lines 39-60), remove the id
property and the broad unknown cast so the test validates the intended type
directly.
---
Nitpick comments:
In `@backend/app/services/agent/resume_draft/mapper.py`:
- Around line 157-173: Remove the unused public select_repos function and update
all test callers that invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
In `@backend/tests/test_resume_draft_mapper.py`:
- Around line 289-300: Update test_default_selection_duration_threshold_boundary
to construct both pushed timestamps using date arithmetic with timedelta from
the fixed created date, rather than interpolating day numbers from
MIN_DURATION_DAYS. Add the required timedelta import and preserve the
exact-threshold and one-day-short assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b9761d-a28f-4e43-bf55-391e5d87cab8

📥 Commits

Reviewing files that changed from the base of the PR and between 01ff6f3 and 7f371c5.

📒 Files selected for processing (40)
  • .claude/rules/backend/agent.md
  • .claude/rules/backend/architecture.md
  • backend/app/messages.json
  • backend/app/prompts/agent_resume_draft.md
  • backend/app/routers/agent.py
  • backend/app/schemas/agent.py
  • backend/app/services/agent/resume_draft/draft_service.py
  • backend/app/services/agent/resume_draft/mapper.py
  • backend/app/services/agent/resume_draft/run_task.py
  • backend/pyproject.toml
  • backend/tests/test_resume_draft_api.py
  • backend/tests/test_resume_draft_mapper.py
  • backend/tests/test_resume_draft_service.py
  • backend/tests/test_worker/test_resume_draft.py
  • docs/adr/0026-resume-draft-project-scope.md
  • docs/api.md
  • web/e2e/github-link.spec.ts
  • web/e2e/resume-draft-apply.spec.ts
  • web/src/api/agent.ts
  • web/src/api/generated.ts
  • web/src/api/paths.ts
  • web/src/api/types.ts
  • web/src/components/forms/CareerResumeForm.tsx
  • web/src/components/forms/ResumeDraftInjectDialog.module.css
  • web/src/components/forms/ResumeDraftInjectDialog.tsx
  • web/src/components/github-link/GitHubLinkDashboard.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.module.css
  • web/src/components/github-link/ResumeDraftCandidateList.test.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.tsx
  • web/src/constants/messages.ts
  • web/src/formMappers.test.ts
  • web/src/formMappers.ts
  • web/src/hooks/useResumeDraftCandidates.test.ts
  • web/src/hooks/useResumeDraftCandidates.ts
  • web/src/hooks/useResumeDraftPdf.test.ts
  • web/src/hooks/useResumeDraftPdf.ts
  • web/src/utils/resumeDraftCandidates.test.ts
  • web/src/utils/resumeDraftCandidates.ts
  • web/src/utils/resumeImport.test.ts
  • web/src/utils/resumeImport.ts

Comment thread.claude/rules/backend/architecture.md
Comment threadweb/src/components/forms/ResumeDraftInjectDialog.tsx
Comment threadweb/src/components/github-link/GitHubLinkDashboard.tsx
Comment threadweb/src/formMappers.ts Outdated
PR #584 のレビュー指摘 6 件(Actionable 4 + Nitpick 2)を実コードで検証し、
いずれも再現したため修正した。
- 再連携で候補が古いままになる不具合を修正した(Functional Correctness)。
useAsyncTaskPage は transitionToPolling で result をクリアしないため、
連携済みユーザーが再連携しても Boolean(result) は true→true で変化せず、
候補の再取得が走らなかった。連携結果の分析時刻(analyzed_at)を鍵にして
取り直すようにし、旧挙動で落ちる回帰テストを追加した
- 追加先の職歴に取引先が 1 件も無い場合、追加ボタンが押せて
DRAFT_TARGET_OUT_OF_RANGE を投げていた。ボタンを無効化して理由を表示する
(追加先はユーザーが明示指定する契約のため、機械が取引先を作らない)
- ResumeFormSource から id を除外した。マッパーは id を参照しないのに必須
だったため、型の説明と実際が食い違い、テストがキャストで隠していた。
キャストを外して型どおり検証されるようにした
- 非同期タスクが GITHUB_LINK の 1 種類のみという陳腐化した記述を修正した
(dispatch_service.py / architecture.md 2 箇所)。RESUME_DRAFT は
ADR-0020 で ResumeDraftHandler として登録済み。ハンドラ構成図にも追記
- 本番から呼ばれなくなった select_repos を削除し、テストの呼び出しを
rank_repos(source)[:PROJECT_LIMIT] へ置き換えた(ミューテーション対象
ファイルの dead code を残さない)
- 継続期間の境界テストを timedelta による日付演算へ変更した
(MIN_DURATION_DAYS が 31 以上になると不正な日付文字列になり、境界とは
無関係な理由で落ちるため)
backend 668 tests / web 383 tests / E2E 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#585(PR #581 相当)が main に squash マージされ、同じ #562 の実装が両側に
別コミットとして入ったためコンフリクトしていた。
- mapper.py / test_resume_draft_mapper.py: 本ブランチ側を採用した。main 側の
select_repos / evaluate_noise は本ブランチの rank_repos /
evaluate_default_selection / build_candidates に置き換わっており、候補一覧 API・
選択 UI がこちらの API に依存しているため
- ただし main 側にしか無かった修正(継続期間の時刻成分切り捨て / PR #581 の
CodeRabbit 指摘)は本ブランチの duration_days へ移植した。_parse_datetime を
追加して UTC の aware datetime で減算する。日粒度でよい「参画中」判定は
_parse_date のまま残す
- 移植分のテスト 2 件(閾値直下 29 日 1 分の境界 / タイムゾーン混在表記)を
本ブランチの evaluate_default_selection の契約に合わせて追加した
- pyproject.toml: only_mutate の同一エントリで、コメント文言のみ本ブランチ側を採用
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaqnCny4wsD6AkBkJeTEEV
@yusuke0610yusuke0610 changed the title feat(resume-draft): 選定順を「継続期間 × 実装量」へ変更しノイズ判定を付与feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)Aug 5, 2026
@yusuke0610
yusuke0610 merged commit 3ff5d83 into mainAug 5, 2026
24 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentDevForge AgentbackendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正webフロントエンド (web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yusuke0610@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567) - #584

Merged
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc
Aug 5, 2026
Merged

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)#584
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0026 の実装。マイルストーン「ドラフト縮小 N/7」のうち #563 / #564 / #565 / #566 / #567 を依存順に実装する。

#562 について: 選定順の変更(決定 3)は本ブランチでも実装したが、並行して #585 が main へ先にマージされた。main を取り込んだ際に、本ブランチの API(rank_repos / evaluate_default_selection / build_candidates)を採用しつつ、main 側にしか無かった継続期間の時刻成分の扱いを移植して統合済み(4d5aab1)。そのため mapper.py には #562 相当の差分も残っている。

変更内容

#563 出力を project 明細へ縮小しプレースホルダ生成を廃止(決定 1)

破壊的変更: GET /api/agent/resume-draft/result のレスポンスから experiences / qualifications を削除し、projects に置き換えた。

  • PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE とチーム構成の固定値生成を削除。GitHub から得られない値(会社・事業内容・役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
  • build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ変更。career_summary / self_pr は projects から独立した候補として返す
  • PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む experience / client は空(プレースホルダを入れない)で、projects が 0 件なら空箱すら作らない

#564 候補一覧 API と選択付きドラフト生成(決定 2)

破壊的変更: POST /api/agent/resume-draft/runrepo_full_names(1 件以上・上限 5 件)が必須になった。

  • GET /api/agent/resume-draft/candidates を追加。入口フィルタを通ったリポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)とデフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
  • LLM の説明文生成を採用分のみに限定(コストが選択数に比例 / P1)。出力スキーマの enum も採用分で縛る
  • PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、正本を schemas/agent.pyRESUME_DRAFT_SELECTION_LIMIT へ移した
  • 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は router で 422。タスク payload 側でも二重ガード
  • 非同期タスク構造・冪等ガード(ADR-0020)は変更なし

#565 リポジトリ候補の選択 UI(決定 2 / 3)

  • 候補一覧のチェックボックス選択。シグナル表示とデフォルト非選択の理由バッジを出す
  • デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
  • 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
  • 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求でエラートーストを出さないため)

#566 ドラフト注入を「置換」から「追加」へ(決定 5)

  • applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、appendResumeDraftProjects を追加。ユーザーが指定した experience / client の projects へ追加するだけで、既存の職歴は書き換えない
  • 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が 1 件も無い場合のみ空の experience / client を 1 件ずつ作る
  • 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
  • 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
  • career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
  • 追加のみになったため上書き確認ダイアログを廃止

#567 rules / docs への同期

  • .claude/rules/backend/agent.mdresume_draft 節を新設計へ更新(出力単位・プレースホルダ廃止・候補提示 + 人間の採用・LLM は採用分のみを不変条件として明記)
  • .claude/rules/backend/architecture.md の構成図を更新
  • docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの契約を記載
  • ADR-0026 の関連リンクに実装状況と、「ADR では固定しない」とした値の正本(mapper.py / schemas/agent.py)への参照表を追記

検証

make ci 相当をすべて green(backend 670 tests / web 383 tests / E2E 39 件)。lint・codegen-drift・lint-tdd・lint-adr-index も通過。

TDD 対象(mapper.py / utils/ / hooks/)は red の失敗出力を確認してから実装している。

レビュー対応

CodeRabbit の指摘 6 件(Actionable 4 + Nitpick 2)は全件を実コードで検証し、c01a7ba で対応済み。うち 2 件は実バグだった(再連携後に候補が古いまま残る/取引先ゼロの職歴へ追加できてしまう)。

ADR-0026 決定 3 / #562。
- select_repos の主キーを「最終 push 日時」から「継続期間 × 実装量」へ変更し、
直近性はタイブレークへ降格した。最終 tie-breaker は full_name 辞書順のまま
維持し、同一入力から常に同一の並びが出る完全順序を不変条件とした
- 候補を落とさず順位付けだけを行う rank_repos を新設し、select_repos はその
上位を PROJECT_LIMIT で打ち切る薄いラッパーにした(ADR-0026 決定 2 の
「機械は候補を落とさない」を #564 の候補一覧 API で使えるようにするため)
- デフォルト非選択の判定 evaluate_default_selection を追加した。継続期間が
閾値未満・topics に学習用途語を含む場合に理由コードを返す。判定結果は
デフォルト選択状態と理由表示にのみ影響し、候補一覧からは除外しない
- スコア式・重み・閾値・topics 正規化規則の正本を mapper.py の定数に置いた
(ADR に数値を複製しない)。float の丸めで同点判定がブレないよう整数演算に限定
- mutmut の only_mutate に mapper.py を追加した
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 1 / #563。
BREAKING CHANGE: GET /api/agent/resume-draft/result のレスポンスから
experiences / qualifications を削除し、projects(プロジェクト明細のリスト)に
置き換えた。web のフォーム注入は #566 で追随する。
- PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE と
チーム構成の固定値生成を削除した。GitHub から得られない値(会社・事業内容・
役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
- build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ
変更した。career_summary / self_pr は projects から独立した候補として返す
- PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む
experience / client は空(会社名・顧客名はプレースホルダを入れない)とし、
projects が 0 件なら空箱すら作らない
- agent_resume_draft.md に「役割・担当工程・チーム規模を書かない」を明記した
- make codegen-types の生成差分(web/src/api/generated.ts)を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / #564。
BREAKING CHANGE: POST /api/agent/resume-draft/run に repo_full_names(1 件以上・
上限 5 件)が必須になった。web の導線は #565 で追随する。
- GET /api/agent/resume-draft/candidates を追加した。入口フィルタを通った
リポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)と
デフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
- ドラフト生成は採用リポジトリの指定を必須にし、LLM の説明文生成を採用分のみに
限定した(コストが選択数に比例する / P1)。出力スキーマの enum も採用分で縛る
- PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、
正本を schemas/agent.py の RESUME_DRAFT_SELECTION_LIMIT へ移した(mapper が
import する。schemas → services の循環 import を避けるため)
- 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は
router で 422 にして捏造リポの構造混入を入口で止める。タスク payload 側でも
採用リポジトリ欠落・未知リポジトリを NonRetryableError で二重ガードする
- 非同期タスク構造・冪等ガード(ADR-0020)は変更していない
- make codegen-types の生成差分を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / 3 / 5 / #565#566#563 のレスポンス契約変更(experiences → projects)に web を追随させる変更で、
両 issue を分けると web の型チェックが通らない期間ができるため 1 コミットにまとめた。
#565 リポジトリ候補の選択 UI:
- GET /resume-draft/candidates を叩く useResumeDraftCandidates と、候補一覧
コンポーネント ResumeDraftCandidateList を追加した。シグナル(継続期間・実装量・
技術スタック・IaC 有無)を表示し、デフォルト非選択には理由バッジを出す
- デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
- 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
- 選択 0 件では生成ボタンを押せないようにした
- 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求で
エラートーストを出さないため。E2E で検知した)
#566 ドラフト注入の「置換」→「追加」:
- applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、
appendResumeDraftProjects を追加した。ユーザーが指定した experience /
client の projects へ追加するだけで、既存の職歴は書き換えない
- 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が
1 件も無い場合のみ空の experience / client を 1 件ずつ作る
- 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
- 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
- career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
- 追加のみになったため上書き確認ダイアログを廃止した
- ResumeFormSource から ResumeDraftResultResponse を外した(保存済み経歴書専用へ)
- 文言はすべて constants/messages.ts に追加した
E2E(resume-draft-apply / github-link)を新しい導線に合わせて更新し、全 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#567。
- .claude/rules/backend/agent.md の resume_draft 節を新設計へ更新した。
出力単位(experience → project)・プレースホルダ生成の廃止・選定が
「候補提示 + 人間の採用」であること・LLM は採用分のみ実行することを
「崩してはいけない不変条件」として明記した
- .claude/rules/backend/architecture.md の resume_draft/ 構成図を更新した
(mapper.py の責務追記・run_task.py の追加)
- docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの
契約(返す単位・バリデーション・409/422 の条件)を記載した
- ADR-0026 の関連リンクに実装状況を追記し、「ADR では固定しない」とした
スコア式・閾値・語彙・上限の正本(mapper.py / schemas/agent.py)への
参照表を足した(数値は複製せず drift を防ぐ)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The resume draft workflow now supports repository candidate discovery, user-selected generation, project-only results, PDF compatibility conversion, and targeted project injection into existing career records.

Changes

Resume draft backend

Layer / File(s)Summary
Project output and repository-selection contracts
.claude/rules/backend/*, backend/app/schemas/agent.py, backend/app/services/agent/resume_draft/mapper.py, backend/app/prompts/agent_resume_draft.md
The backend ranks repositories, exposes candidate metadata, validates selections, and returns project-centric drafts without fabricated employment data.
Validated generation and asynchronous execution
backend/app/routers/agent.py, backend/app/services/agent/resume_draft/*, backend/tests/*resume_draft*, docs/api.md
The API validates selected repositories, passes them to the worker, handles unknown repositories, and converts stored project payloads for PDF rendering.

Frontend selection and injection

Layer / File(s)Summary
Candidate selection and generation controls
web/src/api/*, web/src/components/github-link/*, web/src/hooks/useResumeDraftCandidates.ts, web/src/hooks/useResumeDraftPdf.ts, web/src/utils/resumeDraftCandidates.ts
The dashboard loads candidates, enforces the selection limit, displays selection reasons, and starts generation with selected repository names.
Targeted project injection into career forms
web/src/components/forms/*, web/src/utils/resumeImport.ts, web/src/formMappers.ts, web/e2e/resume-draft-apply.spec.ts
The injection dialog appends projects to a selected experience and client without overwriting existing data. Career summary and self-PR candidates can be applied separately.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • yusuke0610/devforge#463 — Extends the earlier resume-draft implementation across the mapper, service, API, frontend flow, and tests.
  • yusuke0610/devforge#469 — Extends the earlier asynchronous resume-draft flow with selected-repository handling.
  • yusuke0610/devforge#568 — Directly implements the ADR-0026 project-only workflow and append-based injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedDocstring coverage is 93.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passedタイトルは、ドラフトをプロジェクト明細へ変更し、リポジトリ候補の選択制を導入する主要な変更を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-implementation-order-syt0rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド web フロントエンド (web) test テスト追加・修正 agent DevForge Agent feature 新機能 labels Aug 5, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/tests/test_resume_draft_mapper.py (1)

289-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the boundary dates with date arithmetic.

The test formats the day number directly from MIN_DURATION_DAYS. If MIN_DURATION_DAYS becomes 31 or larger, the string becomes an invalid date such as 2026-01-32. _parse_date then returns None, duration_days returns 0, and the test fails for a date-format reason instead of the boundary it verifies. Derive the pushed date from a timedelta so the test stays valid for any threshold value.

♻️ Proposed refactor
 def test_default_selection_duration_threshold_boundary() -> None:
"""閾値ちょうどは選択、1 日足りなければ非選択(境界の固定)。"""
+ created = date(2026, 1, 1)
exactly = _repo(
- "o/exact", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{1 + MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/exact", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS)).isoformat()}T00:00:00Z",
)
one_short = _repo(
- "o/short", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/short", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS - 1)).isoformat()}T00:00:00Z",
)

Add the import:

-from datetime import date+from datetime import date, timedelta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/tests/test_resume_draft_mapper.py` around lines 289 - 300, Update
test_default_selection_duration_threshold_boundary to construct both pushed
timestamps using date arithmetic with timedelta from the fixed created date,
rather than interpolating day numbers from MIN_DURATION_DAYS. Add the required
timedelta import and preserve the exact-threshold and one-day-short assertions.
backend/app/services/agent/resume_draft/mapper.py (1)

157-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused select_repos and update test callers.

select_repos is only called by tests; production code now uses build_candidates or select_requested_repos. Drop the public function and replace the build_skeleton(select_repos(...)) test inputs with rank_repos(source)[:PROJECT_LIMIT].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/agent/resume_draft/mapper.py` around lines 157 - 173,
Remove the unused public select_repos function and update all test callers that
invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/backend/architecture.md:
- Around line 64-69: Update the task-handler registration documentation and
related registry comment to list both TaskType.GITHUB_LINK and
TaskType.RESUME_DRAFT, reflecting the registered ResumeDraftHandler. Replace the
stale GITHUB_LINK-only wording in dispatch_service.py and the architecture
documentation while preserving the existing descriptions.
In `@web/src/components/forms/ResumeDraftInjectDialog.tsx`:
- Around line 73-116: Update the experience-selection and append flow around
ResumeDraftInjectDialog so an experience with no clients cannot be submitted
with the default clientIndex of 0. Disable the append action and show an
explicit unavailable-target message, or otherwise filter those experiences
before selection; preserve valid appending for experiences with at least one
client and ensure appendResumeDraftProjects is never called with an invalid
client target.
In `@web/src/components/github-link/GitHubLinkDashboard.tsx`:
- Around line 74-77: Update the candidate-loading flow around
useResumeDraftCandidates and the runLink/polling lifecycle so candidates are
refreshed when the latest GitHub link result changes, including when result
remains non-null across successful links. Ensure previous-analysis candidates
are loaded during polling as needed, then trigger a reload using a
result-derived key or equivalent change signal once the new result arrives.
In `@web/src/formMappers.ts`:
- Around line 12-21: Update ResumeFormSource in web/src/formMappers.ts (lines
12-21) to omit id along with created_at and updated_at, allowing id-less
pre-save payloads. In web/src/formMappers.test.ts (lines 39-60), remove the id
property and the broad unknown cast so the test validates the intended type
directly.
---
Nitpick comments:
In `@backend/app/services/agent/resume_draft/mapper.py`:
- Around line 157-173: Remove the unused public select_repos function and update
all test callers that invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
In `@backend/tests/test_resume_draft_mapper.py`:
- Around line 289-300: Update test_default_selection_duration_threshold_boundary
to construct both pushed timestamps using date arithmetic with timedelta from
the fixed created date, rather than interpolating day numbers from
MIN_DURATION_DAYS. Add the required timedelta import and preserve the
exact-threshold and one-day-short assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b9761d-a28f-4e43-bf55-391e5d87cab8

📥 Commits

Reviewing files that changed from the base of the PR and between 01ff6f3 and 7f371c5.

📒 Files selected for processing (40)
  • .claude/rules/backend/agent.md
  • .claude/rules/backend/architecture.md
  • backend/app/messages.json
  • backend/app/prompts/agent_resume_draft.md
  • backend/app/routers/agent.py
  • backend/app/schemas/agent.py
  • backend/app/services/agent/resume_draft/draft_service.py
  • backend/app/services/agent/resume_draft/mapper.py
  • backend/app/services/agent/resume_draft/run_task.py
  • backend/pyproject.toml
  • backend/tests/test_resume_draft_api.py
  • backend/tests/test_resume_draft_mapper.py
  • backend/tests/test_resume_draft_service.py
  • backend/tests/test_worker/test_resume_draft.py
  • docs/adr/0026-resume-draft-project-scope.md
  • docs/api.md
  • web/e2e/github-link.spec.ts
  • web/e2e/resume-draft-apply.spec.ts
  • web/src/api/agent.ts
  • web/src/api/generated.ts
  • web/src/api/paths.ts
  • web/src/api/types.ts
  • web/src/components/forms/CareerResumeForm.tsx
  • web/src/components/forms/ResumeDraftInjectDialog.module.css
  • web/src/components/forms/ResumeDraftInjectDialog.tsx
  • web/src/components/github-link/GitHubLinkDashboard.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.module.css
  • web/src/components/github-link/ResumeDraftCandidateList.test.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.tsx
  • web/src/constants/messages.ts
  • web/src/formMappers.test.ts
  • web/src/formMappers.ts
  • web/src/hooks/useResumeDraftCandidates.test.ts
  • web/src/hooks/useResumeDraftCandidates.ts
  • web/src/hooks/useResumeDraftPdf.test.ts
  • web/src/hooks/useResumeDraftPdf.ts
  • web/src/utils/resumeDraftCandidates.test.ts
  • web/src/utils/resumeDraftCandidates.ts
  • web/src/utils/resumeImport.test.ts
  • web/src/utils/resumeImport.ts

Comment thread.claude/rules/backend/architecture.md
Comment threadweb/src/components/forms/ResumeDraftInjectDialog.tsx
Comment threadweb/src/components/github-link/GitHubLinkDashboard.tsx
Comment threadweb/src/formMappers.ts Outdated
PR #584 のレビュー指摘 6 件(Actionable 4 + Nitpick 2)を実コードで検証し、
いずれも再現したため修正した。
- 再連携で候補が古いままになる不具合を修正した(Functional Correctness)。
useAsyncTaskPage は transitionToPolling で result をクリアしないため、
連携済みユーザーが再連携しても Boolean(result) は true→true で変化せず、
候補の再取得が走らなかった。連携結果の分析時刻(analyzed_at)を鍵にして
取り直すようにし、旧挙動で落ちる回帰テストを追加した
- 追加先の職歴に取引先が 1 件も無い場合、追加ボタンが押せて
DRAFT_TARGET_OUT_OF_RANGE を投げていた。ボタンを無効化して理由を表示する
(追加先はユーザーが明示指定する契約のため、機械が取引先を作らない)
- ResumeFormSource から id を除外した。マッパーは id を参照しないのに必須
だったため、型の説明と実際が食い違い、テストがキャストで隠していた。
キャストを外して型どおり検証されるようにした
- 非同期タスクが GITHUB_LINK の 1 種類のみという陳腐化した記述を修正した
(dispatch_service.py / architecture.md 2 箇所)。RESUME_DRAFT は
ADR-0020 で ResumeDraftHandler として登録済み。ハンドラ構成図にも追記
- 本番から呼ばれなくなった select_repos を削除し、テストの呼び出しを
rank_repos(source)[:PROJECT_LIMIT] へ置き換えた(ミューテーション対象
ファイルの dead code を残さない)
- 継続期間の境界テストを timedelta による日付演算へ変更した
(MIN_DURATION_DAYS が 31 以上になると不正な日付文字列になり、境界とは
無関係な理由で落ちるため)
backend 668 tests / web 383 tests / E2E 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#585(PR #581 相当)が main に squash マージされ、同じ #562 の実装が両側に
別コミットとして入ったためコンフリクトしていた。
- mapper.py / test_resume_draft_mapper.py: 本ブランチ側を採用した。main 側の
select_repos / evaluate_noise は本ブランチの rank_repos /
evaluate_default_selection / build_candidates に置き換わっており、候補一覧 API・
選択 UI がこちらの API に依存しているため
- ただし main 側にしか無かった修正(継続期間の時刻成分切り捨て / PR #581 の
CodeRabbit 指摘)は本ブランチの duration_days へ移植した。_parse_datetime を
追加して UTC の aware datetime で減算する。日粒度でよい「参画中」判定は
_parse_date のまま残す
- 移植分のテスト 2 件(閾値直下 29 日 1 分の境界 / タイムゾーン混在表記)を
本ブランチの evaluate_default_selection の契約に合わせて追加した
- pyproject.toml: only_mutate の同一エントリで、コメント文言のみ本ブランチ側を採用
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaqnCny4wsD6AkBkJeTEEV
@yusuke0610yusuke0610 changed the title feat(resume-draft): 選定順を「継続期間 × 実装量」へ変更しノイズ判定を付与feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)Aug 5, 2026
@yusuke0610
yusuke0610 merged commit 3ff5d83 into mainAug 5, 2026
24 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentDevForge AgentbackendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正webフロントエンド (web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yusuke0610@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567) - #584

Merged
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc
Aug 5, 2026
Merged

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)#584
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0026 の実装。マイルストーン「ドラフト縮小 N/7」のうち #563 / #564 / #565 / #566 / #567 を依存順に実装する。

#562 について: 選定順の変更(決定 3)は本ブランチでも実装したが、並行して #585 が main へ先にマージされた。main を取り込んだ際に、本ブランチの API(rank_repos / evaluate_default_selection / build_candidates)を採用しつつ、main 側にしか無かった継続期間の時刻成分の扱いを移植して統合済み(4d5aab1)。そのため mapper.py には #562 相当の差分も残っている。

変更内容

#563 出力を project 明細へ縮小しプレースホルダ生成を廃止(決定 1)

破壊的変更: GET /api/agent/resume-draft/result のレスポンスから experiences / qualifications を削除し、projects に置き換えた。

  • PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE とチーム構成の固定値生成を削除。GitHub から得られない値(会社・事業内容・役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
  • build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ変更。career_summary / self_pr は projects から独立した候補として返す
  • PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む experience / client は空(プレースホルダを入れない)で、projects が 0 件なら空箱すら作らない

#564 候補一覧 API と選択付きドラフト生成(決定 2)

破壊的変更: POST /api/agent/resume-draft/runrepo_full_names(1 件以上・上限 5 件)が必須になった。

  • GET /api/agent/resume-draft/candidates を追加。入口フィルタを通ったリポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)とデフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
  • LLM の説明文生成を採用分のみに限定(コストが選択数に比例 / P1)。出力スキーマの enum も採用分で縛る
  • PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、正本を schemas/agent.pyRESUME_DRAFT_SELECTION_LIMIT へ移した
  • 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は router で 422。タスク payload 側でも二重ガード
  • 非同期タスク構造・冪等ガード(ADR-0020)は変更なし

#565 リポジトリ候補の選択 UI(決定 2 / 3)

  • 候補一覧のチェックボックス選択。シグナル表示とデフォルト非選択の理由バッジを出す
  • デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
  • 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
  • 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求でエラートーストを出さないため)

#566 ドラフト注入を「置換」から「追加」へ(決定 5)

  • applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、appendResumeDraftProjects を追加。ユーザーが指定した experience / client の projects へ追加するだけで、既存の職歴は書き換えない
  • 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が 1 件も無い場合のみ空の experience / client を 1 件ずつ作る
  • 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
  • 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
  • career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
  • 追加のみになったため上書き確認ダイアログを廃止

#567 rules / docs への同期

  • .claude/rules/backend/agent.mdresume_draft 節を新設計へ更新(出力単位・プレースホルダ廃止・候補提示 + 人間の採用・LLM は採用分のみを不変条件として明記)
  • .claude/rules/backend/architecture.md の構成図を更新
  • docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの契約を記載
  • ADR-0026 の関連リンクに実装状況と、「ADR では固定しない」とした値の正本(mapper.py / schemas/agent.py)への参照表を追記

検証

make ci 相当をすべて green(backend 670 tests / web 383 tests / E2E 39 件)。lint・codegen-drift・lint-tdd・lint-adr-index も通過。

TDD 対象(mapper.py / utils/ / hooks/)は red の失敗出力を確認してから実装している。

レビュー対応

CodeRabbit の指摘 6 件(Actionable 4 + Nitpick 2)は全件を実コードで検証し、c01a7ba で対応済み。うち 2 件は実バグだった(再連携後に候補が古いまま残る/取引先ゼロの職歴へ追加できてしまう)。

ADR-0026 決定 3 / #562。
- select_repos の主キーを「最終 push 日時」から「継続期間 × 実装量」へ変更し、
直近性はタイブレークへ降格した。最終 tie-breaker は full_name 辞書順のまま
維持し、同一入力から常に同一の並びが出る完全順序を不変条件とした
- 候補を落とさず順位付けだけを行う rank_repos を新設し、select_repos はその
上位を PROJECT_LIMIT で打ち切る薄いラッパーにした(ADR-0026 決定 2 の
「機械は候補を落とさない」を #564 の候補一覧 API で使えるようにするため)
- デフォルト非選択の判定 evaluate_default_selection を追加した。継続期間が
閾値未満・topics に学習用途語を含む場合に理由コードを返す。判定結果は
デフォルト選択状態と理由表示にのみ影響し、候補一覧からは除外しない
- スコア式・重み・閾値・topics 正規化規則の正本を mapper.py の定数に置いた
(ADR に数値を複製しない)。float の丸めで同点判定がブレないよう整数演算に限定
- mutmut の only_mutate に mapper.py を追加した
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 1 / #563。
BREAKING CHANGE: GET /api/agent/resume-draft/result のレスポンスから
experiences / qualifications を削除し、projects(プロジェクト明細のリスト)に
置き換えた。web のフォーム注入は #566 で追随する。
- PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE と
チーム構成の固定値生成を削除した。GitHub から得られない値(会社・事業内容・
役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
- build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ
変更した。career_summary / self_pr は projects から独立した候補として返す
- PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む
experience / client は空(会社名・顧客名はプレースホルダを入れない)とし、
projects が 0 件なら空箱すら作らない
- agent_resume_draft.md に「役割・担当工程・チーム規模を書かない」を明記した
- make codegen-types の生成差分(web/src/api/generated.ts)を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / #564。
BREAKING CHANGE: POST /api/agent/resume-draft/run に repo_full_names(1 件以上・
上限 5 件)が必須になった。web の導線は #565 で追随する。
- GET /api/agent/resume-draft/candidates を追加した。入口フィルタを通った
リポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)と
デフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
- ドラフト生成は採用リポジトリの指定を必須にし、LLM の説明文生成を採用分のみに
限定した(コストが選択数に比例する / P1)。出力スキーマの enum も採用分で縛る
- PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、
正本を schemas/agent.py の RESUME_DRAFT_SELECTION_LIMIT へ移した(mapper が
import する。schemas → services の循環 import を避けるため)
- 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は
router で 422 にして捏造リポの構造混入を入口で止める。タスク payload 側でも
採用リポジトリ欠落・未知リポジトリを NonRetryableError で二重ガードする
- 非同期タスク構造・冪等ガード(ADR-0020)は変更していない
- make codegen-types の生成差分を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / 3 / 5 / #565#566#563 のレスポンス契約変更(experiences → projects)に web を追随させる変更で、
両 issue を分けると web の型チェックが通らない期間ができるため 1 コミットにまとめた。
#565 リポジトリ候補の選択 UI:
- GET /resume-draft/candidates を叩く useResumeDraftCandidates と、候補一覧
コンポーネント ResumeDraftCandidateList を追加した。シグナル(継続期間・実装量・
技術スタック・IaC 有無)を表示し、デフォルト非選択には理由バッジを出す
- デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
- 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
- 選択 0 件では生成ボタンを押せないようにした
- 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求で
エラートーストを出さないため。E2E で検知した)
#566 ドラフト注入の「置換」→「追加」:
- applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、
appendResumeDraftProjects を追加した。ユーザーが指定した experience /
client の projects へ追加するだけで、既存の職歴は書き換えない
- 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が
1 件も無い場合のみ空の experience / client を 1 件ずつ作る
- 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
- 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
- career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
- 追加のみになったため上書き確認ダイアログを廃止した
- ResumeFormSource から ResumeDraftResultResponse を外した(保存済み経歴書専用へ)
- 文言はすべて constants/messages.ts に追加した
E2E(resume-draft-apply / github-link)を新しい導線に合わせて更新し、全 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#567。
- .claude/rules/backend/agent.md の resume_draft 節を新設計へ更新した。
出力単位(experience → project)・プレースホルダ生成の廃止・選定が
「候補提示 + 人間の採用」であること・LLM は採用分のみ実行することを
「崩してはいけない不変条件」として明記した
- .claude/rules/backend/architecture.md の resume_draft/ 構成図を更新した
(mapper.py の責務追記・run_task.py の追加)
- docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの
契約(返す単位・バリデーション・409/422 の条件)を記載した
- ADR-0026 の関連リンクに実装状況を追記し、「ADR では固定しない」とした
スコア式・閾値・語彙・上限の正本(mapper.py / schemas/agent.py)への
参照表を足した(数値は複製せず drift を防ぐ)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The resume draft workflow now supports repository candidate discovery, user-selected generation, project-only results, PDF compatibility conversion, and targeted project injection into existing career records.

Changes

Resume draft backend

Layer / File(s)Summary
Project output and repository-selection contracts
.claude/rules/backend/*, backend/app/schemas/agent.py, backend/app/services/agent/resume_draft/mapper.py, backend/app/prompts/agent_resume_draft.md
The backend ranks repositories, exposes candidate metadata, validates selections, and returns project-centric drafts without fabricated employment data.
Validated generation and asynchronous execution
backend/app/routers/agent.py, backend/app/services/agent/resume_draft/*, backend/tests/*resume_draft*, docs/api.md
The API validates selected repositories, passes them to the worker, handles unknown repositories, and converts stored project payloads for PDF rendering.

Frontend selection and injection

Layer / File(s)Summary
Candidate selection and generation controls
web/src/api/*, web/src/components/github-link/*, web/src/hooks/useResumeDraftCandidates.ts, web/src/hooks/useResumeDraftPdf.ts, web/src/utils/resumeDraftCandidates.ts
The dashboard loads candidates, enforces the selection limit, displays selection reasons, and starts generation with selected repository names.
Targeted project injection into career forms
web/src/components/forms/*, web/src/utils/resumeImport.ts, web/src/formMappers.ts, web/e2e/resume-draft-apply.spec.ts
The injection dialog appends projects to a selected experience and client without overwriting existing data. Career summary and self-PR candidates can be applied separately.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • yusuke0610/devforge#463 — Extends the earlier resume-draft implementation across the mapper, service, API, frontend flow, and tests.
  • yusuke0610/devforge#469 — Extends the earlier asynchronous resume-draft flow with selected-repository handling.
  • yusuke0610/devforge#568 — Directly implements the ADR-0026 project-only workflow and append-based injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedDocstring coverage is 93.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passedタイトルは、ドラフトをプロジェクト明細へ変更し、リポジトリ候補の選択制を導入する主要な変更を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-implementation-order-syt0rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド web フロントエンド (web) test テスト追加・修正 agent DevForge Agent feature 新機能 labels Aug 5, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/tests/test_resume_draft_mapper.py (1)

289-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the boundary dates with date arithmetic.

The test formats the day number directly from MIN_DURATION_DAYS. If MIN_DURATION_DAYS becomes 31 or larger, the string becomes an invalid date such as 2026-01-32. _parse_date then returns None, duration_days returns 0, and the test fails for a date-format reason instead of the boundary it verifies. Derive the pushed date from a timedelta so the test stays valid for any threshold value.

♻️ Proposed refactor
 def test_default_selection_duration_threshold_boundary() -> None:
"""閾値ちょうどは選択、1 日足りなければ非選択(境界の固定)。"""
+ created = date(2026, 1, 1)
exactly = _repo(
- "o/exact", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{1 + MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/exact", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS)).isoformat()}T00:00:00Z",
)
one_short = _repo(
- "o/short", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/short", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS - 1)).isoformat()}T00:00:00Z",
)

Add the import:

-from datetime import date+from datetime import date, timedelta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/tests/test_resume_draft_mapper.py` around lines 289 - 300, Update
test_default_selection_duration_threshold_boundary to construct both pushed
timestamps using date arithmetic with timedelta from the fixed created date,
rather than interpolating day numbers from MIN_DURATION_DAYS. Add the required
timedelta import and preserve the exact-threshold and one-day-short assertions.
backend/app/services/agent/resume_draft/mapper.py (1)

157-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused select_repos and update test callers.

select_repos is only called by tests; production code now uses build_candidates or select_requested_repos. Drop the public function and replace the build_skeleton(select_repos(...)) test inputs with rank_repos(source)[:PROJECT_LIMIT].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/agent/resume_draft/mapper.py` around lines 157 - 173,
Remove the unused public select_repos function and update all test callers that
invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/backend/architecture.md:
- Around line 64-69: Update the task-handler registration documentation and
related registry comment to list both TaskType.GITHUB_LINK and
TaskType.RESUME_DRAFT, reflecting the registered ResumeDraftHandler. Replace the
stale GITHUB_LINK-only wording in dispatch_service.py and the architecture
documentation while preserving the existing descriptions.
In `@web/src/components/forms/ResumeDraftInjectDialog.tsx`:
- Around line 73-116: Update the experience-selection and append flow around
ResumeDraftInjectDialog so an experience with no clients cannot be submitted
with the default clientIndex of 0. Disable the append action and show an
explicit unavailable-target message, or otherwise filter those experiences
before selection; preserve valid appending for experiences with at least one
client and ensure appendResumeDraftProjects is never called with an invalid
client target.
In `@web/src/components/github-link/GitHubLinkDashboard.tsx`:
- Around line 74-77: Update the candidate-loading flow around
useResumeDraftCandidates and the runLink/polling lifecycle so candidates are
refreshed when the latest GitHub link result changes, including when result
remains non-null across successful links. Ensure previous-analysis candidates
are loaded during polling as needed, then trigger a reload using a
result-derived key or equivalent change signal once the new result arrives.
In `@web/src/formMappers.ts`:
- Around line 12-21: Update ResumeFormSource in web/src/formMappers.ts (lines
12-21) to omit id along with created_at and updated_at, allowing id-less
pre-save payloads. In web/src/formMappers.test.ts (lines 39-60), remove the id
property and the broad unknown cast so the test validates the intended type
directly.
---
Nitpick comments:
In `@backend/app/services/agent/resume_draft/mapper.py`:
- Around line 157-173: Remove the unused public select_repos function and update
all test callers that invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
In `@backend/tests/test_resume_draft_mapper.py`:
- Around line 289-300: Update test_default_selection_duration_threshold_boundary
to construct both pushed timestamps using date arithmetic with timedelta from
the fixed created date, rather than interpolating day numbers from
MIN_DURATION_DAYS. Add the required timedelta import and preserve the
exact-threshold and one-day-short assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b9761d-a28f-4e43-bf55-391e5d87cab8

📥 Commits

Reviewing files that changed from the base of the PR and between 01ff6f3 and 7f371c5.

📒 Files selected for processing (40)
  • .claude/rules/backend/agent.md
  • .claude/rules/backend/architecture.md
  • backend/app/messages.json
  • backend/app/prompts/agent_resume_draft.md
  • backend/app/routers/agent.py
  • backend/app/schemas/agent.py
  • backend/app/services/agent/resume_draft/draft_service.py
  • backend/app/services/agent/resume_draft/mapper.py
  • backend/app/services/agent/resume_draft/run_task.py
  • backend/pyproject.toml
  • backend/tests/test_resume_draft_api.py
  • backend/tests/test_resume_draft_mapper.py
  • backend/tests/test_resume_draft_service.py
  • backend/tests/test_worker/test_resume_draft.py
  • docs/adr/0026-resume-draft-project-scope.md
  • docs/api.md
  • web/e2e/github-link.spec.ts
  • web/e2e/resume-draft-apply.spec.ts
  • web/src/api/agent.ts
  • web/src/api/generated.ts
  • web/src/api/paths.ts
  • web/src/api/types.ts
  • web/src/components/forms/CareerResumeForm.tsx
  • web/src/components/forms/ResumeDraftInjectDialog.module.css
  • web/src/components/forms/ResumeDraftInjectDialog.tsx
  • web/src/components/github-link/GitHubLinkDashboard.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.module.css
  • web/src/components/github-link/ResumeDraftCandidateList.test.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.tsx
  • web/src/constants/messages.ts
  • web/src/formMappers.test.ts
  • web/src/formMappers.ts
  • web/src/hooks/useResumeDraftCandidates.test.ts
  • web/src/hooks/useResumeDraftCandidates.ts
  • web/src/hooks/useResumeDraftPdf.test.ts
  • web/src/hooks/useResumeDraftPdf.ts
  • web/src/utils/resumeDraftCandidates.test.ts
  • web/src/utils/resumeDraftCandidates.ts
  • web/src/utils/resumeImport.test.ts
  • web/src/utils/resumeImport.ts

Comment thread.claude/rules/backend/architecture.md
Comment threadweb/src/components/forms/ResumeDraftInjectDialog.tsx
Comment threadweb/src/components/github-link/GitHubLinkDashboard.tsx
Comment threadweb/src/formMappers.ts Outdated
PR #584 のレビュー指摘 6 件(Actionable 4 + Nitpick 2)を実コードで検証し、
いずれも再現したため修正した。
- 再連携で候補が古いままになる不具合を修正した(Functional Correctness)。
useAsyncTaskPage は transitionToPolling で result をクリアしないため、
連携済みユーザーが再連携しても Boolean(result) は true→true で変化せず、
候補の再取得が走らなかった。連携結果の分析時刻(analyzed_at)を鍵にして
取り直すようにし、旧挙動で落ちる回帰テストを追加した
- 追加先の職歴に取引先が 1 件も無い場合、追加ボタンが押せて
DRAFT_TARGET_OUT_OF_RANGE を投げていた。ボタンを無効化して理由を表示する
(追加先はユーザーが明示指定する契約のため、機械が取引先を作らない)
- ResumeFormSource から id を除外した。マッパーは id を参照しないのに必須
だったため、型の説明と実際が食い違い、テストがキャストで隠していた。
キャストを外して型どおり検証されるようにした
- 非同期タスクが GITHUB_LINK の 1 種類のみという陳腐化した記述を修正した
(dispatch_service.py / architecture.md 2 箇所)。RESUME_DRAFT は
ADR-0020 で ResumeDraftHandler として登録済み。ハンドラ構成図にも追記
- 本番から呼ばれなくなった select_repos を削除し、テストの呼び出しを
rank_repos(source)[:PROJECT_LIMIT] へ置き換えた(ミューテーション対象
ファイルの dead code を残さない)
- 継続期間の境界テストを timedelta による日付演算へ変更した
(MIN_DURATION_DAYS が 31 以上になると不正な日付文字列になり、境界とは
無関係な理由で落ちるため)
backend 668 tests / web 383 tests / E2E 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#585(PR #581 相当)が main に squash マージされ、同じ #562 の実装が両側に
別コミットとして入ったためコンフリクトしていた。
- mapper.py / test_resume_draft_mapper.py: 本ブランチ側を採用した。main 側の
select_repos / evaluate_noise は本ブランチの rank_repos /
evaluate_default_selection / build_candidates に置き換わっており、候補一覧 API・
選択 UI がこちらの API に依存しているため
- ただし main 側にしか無かった修正(継続期間の時刻成分切り捨て / PR #581 の
CodeRabbit 指摘)は本ブランチの duration_days へ移植した。_parse_datetime を
追加して UTC の aware datetime で減算する。日粒度でよい「参画中」判定は
_parse_date のまま残す
- 移植分のテスト 2 件(閾値直下 29 日 1 分の境界 / タイムゾーン混在表記)を
本ブランチの evaluate_default_selection の契約に合わせて追加した
- pyproject.toml: only_mutate の同一エントリで、コメント文言のみ本ブランチ側を採用
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaqnCny4wsD6AkBkJeTEEV
@yusuke0610yusuke0610 changed the title feat(resume-draft): 選定順を「継続期間 × 実装量」へ変更しノイズ判定を付与feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)Aug 5, 2026
@yusuke0610
yusuke0610 merged commit 3ff5d83 into mainAug 5, 2026
24 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentDevForge AgentbackendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正webフロントエンド (web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yusuke0610@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567) - #584

Merged
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc
Aug 5, 2026
Merged

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)#584
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0026 の実装。マイルストーン「ドラフト縮小 N/7」のうち #563 / #564 / #565 / #566 / #567 を依存順に実装する。

#562 について: 選定順の変更(決定 3)は本ブランチでも実装したが、並行して #585 が main へ先にマージされた。main を取り込んだ際に、本ブランチの API(rank_repos / evaluate_default_selection / build_candidates)を採用しつつ、main 側にしか無かった継続期間の時刻成分の扱いを移植して統合済み(4d5aab1)。そのため mapper.py には #562 相当の差分も残っている。

変更内容

#563 出力を project 明細へ縮小しプレースホルダ生成を廃止(決定 1)

破壊的変更: GET /api/agent/resume-draft/result のレスポンスから experiences / qualifications を削除し、projects に置き換えた。

  • PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE とチーム構成の固定値生成を削除。GitHub から得られない値(会社・事業内容・役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
  • build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ変更。career_summary / self_pr は projects から独立した候補として返す
  • PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む experience / client は空(プレースホルダを入れない)で、projects が 0 件なら空箱すら作らない

#564 候補一覧 API と選択付きドラフト生成(決定 2)

破壊的変更: POST /api/agent/resume-draft/runrepo_full_names(1 件以上・上限 5 件)が必須になった。

  • GET /api/agent/resume-draft/candidates を追加。入口フィルタを通ったリポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)とデフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
  • LLM の説明文生成を採用分のみに限定(コストが選択数に比例 / P1)。出力スキーマの enum も採用分で縛る
  • PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、正本を schemas/agent.pyRESUME_DRAFT_SELECTION_LIMIT へ移した
  • 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は router で 422。タスク payload 側でも二重ガード
  • 非同期タスク構造・冪等ガード(ADR-0020)は変更なし

#565 リポジトリ候補の選択 UI(決定 2 / 3)

  • 候補一覧のチェックボックス選択。シグナル表示とデフォルト非選択の理由バッジを出す
  • デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
  • 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
  • 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求でエラートーストを出さないため)

#566 ドラフト注入を「置換」から「追加」へ(決定 5)

  • applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、appendResumeDraftProjects を追加。ユーザーが指定した experience / client の projects へ追加するだけで、既存の職歴は書き換えない
  • 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が 1 件も無い場合のみ空の experience / client を 1 件ずつ作る
  • 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
  • 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
  • career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
  • 追加のみになったため上書き確認ダイアログを廃止

#567 rules / docs への同期

  • .claude/rules/backend/agent.mdresume_draft 節を新設計へ更新(出力単位・プレースホルダ廃止・候補提示 + 人間の採用・LLM は採用分のみを不変条件として明記)
  • .claude/rules/backend/architecture.md の構成図を更新
  • docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの契約を記載
  • ADR-0026 の関連リンクに実装状況と、「ADR では固定しない」とした値の正本(mapper.py / schemas/agent.py)への参照表を追記

検証

make ci 相当をすべて green(backend 670 tests / web 383 tests / E2E 39 件)。lint・codegen-drift・lint-tdd・lint-adr-index も通過。

TDD 対象(mapper.py / utils/ / hooks/)は red の失敗出力を確認してから実装している。

レビュー対応

CodeRabbit の指摘 6 件(Actionable 4 + Nitpick 2)は全件を実コードで検証し、c01a7ba で対応済み。うち 2 件は実バグだった(再連携後に候補が古いまま残る/取引先ゼロの職歴へ追加できてしまう)。

ADR-0026 決定 3 / #562。
- select_repos の主キーを「最終 push 日時」から「継続期間 × 実装量」へ変更し、
直近性はタイブレークへ降格した。最終 tie-breaker は full_name 辞書順のまま
維持し、同一入力から常に同一の並びが出る完全順序を不変条件とした
- 候補を落とさず順位付けだけを行う rank_repos を新設し、select_repos はその
上位を PROJECT_LIMIT で打ち切る薄いラッパーにした(ADR-0026 決定 2 の
「機械は候補を落とさない」を #564 の候補一覧 API で使えるようにするため)
- デフォルト非選択の判定 evaluate_default_selection を追加した。継続期間が
閾値未満・topics に学習用途語を含む場合に理由コードを返す。判定結果は
デフォルト選択状態と理由表示にのみ影響し、候補一覧からは除外しない
- スコア式・重み・閾値・topics 正規化規則の正本を mapper.py の定数に置いた
(ADR に数値を複製しない)。float の丸めで同点判定がブレないよう整数演算に限定
- mutmut の only_mutate に mapper.py を追加した
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 1 / #563。
BREAKING CHANGE: GET /api/agent/resume-draft/result のレスポンスから
experiences / qualifications を削除し、projects(プロジェクト明細のリスト)に
置き換えた。web のフォーム注入は #566 で追随する。
- PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE と
チーム構成の固定値生成を削除した。GitHub から得られない値(会社・事業内容・
役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
- build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ
変更した。career_summary / self_pr は projects から独立した候補として返す
- PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む
experience / client は空(会社名・顧客名はプレースホルダを入れない)とし、
projects が 0 件なら空箱すら作らない
- agent_resume_draft.md に「役割・担当工程・チーム規模を書かない」を明記した
- make codegen-types の生成差分(web/src/api/generated.ts)を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / #564。
BREAKING CHANGE: POST /api/agent/resume-draft/run に repo_full_names(1 件以上・
上限 5 件)が必須になった。web の導線は #565 で追随する。
- GET /api/agent/resume-draft/candidates を追加した。入口フィルタを通った
リポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)と
デフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
- ドラフト生成は採用リポジトリの指定を必須にし、LLM の説明文生成を採用分のみに
限定した(コストが選択数に比例する / P1)。出力スキーマの enum も採用分で縛る
- PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、
正本を schemas/agent.py の RESUME_DRAFT_SELECTION_LIMIT へ移した(mapper が
import する。schemas → services の循環 import を避けるため)
- 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は
router で 422 にして捏造リポの構造混入を入口で止める。タスク payload 側でも
採用リポジトリ欠落・未知リポジトリを NonRetryableError で二重ガードする
- 非同期タスク構造・冪等ガード(ADR-0020)は変更していない
- make codegen-types の生成差分を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / 3 / 5 / #565#566#563 のレスポンス契約変更(experiences → projects)に web を追随させる変更で、
両 issue を分けると web の型チェックが通らない期間ができるため 1 コミットにまとめた。
#565 リポジトリ候補の選択 UI:
- GET /resume-draft/candidates を叩く useResumeDraftCandidates と、候補一覧
コンポーネント ResumeDraftCandidateList を追加した。シグナル(継続期間・実装量・
技術スタック・IaC 有無)を表示し、デフォルト非選択には理由バッジを出す
- デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
- 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
- 選択 0 件では生成ボタンを押せないようにした
- 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求で
エラートーストを出さないため。E2E で検知した)
#566 ドラフト注入の「置換」→「追加」:
- applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、
appendResumeDraftProjects を追加した。ユーザーが指定した experience /
client の projects へ追加するだけで、既存の職歴は書き換えない
- 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が
1 件も無い場合のみ空の experience / client を 1 件ずつ作る
- 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
- 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
- career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
- 追加のみになったため上書き確認ダイアログを廃止した
- ResumeFormSource から ResumeDraftResultResponse を外した(保存済み経歴書専用へ)
- 文言はすべて constants/messages.ts に追加した
E2E(resume-draft-apply / github-link)を新しい導線に合わせて更新し、全 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#567。
- .claude/rules/backend/agent.md の resume_draft 節を新設計へ更新した。
出力単位(experience → project)・プレースホルダ生成の廃止・選定が
「候補提示 + 人間の採用」であること・LLM は採用分のみ実行することを
「崩してはいけない不変条件」として明記した
- .claude/rules/backend/architecture.md の resume_draft/ 構成図を更新した
(mapper.py の責務追記・run_task.py の追加)
- docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの
契約(返す単位・バリデーション・409/422 の条件)を記載した
- ADR-0026 の関連リンクに実装状況を追記し、「ADR では固定しない」とした
スコア式・閾値・語彙・上限の正本(mapper.py / schemas/agent.py)への
参照表を足した(数値は複製せず drift を防ぐ)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The resume draft workflow now supports repository candidate discovery, user-selected generation, project-only results, PDF compatibility conversion, and targeted project injection into existing career records.

Changes

Resume draft backend

Layer / File(s)Summary
Project output and repository-selection contracts
.claude/rules/backend/*, backend/app/schemas/agent.py, backend/app/services/agent/resume_draft/mapper.py, backend/app/prompts/agent_resume_draft.md
The backend ranks repositories, exposes candidate metadata, validates selections, and returns project-centric drafts without fabricated employment data.
Validated generation and asynchronous execution
backend/app/routers/agent.py, backend/app/services/agent/resume_draft/*, backend/tests/*resume_draft*, docs/api.md
The API validates selected repositories, passes them to the worker, handles unknown repositories, and converts stored project payloads for PDF rendering.

Frontend selection and injection

Layer / File(s)Summary
Candidate selection and generation controls
web/src/api/*, web/src/components/github-link/*, web/src/hooks/useResumeDraftCandidates.ts, web/src/hooks/useResumeDraftPdf.ts, web/src/utils/resumeDraftCandidates.ts
The dashboard loads candidates, enforces the selection limit, displays selection reasons, and starts generation with selected repository names.
Targeted project injection into career forms
web/src/components/forms/*, web/src/utils/resumeImport.ts, web/src/formMappers.ts, web/e2e/resume-draft-apply.spec.ts
The injection dialog appends projects to a selected experience and client without overwriting existing data. Career summary and self-PR candidates can be applied separately.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • yusuke0610/devforge#463 — Extends the earlier resume-draft implementation across the mapper, service, API, frontend flow, and tests.
  • yusuke0610/devforge#469 — Extends the earlier asynchronous resume-draft flow with selected-repository handling.
  • yusuke0610/devforge#568 — Directly implements the ADR-0026 project-only workflow and append-based injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedDocstring coverage is 93.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passedタイトルは、ドラフトをプロジェクト明細へ変更し、リポジトリ候補の選択制を導入する主要な変更を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-implementation-order-syt0rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド web フロントエンド (web) test テスト追加・修正 agent DevForge Agent feature 新機能 labels Aug 5, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/tests/test_resume_draft_mapper.py (1)

289-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the boundary dates with date arithmetic.

The test formats the day number directly from MIN_DURATION_DAYS. If MIN_DURATION_DAYS becomes 31 or larger, the string becomes an invalid date such as 2026-01-32. _parse_date then returns None, duration_days returns 0, and the test fails for a date-format reason instead of the boundary it verifies. Derive the pushed date from a timedelta so the test stays valid for any threshold value.

♻️ Proposed refactor
 def test_default_selection_duration_threshold_boundary() -> None:
"""閾値ちょうどは選択、1 日足りなければ非選択(境界の固定)。"""
+ created = date(2026, 1, 1)
exactly = _repo(
- "o/exact", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{1 + MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/exact", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS)).isoformat()}T00:00:00Z",
)
one_short = _repo(
- "o/short", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/short", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS - 1)).isoformat()}T00:00:00Z",
)

Add the import:

-from datetime import date+from datetime import date, timedelta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/tests/test_resume_draft_mapper.py` around lines 289 - 300, Update
test_default_selection_duration_threshold_boundary to construct both pushed
timestamps using date arithmetic with timedelta from the fixed created date,
rather than interpolating day numbers from MIN_DURATION_DAYS. Add the required
timedelta import and preserve the exact-threshold and one-day-short assertions.
backend/app/services/agent/resume_draft/mapper.py (1)

157-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused select_repos and update test callers.

select_repos is only called by tests; production code now uses build_candidates or select_requested_repos. Drop the public function and replace the build_skeleton(select_repos(...)) test inputs with rank_repos(source)[:PROJECT_LIMIT].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/agent/resume_draft/mapper.py` around lines 157 - 173,
Remove the unused public select_repos function and update all test callers that
invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/backend/architecture.md:
- Around line 64-69: Update the task-handler registration documentation and
related registry comment to list both TaskType.GITHUB_LINK and
TaskType.RESUME_DRAFT, reflecting the registered ResumeDraftHandler. Replace the
stale GITHUB_LINK-only wording in dispatch_service.py and the architecture
documentation while preserving the existing descriptions.
In `@web/src/components/forms/ResumeDraftInjectDialog.tsx`:
- Around line 73-116: Update the experience-selection and append flow around
ResumeDraftInjectDialog so an experience with no clients cannot be submitted
with the default clientIndex of 0. Disable the append action and show an
explicit unavailable-target message, or otherwise filter those experiences
before selection; preserve valid appending for experiences with at least one
client and ensure appendResumeDraftProjects is never called with an invalid
client target.
In `@web/src/components/github-link/GitHubLinkDashboard.tsx`:
- Around line 74-77: Update the candidate-loading flow around
useResumeDraftCandidates and the runLink/polling lifecycle so candidates are
refreshed when the latest GitHub link result changes, including when result
remains non-null across successful links. Ensure previous-analysis candidates
are loaded during polling as needed, then trigger a reload using a
result-derived key or equivalent change signal once the new result arrives.
In `@web/src/formMappers.ts`:
- Around line 12-21: Update ResumeFormSource in web/src/formMappers.ts (lines
12-21) to omit id along with created_at and updated_at, allowing id-less
pre-save payloads. In web/src/formMappers.test.ts (lines 39-60), remove the id
property and the broad unknown cast so the test validates the intended type
directly.
---
Nitpick comments:
In `@backend/app/services/agent/resume_draft/mapper.py`:
- Around line 157-173: Remove the unused public select_repos function and update
all test callers that invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
In `@backend/tests/test_resume_draft_mapper.py`:
- Around line 289-300: Update test_default_selection_duration_threshold_boundary
to construct both pushed timestamps using date arithmetic with timedelta from
the fixed created date, rather than interpolating day numbers from
MIN_DURATION_DAYS. Add the required timedelta import and preserve the
exact-threshold and one-day-short assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b9761d-a28f-4e43-bf55-391e5d87cab8

📥 Commits

Reviewing files that changed from the base of the PR and between 01ff6f3 and 7f371c5.

📒 Files selected for processing (40)
  • .claude/rules/backend/agent.md
  • .claude/rules/backend/architecture.md
  • backend/app/messages.json
  • backend/app/prompts/agent_resume_draft.md
  • backend/app/routers/agent.py
  • backend/app/schemas/agent.py
  • backend/app/services/agent/resume_draft/draft_service.py
  • backend/app/services/agent/resume_draft/mapper.py
  • backend/app/services/agent/resume_draft/run_task.py
  • backend/pyproject.toml
  • backend/tests/test_resume_draft_api.py
  • backend/tests/test_resume_draft_mapper.py
  • backend/tests/test_resume_draft_service.py
  • backend/tests/test_worker/test_resume_draft.py
  • docs/adr/0026-resume-draft-project-scope.md
  • docs/api.md
  • web/e2e/github-link.spec.ts
  • web/e2e/resume-draft-apply.spec.ts
  • web/src/api/agent.ts
  • web/src/api/generated.ts
  • web/src/api/paths.ts
  • web/src/api/types.ts
  • web/src/components/forms/CareerResumeForm.tsx
  • web/src/components/forms/ResumeDraftInjectDialog.module.css
  • web/src/components/forms/ResumeDraftInjectDialog.tsx
  • web/src/components/github-link/GitHubLinkDashboard.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.module.css
  • web/src/components/github-link/ResumeDraftCandidateList.test.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.tsx
  • web/src/constants/messages.ts
  • web/src/formMappers.test.ts
  • web/src/formMappers.ts
  • web/src/hooks/useResumeDraftCandidates.test.ts
  • web/src/hooks/useResumeDraftCandidates.ts
  • web/src/hooks/useResumeDraftPdf.test.ts
  • web/src/hooks/useResumeDraftPdf.ts
  • web/src/utils/resumeDraftCandidates.test.ts
  • web/src/utils/resumeDraftCandidates.ts
  • web/src/utils/resumeImport.test.ts
  • web/src/utils/resumeImport.ts

Comment thread.claude/rules/backend/architecture.md
Comment threadweb/src/components/forms/ResumeDraftInjectDialog.tsx
Comment threadweb/src/components/github-link/GitHubLinkDashboard.tsx
Comment threadweb/src/formMappers.ts Outdated
PR #584 のレビュー指摘 6 件(Actionable 4 + Nitpick 2)を実コードで検証し、
いずれも再現したため修正した。
- 再連携で候補が古いままになる不具合を修正した(Functional Correctness)。
useAsyncTaskPage は transitionToPolling で result をクリアしないため、
連携済みユーザーが再連携しても Boolean(result) は true→true で変化せず、
候補の再取得が走らなかった。連携結果の分析時刻(analyzed_at)を鍵にして
取り直すようにし、旧挙動で落ちる回帰テストを追加した
- 追加先の職歴に取引先が 1 件も無い場合、追加ボタンが押せて
DRAFT_TARGET_OUT_OF_RANGE を投げていた。ボタンを無効化して理由を表示する
(追加先はユーザーが明示指定する契約のため、機械が取引先を作らない)
- ResumeFormSource から id を除外した。マッパーは id を参照しないのに必須
だったため、型の説明と実際が食い違い、テストがキャストで隠していた。
キャストを外して型どおり検証されるようにした
- 非同期タスクが GITHUB_LINK の 1 種類のみという陳腐化した記述を修正した
(dispatch_service.py / architecture.md 2 箇所)。RESUME_DRAFT は
ADR-0020 で ResumeDraftHandler として登録済み。ハンドラ構成図にも追記
- 本番から呼ばれなくなった select_repos を削除し、テストの呼び出しを
rank_repos(source)[:PROJECT_LIMIT] へ置き換えた(ミューテーション対象
ファイルの dead code を残さない)
- 継続期間の境界テストを timedelta による日付演算へ変更した
(MIN_DURATION_DAYS が 31 以上になると不正な日付文字列になり、境界とは
無関係な理由で落ちるため)
backend 668 tests / web 383 tests / E2E 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#585(PR #581 相当)が main に squash マージされ、同じ #562 の実装が両側に
別コミットとして入ったためコンフリクトしていた。
- mapper.py / test_resume_draft_mapper.py: 本ブランチ側を採用した。main 側の
select_repos / evaluate_noise は本ブランチの rank_repos /
evaluate_default_selection / build_candidates に置き換わっており、候補一覧 API・
選択 UI がこちらの API に依存しているため
- ただし main 側にしか無かった修正(継続期間の時刻成分切り捨て / PR #581 の
CodeRabbit 指摘)は本ブランチの duration_days へ移植した。_parse_datetime を
追加して UTC の aware datetime で減算する。日粒度でよい「参画中」判定は
_parse_date のまま残す
- 移植分のテスト 2 件(閾値直下 29 日 1 分の境界 / タイムゾーン混在表記)を
本ブランチの evaluate_default_selection の契約に合わせて追加した
- pyproject.toml: only_mutate の同一エントリで、コメント文言のみ本ブランチ側を採用
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaqnCny4wsD6AkBkJeTEEV
@yusuke0610yusuke0610 changed the title feat(resume-draft): 選定順を「継続期間 × 実装量」へ変更しノイズ判定を付与feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)Aug 5, 2026
@yusuke0610
yusuke0610 merged commit 3ff5d83 into mainAug 5, 2026
24 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentDevForge AgentbackendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正webフロントエンド (web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yusuke0610@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567) - #584

Merged
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc
Aug 5, 2026
Merged

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)#584
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0026 の実装。マイルストーン「ドラフト縮小 N/7」のうち #563 / #564 / #565 / #566 / #567 を依存順に実装する。

#562 について: 選定順の変更(決定 3)は本ブランチでも実装したが、並行して #585 が main へ先にマージされた。main を取り込んだ際に、本ブランチの API(rank_repos / evaluate_default_selection / build_candidates)を採用しつつ、main 側にしか無かった継続期間の時刻成分の扱いを移植して統合済み(4d5aab1)。そのため mapper.py には #562 相当の差分も残っている。

変更内容

#563 出力を project 明細へ縮小しプレースホルダ生成を廃止(決定 1)

破壊的変更: GET /api/agent/resume-draft/result のレスポンスから experiences / qualifications を削除し、projects に置き換えた。

  • PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE とチーム構成の固定値生成を削除。GitHub から得られない値(会社・事業内容・役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
  • build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ変更。career_summary / self_pr は projects から独立した候補として返す
  • PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む experience / client は空(プレースホルダを入れない)で、projects が 0 件なら空箱すら作らない

#564 候補一覧 API と選択付きドラフト生成(決定 2)

破壊的変更: POST /api/agent/resume-draft/runrepo_full_names(1 件以上・上限 5 件)が必須になった。

  • GET /api/agent/resume-draft/candidates を追加。入口フィルタを通ったリポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)とデフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
  • LLM の説明文生成を採用分のみに限定(コストが選択数に比例 / P1)。出力スキーマの enum も採用分で縛る
  • PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、正本を schemas/agent.pyRESUME_DRAFT_SELECTION_LIMIT へ移した
  • 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は router で 422。タスク payload 側でも二重ガード
  • 非同期タスク構造・冪等ガード(ADR-0020)は変更なし

#565 リポジトリ候補の選択 UI(決定 2 / 3)

  • 候補一覧のチェックボックス選択。シグナル表示とデフォルト非選択の理由バッジを出す
  • デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
  • 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
  • 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求でエラートーストを出さないため)

#566 ドラフト注入を「置換」から「追加」へ(決定 5)

  • applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、appendResumeDraftProjects を追加。ユーザーが指定した experience / client の projects へ追加するだけで、既存の職歴は書き換えない
  • 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が 1 件も無い場合のみ空の experience / client を 1 件ずつ作る
  • 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
  • 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
  • career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
  • 追加のみになったため上書き確認ダイアログを廃止

#567 rules / docs への同期

  • .claude/rules/backend/agent.mdresume_draft 節を新設計へ更新(出力単位・プレースホルダ廃止・候補提示 + 人間の採用・LLM は採用分のみを不変条件として明記)
  • .claude/rules/backend/architecture.md の構成図を更新
  • docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの契約を記載
  • ADR-0026 の関連リンクに実装状況と、「ADR では固定しない」とした値の正本(mapper.py / schemas/agent.py)への参照表を追記

検証

make ci 相当をすべて green(backend 670 tests / web 383 tests / E2E 39 件)。lint・codegen-drift・lint-tdd・lint-adr-index も通過。

TDD 対象(mapper.py / utils/ / hooks/)は red の失敗出力を確認してから実装している。

レビュー対応

CodeRabbit の指摘 6 件(Actionable 4 + Nitpick 2)は全件を実コードで検証し、c01a7ba で対応済み。うち 2 件は実バグだった(再連携後に候補が古いまま残る/取引先ゼロの職歴へ追加できてしまう)。

ADR-0026 決定 3 / #562。
- select_repos の主キーを「最終 push 日時」から「継続期間 × 実装量」へ変更し、
直近性はタイブレークへ降格した。最終 tie-breaker は full_name 辞書順のまま
維持し、同一入力から常に同一の並びが出る完全順序を不変条件とした
- 候補を落とさず順位付けだけを行う rank_repos を新設し、select_repos はその
上位を PROJECT_LIMIT で打ち切る薄いラッパーにした(ADR-0026 決定 2 の
「機械は候補を落とさない」を #564 の候補一覧 API で使えるようにするため)
- デフォルト非選択の判定 evaluate_default_selection を追加した。継続期間が
閾値未満・topics に学習用途語を含む場合に理由コードを返す。判定結果は
デフォルト選択状態と理由表示にのみ影響し、候補一覧からは除外しない
- スコア式・重み・閾値・topics 正規化規則の正本を mapper.py の定数に置いた
(ADR に数値を複製しない)。float の丸めで同点判定がブレないよう整数演算に限定
- mutmut の only_mutate に mapper.py を追加した
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 1 / #563。
BREAKING CHANGE: GET /api/agent/resume-draft/result のレスポンスから
experiences / qualifications を削除し、projects(プロジェクト明細のリスト)に
置き換えた。web のフォーム注入は #566 で追随する。
- PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE と
チーム構成の固定値生成を削除した。GitHub から得られない値(会社・事業内容・
役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
- build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ
変更した。career_summary / self_pr は projects から独立した候補として返す
- PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む
experience / client は空(会社名・顧客名はプレースホルダを入れない)とし、
projects が 0 件なら空箱すら作らない
- agent_resume_draft.md に「役割・担当工程・チーム規模を書かない」を明記した
- make codegen-types の生成差分(web/src/api/generated.ts)を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / #564。
BREAKING CHANGE: POST /api/agent/resume-draft/run に repo_full_names(1 件以上・
上限 5 件)が必須になった。web の導線は #565 で追随する。
- GET /api/agent/resume-draft/candidates を追加した。入口フィルタを通った
リポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)と
デフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
- ドラフト生成は採用リポジトリの指定を必須にし、LLM の説明文生成を採用分のみに
限定した(コストが選択数に比例する / P1)。出力スキーマの enum も採用分で縛る
- PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、
正本を schemas/agent.py の RESUME_DRAFT_SELECTION_LIMIT へ移した(mapper が
import する。schemas → services の循環 import を避けるため)
- 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は
router で 422 にして捏造リポの構造混入を入口で止める。タスク payload 側でも
採用リポジトリ欠落・未知リポジトリを NonRetryableError で二重ガードする
- 非同期タスク構造・冪等ガード(ADR-0020)は変更していない
- make codegen-types の生成差分を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / 3 / 5 / #565#566#563 のレスポンス契約変更(experiences → projects)に web を追随させる変更で、
両 issue を分けると web の型チェックが通らない期間ができるため 1 コミットにまとめた。
#565 リポジトリ候補の選択 UI:
- GET /resume-draft/candidates を叩く useResumeDraftCandidates と、候補一覧
コンポーネント ResumeDraftCandidateList を追加した。シグナル(継続期間・実装量・
技術スタック・IaC 有無)を表示し、デフォルト非選択には理由バッジを出す
- デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
- 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
- 選択 0 件では生成ボタンを押せないようにした
- 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求で
エラートーストを出さないため。E2E で検知した)
#566 ドラフト注入の「置換」→「追加」:
- applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、
appendResumeDraftProjects を追加した。ユーザーが指定した experience /
client の projects へ追加するだけで、既存の職歴は書き換えない
- 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が
1 件も無い場合のみ空の experience / client を 1 件ずつ作る
- 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
- 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
- career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
- 追加のみになったため上書き確認ダイアログを廃止した
- ResumeFormSource から ResumeDraftResultResponse を外した(保存済み経歴書専用へ)
- 文言はすべて constants/messages.ts に追加した
E2E(resume-draft-apply / github-link)を新しい導線に合わせて更新し、全 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#567。
- .claude/rules/backend/agent.md の resume_draft 節を新設計へ更新した。
出力単位(experience → project)・プレースホルダ生成の廃止・選定が
「候補提示 + 人間の採用」であること・LLM は採用分のみ実行することを
「崩してはいけない不変条件」として明記した
- .claude/rules/backend/architecture.md の resume_draft/ 構成図を更新した
(mapper.py の責務追記・run_task.py の追加)
- docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの
契約(返す単位・バリデーション・409/422 の条件)を記載した
- ADR-0026 の関連リンクに実装状況を追記し、「ADR では固定しない」とした
スコア式・閾値・語彙・上限の正本(mapper.py / schemas/agent.py)への
参照表を足した(数値は複製せず drift を防ぐ)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The resume draft workflow now supports repository candidate discovery, user-selected generation, project-only results, PDF compatibility conversion, and targeted project injection into existing career records.

Changes

Resume draft backend

Layer / File(s)Summary
Project output and repository-selection contracts
.claude/rules/backend/*, backend/app/schemas/agent.py, backend/app/services/agent/resume_draft/mapper.py, backend/app/prompts/agent_resume_draft.md
The backend ranks repositories, exposes candidate metadata, validates selections, and returns project-centric drafts without fabricated employment data.
Validated generation and asynchronous execution
backend/app/routers/agent.py, backend/app/services/agent/resume_draft/*, backend/tests/*resume_draft*, docs/api.md
The API validates selected repositories, passes them to the worker, handles unknown repositories, and converts stored project payloads for PDF rendering.

Frontend selection and injection

Layer / File(s)Summary
Candidate selection and generation controls
web/src/api/*, web/src/components/github-link/*, web/src/hooks/useResumeDraftCandidates.ts, web/src/hooks/useResumeDraftPdf.ts, web/src/utils/resumeDraftCandidates.ts
The dashboard loads candidates, enforces the selection limit, displays selection reasons, and starts generation with selected repository names.
Targeted project injection into career forms
web/src/components/forms/*, web/src/utils/resumeImport.ts, web/src/formMappers.ts, web/e2e/resume-draft-apply.spec.ts
The injection dialog appends projects to a selected experience and client without overwriting existing data. Career summary and self-PR candidates can be applied separately.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • yusuke0610/devforge#463 — Extends the earlier resume-draft implementation across the mapper, service, API, frontend flow, and tests.
  • yusuke0610/devforge#469 — Extends the earlier asynchronous resume-draft flow with selected-repository handling.
  • yusuke0610/devforge#568 — Directly implements the ADR-0026 project-only workflow and append-based injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedDocstring coverage is 93.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passedタイトルは、ドラフトをプロジェクト明細へ変更し、リポジトリ候補の選択制を導入する主要な変更を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-implementation-order-syt0rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド web フロントエンド (web) test テスト追加・修正 agent DevForge Agent feature 新機能 labels Aug 5, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/tests/test_resume_draft_mapper.py (1)

289-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the boundary dates with date arithmetic.

The test formats the day number directly from MIN_DURATION_DAYS. If MIN_DURATION_DAYS becomes 31 or larger, the string becomes an invalid date such as 2026-01-32. _parse_date then returns None, duration_days returns 0, and the test fails for a date-format reason instead of the boundary it verifies. Derive the pushed date from a timedelta so the test stays valid for any threshold value.

♻️ Proposed refactor
 def test_default_selection_duration_threshold_boundary() -> None:
"""閾値ちょうどは選択、1 日足りなければ非選択(境界の固定)。"""
+ created = date(2026, 1, 1)
exactly = _repo(
- "o/exact", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{1 + MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/exact", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS)).isoformat()}T00:00:00Z",
)
one_short = _repo(
- "o/short", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/short", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS - 1)).isoformat()}T00:00:00Z",
)

Add the import:

-from datetime import date+from datetime import date, timedelta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/tests/test_resume_draft_mapper.py` around lines 289 - 300, Update
test_default_selection_duration_threshold_boundary to construct both pushed
timestamps using date arithmetic with timedelta from the fixed created date,
rather than interpolating day numbers from MIN_DURATION_DAYS. Add the required
timedelta import and preserve the exact-threshold and one-day-short assertions.
backend/app/services/agent/resume_draft/mapper.py (1)

157-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused select_repos and update test callers.

select_repos is only called by tests; production code now uses build_candidates or select_requested_repos. Drop the public function and replace the build_skeleton(select_repos(...)) test inputs with rank_repos(source)[:PROJECT_LIMIT].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/agent/resume_draft/mapper.py` around lines 157 - 173,
Remove the unused public select_repos function and update all test callers that
invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/backend/architecture.md:
- Around line 64-69: Update the task-handler registration documentation and
related registry comment to list both TaskType.GITHUB_LINK and
TaskType.RESUME_DRAFT, reflecting the registered ResumeDraftHandler. Replace the
stale GITHUB_LINK-only wording in dispatch_service.py and the architecture
documentation while preserving the existing descriptions.
In `@web/src/components/forms/ResumeDraftInjectDialog.tsx`:
- Around line 73-116: Update the experience-selection and append flow around
ResumeDraftInjectDialog so an experience with no clients cannot be submitted
with the default clientIndex of 0. Disable the append action and show an
explicit unavailable-target message, or otherwise filter those experiences
before selection; preserve valid appending for experiences with at least one
client and ensure appendResumeDraftProjects is never called with an invalid
client target.
In `@web/src/components/github-link/GitHubLinkDashboard.tsx`:
- Around line 74-77: Update the candidate-loading flow around
useResumeDraftCandidates and the runLink/polling lifecycle so candidates are
refreshed when the latest GitHub link result changes, including when result
remains non-null across successful links. Ensure previous-analysis candidates
are loaded during polling as needed, then trigger a reload using a
result-derived key or equivalent change signal once the new result arrives.
In `@web/src/formMappers.ts`:
- Around line 12-21: Update ResumeFormSource in web/src/formMappers.ts (lines
12-21) to omit id along with created_at and updated_at, allowing id-less
pre-save payloads. In web/src/formMappers.test.ts (lines 39-60), remove the id
property and the broad unknown cast so the test validates the intended type
directly.
---
Nitpick comments:
In `@backend/app/services/agent/resume_draft/mapper.py`:
- Around line 157-173: Remove the unused public select_repos function and update
all test callers that invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
In `@backend/tests/test_resume_draft_mapper.py`:
- Around line 289-300: Update test_default_selection_duration_threshold_boundary
to construct both pushed timestamps using date arithmetic with timedelta from
the fixed created date, rather than interpolating day numbers from
MIN_DURATION_DAYS. Add the required timedelta import and preserve the
exact-threshold and one-day-short assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b9761d-a28f-4e43-bf55-391e5d87cab8

📥 Commits

Reviewing files that changed from the base of the PR and between 01ff6f3 and 7f371c5.

📒 Files selected for processing (40)
  • .claude/rules/backend/agent.md
  • .claude/rules/backend/architecture.md
  • backend/app/messages.json
  • backend/app/prompts/agent_resume_draft.md
  • backend/app/routers/agent.py
  • backend/app/schemas/agent.py
  • backend/app/services/agent/resume_draft/draft_service.py
  • backend/app/services/agent/resume_draft/mapper.py
  • backend/app/services/agent/resume_draft/run_task.py
  • backend/pyproject.toml
  • backend/tests/test_resume_draft_api.py
  • backend/tests/test_resume_draft_mapper.py
  • backend/tests/test_resume_draft_service.py
  • backend/tests/test_worker/test_resume_draft.py
  • docs/adr/0026-resume-draft-project-scope.md
  • docs/api.md
  • web/e2e/github-link.spec.ts
  • web/e2e/resume-draft-apply.spec.ts
  • web/src/api/agent.ts
  • web/src/api/generated.ts
  • web/src/api/paths.ts
  • web/src/api/types.ts
  • web/src/components/forms/CareerResumeForm.tsx
  • web/src/components/forms/ResumeDraftInjectDialog.module.css
  • web/src/components/forms/ResumeDraftInjectDialog.tsx
  • web/src/components/github-link/GitHubLinkDashboard.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.module.css
  • web/src/components/github-link/ResumeDraftCandidateList.test.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.tsx
  • web/src/constants/messages.ts
  • web/src/formMappers.test.ts
  • web/src/formMappers.ts
  • web/src/hooks/useResumeDraftCandidates.test.ts
  • web/src/hooks/useResumeDraftCandidates.ts
  • web/src/hooks/useResumeDraftPdf.test.ts
  • web/src/hooks/useResumeDraftPdf.ts
  • web/src/utils/resumeDraftCandidates.test.ts
  • web/src/utils/resumeDraftCandidates.ts
  • web/src/utils/resumeImport.test.ts
  • web/src/utils/resumeImport.ts

Comment thread.claude/rules/backend/architecture.md
Comment threadweb/src/components/forms/ResumeDraftInjectDialog.tsx
Comment threadweb/src/components/github-link/GitHubLinkDashboard.tsx
Comment threadweb/src/formMappers.ts Outdated
PR #584 のレビュー指摘 6 件(Actionable 4 + Nitpick 2)を実コードで検証し、
いずれも再現したため修正した。
- 再連携で候補が古いままになる不具合を修正した(Functional Correctness)。
useAsyncTaskPage は transitionToPolling で result をクリアしないため、
連携済みユーザーが再連携しても Boolean(result) は true→true で変化せず、
候補の再取得が走らなかった。連携結果の分析時刻(analyzed_at)を鍵にして
取り直すようにし、旧挙動で落ちる回帰テストを追加した
- 追加先の職歴に取引先が 1 件も無い場合、追加ボタンが押せて
DRAFT_TARGET_OUT_OF_RANGE を投げていた。ボタンを無効化して理由を表示する
(追加先はユーザーが明示指定する契約のため、機械が取引先を作らない)
- ResumeFormSource から id を除外した。マッパーは id を参照しないのに必須
だったため、型の説明と実際が食い違い、テストがキャストで隠していた。
キャストを外して型どおり検証されるようにした
- 非同期タスクが GITHUB_LINK の 1 種類のみという陳腐化した記述を修正した
(dispatch_service.py / architecture.md 2 箇所)。RESUME_DRAFT は
ADR-0020 で ResumeDraftHandler として登録済み。ハンドラ構成図にも追記
- 本番から呼ばれなくなった select_repos を削除し、テストの呼び出しを
rank_repos(source)[:PROJECT_LIMIT] へ置き換えた(ミューテーション対象
ファイルの dead code を残さない)
- 継続期間の境界テストを timedelta による日付演算へ変更した
(MIN_DURATION_DAYS が 31 以上になると不正な日付文字列になり、境界とは
無関係な理由で落ちるため)
backend 668 tests / web 383 tests / E2E 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#585(PR #581 相当)が main に squash マージされ、同じ #562 の実装が両側に
別コミットとして入ったためコンフリクトしていた。
- mapper.py / test_resume_draft_mapper.py: 本ブランチ側を採用した。main 側の
select_repos / evaluate_noise は本ブランチの rank_repos /
evaluate_default_selection / build_candidates に置き換わっており、候補一覧 API・
選択 UI がこちらの API に依存しているため
- ただし main 側にしか無かった修正(継続期間の時刻成分切り捨て / PR #581 の
CodeRabbit 指摘)は本ブランチの duration_days へ移植した。_parse_datetime を
追加して UTC の aware datetime で減算する。日粒度でよい「参画中」判定は
_parse_date のまま残す
- 移植分のテスト 2 件(閾値直下 29 日 1 分の境界 / タイムゾーン混在表記)を
本ブランチの evaluate_default_selection の契約に合わせて追加した
- pyproject.toml: only_mutate の同一エントリで、コメント文言のみ本ブランチ側を採用
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaqnCny4wsD6AkBkJeTEEV
@yusuke0610yusuke0610 changed the title feat(resume-draft): 選定順を「継続期間 × 実装量」へ変更しノイズ判定を付与feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)Aug 5, 2026
@yusuke0610
yusuke0610 merged commit 3ff5d83 into mainAug 5, 2026
24 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentDevForge AgentbackendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正webフロントエンド (web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yusuke0610@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567) - #584

Merged
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc
Aug 5, 2026
Merged

feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)#584
yusuke0610 merged 7 commits into
mainfrom
claude/issue-implementation-order-syt0rc

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0026 の実装。マイルストーン「ドラフト縮小 N/7」のうち #563 / #564 / #565 / #566 / #567 を依存順に実装する。

#562 について: 選定順の変更(決定 3)は本ブランチでも実装したが、並行して #585 が main へ先にマージされた。main を取り込んだ際に、本ブランチの API(rank_repos / evaluate_default_selection / build_candidates)を採用しつつ、main 側にしか無かった継続期間の時刻成分の扱いを移植して統合済み(4d5aab1)。そのため mapper.py には #562 相当の差分も残っている。

変更内容

#563 出力を project 明細へ縮小しプレースホルダ生成を廃止(決定 1)

破壊的変更: GET /api/agent/resume-draft/result のレスポンスから experiences / qualifications を削除し、projects に置き換えた。

  • PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE とチーム構成の固定値生成を削除。GitHub から得られない値(会社・事業内容・役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
  • build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ変更。career_summary / self_pr は projects から独立した候補として返す
  • PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む experience / client は空(プレースホルダを入れない)で、projects が 0 件なら空箱すら作らない

#564 候補一覧 API と選択付きドラフト生成(決定 2)

破壊的変更: POST /api/agent/resume-draft/runrepo_full_names(1 件以上・上限 5 件)が必須になった。

  • GET /api/agent/resume-draft/candidates を追加。入口フィルタを通ったリポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)とデフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
  • LLM の説明文生成を採用分のみに限定(コストが選択数に比例 / P1)。出力スキーマの enum も採用分で縛る
  • PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、正本を schemas/agent.pyRESUME_DRAFT_SELECTION_LIMIT へ移した
  • 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は router で 422。タスク payload 側でも二重ガード
  • 非同期タスク構造・冪等ガード(ADR-0020)は変更なし

#565 リポジトリ候補の選択 UI(決定 2 / 3)

  • 候補一覧のチェックボックス選択。シグナル表示とデフォルト非選択の理由バッジを出す
  • デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
  • 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
  • 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求でエラートーストを出さないため)

#566 ドラフト注入を「置換」から「追加」へ(決定 5)

  • applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、appendResumeDraftProjects を追加。ユーザーが指定した experience / client の projects へ追加するだけで、既存の職歴は書き換えない
  • 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が 1 件も無い場合のみ空の experience / client を 1 件ずつ作る
  • 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
  • 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
  • career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
  • 追加のみになったため上書き確認ダイアログを廃止

#567 rules / docs への同期

  • .claude/rules/backend/agent.mdresume_draft 節を新設計へ更新(出力単位・プレースホルダ廃止・候補提示 + 人間の採用・LLM は採用分のみを不変条件として明記)
  • .claude/rules/backend/architecture.md の構成図を更新
  • docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの契約を記載
  • ADR-0026 の関連リンクに実装状況と、「ADR では固定しない」とした値の正本(mapper.py / schemas/agent.py)への参照表を追記

検証

make ci 相当をすべて green(backend 670 tests / web 383 tests / E2E 39 件)。lint・codegen-drift・lint-tdd・lint-adr-index も通過。

TDD 対象(mapper.py / utils/ / hooks/)は red の失敗出力を確認してから実装している。

レビュー対応

CodeRabbit の指摘 6 件(Actionable 4 + Nitpick 2)は全件を実コードで検証し、c01a7ba で対応済み。うち 2 件は実バグだった(再連携後に候補が古いまま残る/取引先ゼロの職歴へ追加できてしまう)。

ADR-0026 決定 3 / #562。
- select_repos の主キーを「最終 push 日時」から「継続期間 × 実装量」へ変更し、
直近性はタイブレークへ降格した。最終 tie-breaker は full_name 辞書順のまま
維持し、同一入力から常に同一の並びが出る完全順序を不変条件とした
- 候補を落とさず順位付けだけを行う rank_repos を新設し、select_repos はその
上位を PROJECT_LIMIT で打ち切る薄いラッパーにした(ADR-0026 決定 2 の
「機械は候補を落とさない」を #564 の候補一覧 API で使えるようにするため)
- デフォルト非選択の判定 evaluate_default_selection を追加した。継続期間が
閾値未満・topics に学習用途語を含む場合に理由コードを返す。判定結果は
デフォルト選択状態と理由表示にのみ影響し、候補一覧からは除外しない
- スコア式・重み・閾値・topics 正規化規則の正本を mapper.py の定数に置いた
(ADR に数値を複製しない)。float の丸めで同点判定がブレないよう整数演算に限定
- mutmut の only_mutate に mapper.py を追加した
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 1 / #563。
BREAKING CHANGE: GET /api/agent/resume-draft/result のレスポンスから
experiences / qualifications を削除し、projects(プロジェクト明細のリスト)に
置き換えた。web のフォーム注入は #566 で追随する。
- PLACEHOLDER_COMPANY / PLACEHOLDER_BUSINESS_DESCRIPTION / PLACEHOLDER_ROLE と
チーム構成の固定値生成を削除した。GitHub から得られない値(会社・事業内容・
役割・担当工程・チーム規模)は機械が埋めず空のまま人間が書く
- build_skeleton の experience 生成を廃止し、出力をプロジェクト明細のリストへ
変更した。career_summary / self_pr は projects から独立した候補として返す
- PDF レンダリング時のみ build_pdf_payload で Resume 互換の形へ包む。包む
experience / client は空(会社名・顧客名はプレースホルダを入れない)とし、
projects が 0 件なら空箱すら作らない
- agent_resume_draft.md に「役割・担当工程・チーム規模を書かない」を明記した
- make codegen-types の生成差分(web/src/api/generated.ts)を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / #564。
BREAKING CHANGE: POST /api/agent/resume-draft/run に repo_full_names(1 件以上・
上限 5 件)が必須になった。web の導線は #565 で追随する。
- GET /api/agent/resume-draft/candidates を追加した。入口フィルタを通った
リポジトリを全件返し、シグナル(継続期間・実装量・技術スタック・IaC 有無)と
デフォルト選択状態・非選択理由コードを添える。機械は候補を落とさない
- ドラフト生成は採用リポジトリの指定を必須にし、LLM の説明文生成を採用分のみに
限定した(コストが選択数に比例する / P1)。出力スキーマの enum も採用分で縛る
- PROJECT_LIMIT の意味を「LLM 出力の有界化」から「1 回に選べる上限」へ変更し、
正本を schemas/agent.py の RESUME_DRAFT_SELECTION_LIMIT へ移した(mapper が
import する。schemas → services の循環 import を避けるため)
- 選択 0 件・上限超過はスキーマ検証で 422、連携データに無いリポジトリ指定は
router で 422 にして捏造リポの構造混入を入口で止める。タスク payload 側でも
採用リポジトリ欠落・未知リポジトリを NonRetryableError で二重ガードする
- 非同期タスク構造・冪等ガード(ADR-0020)は変更していない
- make codegen-types の生成差分を同梱
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
ADR-0026 決定 2 / 3 / 5 / #565#566#563 のレスポンス契約変更(experiences → projects)に web を追随させる変更で、
両 issue を分けると web の型チェックが通らない期間ができるため 1 コミットにまとめた。
#565 リポジトリ候補の選択 UI:
- GET /resume-draft/candidates を叩く useResumeDraftCandidates と、候補一覧
コンポーネント ResumeDraftCandidateList を追加した。シグナル(継続期間・実装量・
技術スタック・IaC 有無)を表示し、デフォルト非選択には理由バッジを出す
- デフォルト非選択の候補もユーザーが選び直せる(機械の判定を常に覆せる)
- 上限超過時は未選択のチェックボックスだけを無効化し、選択解除は妨げない
- 選択 0 件では生成ボタンを押せないようにした
- 連携結果がまだ無い画面では候補を取得しない(必ず 409 になる要求で
エラートーストを出さないため。E2E で検知した)
#566 ドラフト注入の「置換」→「追加」:
- applyResumeDraftToForm(experiences 丸ごと置換)を廃止し、
appendResumeDraftProjects を追加した。ユーザーが指定した experience /
client の projects へ追加するだけで、既存の職歴は書き換えない
- 追加先はユーザーの明示指定を必須にし、機械は推測しない。experience が
1 件も無い場合のみ空の experience / client を 1 件ずつ作る
- 同一 client 内に同名 project があれば追加しない(冪等。判定キーは project 名)
- 部分適用しない(追加先が不正なら例外を投げ、フォーム state は変更しない)
- career_summary / self_pr は上書きせず候補として提示し、ユーザーが個別に適用する
- 追加のみになったため上書き確認ダイアログを廃止した
- ResumeFormSource から ResumeDraftResultResponse を外した(保存済み経歴書専用へ)
- 文言はすべて constants/messages.ts に追加した
E2E(resume-draft-apply / github-link)を新しい導線に合わせて更新し、全 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#567。
- .claude/rules/backend/agent.md の resume_draft 節を新設計へ更新した。
出力単位(experience → project)・プレースホルダ生成の廃止・選定が
「候補提示 + 人間の採用」であること・LLM は採用分のみ実行することを
「崩してはいけない不変条件」として明記した
- .claude/rules/backend/architecture.md の resume_draft/ 構成図を更新した
(mapper.py の責務追記・run_task.py の追加)
- docs/api.md にドラフト生成の節を新設し、候補一覧 API を含む 5 エンドポイントの
契約(返す単位・バリデーション・409/422 の条件)を記載した
- ADR-0026 の関連リンクに実装状況を追記し、「ADR では固定しない」とした
スコア式・閾値・語彙・上限の正本(mapper.py / schemas/agent.py)への
参照表を足した(数値は複製せず drift を防ぐ)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
@coderabbitai

coderabbitaiBot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The resume draft workflow now supports repository candidate discovery, user-selected generation, project-only results, PDF compatibility conversion, and targeted project injection into existing career records.

Changes

Resume draft backend

Layer / File(s)Summary
Project output and repository-selection contracts
.claude/rules/backend/*, backend/app/schemas/agent.py, backend/app/services/agent/resume_draft/mapper.py, backend/app/prompts/agent_resume_draft.md
The backend ranks repositories, exposes candidate metadata, validates selections, and returns project-centric drafts without fabricated employment data.
Validated generation and asynchronous execution
backend/app/routers/agent.py, backend/app/services/agent/resume_draft/*, backend/tests/*resume_draft*, docs/api.md
The API validates selected repositories, passes them to the worker, handles unknown repositories, and converts stored project payloads for PDF rendering.

Frontend selection and injection

Layer / File(s)Summary
Candidate selection and generation controls
web/src/api/*, web/src/components/github-link/*, web/src/hooks/useResumeDraftCandidates.ts, web/src/hooks/useResumeDraftPdf.ts, web/src/utils/resumeDraftCandidates.ts
The dashboard loads candidates, enforces the selection limit, displays selection reasons, and starts generation with selected repository names.
Targeted project injection into career forms
web/src/components/forms/*, web/src/utils/resumeImport.ts, web/src/formMappers.ts, web/e2e/resume-draft-apply.spec.ts
The injection dialog appends projects to a selected experience and client without overwriting existing data. Career summary and self-PR candidates can be applied separately.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • yusuke0610/devforge#463 — Extends the earlier resume-draft implementation across the mapper, service, API, frontend flow, and tests.
  • yusuke0610/devforge#469 — Extends the earlier asynchronous resume-draft flow with selected-repository handling.
  • yusuke0610/devforge#568 — Directly implements the ADR-0026 project-only workflow and append-based injection.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Docstring Coverage✅ PassedDocstring coverage is 93.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ Passedタイトルは、ドラフトをプロジェクト明細へ変更し、リポジトリ候補の選択制を導入する主要な変更を明確に示しています。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-implementation-order-syt0rc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド web フロントエンド (web) test テスト追加・修正 agent DevForge Agent feature 新機能 labels Aug 5, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/tests/test_resume_draft_mapper.py (1)

289-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the boundary dates with date arithmetic.

The test formats the day number directly from MIN_DURATION_DAYS. If MIN_DURATION_DAYS becomes 31 or larger, the string becomes an invalid date such as 2026-01-32. _parse_date then returns None, duration_days returns 0, and the test fails for a date-format reason instead of the boundary it verifies. Derive the pushed date from a timedelta so the test stays valid for any threshold value.

♻️ Proposed refactor
 def test_default_selection_duration_threshold_boundary() -> None:
"""閾値ちょうどは選択、1 日足りなければ非選択(境界の固定)。"""
+ created = date(2026, 1, 1)
exactly = _repo(
- "o/exact", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{1 + MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/exact", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS)).isoformat()}T00:00:00Z",
)
one_short = _repo(
- "o/short", created="2026-01-01T00:00:00Z",- pushed=f"2026-01-{MIN_DURATION_DAYS:02d}T00:00:00Z",+ "o/short", created=f"{created.isoformat()}T00:00:00Z",+ pushed=f"{(created + timedelta(days=MIN_DURATION_DAYS - 1)).isoformat()}T00:00:00Z",
)

Add the import:

-from datetime import date+from datetime import date, timedelta
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/tests/test_resume_draft_mapper.py` around lines 289 - 300, Update
test_default_selection_duration_threshold_boundary to construct both pushed
timestamps using date arithmetic with timedelta from the fixed created date,
rather than interpolating day numbers from MIN_DURATION_DAYS. Add the required
timedelta import and preserve the exact-threshold and one-day-short assertions.
backend/app/services/agent/resume_draft/mapper.py (1)

157-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused select_repos and update test callers.

select_repos is only called by tests; production code now uses build_candidates or select_requested_repos. Drop the public function and replace the build_skeleton(select_repos(...)) test inputs with rank_repos(source)[:PROJECT_LIMIT].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/agent/resume_draft/mapper.py` around lines 157 - 173,
Remove the unused public select_repos function and update all test callers that
invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/backend/architecture.md:
- Around line 64-69: Update the task-handler registration documentation and
related registry comment to list both TaskType.GITHUB_LINK and
TaskType.RESUME_DRAFT, reflecting the registered ResumeDraftHandler. Replace the
stale GITHUB_LINK-only wording in dispatch_service.py and the architecture
documentation while preserving the existing descriptions.
In `@web/src/components/forms/ResumeDraftInjectDialog.tsx`:
- Around line 73-116: Update the experience-selection and append flow around
ResumeDraftInjectDialog so an experience with no clients cannot be submitted
with the default clientIndex of 0. Disable the append action and show an
explicit unavailable-target message, or otherwise filter those experiences
before selection; preserve valid appending for experiences with at least one
client and ensure appendResumeDraftProjects is never called with an invalid
client target.
In `@web/src/components/github-link/GitHubLinkDashboard.tsx`:
- Around line 74-77: Update the candidate-loading flow around
useResumeDraftCandidates and the runLink/polling lifecycle so candidates are
refreshed when the latest GitHub link result changes, including when result
remains non-null across successful links. Ensure previous-analysis candidates
are loaded during polling as needed, then trigger a reload using a
result-derived key or equivalent change signal once the new result arrives.
In `@web/src/formMappers.ts`:
- Around line 12-21: Update ResumeFormSource in web/src/formMappers.ts (lines
12-21) to omit id along with created_at and updated_at, allowing id-less
pre-save payloads. In web/src/formMappers.test.ts (lines 39-60), remove the id
property and the broad unknown cast so the test validates the intended type
directly.
---
Nitpick comments:
In `@backend/app/services/agent/resume_draft/mapper.py`:
- Around line 157-173: Remove the unused public select_repos function and update
all test callers that invoke build_skeleton(select_repos(...)) to use
rank_repos(source)[:PROJECT_LIMIT] instead. Keep rank_repos unchanged and
preserve the existing limit applied to test inputs.
In `@backend/tests/test_resume_draft_mapper.py`:
- Around line 289-300: Update test_default_selection_duration_threshold_boundary
to construct both pushed timestamps using date arithmetic with timedelta from
the fixed created date, rather than interpolating day numbers from
MIN_DURATION_DAYS. Add the required timedelta import and preserve the
exact-threshold and one-day-short assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12b9761d-a28f-4e43-bf55-391e5d87cab8

📥 Commits

Reviewing files that changed from the base of the PR and between 01ff6f3 and 7f371c5.

📒 Files selected for processing (40)
  • .claude/rules/backend/agent.md
  • .claude/rules/backend/architecture.md
  • backend/app/messages.json
  • backend/app/prompts/agent_resume_draft.md
  • backend/app/routers/agent.py
  • backend/app/schemas/agent.py
  • backend/app/services/agent/resume_draft/draft_service.py
  • backend/app/services/agent/resume_draft/mapper.py
  • backend/app/services/agent/resume_draft/run_task.py
  • backend/pyproject.toml
  • backend/tests/test_resume_draft_api.py
  • backend/tests/test_resume_draft_mapper.py
  • backend/tests/test_resume_draft_service.py
  • backend/tests/test_worker/test_resume_draft.py
  • docs/adr/0026-resume-draft-project-scope.md
  • docs/api.md
  • web/e2e/github-link.spec.ts
  • web/e2e/resume-draft-apply.spec.ts
  • web/src/api/agent.ts
  • web/src/api/generated.ts
  • web/src/api/paths.ts
  • web/src/api/types.ts
  • web/src/components/forms/CareerResumeForm.tsx
  • web/src/components/forms/ResumeDraftInjectDialog.module.css
  • web/src/components/forms/ResumeDraftInjectDialog.tsx
  • web/src/components/github-link/GitHubLinkDashboard.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.module.css
  • web/src/components/github-link/ResumeDraftCandidateList.test.tsx
  • web/src/components/github-link/ResumeDraftCandidateList.tsx
  • web/src/constants/messages.ts
  • web/src/formMappers.test.ts
  • web/src/formMappers.ts
  • web/src/hooks/useResumeDraftCandidates.test.ts
  • web/src/hooks/useResumeDraftCandidates.ts
  • web/src/hooks/useResumeDraftPdf.test.ts
  • web/src/hooks/useResumeDraftPdf.ts
  • web/src/utils/resumeDraftCandidates.test.ts
  • web/src/utils/resumeDraftCandidates.ts
  • web/src/utils/resumeImport.test.ts
  • web/src/utils/resumeImport.ts

Comment thread.claude/rules/backend/architecture.md
Comment threadweb/src/components/forms/ResumeDraftInjectDialog.tsx
Comment threadweb/src/components/github-link/GitHubLinkDashboard.tsx
Comment threadweb/src/formMappers.ts Outdated
PR #584 のレビュー指摘 6 件(Actionable 4 + Nitpick 2)を実コードで検証し、
いずれも再現したため修正した。
- 再連携で候補が古いままになる不具合を修正した(Functional Correctness)。
useAsyncTaskPage は transitionToPolling で result をクリアしないため、
連携済みユーザーが再連携しても Boolean(result) は true→true で変化せず、
候補の再取得が走らなかった。連携結果の分析時刻(analyzed_at)を鍵にして
取り直すようにし、旧挙動で落ちる回帰テストを追加した
- 追加先の職歴に取引先が 1 件も無い場合、追加ボタンが押せて
DRAFT_TARGET_OUT_OF_RANGE を投げていた。ボタンを無効化して理由を表示する
(追加先はユーザーが明示指定する契約のため、機械が取引先を作らない)
- ResumeFormSource から id を除外した。マッパーは id を参照しないのに必須
だったため、型の説明と実際が食い違い、テストがキャストで隠していた。
キャストを外して型どおり検証されるようにした
- 非同期タスクが GITHUB_LINK の 1 種類のみという陳腐化した記述を修正した
(dispatch_service.py / architecture.md 2 箇所)。RESUME_DRAFT は
ADR-0020 で ResumeDraftHandler として登録済み。ハンドラ構成図にも追記
- 本番から呼ばれなくなった select_repos を削除し、テストの呼び出しを
rank_repos(source)[:PROJECT_LIMIT] へ置き換えた(ミューテーション対象
ファイルの dead code を残さない)
- 継続期間の境界テストを timedelta による日付演算へ変更した
(MIN_DURATION_DAYS が 31 以上になると不正な日付文字列になり、境界とは
無関係な理由で落ちるため)
backend 668 tests / web 383 tests / E2E 39 件 green。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McjCjUC3j8HWfzGbLfMhyJ
#585(PR #581 相当)が main に squash マージされ、同じ #562 の実装が両側に
別コミットとして入ったためコンフリクトしていた。
- mapper.py / test_resume_draft_mapper.py: 本ブランチ側を採用した。main 側の
select_repos / evaluate_noise は本ブランチの rank_repos /
evaluate_default_selection / build_candidates に置き換わっており、候補一覧 API・
選択 UI がこちらの API に依存しているため
- ただし main 側にしか無かった修正(継続期間の時刻成分切り捨て / PR #581 の
CodeRabbit 指摘)は本ブランチの duration_days へ移植した。_parse_datetime を
追加して UTC の aware datetime で減算する。日粒度でよい「参画中」判定は
_parse_date のまま残す
- 移植分のテスト 2 件(閾値直下 29 日 1 分の境界 / タイムゾーン混在表記)を
本ブランチの evaluate_default_selection の契約に合わせて追加した
- pyproject.toml: only_mutate の同一エントリで、コメント文言のみ本ブランチ側を採用
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KaqnCny4wsD6AkBkJeTEEV
@yusuke0610yusuke0610 changed the title feat(resume-draft): 選定順を「継続期間 × 実装量」へ変更しノイズ判定を付与feat(resume-draft): 経歴書ドラフトを project 明細へ縮小し候補選択制にする(#563〜#567)Aug 5, 2026
@yusuke0610
yusuke0610 merged commit 3ff5d83 into mainAug 5, 2026
24 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agentDevForge AgentbackendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正webフロントエンド (web)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yusuke0610@claude