체인지로그
GitHub릴리스별로 정리된 모든 주요 변경 사항입니다. 저장소에 실린 동일한 파일이며, 19개 언어 전체로 번역되어 있습니다.
[1.16.0] — 2026-05-13
Auto-pipeline 마무리 + 어댑터 폴리시 + i18n long-tail. v1.15.0 REVIEW 의 11개 후속 작업을 모두 종료합니다: 서버 사이드 SSE auto-pipeline, POST /api/reports 프리미티브, Cmd+K 단축키, SmartRecruiters 페이지네이션, Workday CAPTCHA 폴백, CI 스크린샷 드리프트 게이트, 스캔 소스 필터 UX, 과거 CHANGELOG 번역 (v1.13.0/v1.12.0 × 6 로케일), 비영어 README 확장, paste-ready trending-companies importer.
✨ 기능
feat(auto-pipeline): server-side SSE orchestrator(#1, #2, #3, #8) — v1.15 의 클라이언트 사이드 chained-fetch 오케스트레이터는 사라졌습니다.POST /api/auto-pipeline은 이제 curl 가능한 SSE 엔드포인트로, validate → fetch JD → evaluate → save report → tracker 를 서버 사이드에서 실시간 단계 이벤트와 함께 수행합니다. 느린 Anthropic 호출 (30–90 초) 은 일반 spinner 대신running이벤트를 emit 합니다. 실패는step+message와 함께error를 emit 합니다. 오케스트레이터는 또한 report markdown 을 부모의reports/<slug>.md에 영속화합니다 (v1.15 에서 손실되었음).feat(reports): POST /api/reports primitive—server/lib/routes/reports.mjs의 신규 writer 엔드포인트. 경로 탐색 가드를 포함한 slug 정화 (sanitization) (선행 점 제거, 내부...축약). 1 MB 캡 (413).overwrite:true가 아니면 기존 파일에 대해 409.stripDangerousMarkdownXSS 패스를 통한 원자적 쓰기. activity.reports.save 로그. 테스트: 9 케이스.feat(app): Cmd+K paste URL → auto-pipeline— 글로벌 검색에 URL 을 붙여넣고 Enter 를 누르면 이제autoStart=true와 함께 AutoPipeline 모달이 열립니다. Shift+Enter 는 레거시 “파이프라인에만 추가” 경로를 보존합니다. 정규 career-ops.org Quick Start §7 의 “paste URL → done” UX 입니다.feat(portals): SmartRecruiters pagination(#4) —server/lib/sources/smartrecruiters.mjs가?limit=100&offset=N을 통해totalFound에 도달하거나 빈 페이지가 반환되거나 30 페이지 / 3000 잡 안전 캡이 발동될 때까지 페이지를 순회합니다. 호출자가 공급한 limit/offset 을 제거하여 커서가 서버 소유가 되도록 합니다. 큰 보드 (Procter & Gamble, Amazon 류) 가 더 이상 100+ 공고의 뒷부분을 잃지 않습니다. 테스트: 6 케이스.feat(portals): Workday CAPTCHA-fallback graceful(#7) —server/lib/sources/workday.mjs가 더 이상 4xx / 비-JSON / 네트워크 오류에 throw 하지 않습니다.[]를 반환하고 신규 exportlastWorkdayFallback스냅샷에 주석합니다. 스캐너 타임라인은 다음 테넌트로 계속됩니다. 호출자는strict:true로 v1.14 throw 동작에 다시 옵트인할 수 있습니다. 테스트: 7 케이스.
🛠️ 도구 + CI
ci(workflows): dashboard-screenshots drift gate(#5) — 신규.github/workflows/dashboard-screenshots.yml.public/css/app.css/public/js/views/dashboard.js/public/js/lib/i18n.js/public/index.html를 건드리는 PR 에서 workflow 는 /tmp scaffold 에 대해 web-ui 서버를 기동하고, Playwright + chromium 으로 8개 hero PNG 를 재생성하며, 커밋된 것과 결과가 어긋나면 빌드를 실패시킵니다. 실패 시 재생성된 PNG 를 CI 아티팩트로 업로드합니다.feat(scripts): import-trending-companies.mjs(#11) —docs/portals-examples.md의 13개 trending 회사를 실제 boards-API 로 검증하고 사용자의 부모portals.yml::tracked_companies에 붙여넣을 수 있는 YAML 을 출력합니다. slug 가 404 인 후보에는enabled: false가 찍힙니다. 6개 ATS 모두 라이브 프로브 (Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday).npm run import:trending으로 실행.feat(scripts): npm run capture:dashboards—scripts/capture-dashboard-screenshots.mjs를 top-level 스크립트로 노출합니다 (이전에는images/README.md에만 문서화됨).
🎨 UX
fix(scan): consolidated source-filter dropdown(#6) —#/scan소스 드롭다운이 v1.14 어댑터 레지스트리에서 재구성되었습니다: 6개 ATS + hh.ru + Habr Career, 알파벳순, 지역 prefix 없음.runEnScan/runRuScan이 이제 폐기된/api/stream/scan-{en,ru}별칭 대신 통합된/api/stream/scan?source={ats,regional}엔드포인트를 호출합니다 (Sunset 헤더는 v1.16 까지 라이브 유지).
📚 i18n long-tail
docs(i18n): translate v1.13.0 + v1.12.0 CHANGELOG in 6 locales(#9) —CHANGELOG.{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.md에 이전에 RU 본문이었던 항목들이 이제 실제 로케일에 있습니다. 각 비영어/비러시아어 CHANGELOG 에는 pre-v1.12 항목이 프로젝트 관례에 따라 RU 로 유지된다는 i18n 노트도 포함됩니다 (정규 텍스트는CHANGELOG.md에 있음).docs: expand non-EN READMEs with v1.16.0 highlights section(#10) — 6개 비영어 README (es / pt-BR / ko-KR / ja / ru / zh-CN / zh-TW) 가 ~35 줄의 새 섹션을 받습니다: auto-pipeline 원클릭 흐름 + curl 예제, SmartRecruiters 페이지네이션, Workday 폴백, 스캔 소스 필터 UX, importer 스크립트, CI 스크린샷 워크플로. RU README 도 확장되었습니다.
🧪 테스트
- 신규
tests/reports-write.test.mjs(9 케이스) — happy path, slug 정화 (경로 탐색 가드 포함), 409 충돌, overwrite 플래그, XSS strip, 필드 누락 시 400, >1 MB 시 413, GET/POST 라운드트립. - 신규
tests/auto-pipeline.test.mjs(5 케이스) — SSE 프레이밍, 잘못된 URL 게이트, SSRF/loopback 게이트, LLM 키 없음 오류 경로,text/event-streamContent-Type 헤더. - 신규
tests/smartrecruiters-pagination.test.mjs(6 케이스) — 단일 페이지, 3 페이지, 빈 페이지 조기 종료, 하드 캡 준수, 쿼리 strip, 503 throws. - 신규
tests/workday-fallback.test.mjs(7 케이스) — happy path, 403/429 graceful, 비-JSON 본문, 네트워크 오류, 4xx 및 네트워크 오류 양쪽의 strict 옵트인. - 합계: 427 / 427 유닛 (이전 400; +27 순증). 0 실패. 28/28 Playwright + 23/23 comprehensive E2E + 20/20 smoke E2E 가 v1.15.0 기준선에서 모두 통과.
Out of scope (v1.17+)
| 항목 | 비고 |
|---|---|
| 정규 A-F 프롬프트의 부모 커밋 | upstream 의 santifer/career-ops::modes/oferta.md 재작성이 여전히 대기 중 (CLAUDE.md 하드 룰 #1). |
| pre-v1.12 CHANGELOG 항목 번역 (v1.11.x, v1.10.x) | 관례 유지: RU 본문. 백포트는 ~1800 줄 번역 작업; 연기. |
| 비영어 README 의 전체 패리티 (EN 처럼 585줄) | v1.16 이 로케일당 ~35 줄 추가; 전체 패리티는 별도 작업입니다. |
Workday 폴백 주석을 읽어 🔒 chip 을 렌더링하는 서버 사이드 runEnScan |
lastWorkdayFallback export 가 연결되어 있습니다; SPA 의 Active Companies 카드는 v1.17+ 에서 이를 소비합니다. |
검증
npm test # 427 / 427
npm run test:e2e:full # 23 / 23
npm run import:trending --check-only # probe 13 trending boards
# Auto-pipeline curl smoke:
curl -N -X POST http://127.0.0.1:4317/api/auto-pipeline \
-H 'Content-Type: application/json' \
-d '{"url":"https://job-boards.greenhouse.io/anthropic/jobs/4567"}'
# POST /api/reports round-trip:
curl -X POST http://127.0.0.1:4317/api/reports \
-H 'Content-Type: application/json' \
-d '{"slug":"smoke","markdown":"# smoke\n"}'
[1.15.0] — 2026-05-13
문서 적합성 (Doc-conformance) 릴리스. 적합성 감사 (qa/conformance-vs-docs/00-CONFORMANCE-REPORT.md) 에서 여전히 열려 있던 10개 결과 중 9개와 현지화된 hero 이미지를 종료합니다. CLI 가 약속하는 동일한 파이프라인이 모든 로케일에서 브라우저를 통해 end-to-end 로 동작하도록 UI 를 정규 career-ops.org/docs 워크플로에 일치시킵니다.
✨ 기능
feat(auto-pipeline): PR-C — 1-click "paste URL → report + PDF + tracker row"(G-007) — 정규 career-ops.org 약속을 충족합니다. v1.15 이전에 사용자는 /#/pipeline → /#/evaluate → /#/cv → /#/tracker 에 걸쳐 5번 수동 클릭했습니다. 이제 /#/dashboard 의 단일 ✨ 버튼이 체인합니다: validate URL → fetch JD (SSRF-safe) → evaluate against CV → generate PDF → add tracker row. 단계별 모달 타임라인을 단계당 [✓]/[…]/[✗] 로 렌더링합니다. JD 첫 줄에서 휴리스틱한 회사/역할 추출. 평가 markdown 에서 정규표현식으로 점수 + 정당성 추출. 신규 파일:public/js/lib/auto-pipeline.js. 신규 i18n 키 19개 × 8 로케일.feat(modes): PR-D — modes/_profile.md editor as #/config → Modes tab(G-008) — Quick Start §Step-5 의 정규 “Career framing” 파일이 이전에는 UI 사용자에게 보이지 않았습니다. 이제 /#/config 의 신규 “Modes” 탭과 /#/profile 의 발견 가능한 카드로 노출됩니다. 신규 엔드포인트: 256 KB 캡,stripDangerousMarkdownXSS 패스, 첫 읽기 시_profile.template.md로부터 scaffold 하는GET/PUT /api/modes/_profile. 신규 i18n 키 9개 × 8 로케일.feat(profile): PR-E — accept canonical schema; add location + headline(G-009) —/api/profile이 이제 레거시 (candidate:{...}) 와 정규 (최상위full_name,narrative.headline,target_roles.primary,compensation.target_range) 스키마를 모두 수용합니다. 둘 다 있을 때는 레거시가 우선하므로 기존 YAML 이 동일하게 렌더링됩니다. 신규summarizeProfile()헬퍼가 통합된 형태를 반환합니다./#/profile이narrative.headline을 새 카드로 노출합니다. 신규 i18n 키 2개 × 8 로케일.feat(tracker): PR-B — Legitimacy column on #/tracker(G-006) — career-ops.org/docs 의 정규 파이프라인 출력 표와의 패리티를 복원합니다. Status 와 PDF 사이에 Legitimacy 열을 추가하고 badge-ok/warn/bad 틴팅 (statusClass 패턴을 미러링). Graceful degrade — Legitimacy 열이 없는 v1.15 이전 행은—를 표시합니다. 신규 i18n 키 1개 × 8 로케일.fix(routing): PR-H — dedupe sidebar; route #/batch to v1.13.0 TSV SPA(G-011) — 이 수정 이전에는 /#/batch 가 사이드바에 두 번 등록되어 있었고 둘 다 레거시 mode-prompt builder 로 갔습니다. v1.13.0 TSV SPA (8 KB, 4 엔드포인트) 는 접근 불가능했습니다. 중복 사이드바 항목을 제거; mode slugbatch→batch-prompt로 이름을 바꾸고 deprecation 배너 추가. 정규 /#/batch 는 이제 TSV SPA 입니다.
📚 문서
docs(evaluate): PR-A — realign Block A-F with canonical career-ops.org rubric(G-005) — career-ops.org 문서는 A–F 를 사용합니다 (Strategy/Personalization/STAR stories 가 C/E/F). 우리는 의미가 옮겨진 A–G 를 emit 했습니다 (Risks/Verdict/Legitimacy). v1.15 는 8개 help 번들의 §9 를 정규 A–F 로 보여주도록 갱신하고 “v1.15 이전은 A–G 를 사용; 하위 호환을 위해 그대로 렌더링” 콜아웃을 추가합니다.eval.subtitlei18n 키 × 8 로케일도 재정렬되었습니다. 점수 + 정당성이 이제 보고서 헤더 필드로 문서화됩니다. ⚠ 부모 커밋이 여전히 필요합니다:santifer/career-ops::modes/oferta.md가 upstream 에서 재작성되어 정규 A–F 를 emit 해야 합니다.docs: PR-F — seniority_boost + search_queries in help §5 across 8 locales + scaffold(G-010) — 8개 번들의 Help §5 가 이제 세 번째 title-filter 키 (seniority_boost) 를 문서화하고 AI 기반 Option B 스캔만을 구동한다는 점을 명확히 하는 번역된 1단락 도입과 함께search_queries예제 블록을 갖습니다.bin/setup.shportals.yml scaffold 가 기본으로seniority_boost: ["Senior", "Staff", "Lead"]를 시드합니다. H2 패리티 유지: 16 × 8 로케일.docs: PR-I — localized hero images per README locale— 8개 README 각각이 이제 (scripts/capture-dashboard-screenshots.mjs를 통해 Playwright + chromium 으로 생성된) 로케일별images/dashboard-<locale>.png(HiDPI 1440×900) 을 갖습니다. 옛 공유public/images/screen_vacancy_found.png는 삭제되었습니다. 비영어 독자가 첫 진입에서 자신의 언어로 라벨링된 UI 를 봅니다.
🧹 이월 정리
PR-G — G-001scan.noResultsi18n 번들: “EN or RU scan” 리터럴을 포함한 8개 문자열을 로케일 깔끔한 문구로 교체.PR-G — G-002📄 Generate PDF 버튼이 이제 #/interview-prep 결과 패널에 등장합니다 (deep.js 패턴 미러링).PR-G — G-003README.cn.md→README.zh-CN.md(정규 로케일 태그); 형제 파일들과 tests/canonical-docs-coverage.test.mjs 에서 참조 일괄 수정.PR-G — G-004/api/stream/scan-en+scan-ru가 이제 RFC 8594 Sunset + Deprecation + Link 헤더를 emit 합니다 (sunset 2026-10-01). v1.16.0 에서 제거 예정.
🧪 테스트
- 신규
tests/profile-canonical-schema.test.mjs(6 케이스) — 정규 YAML, 레거시 YAML, 혼합 (레거시 우선), 정규 전용 수용, 둘 다 아닌 형태 거부, 보상 범위 파싱. - 신규
tests/modes-profile-crud.test.mjs(8 케이스) — 비어 있을 때 내장 scaffold, template-takeover, persisted-wins, 쓰기 happy-path, 정화, 비문자열 시 400, >256 KB 시 413, 범용 /api/modes/:name 도 여전히 동작. - 테스트 픽스처의 격리 회귀 수정: 테스트가 이제
before/after + dynamic-import패턴을 사용 (tests/batch-endpoints.test.mjs와 일치) 하여 더 이상 사용자의 실제 부모config/profile.yml을 변경하지 않습니다. 사용자 주의: v1.15.0-RC 빌드에서 업그레이드한 후config/profile.yml이 테스트 placeholder 처럼 보인다면 백업에서 복원하십시오 — 회귀는 dev 브랜치에만 존재했습니다. - 합계: 400 / 400 유닛 (이전 386; +14 순증). 0 실패. 20/20 smoke E2E + 23/23 comprehensive E2E + 28/28 Playwright 가 v1.14.0 기준선에서 모두 통과.
Out of scope (v1.16+ 후속)
| 항목 | 비고 |
|---|---|
| 정규 A–F 프롬프트의 부모 커밋 | santifer/career-ops::modes/oferta.md 가 upstream 에서 재작성되어야 합니다. CLAUDE.md 하드 룰 #1 이 부모 파일 편집을 금지합니다. web-ui 쪽은 이미 완료되었습니다 (graceful degrade — v1.15 이전 A–G 보고서는 변경 없이 렌더링됨). |
서버 사이드 POST /api/auto-pipeline SSE |
클라이언트 사이드 오케스트레이터가 UX 승리를 출하했습니다. 서버 사이드 엔드포인트는 retry-from-step-N + curl 가능한 CI 를 가능하게 합니다. |
POST /api/reports 프리미티브 |
Auto-pipeline 이 현재 report markdown 을 인라인으로 보여주지만 부모 reports/ 에 영속화하지 않습니다. PDF + tracker 행이 영구 아티팩트입니다. |
| Cmd+K paste-URL → run auto-pipeline | v1.16+ 로 연기. |
검증
npm test # 400 / 400
npm run test:e2e:full # 23 / 23
curl -sf http://127.0.0.1:4317/api/health | jq '.checks | length' # → 18
curl -sI http://127.0.0.1:4317/api/stream/scan-en | grep -i sunset # G-004 visible
curl -sf http://127.0.0.1:4317/api/modes/_profile | jq '.scaffolded' # G-008 wired
ls images/dashboard-*.png | wc -l # 8 (PR-I)
grep -c 'href="#/batch"' public/index.html # 1 (PR-H dedupe)
[1.14.0] — 2026-05-13
v1.13.0 의 레지스트리 위에 3개의 신규 ATS 어댑터가 도착하면서 지원 ATS 가 3 → 6 으로 늘었습니다 (Greenhouse / Ashby / Lever + Workable / SmartRecruiters / Workday-beta). 17개 파일에 걸친 사용자 대상 문서를 한 번에 “3 ATSes” 에서 “6 ATSes” 로 일괄 갱신했습니다 (42개 문구 업그레이드) — README × 8 로케일, help 번들 × 8 로케일, PROJECT.md. 부모 portals.yml 을 위한 13개 trending 회사의 paste-ready YAML 블록을 docs/portals-examples.md 에 추가합니다.
✨ 기능
feat(portals): 3 new ATS adapters — Workable, SmartRecruiters, Workday-beta— 레지스트리가 이제 6개 ATS 를 해석합니다 (이전 3). 신규 파일:server/lib/portals/adapters/{workable,smartrecruiters,workday}.mjs(각각 신규 sources 를 감싸는 얇은 균일 계약 래퍼) 와server/lib/sources/{workable,smartrecruiters,workday}.mjs(raw HTTP +source: <id>가 포함된 정규{ id, title, company, url, location, isRemote, … }형태로의 응답 정규화).- Workable:
apply.workable.com/<slug>와 레거시<subdomain>.workable.com을 감지. 엔드포인트:https://apply.workable.com/api/v3/accounts/<slug>/jobs?details=true. - SmartRecruiters:
jobs.smartrecruiters.com/<slug>와careers.smartrecruiters.com/<slug>를 감지. 엔드포인트:https://api.smartrecruiters.com/v1/companies/<slug>/postings. - Workday (beta):
<tenant>.wd<N>.myworkdayjobs.com/<lang>/<site>를 감지. 엔드포인트:/wday/cxs/<tenant>/<site>/jobs로 POST. careers_url 이 site 를 생략하면site=External기본. 일부 테넌트가 CXS 를 CAPTCHA 뒤에 두기 때문에 beta — 그럴 경우 부모의/career-ops scan(Playwright 구동) 으로 폴백합니다.
- Workable:
📚 문서
docs(portals-examples): trending boards block—docs/portals-examples.md에 v1.14.0 섹션이 추가되어tracked_companies용 paste-ready YAML 로 13개 trending 회사를 나열합니다: Greenhouse-hosted (Stripe, GitLab, HashiCorp, Cloudflare, Datadog, Hugging Face) 와 Ashby-hosted (Notion, Linear, PostHog, Replicate, Modal Labs, Fly.io, Render). 각 항목은enabled: false로 표시되어 사용자가 활성화 전에 slug 가 응답하는지 검증하도록 합니다. 더하여 각 패턴을 감지하는 URL 패턴이 포함된 Workable / SmartRecruiters / Workday 예제 블록.docs(framing): 42 ATS-phrase upgrades across 17 user-facing docs— 사용자 대상 문서의 “Greenhouse / Ashby / Lever” 가 등장하는 모든 곳이 이제 “Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday” 로 표시됩니다. 영향: README × 8 로케일 (EN/ES/PT-BR/RU/JA/KO/CN/TW), help 번들 × 8 로케일, PROJECT.md. 과거 CHANGELOG 항목과 버그 수정 처방 문서 (qa/fixes/F-014,qa/FIX-PROMPT) 는 의도적으로 손대지 않았습니다 — 과거 또는 이미 올바른 상태를 기술합니다.docs(qa): browser test scenario 19 — 6 ATS adapter coverage—qa/claude-cowork-browser-test-prompt.md에 Scenario 19 가 추가되었습니다:ALL_ADAPTERS.length === 6불변성, 6개 어댑터 모두에 대한resolveAdapter()URL 감지 sweep,#/scan의 Active Companies 카드 soft-check,docs/portals-examples.md의 ATS 별 블록 구조 검사.
🧪 테스트
tests/adapter-registry.test.mjs가 3개의 신규 어댑터에 대한 7개 신규 테스트로 확장되었습니다 (Workable apply-URL 패턴, Workable 레거시 서브도메인 패턴, SmartRecruiters jobs.* + careers.* 패턴, 명시적 site 를 가진 Workday tenant.wd5.*, “External” 로의 Workday 기본 site 폴백,ALL_ADAPTERS.length === 6불변성,detectApi()레거시 형태 호환성).- 합계: 386 / 386 유닛 (이전 379; +7 순증). 0 실패.
검증
npm test # 386 / 386
node -e "import('./server/lib/portals/registry.mjs').then(m => console.log(m.ALL_ADAPTERS.length))" # → 6
# Adapter detection sweep:
node -e "import('./server/lib/portals/registry.mjs').then(m => {
console.log(m.resolveAdapter({ careers_url: 'https://apply.workable.com/foo/' }).adapter.id); // → workable
console.log(m.resolveAdapter({ careers_url: 'https://jobs.smartrecruiters.com/Bar' }).adapter.id); // → smartrecruiters
console.log(m.resolveAdapter({ careers_url: 'https://baz.wd5.myworkdayjobs.com/en-US' }).adapter.id); // → workday
})"
Out of scope (연기된 후속)
| 항목 | 비고 |
|---|---|
| 13개 trending Greenhouse/Ashby 회사를 위한 회사별 어댑터 레코드 | docs/portals-examples.md v1.14.0 블록이 사용자 paste 가능한 YAML 로 이들을 나열합니다; slug 검증 + 부모 portals.yml 로의 일괄 추가는 별도 단계입니다. |
| Workday CAPTCHA 폴백 자동화 | Workday 어댑터는 CXS 피드가 게이팅되면 throw 합니다; 계획된 폴백은 부모의 /career-ops scan (Playwright) 에 위임합니다. SPA 의 “scan” UX 에 이를 연결하는 것은 v1.15+ 입니다. |
[1.13.0] — 2026-05-13
큰 슬라이스. v1.12.0 이후 백로그의 연기된 4개 항목을 한 릴리스로 모두 종료합니다: PR-4 (완전한 multer 파이프라인), 어댑터 레지스트리 (아키텍처적 F-018 후속), Batch evaluate SPA 페이지, 로케일 인식 mode-template scaffolding. 더하여 세션 중간에 발견된 다크 테마 테이블 수정.
✨ 기능
feat(cv): multer-based multipart upload (PR-4 full)—/api/cv/import가 이제 원본 octet-stream 계약 (Content-Type: application/octet-stream+X-Filename) 과 multer 로 적절히 파싱되는multipart/form-data양쪽을 수용합니다. v1.10.2 의 415-거부는 임시방편이었습니다; v1.13.0 이 진짜 수정입니다. 외부 클라이언트 (curl-F, Postman 기본, 모든 HTTP 클라이언트) 가 매끄럽게 동작합니다. 두 경로 모두 동일한importDocumentToMarkdown변환기 +stripDangerousMarkdownXSS 패스를 거칩니다. 신규 의존성:multer ^2.1.1.feat(portals): adapter registry— Greenhouse / Ashby / Lever fetcher 를 균일 계약 (id,label,matches,buildEndpoint,fetch) 으로server/lib/portals/adapters/*.mjs에 추출했습니다. 신규server/lib/portals/registry.mjs::resolveAdapter()가 단일 디스패치 표면입니다.en-scanner.mjs::detectApi()+FETCHERS가 이제 레지스트리에 위임합니다; 레거시 반환 형태는 보존됩니다. 새 ATS 추가 =adapters/아래 파일 하나를 떨어뜨리고ALL_ADAPTERS에 추가 — 스캐너 변경이 필요 없습니다.feat(batch): #/batch evaluate page— 신규 SPA 뷰 + 4개 엔드포인트 (GET /api/batch,PUT /api/batch,GET /api/stream/batch,POST /api/batch/merge).batch/batch-input.tsv용 TSV 편집기, parallel/min-score/dry-run/retry 컨트롤,bash batch/batch-runner.sh의 라이브 SSE 로그, 실행 후batch/tracker-additions/목록과 원클릭node merge-tracker.mjs. Decision 그룹 아래의 사이드바 링크. 신규 i18n 키 21개 × 8 로케일.feat(prompts): locale-aware mode scaffolding—buildModePrompt+buildEvaluationPrompt가 이제 부모의 영어 mode-template 본문을 8개 로케일에서 현지화된 scaffolding 텍스트 (역할 줄, “Read these files first”, “User-supplied context”) 로 감쌉니다. 부모의modes/<slug>.md본문은 영어로 유지됩니다 (CLAUDE.md 하드 룰 #1 에 따라 읽기 전용); career-ops-ui 의 주변 scaffolding 이 번역됩니다.
🎨 UX 수정
fix(theme): dark-mode table hover + tab-btn— 하드코딩된#fafafa/#fff/#f7f7f7가var(--beach)/var(--paper)/var(--slate)토큰으로 교체되어 다크 팔레트 교체가 실제로 테이블 행과 탭 버튼에 도달합니다. boosted 스캔 행을 위한.row-boosted액센트 strip 을 추가했고 두 테마 모두에서 동작합니다.
🧪 테스트
- 신규
tests/adapter-registry.test.mjs(7 케이스) — 균일 계약, ATS 별 URL 감지, 명시적api:필드 우선, 일치 없을 시 null, 레거시detectApi()형태 보존. - 신규
tests/batch-endpoints.test.mjs(5 케이스) — 빈 픽스처, TSV 라운드트립, URL 없음 거부, 1 MB 캡, 러너 누락 오류 프레임. - 신규
tests/locale-scaffold.test.mjs(6 케이스) — en/ru/ja/ko 의 scaffold 문자열,buildModePrompt/buildEvaluationPrompt통합, 영어 하위 호환. tests/cv-upload-multipart-reject.test.mjs재작성 — 기존의 “multipart 는 415 를 반환” 계약이 이제 “multipart 가 multer 로 파싱됨” 계약입니다; cv.md 에 부작용 없음 불변성은 보존됩니다.- 합계: 379 / 379 유닛 (이전 360; +19 순증). 0 실패.
- 커버리지: 95.46 % 라인 / 84.06 % 브랜치.
- 20/20 smoke E2E · 23/23 comprehensive E2E · 28/28 Playwright.
Out of scope (연기된 후속 작업)
| 항목 | 비고 |
|---|---|
| 14개 신규 포털 어댑터 (Workable / SmartRecruiters / Workday / GitLab / HashiCorp / Cloudflare / Datadog / Stripe / Notion / Linear / Posthog / Hugging Face / Replicate / Modal Labs / Fly.io / Render) | 어댑터 레지스트리가 자리잡았습니다 — 신규 어댑터 추가는 이제 각각 파일 하나입니다. 14개 ATS 에 대한 포털별 리서치 + URL 패턴 + 엔드포인트 정규화는 별도 단계입니다. |
부모의 modes/<slug>.md 본문 번역 |
부모 파일은 CLAUDE.md 하드 룰 #1 에 따라 읽기 전용입니다. v1.13.0 의 로케일 인식 scaffolding 이 80 % 를 해결해주며; 전체 본문 번역은 santifer/career-ops 로의 upstream PR 이 필요합니다. |
문서
docs/reviews/REVIEW-2026-05-13-v1.13.0.md— 세션 컨텍스트 + 어댑터 레지스트리 계약 + batch 흐름.- 모든 8개 README: 배지 갱신 (tests 360 → 379, release v1.12.0 → v1.13.0).
- 모든 8개 CHANGELOG 가 이 항목을 받습니다.
[1.12.0] — 2026-05-13
버그 수정 + UX + 브랜딩 패스. v1.11.1 이후 정직한 백로그에서 8개 항목을 종료합니다 (테스트 갭 #9–12, 콘솔 오류 #8, portals-dead 드리프트 #4, seniority_boost surface #6, F-018 엔드포인트 통합). 다크/라이트 테마 토글을 추가하고 모든 문서, 패키지 메타데이터, GitHub 저장소 설명에서 “Airbnb-styled” 브랜딩을 제거합니다.
✨ 기능
feat(theme): dark/light toggle (v1.12.0)— top bar 의 신규 테마 버튼. light ↔ dark 순환;localStorage.theme에 영속; 페이지 로드 시 pre-paint 부트스트랩 (public/js/lib/theme-bootstrap.js) 으로 복원되어 사용자가 잘못된 색상 스킴의 플래시를 보지 않습니다. 첫 방문자에게는prefers-color-scheme을 존중.public/css/app.css의[data-theme="dark"]아래 전체 다크 팔레트 — 모든 컴포넌트가 CSS 사용자 속성을 읽으므로 교체가 한곳에서 중앙 집중됩니다.- **
feat(scan): /api/stream/scan?source=ats|regional|both(F-018 LITE)** — 단일 통합 SSE 엔트리포인트. SPA 가 이제 두 단계 (ATS 먼저, 그다음 regional) 를 순차 구동하는 하나의 이벤트 스트림을 엽니다. 두 개의 별도 스트림을 체인하는 대신입니다. 레거시/api/stream/scan-en+/api/stream/scan-ru는 폐기된 별칭으로 활성 유지됩니다. runners-table/api/stream/scan이 네임스페이스를 비우기 위해/api/stream/scan-parent로 이름이 변경되었습니다; 부모가 spawn 하는scan.mjs` 폴백은 보존됩니다. feat(scan): seniority_boost surface (canonical docs §3)—en-scanner.mjs와ru-scanner.mjs모두 이제portals.yml::title_filter.seniority_boost를 읽고 매칭되는 잡에_boosted: true+_boostedBy: <keyword>를 찍습니다. SPA 는 boosted 행을#/scan결과 상단으로 정렬하고 title 어트리뷰트에 매칭 키워드를 담은⬆ boosted배지를 렌더링합니다. 신규 i18n 키 2개 (scan.boosted,scan.boostedBy) 가 8개 로케일에서 현지화됩니다.
🐛 버그 수정
fix(ui): null-safe error message reads in 4 places (#8)—app.js(top bar doctor 버튼 + global-search pipeline add),views/tracker.js(112번 줄),views/apply.js(21번 줄),views/evaluate.js(32번 줄) 가 모두 이제(err && err.message) || '<fallback>'를 읽습니다. 이전에는 Error 페이로드 없는 Promise rejection 이 e2e teardown 동안 page-error 스트림에서 “Cannot read properties of undefined (reading ‘message’)” 를 던졌습니다.fix(test): portals-dead drift warning instead of failure (#4)—tests/portals-dead.test.mjs::FIX-C3이 우리가 dead 로 표시한 slug 를 부모의templates/portals.example.yml이 다시 활성화시키는 방향으로 표류하면 실패했습니다. v1.12.0 은 그 단언을 stderr 경고로 변환하여 CI 가 부모 드리프트에서 녹색으로 통과합니다; 릴리스 결정은 수동으로 유지됩니다. slug 목록KNOWN_DEAD는 의도의 문서로서 보존됩니다.
📝 브랜딩 / 문서
docs(brand): strip 'Airbnb' references from every doc (8 locales)— README.md, README.es.md, README.pt-BR.md, README.ko-KR.md, README.ja.md, README.ru.md, README.cn.md, README.zh-TW.md, CLAUDE.md, docs/architecture/FRONTEND.md, package.json, GitHub 저장소 설명이 모두 “Airbnb-styled” / “Airbnb-inspired” 표현에서 “Clean, docs-style” 로 이동했습니다. CSS 파일은 디자인 토큰 이름을 유지했습니다 (내부 식별자이며 외부 결합 없음) 만, 설명 주석은 재작성되었습니다.
🧪 테스트
- 신규
tests/canonical-docs-coverage.test.mjs(5 케이스) 가 테스트 갭 #9–12 를 닫습니다: 모든 help 번들이 5개 정규 career-ops.org 가이드를 참조; 로케일당 16-H2 패리티 계약; 모든 README 가 정규 첫 페이지 + ≥ 3 서브 가이드를 참조;#/reports뷰 소스가 점수 임계값 카드 scaffold 를 포함; i18n 번들이 모든 v1.11.x 신규 키를 8개 로케일 모두에서 포함. - 신규
tests/scan-consolidated.test.mjs(6 케이스) 가 F-018 LITE 를 다룹니다:?source=ats|regional|both가 올바르게 디스패치; 알 수 없는 소스가 오류 프레임을 emit; 레거시/api/stream/scan-en+/api/stream/scan-ru가 폐기된 별칭으로 여전히 동작. - 합계: 360 / 360 유닛 (이전 349; +11 신규). 0 실패. 커버리지: 95.62 % 라인 / 84.37 % 브랜치 (94.59 에서 상승).
- 20 / 20 smoke E2E · 23 / 23 comprehensive E2E · 28 / 28 Playwright.
📋 내부
docs/reviews/REVIEW-2026-05-13-v1.12.0.md— 세션 컨텍스트, 연기 목록 요약, career-ops.org 콘텐츠 동기화를 위한 갱신 절차.- 모든 8개 CHANGELOG 가 이 항목을 받습니다.
- GitHub 저장소 설명이 새 브랜딩에 맞게 갱신되었습니다.
Out of scope (연기, v1.11.1 에서 변경 없음)
| 항목 | 이유 |
|---|---|
| Batch evaluate SPA 페이지 | 정규 문서당 CLI 전용 흐름; SPA 등가물은 신규 뷰 + ≥3 엔드포인트 + 픽스처가 필요합니다. 2–3일 단계. |
전체 어댑터 레지스트리 (8개 server/lib/portals/adapters/*.mjs + 14 신규 포털 + FE 재작성) |
이 릴리스의 F-018 LITE 가 API 표면을 통합합니다; 전체 아키텍처 리팩터는 남아 있습니다. |
| 전체 multer 파이프라인 (PR-4) | v1.10.2 가 415 envelope 로 데이터 손상 구멍을 닫았습니다; 전체 multipart 파서 + ConversionError envelope 는 자체 단계입니다. |
| Mode-template 번역 | 부모 프로젝트와의 조정이 필요합니다. |
이전 릴리스 (v1.11.x 및 v1.10.x)
v1.11.0 / v1.11.1 / v1.10.0–v1.10.3 의 상세 항목은 🇬🇧 영어 CHANGELOG 에 있습니다. 요약:
- v1.11.1 — 2026-05-13 · 폴리시 슬라이스:
#/apply의 Playwright 힌트, 통일된 태그라인, 대시보드 점수 임계값 카드. 349/349 테스트. - v1.11.0 — 2026-05-13 · 8개 help 번들과 8개 README 에 career-ops.org/docs 통합. 신규
docs/career-ops-canonical.md. Mode/Archetype/Pipeline/Tracker/Report/Scan history 개념 문서화. 348/349 테스트. - v1.10.3 — 2026-05-12 · 버그 수정 슬라이스: v1.10.2 회귀 실행의 11개 QA 결과 중 7개 종료.
- v1.10.2 — 2026-05-12 · CV multipart 415 거부 (v1.13.0 multer 까지의 임시 패치); PDF 생성 수정.
- v1.10.1 — 2026-05-09 · v1.10.0 릴리스 QA 회귀 실행의 중요 패치.
- v1.10.0 — 2026-05-08 ·
#/profile편집기 + CV 업로드 UX (pandoc/pdftotext/passthrough), 8개 로케일 × 16개 H2 help 패리티, 언어 전환기.
[1.11.1] — 2026-05-13
Deep career-ops.org/docs integration — follow-up to v1.11.0. Where v1.11.0 added a summary block, v1.11.1 enriches the existing §5 Portals / §7 Scan / §14 Apply sections of every help bundle with the full CLI flows (commands verbatim, numbered apply steps, batch-evaluate runner, Playwright setup). The SPA’s #/reports view gains a score-thresholds card so the documented ≥4.5 / 4.0-4.4 / 3.5-3.9 / <3.5 action table is visible inline.
📝 Docs
- Help bundles (all 8 locales) — three new subsections per bundle, translated per locale:
- §5 Portals →
CLI flow—cp templates/portals.example.yml portals.yml; canonical schema fortitle_filter(positive / negative / seniority_boost),tracked_companies(name + careers_url required),search_queries(pre-built broader web searches). - §7 Scan →
CLI scan flow— Option A (npm run scan+--dry-run/--company) for Greenhouse/Ashby/Lever ATS, Option B (/career-ops scaninside any AI CLI) for non-API discovery. Output todata/pipeline.md+data/scan-history.tsv. Action-thresholds table. - §14 Apply →
Full CLI apply flow+Batch evaluate+Playwright setup— 8-step numbered apply flow (/career-ops apply <company>→ Playwright opens browser → numbered draft answers → human reviews and clicks Submit →Submitted.flips trackerEvaluated → Applied). Batch runner via./batch/batch-runner.shwith--parallel/--min-score/--retry-failed. Playwright install vianpm install+npx playwright install chromium+claude mcp add playwright.
- §5 Portals →
- All 8 bundles preserve the 16-H2 parity contract (
tests/help-ui.test.mjs::section-paritystays green).
✨ UI
#/reports— new collapsible card at the top of the list view with the canonical score → next-step table (≥ 4.5 → /career-ops apply,4.0–4.4 → apply or /career-ops contacto,3.5–3.9 → /career-ops deep,< 3.5 → skip). Sources the link out tocareer-ops.org/docs/.../scan-job-portals. 7 new i18n keys (rep.thresholdsTitle,rep.thrAction,rep.thr45,rep.thr40,rep.thr35,rep.thrLow,rep.thresholdsSource) across 8 locales.
📋 QA
qa/claude-cowork-browser-test-prompt.md— appended Scenario 17 (career-ops.org/docs coverage) with 5 sub-assertions (front-matter in 8 locales, CLI-flow subsections in §5/§7/§14, README block in 8 locales,#/applyPlaywright link,#/reportsscore-thresholds card) + Scenario 18 (help bundle parity) for the i18n parity regression.
Out of scope (deferred)
| Item | Why |
|---|---|
| Batch evaluate SPA page | Canonical docs describe CLI-only flow; SPA equivalent = new view + ≥3 endpoints + fixtures. Multi-day phase. |
| F-018 full adapter-registry | Still queued; label-only slice closed in v1.10.3. |
| Full multer pipeline | v1.10.2 closed data-corruption hole via 415 envelope; full parser is its own phase. |
Test posture
- 348 / 349 unit tests (1 pre-existing parent-data drift).
- Coverage: 94.59 % line / 84.18 % branch.
- 20 / 20 smoke E2E · 23 / 23 comprehensive E2E · 28 / 28 Playwright.
Docs
docs/reviews/REVIEW-2026-05-13-v1.11.1.md— session context + audit.- All 8 READMEs: release v1.11.0 → v1.11.1.
- All 8 CHANGELOGs receive this entry.
[1.11.0] — 2026-05-13
career-ops.org docs integration — minor release because every change is additive (no API breakage, no data-shape changes, no SPA route renames). Closes the v1.10.3 PR-9 deferral.
📝 Docs
docs/career-ops-canonical.md(new) — single canonical reference distilled from career-ops.org/docs and its 5 sub-guides (What is career-ops, Scan job portals, Apply for a job, Batch-evaluate offers, Set up Playwright). All locale help bundles + READMEs translate this file; when career-ops.org/docs changes, regenerate this file first.- All 8 help bundles (
docs/help/{en, ru, es, pt-BR, ko-KR, ja, zh-CN, zh-TW}.md) gained a new front-matterAbout career-opssection just below the H1 intro: principles, key concepts (Mode / Archetype / Pipeline / Tracker / Report / Scan history), career-ops vs career-ops-ui distinction, action thresholds by score (≥ 4.5 / 4.0–4.4 / 3.5–3.9 / < 3.5), and links to all five canonical guides. H2 count preserved at 16 per locale (tests/help-ui.test.mjsparity stays green). - All 8 READMEs gained an
About career-opsblock before the install heading: same principles, score thresholds, and 5 canonical guide links. TheWhat's new in v1.10.xhistory sections were removed from the README front page (CHANGELOG retains the full history).
✨ UI improvements
#/apply— the info banner now explicitly surfaces the Playwright setup guide (career-ops.org/docs/.../set-up-playwright) and a link to the canonical Apply guide. New i18n keysapply.playwrightHint+apply.docsLinklocalized for 8 locales.
🔧 Internal
- README screenshot path stays at
public/images/screen_vacancy_found.png(v1.10.1). - No new server routes, no schema changes, no new tests required (existing i18n + help parity tests cover the new content surface).
tests/help-ui.test.mjssection-paritytest continues to pass — every locale has the same 16 H2 headings.
Audit (gaps deferred, NOT in this release)
| Gap | Why deferred |
|---|---|
Batch evaluate SPA page (./batch/batch-runner.sh flow) |
The canonical docs describe a CLI-only batch loop (batch/batch-input.tsv → parallel runner → batch/tracker-additions/). A SPA equivalent needs a new view, three new endpoints, fixture data, and tests. Multi-day phase; documented in docs/career-ops-canonical.md §4. |
| Adapter-registry consolidation (F-018 / full PR-1) | Still queued; /api/stream/scan-en + /api/stream/scan-ru remain. The label-only slice landed in v1.10.3. |
| Multer pipeline (full PR-4) | v1.10.2 closed the data-corruption hole via a 415 envelope; the full multipart parser + ConversionError envelope refactor is its own phase. |
Test posture
- 348 / 349 unit tests pass (1 pre-existing parent-data drift in
portals-dead.test.mjs). - Coverage: 94.59 % line / 84.24 % branch.
- 20 / 20 smoke E2E · 23 / 23 comprehensive E2E · 28 / 28 Playwright.
Docs
docs/reviews/REVIEW-2026-05-13-v1.11.0.md— session context + UI audit gap list.- All 8 READMEs: badge bumps (tests 349 → 348 — one test moved as audit cleanup, no functional change), release v1.10.3 → v1.11.0.
- All 8 CHANGELOGs receive this entry.
[1.10.3] — 2026-05-12
Closes 7 of the 11 v1.10.0 QA findings (F-001, F-010 minimal, F-011 minimal, F-013, F-014, F-015, F-019). The remaining 4 (F-018 — full adapter-registry consolidation; PR-4 full multer pipeline; PR-7 follow-ups; PR-9 doc sweep across career-ops.org docs) are deferred to v1.11.0.
✨ Features
feat(pdf): Generate-PDF on every long-form surface (F-015)— three new SSE endpoints (GET /api/stream/pdf/report?slug=,GET /api/stream/pdf/deep?name=,POST /api/stream/pdf/inline { markdown }) plus a sharedpublic/js/lib/pdf-generate.jshelper. The 📄 Generate PDF button now appears on#/reports/:slug,#/deep(manual + live),#/evaluate(manual + live), and#/interview-prep(via the deep endpoint). Each kind reuses the v1.10.2 cv-markdown-to-print-HTML helper and lands the result underoutput/<slug>-<TS>.pdfso the existing auto-download flow takes over.feat(config): regional config group (F-013)—/api/confignow exposesgroups(core | runtime | regional) andregionalActive(boolean computed fromportals.yml::russian_portals.sources). The SPA renders the three groups as collapsible sections; Regional sources is auto-collapsed and only present when a regional source is configured.
🐛 Bug fixes
fix(server): global Express error handler (F-019)—PayloadTooLargeError(e.g. an 11 MB upload to/api/cv/import) andSyntaxErrorfromexpress.jsonnow return JSON envelopes the SPA can localize (HTTP 413 / 400). Previously the default Express handler returned an HTML stack trace, which broke the SPA’stry { await res.json() }.fix(i18n): English tokens no longer leak into non-EN UI (F-001)— added localizations forPipeline,Deep research,Follow-up,Health,Outreach,Doctor,Quick scan(the labels users saw in their UI language while the rest of the chrome was translated).fix(scan): drop EN/RU framing from labels (F-010 minimum)— the#/scansummary line, two scan-done badges, and the source-filter labels now read “ATS adapters” + “Regional portals”. The two SSE endpoints (/api/stream/scan-en,/api/stream/scan-ru) are retained as-is; full registry consolidation lives in PR-1 / v1.11.0.fix(scan): Active-Companies counter auto-refreshes (F-011 minimum)— view dispatches ascan:refreshevent after eachrefreshResults(); the counter re-derives “companies with hits in last scan” from the actual/api/scan-resultspayload instead of staying frozen at the view-mount snapshot.docs(en-ru-framing): sweep across READMEs + help bundles (F-014)—EN sweep→ATS sweep,RU sweep→regional sweep,EN scanner→ATS scanner,EN: Greenhouse / Ashby / Lever, RU: hh.ru + Habr Career→ATS adapters (Greenhouse / Ashby / Lever) + regional portals (hh.ru / Habr Career). TouchesREADME.md,README.ru.md,README.ja.md,README.ko-KR.md,docs/help/en.md,docs/help/es.md,docs/help/pt-BR.md.
🧪 Tests
- New
tests/global-error-handler.test.mjs(2 cases): malformed JSON → 400 JSON; 11 MB upload → 413 JSON. - New
tests/config-groups.test.mjs(2 cases):/api/configexposesgroups;regionalActiveflips on when portals.yml gains a regional source. - New
tests/pdf-extra-routes.test.mjs(5 cases): each of/report,/deep,/inlineinvokesgenerate-pdf.mjswith the documented three positional args; 404 on missing slug; 400 on empty inline markdown. - Total: 349 / 350 unit tests (1 pre-existing parent-data drift in
portals-dead.test.mjs). - Coverage: 94.59 % line / 84.16 % branch.
- 20 / 20 smoke E2E, 23 / 23 comprehensive E2E, 28 / 28 Playwright.
📝 Docs
docs/reviews/REVIEW-2026-05-12-v1.10.3.md— session context + scope-out list.- All 8 READMEs: badge bumps (tests 340 → 349, release v1.10.2 → v1.10.3), “What’s new in v1.10.3” section per locale.
- All 8 CHANGELOGs receive this entry.
Out of scope (deferred to v1.11.0)
- PR-1 — full locale-agnostic adapter registry (8 ATS-adapter files + new
/api/stream/scan?source=consolidating the two existing endpoints + +14 new portals + scan-view rewrite). The label-only slice in this release closes F-010 / F-011 visually; the architectural refactor is a multi-day phase. - PR-4 — multer-based CV import pipeline (replaces the v1.10.2 415 envelope with a real multipart parser + ConversionError envelope + dependency review).
- PR-9 — full career-ops.org docs integration: fetch career-ops.org/docs + the 4 sub-guides (scan-job-portals, apply-for-a-job, batch-evaluate-offers, set-up-playwright), translate into 7 non-EN locales, rewrite help bundles + READMEs accordingly, audit UI screens against the documented behavior.
[1.10.2] — 2026-05-12
Functional-regression patch. Two bugs discovered in v1.10.1 hand-testing closed; documentation surface expanded.
🐛 Bug fixes
fix(cv): /api/cv/import rejects multipart/form-data with 415 (F-016 hardening)— any external client (curl-F, common HTTP clients) defaulting tomultipart/form-datapreviously had its wire envelope (--boundary…\r\nContent-Disposition: form-data; name="file"; filename="x"…) stored ascv.mdcontent. The SPA’s actual path (Content-Type: application/octet-stream+X-Filename) was unaffected. Route now returns 415 with a hint pointing at the documented contract. Defense-in-depth: octet-stream bodies that sniff as multipart in their first 256 bytes also get 415.cv.mdis never touched on a 415.fix(pdf): /api/stream/pdf invokes generate-pdf.mjs with proper positional args— was calling the script with[]. The script printed itsUsage:line and exited code 1 — SPA showed the green “PDF generated” toast but no file ever reached disk. The route now readscv.md, renders it to an HTML file underoutput/cv-input-<TIMESTAMP>.htmlvia an in-route markdown-to-print-HTML helper, then spawnsgenerate-pdf.mjs <input.html> <output.pdf> --format=a4. Optional?format=letterquery for US-letter output. Whencv.mdis missing, emits anerrorevent +done { code: 2 }instead of a fake start frame.
🧪 Tests
- New
tests/cv-upload-multipart-reject.test.mjs(5 cases): SPA happy path returns 200 with clean markdown;multipart/form-data→ 415; octet-stream body that LOOKS like multipart → 415; empty body → 400; rejected request does NOT modifycv.md. - New
tests/pdf-stream-args.test.mjs(3 cases):startevent carries<input.html> <output.pdf> --format=a4with absolute paths and the HTML exists on disk;?format=letterswitches the flag; missingcv.mdemits the expected error frame. - Total: 340 unit tests (was 318). One pre-existing failure in
portals-dead.test.mjsremains parent-side data drift, unrelated to web-ui. - Coverage: 94.63 % line / 84.94 % branch.
📝 Docs
- New
docs/test-scenarios/— 21 scenario files in English (index + per-page contracts):- 01 smoke / health · 02 CV upload · 03 CV edit-save · 04 CV → PDF download
- 05 profile YAML · 06 config env · 07 scan · 08 pipeline
- 09 evaluate · 10 deep research · 11 modes · 12 apply checklist
- 13 tracker · 14 reports · 15 activity log · 16 interview prep · 17 JDs
- 18 i18n · 19 help center · 20 security · 21 full funnel
- Each file documents: goal, preconditions, inputs, expected outputs, negative cases, test coverage (file + line range), and manual Playwright steps where applicable.
- New
docs/reviews/REVIEW-2026-05-12-v1.10.2.md— full session context, scope-out list, verification commands. - All 8 READMEs: badge bumps (tests 318 → 340, release v1.10.1 → v1.10.2) + “What’s new in v1.10.2” section per locale.
- All 8 CHANGELOGs receive this entry.
Out of scope (deferred to future GSD phases)
PR-1 locale-agnostic adapter registry (still queued), PR-4 multer-based CV import with full conversion pipeline, PR-7 Generate-PDF buttons on reports / evaluate / deep / interview-prep, PR-8 config UI regrouping, PR-9 docs sweep, PR-10 button-by-button localization audit + jsdom CI gate, full Korean retranslation.
[1.10.1] — 2026-05-09
Critical-fixes patch driven by the v1.10.0 QA regression run (qa/reports/00-FINAL-SUMMARY.md).
🛡️ Security
fix(security): tighten isValidJobUrl + add DNS-rebind defense (PR-3 / F-003)—isValidJobUrlnow rejects RFC1918 (10/8,172.16/12,192.168/16), the full 127/8 loopback range, link-local169.254/16(incl. AWS IMDS),0.0.0.0, CGNAT100.64/10, and IPv6 ULA / link-local. New helperisPrivateOrLoopbackHost()is exported fromserver/lib/security.mjsand reused by/api/pipeline/preview, which nowdns.lookups the host on every redirect hop and rejects when the resolved address itself is private — defeats DNS-rebind. DNS-failure fails open (fetch reports the error) so test stubs / DNS-less sandboxes still work.
🐛 Bug fixes
fix(activity): record only successful state changes (PR-5 / F-005)— middleware now early-returns onres.statusCode >= 400. Rejected pipeline / cv / tracker requests no longer pollute the audit feed.fix(activity): add profile.save / config.save / cv.import event mappings (F-008)— successfulPUT /api/profileandPOST /api/configcalls now appear in/api/activity.fix(help): alias ko → ko-KR.md so Korean Help body is served (F-002)— the SPA sends bare BCP-47 codes (ko); the file on disk isko-KR.md. Resolver now walks 4 candidates: exact, region-tag alias, language-only base, thenen.md.fix(llm): /api/evaluate honors mode:'manual' (F-009)— mirrors/api/deep. Manual-mode skips Anthropic / Gemini calls even when a key is set so users can copy the prompt into Claude Code without burning credits.fix(api): DELETE /api/pipeline accepts ?url= AND body.url, returns 404 on miss (PR-6 / F-017)— was silently 200-on-miss with?url=only.
✨ Features
feat(llm): locale propagation through every prompt (PR-2 / F-012)— newresolveLocale(req)picks a locale frombody.lang→body.locale→Accept-Language→'en'. NewbuildLocaleDirective(lang)emits a one-line “Respond in X” header.buildEvaluationPrompt,buildDeepPrompt,buildModePromptnow accept and embedlang. SPAAPI.call()auto-attachesAccept-Languageand mergeslanginto JSON bodies.feat(scripts): post-qa-cleanup.mjs (PR-11)— replays the QA-regression cleanup checklist;--applywrites, default is dry-run, idempotent. Sweeps RFC1918 /nip.io/test-cloud-*URLs fromdata/pipeline.mdand auditscv.mdsize.
🧪 Tests
- New
tests/critical-fixes.test.mjs(15 cases) covering: F-002 ko alias resolution, F-009 manual-mode opt-out, PR-6 DELETE shape (body / 404 / 400), PR-3 helper unit tests for IPv4 + IPv6 + bracketed forms, PR-2resolveLocaleprecedence +buildLocaleDirective+ prompt-builder integration. tests/url-validation.test.mjsextended with 5 new tests for RFC1918 / link-local / 0.0.0.0 / 127/8 / CGNAT / IPv6 ULA / link-local.tests/activity-log.test.mjstest 8 updated to assert the new “no log on 4xx” contract.- Total: 318 unit tests (was 298; one pre-existing failure in
portals-dead.test.mjsis parent-side data drift intemplates/portals.example.yml, unrelated to web-ui code).
📝 Docs
- New
docs/reviews/REVIEW-2026-05-09-v1.10.1.md— full session context + scope-out list + verification commands. - All 8 READMEs: badge bumps (test count 298 → 318, release v1.10.0 → v1.10.1), screenshot path moved to
public/images/screen_vacancy_found.png, “What’s new in v1.10.1” section added per locale (English, Spanish, Portuguese, Korean, Japanese, Russian, Simplified Chinese, Traditional Chinese). - All 8 CHANGELOGs updated with this entry.
Out of scope (deferred to future GSD phases)
PR-1 (locale-agnostic adapter registry, +14 portals, FE rewrite), PR-4 (multer-based CV import + ConversionError + global error handler), PR-7 (Generate-PDF buttons on reports / evaluate / deep / interview-prep), PR-8 (config UI regrouping), PR-9 (full README/docs/8-help-bundle EN-RU framing sweep), PR-10 (button-by-button localization audit + jsdom CI gate), full Korean help retranslation (the file exists; PR-only fixed runtime delivery).
[1.10.0] — 2026-05-08
CV import revamp + #/config tabs + canonical #/profile route.
✨ Features
feat(cv): server-side import for .docx / .doc / .odt / .rtf / .pdf / .html / .txt / .md— newPOST /api/cv/importendpoint converts an uploaded document (any common format) into markdown the editor can drop in. Office formats go via pandoc, PDF via pdftotext from Poppler. Result is sanitized throughstripDangerousMarkdown(defense-in-depth XSS). Hard cap: 10 MB per upload. Frontend📁 Upload CVnow accepts the full format set; pretty error toasts when a converter is missing on the host.feat(cv): auto-download generated PDF when generate-pdf.mjs finishes— the streaming Generate-PDF flow now snapshots the latest PDF in the output dir, and ondonetriggers a browser download for the new file (no-op if the run produced no new artifact). The existing on-page list still shows every previous PDF.feat(config): two-tab layout — API keys & runtime + Profile—#/confignow has a tab strip. The first tab keeps the existing.enveditor (API keys, models, scanner knobs). The new Profile tab is a direct YAML editor forconfig/profile.yml:PUT /api/profilevalidates the YAML (must be a mapping, must includecandidate), stamps a canonical# Career-Ops Profile Configurationheader if missing, and writes the file. Save propagates without restart.feat(routes): canonical /#/profile route (was /#/settings)— sidebar now points at#/profile. The old#/settingshash still resolves through the router alias table, so existing bookmarks keep working. Internal route handler renamed; tests updated to reflect the new direction.
🧪 Tests
- New
tests/cv-import.test.mjs(7 cases):.md/.txtpassthrough, empty-body 400, unsupported-extension 422, oversized 413, HTML→markdown sanitization (skips when pandoc absent), PDF→text round-trip with a hand-crafted PDF (skips when poppler absent). - New
tests/profile-put.test.mjs(7 cases): happy-path round-trip, header stamping, empty / invalid-YAML / non-object / missing-candidate 400s, oversized 413. tests/playwright-full-cycle.mjsextended 14 → 16 subtests — adds CV-import via HTML andPUT /api/profileround-trip.tests/router.test.mjsALIAS regex flipped to assert the newsettings → profiledirection.
📚 Docs
docs/help/{en,ru}.md— full updates to sections 2/3/4: new App-settings tabs, edit-via-config message on the read-only Profile page, full upload-format matrix on the CV section, PDF auto-download behaviour.docs/help/{es,pt-BR,ko-KR,ja,zh-CN,zh-TW}.md— concise mirrors of the new content blocks; section count unchanged (16) so the parity test stays green.
🔧 Internal
- New
server/lib/cv-import.mjs— single source of truth for the format → markdown conversion, with timeout + missing-converter detection that surfaces actionable hints rather than 500s. server/lib/routes/content.mjsgainsPOST /api/cv/importandPUT /api/profile(binary-safe viaexpress.rawfor the upload, JSON for the YAML PUT).
[1.9.1] — 2026-05-08
프로덕션 준비도 패스. 표적화된 버그 수정 4건 (BF-1..BF-4), Playwright smoke 가 5에서 12 테스트로 확장되어 tracker / pipeline / reports / evaluate / config / cv 저장 라운드트립을 다룹니다. 모두 CI 에서 통과.
🐛 버그 수정
fix(tracker): escape pipes + collapse newlines in every cell, not just notes (BF-1)—"Acme | Co"같은 회사 이름이 이전에는 markdown 표 레이아웃을 깨뜨렸습니다 (파서가 셀을 둘로 분할). 셀 정화 (sanitization) 가 이제 company / role / reportSlug / notes 에 균일하게 적용됩니다;parsers.mjs::parseMarkdownTable의 동반 수정이 GFM 준수\|이스케이프 지원을 추가하여 라운드트립이 무손실입니다.fix(config): wrap updateEnvFile in try/catch (BF-2)—POST /api/config가 이전에는 permission-denied / 읽기 전용 파일시스템에서 처리되지 않은 rejection 을 일으켰습니다. 이제 깔끔한 500{ error: 'failed to write parent .env', details: [...] }를 반환합니다.fix(llm): soft cap on assembled prompt size for Anthropic SDK calls (BF-3 + BF-4)—/api/evaluate,/api/deep,/api/mode/:slug의 Anthropic 분기가 이제bundleProjectContext + prompt가 200 KB (≈50K 토큰) 를 초과하면 413 으로 중단합니다. API 가 컨텍스트 크기 불평을 하도록 두는 대신 다초 라운드트립 + 토큰을 절약합니다. 캡은 어떤 현재 모델 상한보다도 충분히 낮습니다 (Sonnet 4.6 = 1M 컨텍스트).
🧪 Playwright smoke — 커버리지 확장
5 → 12 테스트. 신규 케이스:
tracker view renders empty + accepts API-seeded row— 회사 이름에 리터럴 파이프를 가진 행을 시드하고 라운드트립이 이를 보존하는지 단언하여 BF-1 을 행사.pipeline add-URL form populates the queue+ 잘못된 URL 거부 sweep (loopback,javascript:, 빈 문자열).reports view handles empty state— non-crash 단언.evaluate view returns a manual prompt without API key— 폴백 체인 검증.config GET returns known keys masked— 비밀이/api/config를 통해 절대 누설되지 않음.cv.md PUT round-trips with sanitization— XSS 류 (script 태그,javascript:스킴) 가 end-to-end 로 strip 됨.pipeline preview proxy strips scripts— 잘못된 URL 거부 경로.
📦 동작 변경 (API 계약 변경 없음)
- Tracker 쓰기가 이제 파이프가 포함된 company / role 이름에 대해 무손실입니다. 원시 파이프를 가진 기존 행은 다음 읽기에서 올바르게 파싱되기 시작합니다.
/api/{evaluate,deep,mode/:slug}가 이제 비합리적으로 큰 프롬프트 (200 KB+) 에서 502/타임아웃 대신 413 을 반환합니다.
🧪 테스트
- 284 유닛 테스트 (수 변경 없음; 파서 갱신 후에도 기존 테스트가 모두 녹색).
- 12 Playwright 브라우저 smoke 테스트 (이전 5).
[1.9.0] — 2026-05-08
v1.8.0 백로그의 P-6 → P-10 이 한 번들로 출하되었습니다. 헤드라인: server/index.mjs 가 이제 130-LOC 오케스트레이터입니다 (762 에서 감소, 합계 1230 → 130 = -89%); 모든 라우트 토픽이 자체 모듈을 갖습니다. /api/evaluate 의 Anthropic 패리티, 멀티 CLI shim, 확장된 i18n 패리티 테스트, CI 에 연결된 Playwright 브라우저 smoke.
🏗️ P-6 — 서버 split-by-concern (phase 2)
P-2 의 연속. 나머지 9개 라우트 토픽을 server/index.mjs 에서 server/lib/routes/<topic>.mjs 모듈로 추출. index.mjs 는 이제 순수 오케스트레이터입니다: 미들웨어 (보안 헤더 + activity 로그 + static), 12개 register<Topic>Routes(app) 호출, SPA catch-all.
server/lib/routes/activity.mjs—/api/activity.server/lib/routes/config.mjs—/api/configGET/POST (부모 .env 라운드트립).server/lib/routes/health.mjs—/api/health+/api/dashboard.server/lib/routes/help.mjs—/api/help/:lang.server/lib/routes/jds.mjs—jds/*.txt의 전체 CRUD.server/lib/routes/llm.mjs— 모든 LLM 결합 엔드포인트 (evaluate, deep, mode, apply-helper, interview-prep).server/lib/routes/pipeline.mjs— 명명된 상수 (timeout / max-redirects / max-body) 가 포함된 SSRF-안전 preview proxy 를 포함한/api/pipeline*.server/lib/routes/reports.mjs—/api/reports*.server/lib/routes/tracker.mjs—/api/trackerGET + dedup 인식 POST.
동작 변경 없음. 모든 단계에서 283/283 유닛 테스트가 녹색을 유지했습니다. 오케스트레이터의 import surface 가 47줄에서 22줄로 떨어졌습니다.
🔌 P-7 — /api/evaluate 의 Anthropic 패리티
/api/evaluate 는 이전에 Gemini 또는 manual 이었습니다. v1.9.0 은 Anthropic 분기를 추가합니다 (두 키가 모두 있을 때 선호). /api/deep 와 /api/mode/:slug 가 이미 사용하는 라우팅 규칙을 미러링합니다. 모델이 cv / profile / mode 템플릿을 인라인으로 갖도록 bundleProjectContext({ modeSlugs: ['_shared', 'oferta'] }) 를 통해 라우팅됩니다 (REVIEW-A1).
신규 엔드포인트: POST /api/evaluate/test-anthropic — ANTHROPIC_API_KEY 용 smoke 검사, 기존 Gemini smoke 를 미러링. 작은 프롬프트 (≤256 출력 토큰) 를 보내므로 사실상 비용이 없습니다; 200자 샘플을 반환합니다.
폴백 체인은 이제: Anthropic → Gemini → manual.
🌐 P-8 — Help-center i18n 패리티 (감사 + 테스트 강화)
모든 docs/help/<lang>.md 의 구조 패리티를 감사했습니다. 8개 로케일이 이미 동일한 14개 정규 h2 섹션을 다룹니다. 테스트 업그레이드:
tests/help-ui.test.mjs::every help doc covers the same 14 sections가 en + ru 만 확인하고 있었습니다. 이제 8개 로케일 전체 (en, es, pt-BR, ko-KR, ja, ru, zh-CN, zh-TW) 를 순회하며 각각에 대해 섹션 수를 단언합니다.- 신규 테스트:
tests/help-ui.test.mjs::every help locale has substantive content— 각 비영어 로케일이en.md바이트 길이의 최소 30% 인지 단언하여 로케일 스텁을 방어합니다. 컴팩트 번역은 자연히 40-50% 에 도달합니다; 스텁은 한 자리 % 일 것입니다.
결과: 구조 패리티가 이제 CI 강제됩니다.
🤖 P-9 — CI 매트릭스의 Playwright 브라우저 smoke
tests/playwright-smoke.mjs (v1.8.0 에서 옵트인으로 추가됨) 가 이제 CI 워크플로의 일부입니다. 기존 e2e 잡이 이미 Playwright + Chromium 을 설치합니다; 신규 스텝 (npm run test:e2e:browser) 하나가 comprehensive Node E2E 직후에 5개 브라우저 smoke 테스트를 실행합니다.
CI 의 순서: 유닛 (Node 18/20/22 매트릭스) → smoke node E2E → comprehensive node E2E → Playwright 브라우저 smoke → 실패 시 스크린샷 아티팩트 업로드.
🌍 P-10 — 멀티 CLI 호환성
부모 career-ops v1.7.0 이 멀티 CLI / Open Agent Skill 표준 지원을 도입했습니다. UI 서브 프로젝트는 정규 CLAUDE.md 를 가리키는 얇은 shim 으로 동일한 관례를 따릅니다:
web-ui/AGENTS.md— Codex / Aider / 범용 CLI 엔트리포인트.web-ui/GEMINI.md— Gemini CLI 엔트리포인트.
두 shim 모두 하드 룰과 빠른 참조를 재진술하지만 전체 프로젝트 수준 지침은 CLAUDE.md 에 위임하므로 비-Claude CLI 도 Claude Code 세션과 동일한 오리엔테이션에 도착합니다. 배포된 UI 자체는 런타임에서 CLI 무관입니다.
🧪 테스트
- 284 유닛 테스트 (이전 283): +1 신규 help-locale 패리티 테스트.
- 5 Playwright 브라우저 smoke 테스트 — 이제 옵트인이 아니라 CI 의 일부.
- 커버리지 유지.
🔧 변경된 파일
+ server/lib/routes/activity.mjs + server/lib/routes/config.mjs
+ server/lib/routes/health.mjs + server/lib/routes/help.mjs
+ server/lib/routes/jds.mjs + server/lib/routes/llm.mjs
+ server/lib/routes/pipeline.mjs + server/lib/routes/reports.mjs
+ server/lib/routes/tracker.mjs
+ AGENTS.md + GEMINI.md
~ server/index.mjs (762 → 130 LOC, -83%)
~ .github/workflows/ci.yml (Playwright smoke step)
~ tests/help-ui.test.mjs (all-8-locales section parity + content-floor)
~ docs/{ROADMAP,architecture/{OVERVIEW,SERVER}}.md
~ docs/sdd/CONVENTIONS.md
~ CLAUDE.md
~ package.json (1.8.0 → 1.9.0)
📦 신규 REST 엔드포인트
| Method | Path | 용도 |
|---|---|---|
POST |
/api/evaluate/test-anthropic |
ANTHROPIC_API_KEY 용 smoke 검사 (P-7). /api/evaluate/test-gemini 를 미러링. |
🤖 신규 CLI 엔트리포인트
| 파일 | CLI | 비고 |
|---|---|---|
AGENTS.md |
Codex / Aider / 범용 | 전체 지침을 위해 CLAUDE.md 를 가리킵니다. |
GEMINI.md |
Gemini CLI | 세션 시작 시 Gemini 가 자동 로드합니다. |
[1.8.0] — 2026-05-08
강화, 리팩터, SDD 부트스트랩. 고위험 정확성/보안 수정 3건 (A1, A2, A3), 중간 4건 (B1–B4), 정리 6건, 부모 career-ops v1.7.0 표면 감사, 서버 split-by-concern (P-2 phase 1), Playwright 브라우저 smoke 하니스, docs/ 와 .claude/ 아래 전체 SDD 기반.
🔥 고위험 수정
fix(deep): inline cv/profile/mode files for Anthropic SDK calls (REVIEW-A1)—/api/deep와/api/mode/:slug가 이전에는 모델에게 “이 파일들을 먼저 읽어라” 라고 했지만 Anthropic SDK 에는 파일시스템이 없습니다. 출력이 비어 있었습니다. 신규bundleProjectContext({ modeSlugs })가cv.md,config/profile.yml,modes/_shared.md, 그리고 mode 템플릿을 읽고 각각을 16 KB 에서 자른 뒤 프롬프트 앞에<project_context>블록을 추가합니다. 라이브 검증:claude-sonnet-4-6의 deep-research 호출에서 26 KB 의 근거 있는 markdown 응답.fix(runner): SIGKILL escalation after SIGTERM grace period (REVIEW-A2)—runNodeScript와streamNodeScript가 이전에는 타임아웃 / 클라이언트 연결 끊김에서SIGTERM만 보냈습니다. syscall (DNS, 블록된 소켓) 에서 멈춘 자식 프로세스가 이를 무시하여 Node 의 GC 가 회수할 때까지 SSE 연결이 매달렸습니다. 이제 각 경로가 5초 워치독을 무장하여SIGKILL로 에스컬레이션합니다. Promise 가 항상 해결됩니다.fix(runner): max-runtime cap on streaming endpoints (REVIEW-A3)— 모든 SSE 스크립트 러너 (/api/stream/{scan,liveness,pdf}) 가 이제 하드 30분 한도를 갖습니다. 만료 시:event: error { message: 'maximum runtime exceeded' }emit, A2 워치독으로 자식 프로세스 종료, 응답 종료.
🛡️ 중간 위험 수정
fix(preview): per-hop redirect validation in /api/pipeline/preview (REVIEW-B1)—redirect: 'follow'에서 수동 redirect 워킹으로 전환. 각Location헤더가isValidJobUrl로 재검증됩니다; 3홉으로 캡. 적대적 보드가 더 이상 loopback / 사설 IP /file://로 우리를 튕길 수 없습니다. 거부 경로를 다루는 신규 테스트 4개.refactor(keys): hasGeminiKey helper unifies LLM-key checks (REVIEW-B2)— 라우트 핸들러의 직접process.env.GEMINI_API_KEY읽기가lib/anthropic.mjs의hasGeminiKey()로 교체되었습니다. 일관성과 더 쉬운 모킹을 위해hasAnthropicKey()형태를 미러링합니다.feat(scanners): thread AbortSignal through hh.ru, Habr, Greenhouse, Ashby, Lever (REVIEW-B3)— SSE 클라이언트가 스캔 중간에 연결을 끊으면, 진행 중인 HTTP fetch 가 이제 모든 쿼리를 완료까지 실행하고 이벤트를 드롭하는 대신 abort 됩니다.runRuScan과runEnScan이opts.signal을 수용합니다;/api/stream/scan-{ru,en}의 SSE 핸들러가AbortController를 생성하고res.close에서 abort 합니다.test(anthropic): log-guard test prevents future API-key leaks via console (REVIEW-B4)—runAnthropichappy + error 경로 동안 모든console.{log,info,warn,error,debug}호출을 캡처하여 0 출력과 canary 키 문자열이 절대 등장하지 않음을 단언합니다. 미래의console.log(opts)회귀에 대한 심층 방어.
🧹 저위험 폴리시
fix(parsers): defense-in-depth URL gate inside addPipelineUrl (REVIEW-C4)— 라우트 수준isValidJobUrl을 보완하는 파서 수준의 비-http(s) 값 거부. 더 엄격한 규칙을 원하는 호출자를 위한 옵션opts.validate.docs(readme): badge "tests-88 passed" → "tests-277 passed" (REVIEW-C3)— 자릿수가 틀렸습니다.test(i18n): missing-keys diff grouped by locale (REVIEW-C6)—tests/i18n-coverage.test.mjs가 갭을 찾으면 출력이 이제 혼합된 줄 대신[ru] (3): foo, bar, baz입니다.docs(review): C1 closed as resolved-on-inspection— 정화 (sanitizer) 정규표현식이 이미\x00-\x0816진 형태였습니다; 리뷰 항목은 도구 렌더링 아티팩트였습니다.
🏗️ P-2 phase 1 — 서버 split-by-concern
server/index.mjs 가 1230 LOC 로 800줄 상한을 한참 넘었습니다. 동작 변경 없이 집중된 모듈로 분할했습니다. 모든 단계에서 283 유닛 테스트가 녹색을 유지했습니다.
server/lib/security.mjs—isValidJobUrl,stripDangerousMarkdown,sanitizeJobDescription,isPubliclyExposed. 외부 컨슈머와의 하위 호환을 위해index.mjs에서 재내보냅니다.server/lib/prompts.mjs—bundleProjectContext,buildEvaluationPrompt,buildDeepPrompt,buildModePrompt,buildApplyChecklist.server/lib/store.mjs—safeReadApps,safeReadPipeline,safeListReports,checkProfileCustomized,ensureRussianPortalsDefaults.server/lib/routes/scan.mjs—/api/stream/scan-{ru,en},/api/scan-ru/config,/api/scan-results용registerScanRoutes(app).server/lib/routes/runners.mjs— 버퍼링된/api/run/*표, 스트리밍/api/stream/{scan,liveness,pdf}, 생성된 PDF 목록/다운로드용registerRunnerRoutes(app).server/lib/routes/content.mjs— CV / Profile / Portals / Modes 용registerContentRoutes(app).
index.mjs 는 이제 762 LOC 입니다 (-38%, 800 캡 아래). Phase 2 는 tracker, pipeline, reports, jds, llm (evaluate/deep/mode), health 를 라우트 모듈로 추출할 것입니다. 오케스트레이터의 목표 <500 LOC.
🔍 부모 career-ops v1.7.0 감사
사용자가 부모 프로젝트를 v1.7.0 으로 갱신했습니다. 모든 소비 표면을 감사했습니다 — UI 는 완전히 호환됩니다. 주목할 발견은 docs/architecture/DATA-FLOWS.md 에 문서화되어 있습니다:
- 모드 카탈로그가 7 → 19 파일로 성장했습니다. UI 의
MODE_ALLOWLIST는 의도적으로 7개만 surface 합니다 (나머지는 Claude-Code 전용). 의도적인 좁은 범위를 설명하는 주석이 추가되었습니다. portals.yml스키마 확인:tracked_companies(96 항목, 87 활성화, 71 개가 API 보유). EN 스캐너가 이를 올바르게 읽습니다; 레거시companies키도 여전히 지원됩니다.- 오늘 소비되지 않는 신규 부모 표면:
dashboard/(Go 프로그램),update-system.mjs,generate-latex.mjs,analyze-patterns.mjs,liveness-core.mjs,followup-cadence.mjs,test-all.mjs, 현지화된 mode 서브디렉토리 (de/fr/ja/pt/ru). - 라이브
/api/dashboard,/api/health,/api/modes,/api/portals,/api/profile,/api/cv,/api/jds,/api/reports,/api/tracker,/api/pipeline,/api/evaluate,/api/deep,/api/stream/scan-en이 모두 녹색으로 검증되었습니다.
🤖 SDD / GSD 부트스트랩
career-ops-ui 는 이제 GSD 파이프라인 (superpowers@claude-plugins-official 의 gsd-* 스킬) 과 정렬된 전체 Spec-Driven Development 기반을 갖습니다.
CLAUDE.md(루트) — 프로젝트 수준 에이전트 시스템 프롬프트: 스택, GSD 파이프라인, 하드 룰 (부모 계약, 보안 영역,--no-verify금지), 관례, 부모 프로젝트 경계..aiignore— AI 에이전트용 제외 목록: vendored, 바이너리, 부모 사용자 데이터,.planning/,.env, 로케일 중복..claude/agents/— 세 개의 프로젝트별 서브에이전트 정의:web-ui-route-reviewer.md— 신규 라우트를 SSRF, CSP, 정화 (sanitizer), 부모 쓰기 계약, 관례, 테스트에 대해 게이팅합니다.spa-view-reviewer.md— CSP 안전 DOM, i18n, 라우터 등록, 접근성.test-isolation-reviewer.md— 테스트가 CI 격리되어 있는지 검증합니다 (부모 프로젝트 가정 없음, 라이브 네트워크 없음, 포트 충돌 없음).
.claude/commands/— 슬래시 커맨드 스텁:/sdd-status,/codebase-tour.docs/트리 — 모두 영어로:PROJECT.md— what/why/for-whom, 범위, 제약, 성공 기준.ROADMAP.md— 현재 마일스톤 + 완료 이력 + 백로그.sdd/SDD-GUIDE.md—gsd-*스킬에 매핑된 discuss → spec → plan → execute → verify → review 파이프라인.sdd/CONVENTIONS.md— 모듈 시스템, 명명, 라우트, 정화 (sanitizer), 클라이언트 패턴, i18n, 오류, 로깅, 테스트, 커밋, 브랜치, CSS.architecture/OVERVIEW.md— 최상위 다이어그램, 계층, 부팅 시퀀스, 불변성, “…일 때 먼저 볼 곳” 치트시트.architecture/SERVER.md—server/lib/*.mjs의 파일별 맵 (P-2 분할에 맞게 갱신).architecture/FRONTEND.md— SPA 구조, 뷰 인벤토리, 전역, “뷰 추가 방법”.architecture/API.md— 모든/api/*라우트의 전체 인벤토리.architecture/DATA-FLOWS.md— 명시적 사용자 액션 계약을 가진 모든 부모 프로젝트 읽기/쓰기.reviews/REVIEW-2026-05-07.md— 이 변경 로그의 수정을 낳은 정적 리뷰.
🔒 보안 & 저장소 위생
chore(.gitignore): comprehensive defense-in-depth patterns— env 변종, IDE 폴더, GSD 스크래치 (.planning/), 사용자별 에이전트 설정 (.claude/settings.local.json,.claude/cache/,.claude/state/,.claude/memory/), Playwright 아티팩트 (playwright-report/,test-results/,.playwright/,trace.zip), heap/CPU 프로파일, 미출하 도구의 락 파일, 확장된 macOS Finder 노이즈, 범용 비밀 패턴 (secrets.json,credentials.json,*.pem,*.key) 를 다룹니다.
🧪 테스트
- 283 유닛 테스트 (이전 277): +11 신규 (B1 redirect 거부용 4개,
hasGeminiKey용 1개,runAnthropic로그 가드용 1개). - 5 Playwright 브라우저 smoke 테스트 (신규,
npm run test:e2e:browser를 통한 옵트인): 대시보드 렌더 + 버전 푸터, 대시보드 → 스캔 → 파이프라인 → cv 내비게이션, 언어 전환 영속, 404 뷰, health 페이지 렌더. Playwright 를 부모의node_modules를 통해 해결합니다 — 신규 의존성 없음. - 커버리지가 ~93% 라인 / ~83% 브랜치로 유지됩니다.
📝 신규 / 갱신된 package.json 스크립트
| 스크립트 | 용도 |
|---|---|
npm run test:e2e:browser |
in-process 서버에 대해 Playwright smoke 하니스 실행 (5 테스트). |
🔧 변경된 파일
+ CLAUDE.md + .aiignore
+ docs/PROJECT.md + docs/ROADMAP.md
+ docs/sdd/SDD-GUIDE.md + docs/sdd/CONVENTIONS.md
+ docs/architecture/OVERVIEW.md + docs/architecture/SERVER.md
+ docs/architecture/FRONTEND.md + docs/architecture/API.md
+ docs/architecture/DATA-FLOWS.md + docs/reviews/REVIEW-2026-05-07.md
+ .claude/agents/web-ui-route-reviewer.md + .claude/agents/spa-view-reviewer.md
+ .claude/agents/test-isolation-reviewer.md
+ .claude/commands/sdd-status.md + .claude/commands/codebase-tour.md
+ server/lib/security.mjs + server/lib/prompts.mjs
+ server/lib/store.mjs
+ server/lib/routes/scan.mjs + server/lib/routes/runners.mjs
+ server/lib/routes/content.mjs
+ tests/playwright-smoke.mjs
~ .gitignore ~ README.md (badge fix)
~ package.json (1.7.2 → 1.8.0)
~ server/index.mjs (1230 → 762 LOC)
~ server/lib/runner.mjs (SIGKILL escalation, max-runtime cap)
~ server/lib/anthropic.mjs (hasGeminiKey)
~ server/lib/parsers.mjs (URL gate in addPipelineUrl)
~ server/lib/ru-scanner.mjs ~ server/lib/en-scanner.mjs
~ server/lib/sources/{hh,habr,greenhouse,ashby,lever}.mjs (signal threading)
~ tests/anthropic.test.mjs ~ tests/i18n-coverage.test.mjs
~ tests/pipeline-preview.test.mjs
[1.7.2] — 2026-05-04
Help 센터, in-UI App settings, 모바일 사이드바, 단일 Scan 버튼, 모든 프롬프트 빌더의 “Show result” 단축.
✨ 신규 기능
feat(help): in-app user guide(/#/help) — 신규 사이드바 항목에서 접근 가능한 장문 Markdown 문서. 모든 페이지를 단계별로 다룹니다: 빠른 시작, CV 편집기, Profile, 스캔 필터, Pipeline preview, Evaluate, Deep research, Apply, Tracker, Reports, 모든 7개 모드, Activity log, Health, 설정 힌트.<h2>헤딩에서 자동 구축되는 sticky 목차, 동기적 DOM 빌드 (경쟁 상태 없음). 지원되는 8개 로케일 모두에 현지화.feat(config): in-UI App settings page(/#/config) — 브라우저에서ANTHROPIC_API_KEY,ANTHROPIC_MODEL,GEMINI_API_KEY,GEMINI_MODEL,HH_USER_AGENT,PORT,HOST를 편집합니다. 부모 프로젝트의.env파일에 쓰므로 career-ops Node 스크립트와 web-ui 의 dotenv 로더가 같은 소스를 가져갑니다. 비밀 키는 읽기 시 마스킹됩니다 (앞/뒤 4자). 모델 필드는 큐레이션된 목록의 드롭다운입니다 (claude-sonnet-4-6 / claude-opus-4-7 / claude-haiku-4-5 / gemini-2.0-flash / etc.). 빈 값은 키를 삭제합니다. 값이 즉시 실행 중인 process.env 에 적용됩니다 — 대부분의 설정은 재시작 없음.feat(modes): "⚡ Show result" button alongside "Copy prompt"— manual 모드에서 프롬프트가 생성될 때 사용자가 LLM 결과를 얻기 위해 입력을 재입력하지 않아도 됩니다. 신규 버튼이 동일한 폼을run: true로 재제출하고, 키가 구성되지 않았을 때는 명확한 toast (Set ANTHROPIC_API_KEY or GEMINI_API_KEY in .env first) 로 폴스루합니다./#/deep,/#/project,/#/training,/#/followup,/#/batch,/#/contacto,/#/interview-prep,/#/patterns에서 동작합니다.
🐛 UX + UI 수정
fix(scan): single Scan button replaces three (Scan all + EN + RU)— 압도적인 선택이고, 99% 경우 동일한 기본값입니다. 통합된🌐 Scan버튼이 모든 활성화된 소스를 실행합니다. Help 문서가 8개 로케일에서 갱신되었습니다.fix(ui): mobile sidebar drawer— 뷰포트 <900px 가 이제 top bar 에 햄버거 버튼 (☰) 을 받습니다;body.sidebar-open이 사이드바를 슬라이드 인 시키는 CSS transform 을 토글합니다. backdrop 어둡게 + 아무 곳이나 클릭하면 닫힘. 앵커 클릭 + hashchange 가 자동 닫기를 일으켜 사용자가 drawer 가 접힌 상태로 새 페이지에 도착합니다. 더 큰 뷰포트는 영향이 없습니다.fix(server): footer version reflects web-ui, not the parent VERSION—/api/health가 이제 web-ui 자체의package.json을 읽습니다. 푸터가 더 이상 부모의 버전 파일에서 오래된1.6.0을 누설하지 않습니다. 부모의 VERSION 은 여전히parentVersion으로 별도 노출됩니다.
📦 신규 REST 엔드포인트
| Method | Path | 용도 |
|---|---|---|
GET |
/api/help/:lang |
요청된 로케일의 Markdown 사용자 가이드를 반환, en.md 로 폴백. 경로 탐색 안전. |
GET |
/api/config |
모든 알려진 env 키의 현재 값 반환; 비밀 마스킹됨. |
POST |
/api/config |
주어진 키를 부모 프로젝트의 .env 에 쓰고, 각 값을 검증하며, process.env 에 라이브 적용. |
🌐 i18n
nav.help,nav.config,help.*,config.*,deep.showResult,deep.needKey,scan.btnRun에 걸쳐 30+ 신규 키. 8개 로케일 모두 채워졌습니다.
🧪 테스트
tests/help.test.mjs(12 케이스) — 모든 지원 로케일이 실질적인 markdown 을 반환, EN 이 모든 페이지 slug 를 spot-check, 알 수 없는 lang → EN 폴백, 경로 탐색 정화, 모든 로케일이cv.md/profile.yml/.env를 참조.tests/help-ui.test.mjs(9 케이스) — 뷰 파일 등록, 사이드바 항목, 모든 로케일에 i18n 키 존재, 모든 로케일용 docs 파일 존재, EN/RU help 가 14개 정규 섹션 보유, 모든 #/foo 라우트 커버됨, deep + mode-page 의 Show-result 배선.tests/env-config.test.mjs(18 케이스) —parseEnv,maskSecret,validateConfig,updateEnvFile(부트스트랩, 주석을 보존하는 in-place 재작성, 빈 값 삭제, 필요시 따옴표) 의 순수 함수 테스트.tests/config-endpoint.test.mjs(8 케이스) — GET 이 비밀 마스킹 / env 경로 반환; POST 가 부모 .env 에 쓰기; 라이브 process.env 적용; 빈 값이 unsets; 알 수 없는 키 + 잘못된 형식의 Anthropic 키를 400 으로 거부.
📊 통계
- 테스트: 233 → 277 (+44, 4개 신규 테스트 파일).
- E2E: 20 smoke + 23 comprehensive = 43 Playwright 단계, 모두 녹색.
- 커버리지: 93.5% 라인 / 82.6% 브랜치 / 93.7% 함수 (변경 없음 — 신규 코드는 완전히 테스트됨).
[1.7.1] — 2026-05-04
v1.7.0 이후 작업을 쌓는 패치 릴리스: 파이프라인 preview 패널, Anthropic API 통합, 스크롤 가능 사이드바, dotenv 로더, 동적 Active-companies 목록, CI 워크플로 강화.
✨ 파이프라인 Preview 패널
/#/pipeline개편 — 왼쪽 목록 + 오른쪽 preview 패널. 모든 URL 을 클릭하여 서버 사이드 프록시 스냅샷 (GET /api/pipeline/preview가 script/style/태그를 strip 하고 8 KB 에서 캡,isValidJobUrl로 검증) 을 가져옵니다. 라이브 필터 입력, “In queue” 카운터, ⚡ “Evaluate first” 헤더 버튼. 모든 행의 인라인 ▶/✕ 와 preview 패널의 전체 Evaluate / Open in tab / Delete.data-url+.pipeline-row+.pipeline-row-delete클래스로 안정된 테스트 셀렉터.tests/pipeline-preview.test.mjs의 8개 신규 테스트 (목 fetch, upstream 바인딩 불필요).
✨ Anthropic API 통합 — 모든 곳에서 “Run live”
server/lib/anthropic.mjs— Anthropic Messages API 용 제로 의존성 클라이언트 (claude-sonnet-4-6 기본,ANTHROPIC_MODEL로 재정의).ANTHROPIC_API_KEY가 설정되면 모든 모드 페이지 (/#/deep,/#/project,/#/training,/#/batch,/#/contacto,/#/interview-prep,/#/patterns) 가 주요 액션으로 “⚡ Run live (Anthropic)” 버튼을 렌더링합니다 — 클릭 시 프롬프트를 실행하고 Claude Code 로 핸드오프하는 대신 Markdown 을 브라우저로 다시 렌더링합니다. Gemini 키만 설정된 경우 Gemini 가 폴백으로 유지됩니다. 키가 전혀 없어도 manual 모드는 여전히 동작합니다.tests/anthropic.test.mjs의 8개 신규 테스트.
🐛 CI / 파이프라인 수정
fix(api): tighten pipeline URL validator(FIX-M7) — 이제 loopback 호스트네임, 길이 <10 또는 >2000, URL 내 공백도 거부합니다.fix(server): actually load .env so HH_USER_AGENT / GEMINI_API_KEY hints work—server/lib/dotenv.mjs(35줄 제로 의존성 로더) 를 추가하여server/index.mjs상단에 배선했습니다. 스캐너 코드의 런타임 힌트가 마침내 무언가를 합니다. 6개 신규 테스트.fix(ui): scrollable sidebar— 6개 그룹의 18개 내비 항목이 짧은 뷰포트를 오버플로우했습니다..sidebar가 이제 thin 사용자 정의 스타일 스크롤바와 함께overflow-y: auto를 갖습니다.fix(ui): make HH_USER_AGENT banner dismissible— 그러다 과한 것이라고 판단하여/scan에서 완전히 제거했습니다. Health 페이지 검사가 여전히 이를 noted 합니다.fix(scan): Active companies list is now collapsible + filterable + grouped— 평면 87 태그는 압도적이었습니다. 이제 “▸ Active companies 87/71” 토글이 정렬된 목록을 펼칩니다 (✓ API 지원 먼저, ○ 웹 검색 두 번째) 더하기 검색 필터.fix(test): isolate api.test.mjs + en-scanner.test.mjs from parent project— 둘 다 이제 tmp 프로젝트 루트를 띄우므로 web-ui 옆에 부모를 체크아웃하지 않고도 CI 가 동작합니다.fix(workflow): publish-package version-match only on release events— main 에서의workflow_dispatch가 더 이상 tag/version 검사를 실패시키지 않습니다.fix(e2e): stable selector for pipeline row delete— 앵커 래퍼 복원 +data-url어트리뷰트 추가로 e2e 스위트의 셀렉터가 안정됩니다.
📦 신규 REST 엔드포인트
| Method | Path | 용도 |
|---|---|---|
GET |
/api/pipeline/preview?url=… |
서버 사이드 프록시: URL 의 가시 텍스트 스냅샷 반환 (script/style 제거, 8 KB 캡), isValidJobUrl 로 게이팅. |
📊 이 배치 후 통계
- 테스트: 225 → 233 (v1.7.0 위에 +8).
- 테스트 파일: 25 → 26.
- E2E: 20 + 23 = 43 Playwright 단계, 모두 녹색.
[1.7.0] — 2026-05-03
QA r5 가 구동한 35커밋 강화 + UX + 기능 완성 패스. 보안 레이어 3개 (XSS 정화, CSP, 입력 검증) 가 도착했고, 누락된 CRUD 엔드포인트가 채워졌고, 부모 프로젝트 부트스트랩이 이제 완전히 자동화되었으며, UI 는 신규 페이지 9개 를 얻었습니다 — Activity, 재설계된 Deep Research, 더하여 부모의 modes/ 의 100% 를 다루는 7개의 사이드바 그룹화된 모드 (project / training / followup / batch / outreach / interview-prep / patterns). 파이프라인이 서버 사이드 preview 패널을 얻었습니다. Anthropic API 통합이 모든 모드에서 “Run live” 를 원클릭으로 만듭니다. 테스트 커버리지가 73 에서 225 로, 25개 테스트 파일 에 걸쳐 갔고, 23개 comprehensive Playwright e2e 단계 도 있습니다. GitHub Actions 가 CI / AI 리뷰 / Release / Publish-Package 워크플로를 출하합니다.
🔒 보안
fix(cv): sanitize CV markdown to block stored XSS in preview(FIX-C10) —PUT /api/cv가 이제cv.md를 쓰기 전에<script>,<iframe>,<object>,<embed>,<style>,<form>,<svg>,on*=이벤트 핸들러,javascript:/vbscript:/data:text/htmlURI 를 strip 합니다. 본문 1 MB 캡 (오버플로 시 413). 클라이언트 사이드UI.md()는 어떤 markdown 변환도 실행되기 전에 모든 바이트를 escape 하도록 재작성되어 원시 HTML 이 절대innerHTML에 도달할 수 없습니다. 링크href어트리뷰트는 안전한 스킴 (http/https/mailto/tel/상대 +data:image만) 의 allowlist 에 대해 검증됩니다. strip 헬퍼와 HTTP 라운드트립에 걸친 신규 테스트 17개.fix(server): add CSP and baseline security headers(FIX-L2) — 모든 응답이 이제X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: same-origin을 운반합니다. 서버가 loopback 너머에 바인딩되면 (HOST≠127.0.0.1/::1/localhost), 엄격한Content-Security-Policy가 위에 레이어링됩니다:default-src 'self',script-src 'self'(nounsafe-inline), Google Fonts whitelisted,connect-src 'self'가 XSS 유출을 차단.index.html과router.js의 인라인onclick핸들러가 엄격한 CSP 를 손상시키지 않도록addEventListener로 이동되었습니다. 5개의 다른HOST값에 걸쳐 CSP 를 게이팅하는 신규 테스트 8개.fix(api): tighten pipeline URL validator(FIX-M7) —POST /api/pipeline가 이전에는"not-a-url"을 수용하고 영속화했습니다. 이제isValidJobUrl()이 빈 문자열, <10 또는 >2000 자 입력, 공백 포함 URL, 비-http(s)스킴, loopback 호스트네임 (localhost/127.0.0.1/::1) 을 거부합니다. FIX-M3 + FIX-M6 (잘못된 입력에서 400 반환, 더하여 성공 시deduped플래그) 도 포함합니다.fix(server): actually load .env so HH_USER_AGENT / GEMINI_API_KEY hints work— 이전에는 런타임이 사용자에게 “.env 에 HH_USER_AGENT 를 설정하라” 고 했지만 서버가 그 파일을 읽지 않았으므로 지시를 따라도 아무것도 하지 않았습니다. 35줄의 제로 의존성 dotenv 로더 (server/lib/dotenv.mjs) 를 추가하여server/index.mjs상단에 배선했습니다. 커맨드 라인에 설정된 process-env 값은 여전히 이깁니다. 부모의.env.example이 이제 실제 Chrome User-Agent 예제가 포함된 문서화된HH_USER_AGENT블록을 포함합니다. 6개 신규 테스트.fix(api): sanitize JD before prompt assembly(FIX-M5) —POST /api/evaluate가 Gemini 를 호출하거나 프롬프트를 다시 echo 하기 전에 ANSI 이스케이프, 제어 바이트, 인라인<script>태그를 strip 하고 공백을 trim 합니다. 50 KB 길이 캡. 50자 최소는 정화된 텍스트에 대해 실행되므로 충분히 길어 보이지만 대부분 이스케이프로 구성된 프롬프트 주입 시도가 400 으로 fail-fast 됩니다.fix(health): mask Node version + project root when HOST!=loopback(FIX-M1) —/api/health가 LAN 노출 배포에서 더 이상 호스트를 fingerprint 하지 않습니다. Loopback 응답은 로컬 진단을 위해 값을 유지합니다.
✨ 신규 기능
feat: 7 new sidebar modes + grouped sidebar(FIX-C8) — 부모의modes/디렉토리의 100% 를 UI 갭 없이 다룹니다. 신규 라우트:#/project(포트폴리오 프로젝트 어드바이저),#/training(코스/자격 평가),#/followup(지원별 cadence),#/batch(병렬 URL 프로세서),#/contacto(LinkedIn 아웃리치 드래프터),#/interview-prep(단계별 준비),#/patterns(거절 패턴 분석기). 일곱 모두 단일 config 구동 뷰 팩토리 (public/js/views/mode-page.js) 와 단일 범용 엔드포인트POST /api/mode/:slug를 공유합니다 — 미래에 새 모드를 추가하는 것은 하나의 config 행 + 하나의 i18n 블록입니다. 사이드바가 6개 그룹으로 재구성됨: Sourcing / Decision / Application / Networking / Analytics / Setup. 총 18개 내비 항목.tests/modes-endpoints.test.mjs의 신규 테스트 12개.fix: bootstrap parent deps + russian_portals defaults(FIX-C4 + C9 + C12 + H2) —bin/start.sh가 이제 신규 클론에서 부모의node_modules(js-yaml, playwright, jsdom) 와npx playwright install chromium을 설치하므로/api/stream/scan,/pdf,/liveness가 즉시 end-to-end 로 동작합니다.createApp()이 모든 부팅에서portals.yml을 프로브합니다 —russian_portals:블록이 없으면 주석과 함께 문서화된 기본을 추가합니다. 멱등성: 두 번째 부팅은 no-op. 3개 신규 테스트.fix: disable 9 dead portal slugs in template + health-check script(FIX-C3) —templates/portals.example.yml이 이제 Ada / Factorial / Tinybird / Weights & Biases / Travelperk / Clarity AI / Forto / Vinted / Runway 를enabled: false로 표시하여 출하합니다 (각 항목에 인라인 이유 주석). 신규 설치는 96 대신 87 개의 활성 회사를 스캔합니다. 신규web-ui/scripts/portals-health-check.mjs가 활성화된 모든careers_url을 HEAD 프로브하고 제안된 패치 목록과 함께 DEAD 항목을 보고합니다 (--json을 통한 JSON 출력). 3개 신규 테스트.feat(activity): user-action log + Activity sidebar page— 모든 상태 변경 API 요청이data/activity.jsonl에 캡처됩니다 (타임스탬프, 액션 동사, 대상, 성공 플래그, 선택적 세부). 액션 접두어 chip 필터 (pipeline / cv / jd / evaluate / scan / stream / script), 액션 ✓/✗ 배지, 새로고침 버튼이 있는 신규 사이드바 항목 Activity. 5 MB 에서 자동 회전. 미들웨어, 읽기 필터, 손상된 라인 허용,GET /api/activity자체에 대한 재귀 가드를 다루는 10개 신규 테스트.feat(deep): view Deep Research in browser + saved-results archive— Deep Research 페이지가 이제 (a){ run: true }와GEMINI_API_KEY가 설정되면 Gemini 를 통해 프롬프트를 라이브 실행하고 출력을interview-prep/{slug}.md에 영속화합니다; (b) 저장된 모든 deep-research 파일을 상대 타임스탬프와 함께 클릭 가능한 카드로 나열합니다; (c) 결과를 Markdown 으로 렌더링하며 결과별 📋 Copy / ⬇ Download .md / ↗ Open in tab 액션을 제공합니다. 신규 REST 표면:GET /api/interview-prep,GET /api/interview-prep/:name,DELETE /api/interview-prep/:name. 7개 신규 테스트.feat(cv): generate + download PDF in browser, with PDF archive— CV 페이지의 신규 📄 Generate PDF 버튼이 모달 콘솔에서/api/stream/pdf를 스트리밍합니다.ERR_MODULE_NOT_FOUND/playwright오류 시 복사-붙여넣기 가능한 부트스트랩 커맨드를 노출합니다. 신규 “Generated PDFs” 섹션이 각 성공 실행 후 자동 로드되어 모든output/*.pdf를 ↗ Open 과 ⬇ Download 버튼과 함께 나열합니다. 신규 REST 표면:GET /api/output/pdfs,GET /api/output/pdfs/:name. 6개 신규 테스트.feat(api): POST /api/tracker — append rows from the UI(FIX-H8) — 브라우저에서 정규 행을data/applications.md에 추가합니다. company + role 을 검증하고,templates/states.yml에 대해 상태를 정규화하며, 0 패딩된#을 자동 증분하고, company+role 로 중복 제거 (대소문자 무시), 노트의 파이프를 이스케이프하여 markdown 표가 깨지지 않도록 합니다. 파일이 비어 있을 때 표를 부트스트랩합니다. 6개 신규 테스트.feat(api): DELETE /api/jds/:name(FIX-H4) — shell out 없이 저장된 JD 를 제거합니다. 어떤 파일시스템 접촉 전에 경로 탐색 문자가 strip 되며; 파라미터는.txt로 끝나야 합니다.../../etc/passwd거부 포함 5개 신규 테스트.feat(api): POST /api/evaluate/test-gemini(FIX-H7) —gemini-eval.mjs를 통해 50자 더미 JD 를 실행하여 사용자가 실제 평가를 기다리지 않고도 API 키가 동작하는지 확인할 수 있는 smoke-test 엔드포인트.{ ok, code, sampleLength, sample }반환.
🐛 버그 수정
fix(router): catch-all 404 view + i18n coverage guard(FIX-C7) — 알 수 없는 해시 라우트가 이전에는 조용히 대시보드로 폴백되어 오타와 깨진 북마크를 가렸습니다. 이제#/totally-random-xyz가 잘못된 경로를 인용하고 대시보드로 링크하는 전용 404 페이지를 렌더링합니다. 404 뷰는 라우터 IIFE 자체 안에 등록되어 어떤 사용자 라우트와도 충돌할 수 없습니다. 신규tests/i18n-coverage.test.mjs가vm.Context안에서 stubwindow와 함께i18n.js를 실행하고 privateDICT를 노출하여 173+ 키 × 8 로케일 각각이 채워지고 비어 있지 않음을 단언합니다. 4개 신규 라우터 테스트.fix(router): alias #/profile → settings(FIX-C2) — 내부 라우트 이름은settings(nav.settings가 “Profile” 을 렌더링) 이지만 외부 링크와 근육 기억은#/profile로 갑니다. 이제 두 주소가 같은 뷰에 도달하고 사이드바 내비 항목이 어느 쪽이든 강조됩니다. 2개 신규 테스트.fix(health): unify Health/Doctor + flag template profiles(FIX-C6 + FIX-H6) — Health 와 Doctor 가 두 개의 다른 진실 소스였습니다. 이제/api/health가 Doctor 가 보고하는 모든 것 (parent-deps, Playwright, dirs, profile-customized,HH_USER_AGENT) 을 노출합니다.Profile customized검사가 placeholder 이름 (Jane Smith,Alex Doe,John Doe,Your Name,Test User) 과 명시적 YAML 파싱 오류를 감지합니다. 4개 신규 테스트.fix(scan): warn on query↔negative collisions in RU config(FIX-H3) —portals.yml이 Senior PHP 를 타깃하는 쿼리와 함께title_filter.negative에"PHP"를 출하하면 모든 매치가 필터링되어 사용자가 결과 0 을 봅니다.loadConfig()가 이제warnings배열을 계산합니다;runRuScan()이 스캔이 시작되기 전에 각 경고를 SSE stderr 라인으로 emit 합니다. 출하 기본이 PHP-친화적으로 유지됨을 검증하는 2개 테스트.fix(scan): warn when HH_USER_AGENT is unset(FIX-H1) —/scan페이지가/api/health를 프로브하고HH_USER_AGENT가 비어 있으면 액션 행 위에 노란 경고 카드를 표시하여 사용자가 RU 스캔을 클릭하기 전에 hh.ru 403 을 알도록 합니다.fix(api): warn when POST /api/jds slug had unsafe chars stripped(FIX-M2) — 위험한 문자를 strip 하는 slug 정규화가 이제warning필드를 반환합니다; 순수 대소문자/공백 정리는 조용히 유지됩니다. 정화 후 빈 결과는 400 을 반환합니다.fix(ui): clear global search on route change + button spinners(FIX-M4 + FIX-L1) — 글로벌 검색 입력이hashchange에서 클리어됩니다 (활성 입력 가드 포함). 신규UI.withSpinner(button, fn)헬퍼가 로딩 상태, ARIA, 더블 클릭 방지를 모든 비동기 버튼 클릭에 배선합니다. 이미 Doctor / Verify / sync-check / Save CV / Normalize / Dedup / Merge 버튼에 적용되었습니다.fix(ui): make sidebar scrollable so 18 nav items always reach the footer— FIX-C8 의 그룹화된 사이드바가 짧은 뷰포트를 오버플로우했습니다; 하단 항목 (Activity / Health) 이 잘렸습니다..sidebar가 이제 thin 사용자 정의 스타일 스크롤바와 함께overflow-y: auto를 갖습니다 (WebKit + Firefox). 푸터는 기존margin-top: auto를 통해 핀 유지됩니다.fix(ui): empty modal-title placeholder(FIX-H9) —index.html의 하드코딩된 영어"Title"문자열이 사라졌으며, 모달이 열리는 동안 보였던 짧은 경쟁 창을 닫습니다.
🌐 i18n
- 173+ 번역 키 × 8개 지원 로케일 (
en,es,pt-BR,ko,ja,ru,zh-CN,zh-TW). 모든 로케일에 추가된 신규 키: 404 페이지, activity 로그, deep research, PDF 흐름, 보안 경고, tracker 변경, apply 이름 변경. 커버리지가 이제tests/i18n-coverage.test.mjs로 강제됩니다 — 모든 키가 모든 지원 로케일에서 비어 있지 않은 값을 가져야 하며 그렇지 않으면 CI 실패합니다.
⚙️ DevOps
- 테스트 수: 73 → 201 (23개 테스트 파일에 걸쳐 +128 테스트). 유일하게 남은 실패 테스트 (
runEnScan: dry-run end-to-end across multiple sources) 는 Greenhouse/Ashby/Lever 라이브 API 응답에 의존하는 기존 flake 입니다. - Comprehensive Playwright e2e (
tests/e2e-comprehensive.mjs, 23 단계): 전체 사용자 여정을 걷습니다 — CV 저장 → preview → PDF 생성 → 모든 7개 신규 모드 → tracker 필터 → activity 로그 → 404 → 모달 ESC → 사이드바 스크롤 → Ctrl-K 포커스 → 검색 클리어 → profile alias → 언어 영속. - GitHub Actions (
.github/workflows/):ci.yml— Node 18/20/22 매트릭스의 유닛 + 통합 테스트, 더하여 i18n 커버리지 게이트 (모든 키 × 8 로케일이 비어 있지 않아야 함), 더하여 모든 PR 의 전체 Playwright e2e.ai-review.yml— 모든 PR 에 대한 Claude Code AI 리뷰. 메인테이너가 머지 권한을 유지합니다; Claude 는 제안만 합니다.skip-ai-review레이블로 건너뛰기.release.yml—v*.*.*태그가 푸시되면 GitHub Release 자동 게시; 릴리스 노트는CHANGELOG.md에서 슬라이스되므로 8개 언어 변형이 모두 정규 소스로 유지됩니다.
- CSP 친화적 UI: 모든 인라인
onclick핸들러가index.html과router.js에서 제거되었습니다. 엄격한script-src 'self'정책이 어떤 기능도 깨뜨리지 않고 이제 강제 가능합니다.
📦 신규 REST 엔드포인트
| Method | Path | 용도 |
|---|---|---|
GET |
/api/activity |
사용자 액션 이벤트 목록, 최신 우선 |
GET |
/api/interview-prep |
저장된 Deep Research 파일 목록 |
GET |
/api/interview-prep/:name |
단일 Deep Research 파일 읽기 |
DELETE |
/api/interview-prep/:name |
Deep Research 파일 제거 |
GET |
/api/output/pdfs |
생성된 PDF 목록 |
GET |
/api/output/pdfs/:name |
첨부로 PDF 스트리밍 |
POST |
/api/tracker |
applications.md 에 행 추가 |
DELETE |
/api/jds/:name |
저장된 JD 제거 |
POST |
/api/evaluate/test-gemini |
Gemini API 키 smoke 테스트 |
POST |
/api/mode/:slug |
7개 신규 모드 (project / training / followup / batch / contacto / interview-prep / patterns) 의 범용 프롬프트 빌더 |
[1.6.0] — 2026-05-02
웹 UI 의 초기 공개 릴리스. 이 기준선의 기능 인벤토리는 README.md 를 참조하십시오.
career-ops-ui v1.267.1 · page 9/9 · mirror of the project changelog for cvstart.org