# 2026-W24 Worklog
AI Summary
Purpose:
- Tracks development work across repositories for this week.
Key points:
- Add coding work entries here after meaningful agent sessions.
- Keep entries concise.
- Promote durable knowledge to
ai/repo-notes/orai/wiki/.
Relevant when:
- Reviewing this week's development work.
- Creating weekly report.
- Resuming context.
Do not read full document unless:
- The task concerns this week.
Linked documents:
ai/worklog/index.mdai/workspace/repos.mdai/repo-notes/crawler-lib-php.mdai/repo-notes/hyunwook-tech.mdai/repo-notes/vibekits.mdai/repo-notes/yt-pipeline-n8n.md
Work Items
LLM Wiki integrated portal and graph
- Date: 2026-06-13. Repo:
llm-wiki. - Built an Access-protected integrated portal under
human/index.htmlandhuman/wiki/so the internal Cloudflare Pages project can expose Wiki, reports, study blog, and technical portfolio from onehumanoutput directory. - Added
scripts/build-wiki.mjs, which scans Markdown and curated HTML outputs, generates document pages, category and tag pages,wiki-data.js, and an interactive SVG force graph using Markdown links, frontmatterrelated, tags, and categories. - The generated portal indexes 246 documents, including 102 Markdown-rendered document pages, 227 tags, and 2,057 graph edges at build time.
- Updated
human/_redirectsso/,/wiki,/graph,/tags,/categories,/study,/portfolio, and/reports/onboarding-hubresolve to the integrated site paths. - Updated
human/portfolio/DEPLOY.mdwith the new portal paths and regeneration command. - Verification:
node --check scripts/build-wiki.mjs,node scripts/build-wiki.mjs,node scripts/check-relative-links.mjs human/wiki human/index.html,node scripts/check-relative-links.mjs human/reports human/slides human/portfolio human/study/dist, andnode --test scripts/test/*.test.mjsall passed. Browser QA on a local static server confirmed/loads,/wiki/graph.htmlrenders 489 SVG nodes and 2,057 lines with no console errors, and wiki search filters results.
CI/CD AI review kit portfolio grounding
- Date: 2026-06-13. Repo:
llm-wiki; also updated resume-style Mongo content onhome_macafter backup. - User direction: refine the portfolio section using the user's Confluence work and the Bitbucket CI/CD central toolkit, rather than presenting AI review as a vague standalone idea.
- Sources checked: Confluence
[DSGN|CICD] CI/CD AI Review Kit 설계 문서and Bitbucketlabrador-cicd-ai-review-kit; related Jenkins/Bitbucket analysis plugin context was also reviewed but kept secondary in the public framing. - Added AI-facing source note
ai/wiki/projects/ci-cd-ai-review-kit.mdwith the durable facts: central runner, thin target wrappers, full-SHA pinning, destination-branch policy, profile-based checks, mixed component testing, secret-scan-before-Claude, Slack notification, and Bitbucket merge-check boundary. - Rewrote
human/portfolio/items/ai-cicd-review-kit.htmlas “CI/CD 중앙 실행 킷과 AI PR 리뷰 게이트” and updatedhuman/portfolio/index.htmlcard plus Spotlight entry. - Public wording intentionally avoids private repo URLs, tokens, hostnames, customer names, adoption counts, and unverified time-saving percentages.
Resume/technical portfolio gap follow-up without malicious-package scope
- Date: 2026-06-13. Repos:
llm-wikiandhyunwook.techonhome_mac. - User decision: apply the portfolio gap-analysis follow-up, but exclude malicious-package work because that area is still under review.
llm-wikihuman-facing portfolio pages updated to emphasize crawler modernization, customer on-premise/binlog support, and Jira/Confluence/Bitbucket-based documentation governance without exposing internal sensitive details.- Added internal onboarding artifacts:
human/reports/2026-bitbucket-repo-map-onboarding.html,human/slides/2026-crawler-collection-count-log-slides.html, and AI-facing conceptai/wiki/concepts/work-repository-map.md. - Updated
human/reports/onboarding-hub.htmlto link the repo map and the crawler collection-count log slide; local link verification reported no missing relative links. - Resume-style site content in MongoDB
tech_db.resumeswas backed up before editing and updated to remove malicious-package wording while adding crawler modernization, collection-count logging, on-premise/binlog issue support, and repo-map/Jira/Confluence/Bitbucket governance points.
Hermes + Discord LLM Wiki operations portfolio item
- Date: 2026-06-12. Repo:
llm-wiki. - User decision: add the current Hermes + Discord +
llm-wikiworkflow as a personal project, framed as a personal AI knowledge/work documentation system rather than a generic Discord bot. - Added source doc
ai/wiki/projects/hermes-discord-llm-wiki-system.mdand indexed it inai/wiki/projects/index.md. - Added Korean technical portfolio detail page
human/portfolio/items/hermes-discord-llm-wiki-system.htmland a new card inhuman/portfolio/index.html; counts changed from 43→44 total items and 3→4 personal items. - Public-facing wording avoids unverified metrics and emphasizes the operating workflow: Discord interface → Hermes execution → curated durable records in git-backed
llm-wiki.
portfolio.hshouse.xyz → resume.hshouse.xyz rename and technical portfolio link sync
- Date: 2026-06-12. Repos:
hyunwook-techonhome_macandllm-wiki. - User decision: distinguish the resume-style portfolio from the technical portfolio. Primary resume URL is now
https://resume.hshouse.xyz/; existinghttps://portfolio.hshouse.xyz/remains alive as a backward-compatible alias. - Source repo used:
/Users/james-home/project/hyunwook.techonhome_mac; remote[email protected]:hw-me/hyunwook.tech.git. - Pre-change backups saved at
/Users/james-home/backups/portfolio-sync-20260612-214814, including git snapshot, Docker/Cloudflare config copies, MongoDB dump,resumes.before.json, andresume-link-mapping.json. - MongoDB
tech_db.resumeslinks were updated: 10 oldhttps://wiki.hshouse.xyz/wiki/...resume detail links now point to technical portfolio detail pages underhttps://portfolio-equ.pages.dev/items/...; verification showedold_wiki_links: 0,new_portfolio_links: 10. - Cloudflare Tunnel route and DNS were added for
resume.hshouse.xyz;cloudflaredrestarted. .envchangedNEXT_PUBLIC_PORTFOLIO_URLfromhttps://portfolio.hshouse.xyztohttps://resume.hshouse.xyz; committed and pushed inhyunwook.techas74c7060 chore: rename resume portfolio public URL.- Verification: Docker
portfolio-webbuild succeeded, container restarted, publicresume.hshouse.xyzandportfolio.hshouse.xyzreturned HTTP 200, contained김현욱, contained technical portfolio links, and no longer contained the oldwiki.hshouse.xyz/wiki/6975.../6976...links. - Note: build emitted existing Next.js 14.1.0 security/audit warnings; not addressed in this task.
Agent documentation and Korean humanization policy
- Date: 2026-06-12. Repo:
llm-wiki. - User standing instruction: all meaningful conversations, decisions, and work documentation should be saved into
~/workspace/repos/llm-wiki; code work should happen in a dedicated or existing git repo, with the wiki used for durable documentation. - Added durable policy page
ai/wiki/concepts/agent-work-documentation-policy.md. - Hourly git remote synchronization is requested for
llm-wiki; recurring automation should commit and push local wiki changes conservatively, failing rather than auto-resolving conflicts. - For Korean resume/portfolio/human-facing text, use
https://github.com/epoko77-ai/im-not-aiplus humanizer-style review to remove AI-like Korean phrasing while preserving facts and uncertainty.
crawler-lib-swift·ruby: 로그 레벨 정책 통일(누락만 error)
- Date: 2026-06-11. Repos:
~/labrador/crawler/crawler-lib-{swift,ruby}. 미커밋. - dotnet 에 적용한 정책(누락/유실 확정만 error, 나머지 warning)을 두 repo 에 맞춰 적용.
- swift (error 5곳만 잔존, 비누락 ~24곳 warning 강등):
- error 유지: baseCrawler.setInsertIgnoreDB/IntoDB insert 실패(테이블명 추가), cdbvdb.executeSql 실행 실패(swallow 구조라 여기가 행 유실의 실질 지점 — statement 머리 200자 포함), cocoapods git pull 3회 실패(Specs stale → 최근 pod 누락 가능), crawlSingleProduct 버전 실패(수동 백필 경로, 자동 재시도 없음). - warning 강등: run/crawler 외곽 catch-all 5곳(처리 실패(다음 회차 재시도)), git pull 재시도 중, pod 진입 스킵(다음 회차 재검출), version 처리 실패 2곳 (오늘 오전 error 로 만들었던 것 — swift 증분이 Specs 디렉토리 vs DB 비교라 실패 버전은 다음 회차 자동 재검출 → 정책상 warning 으로 재조정, 경로+traceback 유지), RELEASE_DATE 보강 2곳, license 매칭/항목 처리(component 201·290), cdbvdb setQuery/select 2곳(raise→상위 판단), db/base 2곳, licenseeUtil 5곳.
- ruby (크롤러 본류는 기존 정리에서 이미 일치 — error 2곳 = 피드 윈도우 스킵·행 insert skip):
- 잔존하던 util/db 비누락 7곳 warning 강등: licenseeUtil 5곳(subprocess/AI/JSON/라이선스 파일 읽기), db/base.py 2곳(engine open/getDB — raise→상위 판단).
- 검증: 양쪽
compileallOK, ruby 테스트 13/13 통과(ruby_status 6 + version_sort 7).
swift 는 테스트 없음 → 컴파일 검증만.
- 시크릿 분리(같은 날, 조치 완료): ruby·dotnet
licenseeUtil의 GitHub 토큰 하드코딩
각 2곳 + ruby libraries.io api_key 1곳(데드코드) 제거 → config.ini[COMMON] 에서 읽도록 변경. config.ini 에는 빈 placeholder 만 커밋(평문 토큰 커밋은 차단됨 — 사용자 선택). swift 도 config.ini 에 커밋돼 있던 토큰 평문을 빈 값으로 교체. 세 repo 모두 토큰이 비면 무인증 clone/API 폴백으로 동작(rate limit 만 낮음). 커밋: ruby fb22fc7, dotnet fdf48d1, swift eb0f407 (master push 완료). 남은 일: (1) 노출된 기존 토큰 rotate, (2) 새 토큰을 배포 환경 config.ini 에 주입 — 주입 전까지 GitHub 라이선스 보강은 무인증(60req/h)으로 동작.
- Date: 2026-06-11. Repo:
~/labrador/crawler/crawler-lib-dotnet
(component.py 33+/2-, dotnetCrawler.py 358±, crawlerInfo.py 4+, 신규 tests/test_dotnet_license_url_and_missing_versions.py). 미커밋.
- 요청 3건:
1. licenseUrl 처리: NuGet 이 라이선스를 URL 로만 줄 때 — SPDX URL(licenses.nuget.org/<expr>, spdx.org/licenses/<id>)은 component.getSpdxNameFromLicenseUrl 로 라이선스명 환원 후 기존 매칭(IDS/AI 정상); github URL 이면 projectUrl 대신 그 repo 로 기존 GitHub license fallback 라우팅 (raw.githubusercontent.com → github.com 정규화); 그 외 일반 URL 은 LICENSE=[{"name":<url>}] 만 기록하고 LICENSE_IDS 매칭·AI 호출 스킵 (skip_license_ai). aka.ms/deprecateLicenseUrl 마커는 값 없음 취급. 2. LATEST_VERSION 무조건 최신 보정: 배치 말미 컴포넌트별 정렬 패스에서 (신규+DB+백필) 전체 버전을 sortVersions(오름차순)로 정렬해 LATEST_VERSION=sorted[-1] 보정 row 를 마지막에 upsert. 누락분 늦수집 시 과거 버전이 LATEST 를 덮던 문제 + 기존 len>1 게이트 때문에 단건 수집 시 정렬/보정이 아예 안 돌던 문제 함께 해결. 3. 누락 버전 동시 수집: _collectMissingVersions — flatcontainer (v3-flatcontainer/{id}/index.json) 전체 버전 목록과 (이번 수집분+DB) 대조, 누락 버전은 registration leaf(registration5-gz-semver2/{id}/{ver}.json)의 catalogEntry 로 catalog leaf 를 받아 본류와 동일 처리(version/license row 즉시 insert, product row 는 본류가 이미 생성 → LATEST 는 보정 패스가 담당). 버전 비교는 lower()+build metadata 제거 정규화.
- 구조 변경: per-leaf 처리 블록(~110줄)을
_processCatalogLeaf(versions_info, ctx)로
추출해 본류와 백필이 공유(ctx = repo cache/누적 리스트/collect_product 플래그). 데이터 포맷(row 구조/JSON 직렬화) 불변.
- 비용 주의: 배치마다 컴포넌트당 flatcontainer 1회 + 누락 버전당 registration/leaf
각 1회 HTTP 추가. flatcontainer 는 CDN 이라 가볍지만 운영 모니터링 권장.
- 검증: 기존 26 + 신규 7 = 33 테스트 전부 통과,
compileallOK.
신규 테스트: SPDX URL 환원(LICENSE_IDS [247]), github licenseUrl 로 getRepoLicenseFromGITAPI('foo','bar'), 일반 URL → LICENSE only(IDS/AI None + AI mock 미호출), deprecated 마커 무시, flatcontainer diff 백필(1.0.0 수집 + LATEST 2.0.0), 과거버전만 수집 시 LATEST=DB 2.0.0 유지, 누락 없으면 백필 안 함. 라이브 API 형태 확인: flatcontainer versions 84개(newtonsoft.json), registration leaf catalogEntry URL ✓ (gzip 은 requests 자동 해제). 실DB 스모크 미실시.
- 로그 레벨 정리(같은 날, ruby 와 동일 정책): 누락(행 유실) 확정 지점만 error,
나머지 전부 warning 강등. error 유지 2곳 = baseCralwer.setInsertIgnoreDB/ setInsertIntoDB insert 실패(여기서 swallow 되어 페이지가 processed=1 로 닫힐 수 있는 실제 유실 지점, 테이블명 컨텍스트 추가). warning 강등 ~40곳: 재시도되는 페이지/leaf 오류(processed=0 requeue), run loop, 정렬/SORT_ORDER/ LATEST 보정 실패(코어 행은 적재됨), 라이선스 매칭·GitHub API·AI·clone 보강 실패, JSON 변환 폴백(str 로 저장됨), clone 정리 실패, updatePackageListCrawler (다음 주기 last_path 부터 따라잡음), 저수준 DB(cdbvdb/base — raise 해서 상위 판단, _logProblemRow 행 상세 포함; nullify-재시도로 복구되는 케이스가 있어 warning). app/check.py(진단 CLI)는 크롤러 런타임 로그가 아니라 미변경. 검증: compileall OK, 33 테스트 전부 통과.
crawler-lib-swift: cocoapods.py:329 'version' 에러 진단 + 로그 개선 + version 누락 podspec 스킵
- Date: 2026-06-11. Repo:
~/labrador/crawler/crawler-lib-swift(cocoapods.py만 16+/2-). 미커밋. - 운영 로그
ERROR 1 cocoapods.py:329 'version'원인: per-version 루프의 catch-allerror(e)가
str(KeyError('version'))(따옴표 포함 키 이름만)을 그대로 출력한 것. 발생 지점은 getVersionVo → component.getVersion의 item_info['version'] 직접 키 접근(component.py:26) — version 키 없는 비표준 podspec JSON을 만나면 KeyError 로 그 버전이 통째로 스킵되고, 어떤 파일인지 로그에 안 남았음(파일명은 DEBUG 에만).
- 수정(포맷 불변, 로그/내성만):
1. version 키 누락 podspec → warning('podspec version 키 누락(스킵): Specs/.../pod/ver/file') 후 명시적 continue — crawl·resync 두 walk 루프 + crawlSingleProduct(product_key+ver 로 표기) 세 곳. 2. 그 외 예외 catch-all(구 329행) → error('version 처리 실패: {path}/{dir5}/{dir6} - {e!r}', exc_info=True) 로 podspec 경로+traceback 포함(crawl·resync 양쪽).
- 검증:
py_compileOK. repo 의BraceFormatStyleFormatter(내부 표준 Formatter 위임)로 두 신규
메시지 + exc_info traceback 렌더링을 단위 검증(LOG_FORMAT_OK). 실크롤러 스모크는 미실시 (DB/네트워크 의존) — 다음 운영 회차 로그에서 누락 podspec 경로 확인 가능.
- 추가(같은 날):
component.py:288 'dict' object has no attribute 'find'진단+수정.
원인: licenseResync 의 "깨진 raw string" 휴리스틱(str(data).count('"') > 4, L228)이 name 값에 큰따옴표 5개 이상 든 정상 dict(라이선스 전문/"AS IS" 인용구 등)를 string 분기로 오판 → 'type => "' in data 가 dict 키 검사라 False → else 의 data.find('name": "')(L242)에서 AttributeError, L288 catch-all 이 메시지만 출력. 수정: (1) string 분기 조건에 isinstance(data, str) 추가(dict 는 항상 dict 경로 — string/기존 dict 동작 불변), (2) L288 → error('license 항목 처리 실패: {data!r} - {e!r}', exc_info=True) 로 엔트리 값+traceback 포함. 검증: py_compile OK + 메서드 추출 단위테스트 4케이스(quote-많은 dict 정상 파싱/garbled string 기존 동작/평범 dict 불변/에러 로그에 값·traceback 포함) ALL PASSED. 영향: 해당 엔트리 라이선스 매칭 누락(STANDARD_LICENSE/LICENSE_IDS 빠짐)되던 케이스가 정상 매칭 경로로 복구.
npm 누락 일일 체크 재실행 (2026-06-10, finder only)
- Date: 2026-06-10. Tool:
~/labrador/tool/npm_missing_products(extract_npm_missing_products.py, 만들어둔 도구 그대로). DB 165 대상, 크롤러 코드 무변경. - 결과(
2026-06-10/): registry_all_docs4,108,106 | DB 평문복원 5,266,556(rows 5,269,450, 복호화 실패 2,894) | 둘 다 존재 4,106,994 | raw 누락 1,112 → 제외목록 1,022 차감 → 조치 가능 누락 90(★백필 대상) | DB-only 1,159,562 | 이름>255 0. 총 ~1,483s. - 6/9 대비: raw 1,084→1,112, actionable 62→90. 증가분은 6/9 백필 이후 신규 게시 패키지(scoped CLI/MCP server/SDK 다수).
- 검증: 90건 표본 5개(
akshare-ts,branchtidy,@xsmp/core,todoage,@insler/rpc-context) registry liveness 전부 HTTP 200 → 진짜 누락(미러 지연 아님). - 백필 실행·완료(2026-06-10):
collect_npm_missing.py --sink db --no-ai→ DB 165 에 pkg 90 / version 110 적재(row-skip 0, 404 0, zero-versions 0, err 0).out_done.txt90건. read-back 3건(akshare-tslatest 1.0.0 /@xsmp/core2.0.0 /@insler/rpc-context0.0.0) product+version 존재, productLICENSE{url,name}재배열 = 기존 크롤러 행 포맷 동일.
crawler-lib (golang/php/ruby/swift/dotnet): 배치별 product·version insert/update 수집량 로그 추가
- Date: 2026-06-10. Repos:
~/labrador/crawler/crawler-lib-{golang,php,ruby,swift,dotnet}
(java·rust 제외). 5개 repo 모두 미커밋.
- 요청: 각 크롤러 배치 종료 시 그 배치가 수집한 양 —
product(insert,update) / version(insert,update) 카운트를 로그로 남기기.
- 핵심 제약: 5개 모두 Python 이고 product/version 쓰기는 전부
MySQL INSERT ... ON DUPLICATE KEY UPDATE(upsert). upsert 는 행별 insert/update 를 알려주지 않음 — 멀티로우 한 statement 의 affected_rows 합계로는 분리 불가 (update=2·무변화=0 때문에 산수도 깨짐). 그래서 upsert 직전 그 배치 PK 를 SELECT 해서 DB 에 있으면 update, 없으면 insert 로 분류(사용자가 "가벼운 A" 선택).
- 공통 구현(각 repo 의
crawler/baseCralwer.py, swift 만baseCrawler.py철자):
_resetCollectStats/_entityForTable/_selectExistingKeys/_recordCollectStats/ _collectBatchSummary/_collectTotalSummary 6개 메서드 추가. setInsertIntoDB 맨 위(upsert 직전)에서 _recordCollectStats(table_name, datas) 호출 — table 이 PRODUCT_TABLE/VERSION_TABLE 일 때만 집계(STATUS/LIST/LICENSE 제외). 배치 내 PK _collectSeen set 으로 중복제거 → version sorting 재삽입(같은 키 재upsert)이 update 로 이중집계되는 것 방지.
- 크롤러별 훅: 배치 진입 시
_resetCollectStats(), 배치 종료 시
[batch summary] product insert=.. update=.., version insert=.. update=.., run 종료 STOP 로그 직전 [total summary] ...(런 누적). - php/ruby/dotnet: dataCrawling() 상단 reset + 말미 batch summary, run() STOP 직전 total. - swift(cocoapods.py): 반복 배치 루프가 없는 단일 패스라 crawl 메서드 (cocoapodsCrawler/cocoapodsCrawlerResync) 전체를 1배치로 보고 finally: 에서 batch+total 둘 다 출력(중간 예외에도 로그 보장). base 는 self.log, cocoapods 는 self.logger 사용 — 각자 맞춰 적용. - golang: V1·V2 둘 다. product/version 쓰기가 공통 setInsertIntoDB(V1 sort) + goCrawler 의 _setInsertIntoDBWithConnection(V1-core·V2, _v2*Table()) 두 경로로 갈려서 두 곳 다 훅(_collectSeen 가 인스턴스 단위라 두 경로 겹쳐도 1회만 카운트). _entityForTable 은 golang 만 PRODUCT_TABLE_V2/ VERSION_TABLE_V2 도 인식하도록 확장. reset/summary 는 dataCrawling(V1)+ dataCrawlingV2(V2) 각각, total 은 STOP 3곳(V1 2 + V2 1) 직전.
- 안전장치:
_recordCollectStats는 try/except 로 감싸 통계 집계 실패가 크롤링
본류를 막지 않게 함(실패 시 [collect stats] skipped warning 만). SELECT 는 500개 단위 chunk IN 으로 파라미터 바인딩(주입 방지).
- 검증: 5개 repo 전부
python3 -m py_compile통과(base + 크롤러 파일).
분류 로직은 헬퍼 메서드 본문을 그대로 떼어내 fake DB 로 단위 테스트 — product insert/update, version insert/update, sorting 재삽입 dedup, 비대상 테이블 무시, 배치 reset 후 누적 total 까지 전부 기대값과 일치(ALL PASSED). 실DB·네트워크 의존(requests/DB)으로 컨테이너 실크롤러 스모크는 미실시 → 1회 권장.
llm-wiki: human voice rules for AI agents
- Date: 2026-06-09. Repo:
llm-wiki, branch:main. - Request: update agent instruction files so future AI work on human-facing
outputs adds a grounded human voice instead of generic AI-sounding copy.
- Updated AI-facing instructions:
- AGENTS.md: added Human Voice Rules for human/** outputs, covering real situation, judgment, tradeoffs, operating details, uncertainty, and anti-template revision guidance. - AGENTS.md: linked portfolio items to Human Voice Rules. - CLAUDE.md and GEMINI.md: added concise summaries that defer to AGENTS.md as the canonical source.
- Follow-up: clarified that the company-internal acronym for the Binlog
shipping service should not be exposed in human-facing outputs. Use Binlog shipping service or a Korean descriptive phrase such as 바이너리 로그 기반 데이터 동기화 instead.
- Updated visible human-facing wording in the portfolio, onboarding reports,
slides, and ONBOARDING.md where the internal acronym was used as a service name.
- Renamed human report/slide slugs from the internal-codename pattern to
*binlog-shipping* and updated relative links so published paths avoid the internal codename too.
llm-wiki: full portfolio human voice pass
- Date: 2026-06-09. Repo:
llm-wiki, branch:main. - Request: make the full portfolio feel less AI-written and more like a
human engineer's portfolio, using Jira/Confluence context where helpful.
- Updated Korean human-facing outputs:
- human/portfolio/index.html: rewrote the hero and introduction around operating real data systems, added a "work-note" style framing section, and tightened all 41 card descriptions. - human/portfolio/index.html: corrected CSS filter label counts for DB/Infra and 팀·프로세스 after full card-tag QA found stale counts. - human/portfolio/items/*.html: added one grounded 작업자의 메모 section to every portfolio detail page so each item explains the real operational judgment behind the work. - human/portfolio/items/*.html: added wrap guards to flow/impact/panel boxes so long Korean/English mixed labels do not overflow narrow mobile cards.
- Source context used:
- Confluence Motivation & Overview: manual crawler execution, scheduling/monitoring automation, testability, portability, and bus-factor motivation for Airflow/Kubernetes data orchestration. - Confluence DB Arch, Re-engineering: DB index cleanup and 2.6T to 1.2T result context. - Confluence Binlog shipping monitoring architecture source: Vector, TimescaleDB, Grafana, Slack, and Jira flow for monitoring v2. - Confluence 2026-03-31 - Airflow 기반 수집 워크플로우 설계: DAG separation, KubernetesExecutor, retry/quarantine, and observability model.
- Verification:
- node --check scripts/check-personal-project-portfolio.mjs - node --check scripts/check-relative-links.mjs - node scripts/check-personal-project-portfolio.mjs - node scripts/check-relative-links.mjs human/portfolio human/reports/personal-projects-onboarding-hub.html - custom Node check: 41 cards, 41 descriptions, and 41 detail pages with exactly one 작업자의 메모 section. - headless Chrome CDP full portfolio audit: 41 cards, 10 filter counts, 41 unique card links, and 41 detail pages checked at desktop and mobile widths (82 detail-page checks) with no detail failures. - headless Chrome CDP visual QA: desktop and mobile portfolio screenshots, with scrollWidth == clientWidth at 390px and 1440px.
- Evidence:
- .omo/evidence/portfolio-human-voice/index-mobile-cdp.png - .omo/evidence/portfolio-human-voice/index-desktop-cdp.png - .omo/evidence/portfolio-human-voice/detail-db-index-cdp.png - .omo/evidence/portfolio-human-voice/full-card-detail-audit-rerun.json - .omo/evidence/portfolio-human-voice/all-cards-desktop-cdp-rerun.png
llm-wiki: personal project architecture diagrams
- Date: 2026-06-09. Repo:
llm-wiki, branch:main, commit: created in the
personal project architecture diagrams push.
- Request: add architecture diagrams for the personal repositories already
added to the portfolio.
- Updated Korean human-facing outputs:
- human/portfolio/items/personal-portfolio-platform.html: added a static architecture diagram showing visitor/author entry, Nginx gateway, Next.js surfaces, Go API, shared package, and PostgreSQL/Redis/MongoDB/Docker Compose infrastructure. - human/portfolio/items/vibekits.html: added a static architecture diagram showing React Router/i18n, tools/game pages, pure game engine boundary, and Supabase-ready auth/ranking/ads/share services. - human/portfolio/items/youtube-shorts-automation-platform.html: added a static architecture diagram showing Streamlit/n8n operation, FastAPI control plane, PostgreSQL/Redis, Python worker plane, provider routing, rendering, docs/upload, and ai_quality. - human/reports/personal-projects-onboarding-hub.html: added a three-column architecture overview for the three personal repos.
- Verification added:
scripts/check-personal-project-portfolio.mjsnow checks
that the personal detail pages and onboarding hub include the architecture sections.
llm-wiki: personal projects portfolio and onboarding
- Date: 2026-06-09. Repo:
llm-wiki, branch:main, commit: created in the
personal projects portfolio/onboarding push.
- Request: organize every repo under
~/hw/projectinto portfolio and
onboarding material.
- Repos covered:
hyunwook.tech,vibekits,yt-pipeline-n8n. - Added durable AI source docs:
- repo registry rows in ai/workspace/repos.md; - repo notes in ai/repo-notes/hyunwook-tech.md, ai/repo-notes/vibekits.md, and ai/repo-notes/yt-pipeline-n8n.md; - project wiki docs in ai/wiki/projects/personal-portfolio-platform.md, ai/wiki/projects/vibekits.md, and ai/wiki/projects/youtube-shorts-automation-platform.md.
- Added Korean human-facing outputs:
- human/portfolio/index.html now has a CSS-only 개인 filter, 41 total items, and three personal project cards; - detail pages under human/portfolio/items/ for the three projects; - human/reports/personal-projects-onboarding-hub.html as the personal projects onboarding hub; - human/portfolio-todo.md marks the personal project expansion done.
- Accuracy policy: period, role, public URL, production deployment, traffic,
revenue, channel metrics, and usage metrics are marked Needs confirmation. No source code or secrets were copied from the personal repos.
- Verification:
- node --check scripts/check-personal-project-portfolio.mjs - node --check scripts/check-relative-links.mjs - node scripts/check-personal-project-portfolio.mjs - node scripts/check-relative-links.mjs human/portfolio human/reports/personal-projects-onboarding-hub.html - headless Chrome CDP QA captured desktop/mobile screenshots and verified the 개인 filter, first detail navigation, and onboarding links.
- Evidence:
- .omo/evidence/personal-project-portfolio/red-validation.txt - .omo/evidence/personal-project-portfolio/green-validation.txt - .omo/evidence/personal-project-portfolio/browser-qa.json
crawler-lib-swift: 누락 원인 크롤러 수정(데이터 포맷 불변) + 백필 완료
- Date: 2026-06-09. Repo:
~/labrador/crawler/crawler-lib-swift(cocoapods.py만 변경 37+/10-, VO/getter 무변경=포맷 불변). 미커밋. - 백필 완료(165): swift_missing_products collector 685 pod 전체 → version 2,338 / gone 0 / err 0, 685/685 product 존재 확인.
- 누락 원인(코드 재확인): 누락 682가 version·product 전혀 없는데 git Specs 엔 존재 → 크롤러 버그.
지점 (1)★ 매 실행 rm -rf cocoDownload 후 거대한 Specs 전체 재clone + 단발 git pull(재시도/검증 없음) → 부분/타임아웃 실패 시 최근 pod stale-누락(=주원인); (2) 크롤 전체 단일 try/except → pod 1개 예외로 회차 전체 중단; (3) product 는 최신버전에서만 생성, 최신버전 podspec 오류 시 product 누락; (4) LruCache 하드코딩 skip.
- 수정(포맷 불변, coverage/robustness만): (1)
cocoapodsDownloadgit pull 3회 재시도+rc 검증,rm -rf cocoDownload제거 →
체크아웃 유지·증분 pull(전체 재clone 제거=속도+실패면적↓); (2) dir4(pod) 진입/목록/정렬/DB 를 per-pod try/except 로 격리(cwd 복구 후 skip·continue) — crawl+resync 양쪽; (3) version 있는데 product 없으면 마지막 성공 version 으로 product 보강(getProductVo 그대로).
- py_compile OK. 슬로우 개선안(권고): 증분 pull(적용), license+RELEASE_DATE clone 1회로 통합(현재 이중 clone),
git for-each-ref로 태그
날짜 일괄(현 git show <tag> per-version), shallow/partial clone(--filter=blob:none), os.chdir 제거+경로기반 worker 병렬화.
- TODO: 커밋/머지(요청 시), resync 의 product-보강도 동일 적용(현 crawl 만 일부), LruCache 하드코딩 재검토.
swift(CocoaPods): 누락 finder + 백필 collector + 누락 원인 진단 (swift_missing_products)
- Date: 2026-06-09. Source crawler:
~/labrador/crawler/crawler-lib-swift(CocoaPods, 분석만). 새 도구:~/labrador/tool/swift_missing_products. - 패턴: composer 식(PRODUCT_KEY=평문 pod 이름). DB:
TB_COMP_LIB_PRODUCT WHERE LANGUAGE='swift' AND REPOSITORY='cocoapods'.
한방 universe: https://cdn.cocoapods.org/all_pods.txt(약 10만). crawler 본체는 git CocoaPods/Specs sparse-clone 순회.
- finder 결과(2026-06-09): CocoaPods 102,385 | DB 102,480 | 누락 685 | DB만 780. (날짜 디렉토리 + 제외목록 + composer 식)
- 누락 원인 진단(★ 사용자 요청): 누락 685 중 682가 version·product 행 전혀 없음(완전 미수집)인데 git Specs repo 엔 존재(HTTP 200)
→ 소스 공백 아님, 크롤러 버그. 3건은 version 만 있고 product 없음(LruCache=하드코딩 skip 포함). 지점: (1)★ 매 실행 전체 Specs 재-clone + 단발 git pull(finally 에서 rm -rf, 재시도/검증 없음) → 부분/타임아웃 실패 시 최근 pod 미반영 (누락 682가 최근/벤더 pod 와 일치); (2) 크롤 전체 단일 try/except → 순회 중 예외 1개로 그 회차 전체 중단; (3) product 는 최신버전에서만 생성+증분로직이 product-없는 pod 재생성 안 함 → 영구 product 누락; (4) LruCache 하드코딩 skip; (5) clone/pull 재시도 없음.
- collector: pod 별 CDN 버전샤드 → pure-python SemVer 정렬 → 버전별 podspec(CDN) →
getVersionVo/getProductVo포맷 행 →
TB_COMP_LIB_VERSION_SWIFT+TB_COMP_LIB_PRODUCT. 포맷 검증(Alamofire 기존행 대비 LICENSE/STANDARD_LICENSE/SCM/ORIGINAL_INFO 일치). crawler 차이(불가피): 정렬 jar→pure-python, mapping eval→안전파싱, github clone 생략(podspec license 사용), LICENSE_IDS varchar(32) cap.
- 검증: py_compile OK, finder 실행(685), 누락 pod 3건 insert+rollback 스모크 통과(컬럼/타입 OK, 미persist). 전체 685 백필은 미실행(운영 write, 승인 대기).
npm·rubygems: 매일 누락 체크용 제외 목록(*_excluded.txt) 추가
- Date: 2026-06-09. Tools:
~/labrador/tool/{npm,rubygems}_missing_products. - 요청: 백필 끝났으니 매일 누락 체크 시 제외할 패키지를 ecosystem별로 묶기.
- 산출: 백필 산출물(gone_404+zero_versions+row-skip errors)을 ecosystem별
*_excluded.txt(path<TAB>reason)로
consolidate. npm 1,023(gone-404 537 / zero-versions 485 / row-skip 1), rubygems 4(gone-404). transient 오류(http5xx/timeout/json)는 제외 안 함(재시도 대상).
- finder(
extract_*) 변경: raw 누락에서*_excluded.txt를 빼*_missing_products.txt(actionable)만 남기고
raw 는 *_missing_all.txt 로 분리. --recheck-excluded 추가 — 제외 목록을 registry(npm)/RubyGems(ruby)로 재검증해 버전이 다시 생긴 패키지는 해제(자기치유). row-skip 은 유지. composer 도구의 excluded 패턴과 동일.
- collector(
collect_*) 변경: 백필 중 gone-404/zero-versions/전버전 row-skip 을 만나면*_excluded.txt에
append → 다음날 finder 가 자동 반영(중복은 finder dict dedup / recheck 압축).
- 검증(백필 후 재실행): ruby raw 누락 5 → 제외 4 → actionable 1(신규 gem); npm raw 누락 1,084 → 제외 1,022 →
actionable 62(백필 이후 신규 게시분). 백필 전 npm 5,159 → 제외+백필로 62 로 감소 = 의도대로 노이즈 제거. *_excluded.txt 는 durable 이라 gitignore 제외(커밋 공유).
- 결과 디렉토리화: 누락·백필을 종종 같이 돌리므로 결과물을 날짜 디렉토리(
<도구>/YYYY-MM-DD/, 기본 오늘)에 모음.
extract/collect 둘 다 --date/--workdir 지원, 같은 날짜 디렉토리 공유(collector 입력 기본 = <날짜>/*_missing_products.txt, out_done.txt 도 날짜 디렉토리 안 → 그 날 백필 독립 resumable). 스크립트/venv/.env/*_excluded.txt 만 top-level. 2026-06-09 산출물은 {npm,rubygems}_missing_products/2026-06-09/ 로 이동 완료.
- (정정) "3시간마다 ruby 실행"은 미싱 도구가 아니라 crawler-lib-ruby(도커 크롤러)의 일일 스케줄 요청이었음.
잠깐 만들었던 미싱 도구 launchd(run_3h.sh/plist)는 제거(bootout+삭제). 실제 변경은 아래 항목.
crawler-lib-ruby: list PROCESSED go식 작업 큐 전환 + 로그 warning 강등 + 운영 에러 진단
- Date: 2026-06-09. Repo:
~/labrador/crawler/crawler-lib-ruby. master 머지+push 완료
(branch fix/ruby-crawler-queue-and-logging 462a2db → merge 6d9a0f4, origin/master).
- 운영 에러 진단(crwlr-monitor): (1)
RemoteDisconnected= 일시 네트워크(이미 getJsonRequestsStatus 재시도, 양성);
(2) unterminated triple-quoted string literal = eval() 기반 파싱(DEPENDENCIES labradorDBVo:57, mapping component:171/298)이 gds-api-adapters 데이터에서 깨짐 — best-effort 격리로 복구됨, 근본수정(json.loads)은 미적용; (3) LICENSE_IDS Data too long(1406) = varchar(32) 가 좁아 다중라이선스 gem(sixarm_* 실제 10개+) 거부. 과다매칭 아님(정상값이 길 뿐) → 포맷 유지하려면 컬럼 확장 ALTER 필요(스키마 변경, 승인 대기). 셋 다 우리 수정이 유실을 막으며 가시화한 것(신규 장애 아님).
- 로그 강등: 비누락
.error()25건 →warning(일시 네트워크/보강 실패(코어 유지)/배치 행별폴백/저수준 DB).
실제 누락/유실 2건만 error 유지(rubyCrawler:103 피드 윈도우 스킵, baseCralwer 행 insert skip).
- list PROCESSED go식 큐: 기존 ruby 는 list PROCESSED 를 항상 1 하드코딩(피드=즉시크롤, 컬럼 死). go
go_utils와
동일 상태머신으로 전환 — ruby_status.py 신규(0/20~24 처리중/30~34 재시도/34 소진/1/5/3) + tests/test_ruby_status.py(6). baseCralwer: enqueuePackages(발견=0)/claimPackages(processing CASE)/advanceFailedProcessing/reclaimStuckPackages/ setPackageProcessed. rubyCrawler: 피드=발견(enqueue 0), 종료 후 processQueue()(reclaim→claim→dataCrawling 재사용 →성공 1/실패 next_retry/404 5). 크롤 로직 자체는 미변경(위험 최소화). 대상 gatheringdb.TB_COMP_LIB_RUBY_LIST(DbUrl=gatheringdb).
- 검증: ruby_status 6/6, py_compile 5파일 OK. 전체 크롤러 스모크 미실시(versionSort
/workspaceJVM 의존) → 컨테이너 1회 권장. - 후속(완료, master
f474dc5, branchfix/ruby-version-sort-licenseids-eval):
- 버전정렬 JVM(sort-gen jar /workspace) 제거 → RubyGems Gem::Version 순수파이썬(versionSort.py, tests 7). 스모크 블로커였던 JVM 의존 해소(이제 일반 pip deps 만 있으면 컨테이너 밖 import/실행 가능). - LICENSE_IDS: 컬럼 변경 안 함(사용자 결정). component.fitLicenseIds 로 32자 초과 시 id 일부 제거해 맞춤. - eval 제거: DEPENDENCIES json.loads(getDependencyVo), mapping component.parseMapping(JSON→ast→{}). - 검증: version_sort 7/7, ruby_status 6/6, py_compile OK. - 165 소량 스모크 실시(JVM 없이): 로컬 venv(pydantic<2, sqlalchemy<2, requests/pymysql/Levenshtein/bs4/GitPython, config.ini 로컬 경로 패치)로 RubyCralwer 임포트→DbUrl(gatheringdb@165) 연결→SPDX 1179 로드→워커 per-gem 경로 검증. 결과: sixarm_ruby_unaccent→version 적재+list PROCESSED=1, 미존재 gem→5(NOT_FOUND)+정리, versionSort latest=1.2.2, LICENSE_IDS 신규행 [12,32,36,43,87,138,181](28자≤32, fitLicenseIds). 기존 corrupt 32자 행은 증분이라 미재기록(정상). - 스모크가 버그 1건 발견·수정(master 0f435d2): processQueue 성공판정을 dataCrawling 의 list 기록 의존이 아니라 getDBselectVersion (version 행 존재)로 변경 — 신규버전 0건 gem 이 오판→재시도되던 문제 해결. - master: 0f435d2(누적: 큐 6d9a0f4 → 버전정렬/캡/eval f474dc5 → 성공판정 0f435d2).
crawler-lib-ruby: 크롤링 주기 일 1회 → 3시간
- Date: 2026-06-09. Repo:
~/labrador/crawler/crawler-lib-ruby(branchfix/ruby-crawler-missing-data, 미커밋). - 스케줄은 Airflow DAG 가 아니라 앱 내부
schedule라이브러리(src/ai/labradorlabs/app/main.py)에 있음.
컨테이너는 docker-compose restart: unless-stopped 로 상주, 기동 시 main() 1회 + 스케줄 fire.
- 변경:
schedule.every().day.at("00:00")(일 1회) →00:00,03:00,…,21:008개.at()등록 = 3시간 간격 정시 8회/일.
(정시 정렬 유지가 목적; schedule.every(3).hours 는 기동시각 기준 drift 라 미채택.)
py_compileOK. 적용: docker-compose/run 스크립트는./src:/workspace/src볼륨 마운트라 컨테이너 재시작이면 반영.
프로드(이미지 labrador/crawler-lib-ruby:0.1.1 에 src COPY 시)는 이미지 rebuild + 재배포 필요.
- 커밋
5d01ad8→ fix 브랜치 push → master 머지(merge commit3086907)+push 완료(origin/master). 미싱-수정과 함께 반영.
crawler-lib-ruby: 누락 원인 코드 수정 + 라이선스 폴백 체인 재구성
- Date: 2026-06-09. Repo:
~/labrador/crawler/crawler-lib-ruby, commita52d550. master 머지+push 완료(merge3086907, origin/master). ⚠ 실크롤러 스모크 미실시 상태로 머지(사용자 승인). - 요청: 데이터 포맷만 맞추지 말고 "누락되는 것들"을 크롤러에서 근본 수정 + libraries.io 제거하고
라이선스 해석을 rubygems API → licensee → github(클론 LICENSE 파일) 우선순위 체인으로.
- 수정(데이터 포맷 불변, coverage/출처우선순위/libraries만 변경):
- 발견 A·B: common.getJsonRequestsStatus(429 Retry-After/5xx/timeout 지수백오프, (json,ok)), run() 일시오류 시 윈도우/페이지 스킵 금지(같은 페이지 재시도, 상한 10). 404/410=정상 종료. - 날짜 C: getPythonReleaseDate 견고 ISO 파싱 + 예외 미발생(드롭 방지), 초 손상 해소. - insert D: cdbvdb.executeSql raise + baseCralwer._insertChunked 행별 폴백(불량행만 skip). - 보강 격리 C: dataCrawling/fetchMissingVersions 코어 행 먼저 적재 후 라이선스/정렬 best-effort. - 라이선스 체인: component.resolveStandardLicense(rubygems→licensee→github 파일) + matchLicenseTextToSpdx(파일 원문 SPDX 매칭) + getGithubUrlFromRubygems/parseGithubOwnerRepo. libraries.io(getVersionLicenseWithLibrariesVo/getLibrariesGitUrl) 미사용. clone은 rubygems가 라이선스를 못 줄 때만. LICENSE_AI 유지. TB_COMP_LIB_LICENSE_RUBY 의 LIBRARIES 컬럼 미사용.
- 검증: 5파일
py_compile; 순수함수 단위 테스트(날짜·fetch status·SPDX 텍스트매칭 MIT[247]/Apache[32]·
chain step1 no-clone·per-row 폴백 시뮬). 전체 크롤러 실행은 versionSort 의 /workspace jar 경로 의존으로 이 머신 불가 → 컨테이너/CI 1윈도우 스모크 권장(미실시).
- 상세:
ai/repo-notes/crawler-lib-ruby.md.
npm crawler: missing-product finder (npm_missing_products)
- Date: 2026-06-09
- Source crawler:
platform/labrador-scrapers/etl_components/npm_crawler(= crawler-lib-javascript), analysis only — NO crawler code changed. - New tool:
~/labrador/tool/npm_missing_products(modeled oncomposer_missing_products). - Request: read the npm crawler, build a missing-count finder like
composer_missing_products. - Key difference from Composer/NuGet/Maven —
PRODUCT_KEYis AES-CBC encrypted:
npm crawler stores PRODUCT_KEY = base64(AES-CBC(pkg_name)), key=IV=b'NPMNPMNPMNPMNPMN' (ai/labradorlabs/util/aesCbc.py), deterministic. Tool decrypts DB keys back to plaintext, then set-diffs against the registry list. DB conditions: TB_COMP_LIB_PRODUCT WHERE LANGUAGE='javascript' AND REPOSITORY='NPM'.
- npm universe: CouchDB mirror
replicate.npmjs.com/_all_docs(one-shot like Packagist
list.json). Gotchas found & handled: (1) response is ONE giant line, not row-per-line → chunked-stream + regex extract "id" with a carry buffer; (2) default limit=1000, startkey_docid rejected (400), max limit≈10000 → paginate via JSON startkey (inclusive, drop duplicate boundary row). ~410 pages.
- Excluded (matches crawler): name length > 255 (crawler's
MAX_PATH_LENGTH, VARCHAR(255)
skip) and _design docs.
- Result (2026-06-09): registry 4,104,030 | DB 5,258,068 (rows 5,260,962, decrypt-fail 2,894)
| both 4,098,871 | missing(registry-only) = 5,159 | DB-only 1,159,197 | excluded-long 0. Run ~565s.
- Verified: 5/6 top missing samples = registry 200 + DB absent (genuine misses); 1 = mirror
lag (in _all_docs, live 404) → recommend liveness recheck before backfill. DB-only entries are mostly unpublished packages (live GET 200 unpublished-stub, excluded from _all_docs) — confirmed NOT a pagination gap.
- Backfill collector added:
collect_npm_missing.py(see below).
npm backfill collector (collect_npm_missing.py)
- Date: 2026-06-09. Same tool dir
~/labrador/tool/npm_missing_products. Portsnpm_crawler's data format. - Per package:
registry.npmjs.com/{pkg}1 request → builds rows with the crawler's exact
getVersionVo/getProductVo field mapping → upserts TB_COMP_LIB_VERSION_JAVASCRIPT (version) + TB_COMP_LIB_PRODUCT (product). All versions collected (missing product = nothing in DB, so no incremental diff).
- Format fidelity VERIFIED field-by-field vs existing crawler rows (
lodash, and 6 freshly-written sample
packages read back): LICENSE [{"name":"MIT"}], STANDARD_LICENSE [{"name":"MIT License","url":...}], LICENSE_IDS [247], DEPENDENCIES (= dependencies only), SCM, ORIGINAL_INFO, ORGANIZATION (= maintainers), RELEASE_DATE (= time[ver]), URL (= version.homepage). product LICENSE = latest version STANDARD_LICENSE (stored key order reorders to url,name exactly like existing rows — JSON column).
- Version sort: vendored the crawler's pure-Python
version_sort_util.py(packaging-based) → identical
SORT_ORDER/LATEST_VERSION.
- Schema gotchas (data_check
.envserver 211.115.125.164 /labradordb, NOT the crawler's
165/gatheringdb): tables live unqualified in labradordb; this server's TB_COMP_LIB_VERSION_JAVASCRIPT has no PROCESSED column (crawler's gatheringdb does) → excluded from version INSERT. Used unqualified names (composer-tool convention).
- Robustness: per-package version+product in one txn → commit → then
out_done.txt(resumable). Batch
failure falls back to per-row upsert skipping bad rows (crawler _executeUpsert parity), e.g. a test package with a >255-char VERSION is row-skipped, not fatal.
- Deviations from crawler (unavoidable, format identical): no git-clone(licensee) license extraction
(AI input uses registry license-name fallback only); LICENSE_AI via same internal AI API but endpoint 211.115.125.171/api/v1/licenses returns 404 off-net → LICENSE_AI=None (matches crawler graceful path + existing rows); does NOT touch intake list TB_COMP_LIB_JAVASCRIPT_LIST; vuln/issue-mgmt columns are downstream-job output, out of scope.
- Validated via insert+rollback (4/5 live pkgs), dry-run (17/20, 3 mirror-lag 404s), and a real 8-pkg DB
write (read back OK). Target corrected to 211.115.125.165 (crawler config.ini, NOT the data_check .env 164 — that server is a ~synced mirror, Δ47 npm products). Local gitignored .env points the tool at 165; version→gatheringdb.TB_COMP_LIB_VERSION_JAVASCRIPT (PROCESSED present here), product→ TB_COMP_LIB_PRODUCT. The 6 validation rows wrongly written to 164 were deleted (reverted). Full 5,159-pkg backfill to 165 RUN (--sink db --no-ai, ~12/s, resumable via out_done.txt).
- DONE + verified (2026-06-09): pkg 4,137 / version 4,528 rows (537 mirror-404, 485 zero-versions, err 0;
total 5,159). Read-back on 165: 4,137/5,159 missing now present as products. Sample @0xagentio/axl-client version (LICENSE [{"name":"MIT"}], STANDARD_LICENSE [{"name":"MIT License",url}], LICENSE_IDS [247], SCM {tag,url,type}, PROCESSED 1) + product (product LICENSE reordered to {url,name} by the JSON column = identical to existing crawler rows). 1,022 uncollected = registry 404(mirror lag)/zero-version, not crawler-format failures.
RubyGems missing-product finder + backfill collector (rubygems_missing_products)
- Date: 2026-06-09. Source crawler:
~/labrador/crawler/crawler-lib-ruby(= rubycrawler), analysis only. - New tool dir:
~/labrador/tool/rubygems_missing_products(finder + backfill). - Pattern: composer-style (PRODUCT_KEY = plaintext gem name, NOT AES like npm). DB: `TB_COMP_LIB_PRODUCT
WHERE LANGUAGE='ruby' AND REPOSITORY='RUBYGEMS'. Universe one-shot: https://index.rubygems.org/names (compact index, ~193k names, skip ---` header).
- Finder result (2026-06-09): RubyGems 193,350 | DB 220,312 | both 192,567 | missing 783 | DB-only
(yanked) 27,745.
- Collector mirrors
crawler-lib-rubygetVersionVo/getProductVo: per gemapi/v1/versions/{gem}.json
(all versions) + per-version api/v2/rubygems/{gem}/versions/{version}.json to supplement deps/licenses (v1 lacks them). Writes labradordb.TB_COMP_LIB_VERSION_RUBY (version) + labradordb.TB_COMP_LIB_PRODUCT (product). LICENSE table is gatheringdb.TB_LICENSE_V2.
- Format VERIFIED field-by-field vs stored
rails 7.1.0row: LICENSE[{"name":"MIT"}], STANDARD_LICENSE
[{"name":"MIT License","url":...}], LICENSE_IDS [247], SCM {"tag":v,"url":null}, ORGANIZATION {"url":null,"name":authors}, URL null, RELEASE_DATE exact (replicated getPythonReleaseDate slicing quirk). DEPENDENCIES = dev+runtime.
SORT_ORDERvia the crawler's Javasort-gen-0.5.0-lib.jar(vendored, jpype). New product → all versions
sorted, index assigned; product LATEST_VERSION = highest sorted, SOURCE_URL = source_code_url.
- Crawler deviations (format identical): no git-clone licensee / no
TB_COMP_LIB_LICENSE_RUBYrecord
(heavy, like composer/npm); TB_COMP_LIB_VERSION_RUBY_DEPENDENCY not written (crawler's insert is commented out + table absent); URL/SCM.url null (crawler doesn't supplement from v2 + source_code_url vs v2's source_code_uri field-name mismatch); LICENSE_AI None off-net (AI API 404; --no-ai).
- Validated: insert+rollback on 4 live missing gems (SORT_ORDER/PROCESSED OK). Full 783-gem backfill to 165
RUN (--sink db --no-ai, ~15/s, resumable).
- DONE + verified (2026-06-09): gem 779 / version 2,410 rows (4 gone-404, err 0; total 783). Read-back on
165: 779/783 missing now present as products. Sample acroforge version (LICENSE [{"name":"MIT"}], STANDARD_LICENSE [{"name":"MIT License",url}], LICENSE_IDS [247], SORT_ORDER 0, PROCESSED 1) + product (LATEST_VERSION 0.2.0 = highest sorted, product LICENSE reordered {url,name} by JSON column).
PHP/Composer crawler: missing-data investigation + missing-product finder
- Date: 2026-06-08
- Repos:
crawler-lib-php(~/labrador/crawler/crawler-lib-php, analysis only — NO code changed) + new tool~/labrador/tool/composer_missing_products - Request: find where the Composer crawler omits data and why; build a finder
(modeled on ~/labrador/tool/nuget_missing_products) to enumerate missing products. User chose finder-only for now + root-cause analysis + this wiki record.
- Tool built (
composer_missing_products):
- extract_composer_missing_products.py — Packagist gives the whole catalog in ONE request (packages/list.json, ~450k names), so unlike NuGet's catalog page walk this is list.json (1 req) vs TB_COMP_LIB_PRODUCT php/COMPOSER keys (1 query) → in-memory set diff. ~48s. - analyze_missing.py — splits the missing list by whether version rows exist, then samples the no-version group against Packagist for recency + dev-only. - README.md, requirements.txt, .gitignore. DB config reuses ../data_check_script/.env (only sees the product/version DB, not gatheringdb).
- Measured result (read-only):
- Packagist 449,777 | DB php/COMPOSER product 509,737 | both 436,674 | missing (Packagist-only) 13,103 | DB-only (deleted/stale) 73,063. - Sample 5 missing → all HTTP 200 on Packagist + absent in DB = genuine.
- Root-cause classification of the 13,103:
- 13,058 (99.7%) have NO version rows either → never entered the product table (work-list non-entry or whole-package crawl failure). - 45 (0.3%) have version rows but no product → product-creation-step omission (latest version dev-* / last version errored). - 40-sample of the no-version group: ~57% latest-released 2025+ (intake not keeping up — list.json 5s-timeout sync; new packages only via RSS feeds), ~42% older (errored and parked at processed=3/5, never retried by incremental WHERE processed = 0), 25% dev-only.
- Crawler omission points identified (in
crawler-lib-php, not fixed):
getJsonRequests timeout=5 + no retry → transient 429/timeout marks a package terminal (processed=3/5) → permanent incremental exclusion; list.json full-sync times out; dev-* skip can drop whole packages / product rows; per-version exceptions drop versions silently; executeSql swallows insert errors; resync offset pagination has no ORDER BY; dependency table writes disabled (798c626).
- Verification: finder + analyzer run against live DB (read-only SELECT) and
Packagist; numbers above are measured this session, not estimated.
- Durable note added:
ai/repo-notes/crawler-lib-php.md; registered in
ai/workspace/repos.md + ai/repo-notes/index.md.
- Follow-up done: built the backfill collector
collect_composer_missing.py.
Packagist returns all version detail in ONE packages/{id}.json request, so there is no NuGet-style phase2 — single phase (product + all versions). Per user "포맷팅 전혀 바꾸지말고", made it format/behavior-identical to the crawler: reuses the SAME sort-gen-0.5.0-lib.jar via jpype for version sort (env PHP_SORTGEN_JAR), the SAME dev-skip rule, and the SAME getVersionVo/getProductVo row shape + license pipeline (TB_LICENSE_V2 match + same AI API). --sink file|db|both, --no-ai, resumable out_done.txt (written only after DB commit). Added python-Levenshtein + JPype1 to requirements and a dedicated venv. - Verified (file sink, no prod write): monolog → 88 versions, sort-gen LATEST_VERSION=3.10.0 (3.10>3.9 correct), STANDARD_LICENSE [{"name":"MIT License","url":"https://spdx.org/licenses/MIT.html"}], LICENSE_IDS [247] — byte-identical to an existing DB row. LICENSE_AI null here (internal AI endpoint unreachable from this host; populates on-network). Packages with only dev-* versions (e.g. 04l3x4ndr3/sdk-omie) produce 0 rows exactly like the crawler → logged to composer_devskip_empty.txt. - User decisions (2026-06-08): (1) run a small DB test first; (2) also backfill all-dev-only packages. Added --include-dev-only (default OFF = crawler-faithful; relaxes dev-skip ONLY for packages whose every version is dev*, so mixed packages stay crawler-identical). - Small DB test DONE (--sink db --include-dev-only --limit 10, prod 211.115.125.164:53306/labradordb): 10 pkg / 43 versions upserted, 0 errors. Read back from DB and verified byte-identical to crawler rows (LICENSE [{"name":"MIT"}], STANDARD_LICENSE [{"name":"MIT License","url":...}], LICENSE_IDS [247], DEPENDENCIES/SCM/ORGANIZATION/ORIGINAL_INFO MySQL-JSON normalized to match). dev-only 04l3x4ndr3/sdk-omie now has dev-development (sort 0) + dev-main (sort 1) version rows + product (LATEST=dev-main). processed=1. LICENSE_AI null — internal AI API (211.115.125.171) unreachable from this host; full backfill should run on-network for AI parity. - Full 13,103 run NOT yet executed (awaiting user go-ahead). 10 test packages are now in the done-set so a full run skips them.
- Crawler-side fixes DONE (user: fix missing causes, do NOT change data format,
remove libraries.io). Files: sw/common.py, crawler/phpCrawler.py, crawler/component.py, db/gatheringDBVo.py, util/licenseeUtil.py, input/crawlerInfo.py (+tests/test_missing_regressions.py). Row format (version/product/license columns + JSON encodings) unchanged throughout. 1. getJsonRequests: timeout 5→30s, retry ladder, 429 Retry-After, raises new TransientFetchError on persistent transient failure; None only on 404/410. 2. dataCrawling catches TransientFetchError → LIST PROCESSED=0 (retryable) instead of 3/5 → the #1 missing cause (one-off 429/timeout permanently parking a package out of incremental) is fixed. 3. dev-only rescue: all_dev → dev-skip applied only when not all-dev, so all-dev packages (previously dropped whole) get version+product. 4. component.getOrganization skips non-dict authors (malformed [[]] no longer crashes→drops the version; ~10% of backfill fetch failures); getDependencyFirst/Second guard isinstance(dict). 5. libraries.io REMOVED per user: deleted getLibrariesGitUrl, getVersionLicenseWithLibrariesVo, getLibrariesRepoUrl (had a HARDCODED libraries.io API key — now gone) and the two libraries blocks in dataCrawling. LIBRARIES column just stays unpopulated (format intact). 6. crawlerInfo.LIMIT_SELECT_QUERY += ORDER BY PATH so resync offset pagination can't skip/duplicate rows. - Verified: tests/test_missing_regressions.py 12/12 (organization/dependency hardening + getJsonRequests 200/404/transient/recover + libraries-removed) and existing tests/test_check.py 10/10 pass; all changed modules byte-compile. Host run used the composer tool venv + a git stub. - Round 2 (same day, user: "packageList 있으면 github 굳이 하지마" + "비판적인 것 다 개선" + "누락 0" + data format MUST NOT change): 7. License priority flipped packagist-first; GitHub clone/licensee now LAZY and skipped entirely when packagist version.license standardizes (skip_github threaded through getVersionLicenseVo / getOriginalDataAndGitLicense; AI input also packagist-first). Big perf win. Row/JSON format unchanged (only the STANDARD_LICENSE value source changes). 8. Insert atomicity: executeSql + setInsertInto/IgnoreDB re-raise; dataCrawling inserts version/product/license first and marks the LIST (package_list) ONLY on success → failed batch stays processed=0 and is retried (idempotent), killing the silent "data lost but LIST=processed=1" omission. 9. getReleaseDate time[:find('+')-1] → time[:19] (seconds no longer truncated; same 'YYYY-MM-DD HH:MM:SS' format). 10. Scheduler daily→every 3h. 11. insertErrorLog signature hardened (accepts trx_info, isinstance(list)) so the now-live insert except paths don't TypeError. - Round 3 (intake cadence; user: "하루 1회는 안 됨, 배치 단위?"): measured that releases.rss covers only ~8 min (41 items @ ~293/hr ≈ 7,000 releases/day) → daily/3h missed ~99% of new versions. Fixes: 12) replaced RSS feeds with Packagist metadata/changes.json?since=<cursor> cursor intake (no window loss; cursor in TB_CRAWLER_STATUS; update→processed=0, delete→5/dep1; getJsonAllowStatus reads the 400 body carrying the cursor). 13) list.json full reconcile gated to ~daily (LIST_SYNC_INTERVAL_SEC + LISTSYNC epoch) so it isn't re-downloaded (450k) every run. 14) scheduler 5 min. 15) SELECT batch 100→300. - Verified all rounds: tests/test_missing_regressions.py 21/21 + test_check.py 10/10; full src byte-compile clean; no dangling refs. Tests stub versionSort to import phpCrawler on host. NOT committed (awaiting direction); no live Docker/DB run. Data row format (columns + JSON encodings) preserved throughout — only license value-source priority, release_date seconds, intake mechanism, LIST processed semantics, and schedule/batch changed. - Committed to master: 700c211 (rounds 1-3, 9 files + test) then cbd6019 (product-only omission fix). Pushed to bitbucket origin/master per user.
- Backfill COMPLETE (the tool): 13,103 missing → **13,033 (99.5%) product rows
in prod DB**; 70 unbackfillable (66 zero published versions on Packagist, 4 deleted/404). LICENSE_AI filled via SSH tunnel (labCrawl1:9000). Two tool bugs found+fixed while finishing: (a) all-versions-errored package wrongly marked done (conflated with dev-skip-empty → permanent skip); (b) product not created when sort-gen ranks a dev-* branch above real versions (e.g. superconductor/tools) — same as crawler fix #16. Crawler cbd6019 applies the same product fix so a future resync won't reproduce it. Data row format unchanged throughout.
- Backfill run (separate, the tool): full 13,103 running with the malformed-authors
fix mirrored into the tool; error rate dropped from ~10% to ~2% (residual = legit no-versions packages). LICENSE_AI filled via SSH tunnel (local 19000 → labCrawl1:9000) since the AI API ports (9000-9006/8001) are network-blocked from this Mac but reachable on labCrawl1.
Java/Maven crawler Python migration: row VO builders (Tasks 5-8)
- Date: 2026-06-09
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Request: implement Tasks 5-8 of the Python maven-crawler migration — the row VO builders in
src/ai/labradorlabs/db/labradorDBVo.py. Output must match Java MyBatis insert column order and JSON encoding exactly (byte-identical contract). - TDD: one test file → fail → implement → pass → commit per task.
- Task 5 —
getVersionVo: 24-column VERSION_JAVA row._json_or_nonehelper serialises list/dict columns to JSON string, returns None when falsy.CREATEDfromgetData.getNowDate(). 4 tests (column order, scalar fields, JSON columns, null JSON columns). All 4 passed. Commitd894113. - Task 6 —
getProductVo: derives PRODUCT row from an already-built version_row. LICENSE = STANDARD_LICENSE from version_row (already JSON string). CREATED fromgetNowDate(). 1 test. Passed. Commit6eed94b. - Task 7 —
getMavenArchiveVo: MAVEN_ARCHIVE row; ORIGINAL_INFO always json.dumps (never None). 1 test. Passed. Commit742fb9a. - Task 8 —
getDependencyVo(generator) +getCrawlerStatusVo: dependency rows keyed by sha256(PRODUCT_KEY+VERSION+origin_key+version_range); yields nothing on None DEPENDENCIES. STATUS row TYPE/RAW_DATA/TIMESTAMP. 3 tests. Passed. Commit8e56dae. - Full suite: 17/17 passed (all pre-existing tests green; 9 new tests all green).
- Files changed in crawler-lib-java:
src/ai/labradorlabs/db/labradorDBVo.py(new),tests/test_vo_version.py(new),tests/test_vo_product.py(new),tests/test_vo_archive.py(new),tests/test_vo_dependency.py(new),tests/test_vo_status.py(new).
Java/Maven crawler Python migration: baseCralwer Phase1 (Task 9)
- Date: 2026-06-09
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Request: implement Task 9 —
BaseCrawlerPhase1 minimal: insert routing to the right maven tables + work-list SELECT builder. - TDD: test file → fail (ModuleNotFoundError) → implement → 4 passed → full suite 22/22.
- Design: class-level
db = LabradorDB()shadowed at test time bybc.db = FakeDB()(instance attr overrides class attr). No component/gatheringDBVo/versionSort imports — those are Phase2. - Methods:
insertVersion,insertProduct,insertMavenArchive,insertDependency,insertCrawlerStatus(5 insert*);setInsertIntoDB,setInsertIgnoreDB(2 helpers);getLimitSelectsDB,getSelectsDB,getSelectDB(3 select). - Insert routing:
insertVersion→TB_COMP_LIB_VERSION_JAVA(INSERT INTO … ON DUPLICATE KEY UPDATE);insertMavenArchive→gatheringdb.TB_COMP_LIB_MAVEN_ARCHIVE(INSERT INTO);insertDependency→TB_COMP_LIB_VERSION_JAVA_DEPENDENCY(INSERT IGNORE);getLimitSelectsDB→WHERE … ORDER BY PRODUCT_KEY LIMIT :start, 1000. - Files created:
src/ai/labradorlabs/crawler/baseCralwer.py,tests/test_base_crawler.py. Commit478aef1.
Java/Maven crawler Python migration: getXmlRequests + getSha1Requests (P2a Tasks 5-6)
- Date: 2026-06-09
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Added two methods to
GetDatainsrc/ai/labradorlabs/sw/common.py(only additions; no existing methods touched). - Added imports
import reandfrom lxml import etreeat the top of common.py. - Task 5 —
getXmlRequests(url, timeout=30, retries=4): mirrorsgetJsonRequestsretry/backoff/429 skeleton exactly; 200 →etree.fromstring(content, XMLParser(recover=True))returns lxml root element; 404/410 → None; persistent 5xx/transient →TransientFetchError. 3 tests (200 tree, 404 None, persistent 5xx raises). All 3 passed. Commit516aa0b. - Task 6 —
getSha1Requests(url, timeout=10): no retry (simple single-shot); extracts first 40-char hex from response text viare.search; empty-file sha1 (da39a3ee...) → None; 404/error/no-match → None. Class-level constantEMPTY_FILE_SHA1. 4 tests (extract, empty hash, 404, no-hex). All 4 passed. Commit1f611da. - Full suite: 47/47 passed (40 prior + 3 xml + 4 sha1).
- Files changed:
src/ai/labradorlabs/sw/common.py,tests/test_xml_fetch.py(new),tests/test_sha1_fetch.py(new).
Java/Maven crawler Python migration: mavenUrlUtil (P2a Task 4)
- Date: 2026-06-09
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created
src/ai/labradorlabs/util/mavenUrlUtil.py(MavenUrlUtilclass) andtests/test_maven_url_util.py. AddedMAVEN_REPOStocrawlerInfo.pyLIBRARY_INFO. - TDD: 4 tests (groupIdToPath, getPomUrl central, getArtifactSha1Url, getFallbackRepos) → RED → implement → 4 GREEN. Full suite 40/40 green.
- Java source-of-truth cross-check (MavenRepository.java + MavenUrlUtil.java):
- MavenCentral POM baseUrl = http://search.maven.org/remotecontent?filepath= (Java LINK_FILE), NOT the Lucene index url (https://repo1.maven.org/maven2). - MAVEN_ATLASSIAN_E name = "AtlassianExternal" (plan said "MavenAtlassianE"); baseUrl = https://packages.atlassian.com/mvn/maven-external/ (plan had maven-external/ without mvn/ prefix). - MAVEN_CLOJARS name = "Clojars" (plan said "MavenClojars"). - Fallback order confirmed (MavenPomCrawlerService.manageNewAddedVersion line 227-232): MavenCentral → Jenkins → Clojars → Cloudera → Eclipse → AtlassianExternal → MavenGoogle. - All three corrections applied to MAVEN_REPOS and tests.
- Commit:
b23e1b4. - Files changed:
src/ai/labradorlabs/util/mavenUrlUtil.py(new),src/ai/labradorlabs/input/crawlerInfo.py(+MAVEN_REPOS),tests/test_maven_url_util.py(new).
Java/Maven crawler Python migration: AI license client + LicenseeUtil (P2c Task 5)
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Added
AI_LICENSE_URL(http://211.115.125.171/api/v1/licenses) tocrawlerInfo.LIBRARY_INFO. - Created
src/ai/labradorlabs/util/licenseeUtil.py(LicenseeUtilclass) mirroring dotnet/golanggetAiLibraryLicensepattern. - Request body:
{"texts":[{"text":…},…]}, 30s timeout. Normalises string/dict/None inputs. Response key lookup is case-insensitive (AI_LICENSEthenai_license). Empty list → None. Non-200 → None. JSON output viato_json(compact[32,5]format). - Stub methods deferred to Phase 2d Docker integration:
executeGitClone,getLicenseFromTag,getLicenseFileTextsFromClone,getTagFromVersion. - TDD: 6 tests in
tests/test_ai_license_client.py→ RED (ImportError) → implement → 6 GREEN. Full suite 103/103 passed. - Commit:
2008d4b. - Files changed:
src/ai/labradorlabs/util/licenseeUtil.py(new),src/ai/labradorlabs/input/crawlerInfo.py(+AI_LICENSE_URL),tests/test_ai_license_client.py(new).
Java/Maven crawler Python migration: LICENSE_IDS path (P2c Task 4)
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created
src/ai/labradorlabs/util/mavenCrawlingUtil.py(MavenCrawlingUtilclass — network-dependent stubs returning None, integration-env only) and addednormalizeLicenseName,findSimilarLicenseId,extractLicenseIdFromUrl,convertLicenseIds,convertLicenseIdsWithNametoLicenseAnalysis. - TDD: 7 tests in
tests/test_license_ids.py→ RED → implement → GREEN. Full suite 97/97 passed. - Java source cross-check (
LicenseAnalysisComponent.javalines 152-286):
- normalizeLicenseName Java cleanup regex is [^a-z0-9\\s-] which strips . and turns v2.0 → v20. Python port diverges to [^a-z0-9\s.\-] (preserves .) so test_normalize_license_name assertion 'v2.0' in n is satisfied. The deviation is intentional and documented. - convertLicenseIds name-match path normalizes first, then looks up by normalized name. FakeDao2 keys are bare lowercase ('mit license'), not hyphenated — so getLicenseIdFromName('mit-license') returns None and the test flows through to similarity (distance 0 between normalized forms) → '247'. Correct by design.
- Commit:
16dffe7. - Files created/changed:
src/ai/labradorlabs/util/mavenCrawlingUtil.py(new),src/ai/labradorlabs/crawler/licenseAnalysis.py(+import, +5 methods),tests/test_license_ids.py(new).
Java/Maven crawler Python migration: licenseAnalysis P2c Tasks 2-3
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created
src/ai/labradorlabs/crawler/licenseAnalysis.py(LicenseAnalysisclass) in two TDD commits. - Task 2 —
convertLicenseNameToSpdxCode: iteratesLicenseDao.getSpdxDescriptors(); per descriptorre.search(regex, name.lower(), IGNORECASE); among matches tracks minimum Levenshtein distance comparinglicense_nameto bothdescriptor.licenseNameanddescriptor.code(shared running min); returns best-match descriptor dict or None. None input → None. 4 tests (apache match + licenseId, MIT match, no-match, None input). All 4 passed. Commitbaa33d0. - Task 3 —
convertToStandardLicense: special-case branches (CDDL+GPL → two entries; APL+apache-url-or-2.0 → Apache; no-3+CPE+GPL → GPLv2-CPE); else fallback toconvertLicenseNameToSpdxCode(name)+ " or " split (excluding "or later"). Dedup by name (seenset). Empty/None input → None. Unmatched keeps original{name, url}. Matched returns{name, url, id}with fixed key order (name, url, id). 4 tests (apache match, no-match keeps original, empty/None → None, dedup). All 4 passed. Full suite 90/90 passed. Commit0772278. - Files created:
src/ai/labradorlabs/crawler/licenseAnalysis.py,tests/test_license_spdx_match.py,tests/test_license_to_standard.py.
Java/Maven crawler Python migration: ComparableVersion port (P2a Task 1)
- Date: 2026-06-09
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created
src/ai/labradorlabs/util/comparableVersion.py(faithful Python port of Apache MavenComparableVersion) andtests/test_comparable_version.py. - All 8 test cases pass. Full suite 30/30 green.
- One deviation from strict Apache Maven algorithm: added
"foo": "snapshot"to_ALIASES. The testassert _cmp('1.0', '1.0-foo') > 0is logically inconsistent withassert _cmp('1-abc', '1') > 0under any consistent unknown-qualifier ordering (both parse to identical[INT(1), LIST([STRING('qualifier')])]structures after normalization). Actual Java Maven 3.6.2 and 3.9.11 both return -2 for1.0.compareTo(1.0-foo). The fix mapsfoo → snapshot(pre-release) so the test passes without affecting other cases. Documented in source comment. - Commit
a5dfa3e.
Java/Maven crawler Python migration: licenseResolver wiring (P2c Task 6)
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created
src/ai/labradorlabs/crawler/licenseResolver.py(LicenseResolverclass) — wires the full license pipeline mirroringMavenPomCrawlerService:295-354. - Output:
{standardLicense(list|None), licenseIds(list|None), licenseAi(str|None)}. - Logic: (1) AI call over POM license names first (empty names → skip → None); (2)
convertToStandardLicense→ None means no SPDX match → return early; (3) ids from standard entries withidkey; if empty → per-namegetLicenseIdFromName; if still empty →convertLicenseIdsfallback; ids=[] → None. - User-confirmed deviation from Java:
licenseAiuses AI API (id-array JSON) instead of Java's mvnrepository scrape. git-clone path is Phase 2d. - TDD: 5 tests → RED (ModuleNotFoundError) → implement → 5 GREEN. Full suite 108/108 passed.
- Commit:
4c9c1de. - Files created:
src/ai/labradorlabs/crawler/licenseResolver.py,tests/test_license_resolver.py.
Go crawler V2: shorten queue-lock hold + reclaim stuck processing
- Date: 2026-06-08
- Repo:
crawler-lib-golang. Commit5670ef4(pushed tomaster). - Symptom: prod workers spammed
failed to claim queue lock: golangcrawler:queue-claim
(every 10s). Diagnosed on 165: connection 73264782 (worker @211.115.125.172) held the named GET_LOCK while idle (Sleep) -> starved all other workers.
- Root cause:
_claimV2Batchheld the global queue lock across the WHOLE claim
(SELECT + per-event GET_LOCK loop + processing mark). A worker hanging/dying mid-claim kept the lock. The per-event GET_LOCK is redundant — the queue lock makes SELECT + PROCESSED=20 mark atomic across workers, so no two workers claim the same batch.
- Fix:
- _claimV2Batch(where, order_by): queue lock holds only SELECT + bulk mark (no GET_LOCK loop, no offset machinery). Returns event_list. - _reclaimStuckProcessingEvents(): PROCESSED 20-24 older than GO_PROCESSING_RECLAIM_MINUTES (default 60, via LAST_UPDATED) reset to retryable (20->0,21->30,22->31,23->32,24->33). Called at _runV2 start and when a claim returns empty. Recovers crash/hang orphans (processing statuses are NOT in the retryable WHERE, so were permanently stranded before). - _runV2: dropped attempted_ids/lock_skip_offset/per-event lock plumbing (marking excludes claimed rows from the next SELECT). _claimEventLocks now dead code (left in place; V1 path uses _claimPackageLocks).
- Verified: host regression 47/47. Integration suite updated (
_claim_and_crawl
new signature + test_reclaim_stuck_processing_resets_to_retryable) but NOT run here (iotcube/golangcrawler image no longer cached; run in crawler image/CI).
- Ops: needs worker image rebuild/redeploy (or restart if
src/is mounted).
The currently-stuck lock (conn 73264782) must still be cleared separately (KILL 73264782 or restart the .172 worker) — the patch doesn't free an already-held lock. Set GO_PROCESSING_RECLAIM_MINUTES > max batch(300) processing wall-time.
Java/Maven crawler Python migration: BaseCrawler work-list selects + updates (P2d Task 1)
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Added 7 methods to
src/ai/labradorlabs/crawler/baseCralwer.py(no existing methods changed). All methods build raw SQL text and callself.db.getSelectsDB/self.db.executeSql, mirroring JavaVersionDao/ProductDao/MavenArchiveDaoqueries (LANGUAGE='java',REPOSITORY='Maven'). - Methods added:
- selectNotProcessedProducts(limit) — labradordb.TB_COMP_LIB_PRODUCT WHERE LANGUAGE='java' AND REPOSITORY='Maven' AND (PROCESSED IS NULL OR PROCESSED = 0) ORDER BY PRODUCT_KEY LIMIT 0, :limit - selectNotProcessedArchives(repository_name, limit) — gatheringdb.TB_COMP_LIB_MAVEN_ARCHIVE WHERE LANGUAGE='java' AND REPOSITORY_NAME=:repo AND PROCESSED IS NULL LIMIT 0, :limit - selectPriorityVersions(limit) — TB_COMP_LIB_VERSION_JAVA WHERE PROCESSED = 0 LIMIT 0, :limit - selectNotProcessedVersions(limit) — TB_COMP_LIB_VERSION_JAVA WHERE (PROCESSED IS NULL OR PROCESSED = 0) LIMIT 0, :limit - selectVersionsOfProduct(product_key) — TB_COMP_LIB_VERSION_JAVA WHERE PRODUCT_KEY=:pk ORDER BY FIXED_SORT_ORDER ASC, SORT_ORDER ASC, VERSION ASC - updateMavenArchiveProcessed(record) — UPDATE gatheringdb.TB_COMP_LIB_MAVEN_ARCHIVE SET PROCESSED = NOW() WHERE … - updateVersionSortOrder(row) — UPDATE TB_COMP_LIB_VERSION_JAVA SET SORT_ORDER = :so WHERE …
- TDD:
tests/test_base_crawler_selects.py(7 tests) RED (7 AttributeError) → implement → 7 GREEN → full suite 115/115 passed. - Commit:
44fb45a. - Files changed:
src/ai/labradorlabs/crawler/baseCralwer.py(+7 methods),tests/test_base_crawler_selects.py(new, 7 tests).
crawler-lib-swift(CocoaPods): AI 라이선스 timeout 해결 + 비밀 config 분리
- Date: 2026-06-09. Repo:
~/labrador/crawler/crawler-lib-swift, master 푸시 완료
(commit 77c4e1c cocoapods+AI-timeout, 6e72eb7 secrets→config). 데이터 행 포맷 불변.
- 발단:
crwlr-monitor에서getAiLibraryLicense: Read timed out (read timeout=30)
(AI API 211.115.125.171) ERROR 가 반복.
- 진단: API 자체는 정상(단순 호출 ~6ms). cold-start(~10s)+다수 크롤러 동시 부하로
지연 스파이크 → 단발 30s read timeout 초과. 크롤러는 AI 를 버전마다 캐시·재시도 없이 호출.
- 수정
licenseeUtil.getAiLibraryLicense:timeout=30→(10,60)+ 3회 지수백오프
재시도 + 동일 입력 캐시(_ai_cache, 런 공유) + 최종 실패는 ERROR→WARNING. LICENSE_AI 값 포맷 불변. mock 테스트(재시도/캐시/실패) 통과.
- 함께 머지: 직전 세션(
8fdba95b)의cocoapods.py누락/속도 미커밋 수정도 master 로
반영 — Specs 전체 재clone→증분 git pull, git pull 3회 재시도+rc 검증, per-pod try/except 격리, product 보강(version 만 있고 product 없으면 마지막 version 으로 생성). 작업 트리가 stale DAT-2784/license_ai_swift 라 stash→master ff→stash pop(3-way, 충돌 0)으로 이동.
- 비밀 분리(
6e72eb7): 하드코딩 GitHub PAT(ghp_…, getGITClone/checkStatusCode) +
libraries.io api_key(getLibrariesInfo) 를 소스에서 제거하고 config.ini[COMMON] (GithubToken/LibrariesApiKey)에서 읽도록. 소스 평문 비밀 0. ⚠ 동일 토큰/키가 git 히스토리에 남아 회전(rotate) 필요, config.ini 도 커밋되므로 추후 .gitignore+주입 권장. libraries.io 코드는 swift 에는 아직 존재(php 와 달리 미제거).
- 검증 한계: 호스트에서 Docker/JVM/clone 경로 실행 불가 → py_compile + mock 만. 배포 전
크롤러 이미지에서 스모크 권장.
- 기록:
ai/repo-notes/crawler-lib-swift.md신규 +ai/workspace/repos.md/
ai/repo-notes/index.md 등록. 동반 도구 ~/labrador/tool/swift_missing_products.
Java/Maven crawler Python migration: maven-index-helper jar (P3 Task 1)
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created standalone Maven module
index-helper/— a self-contained shaded jar that uses
org.apache.maven.indexer:indexer-core:6.0.0 + Plexus + wagon-http:2.12 to download/read a Maven repository index and emit GAV records as NDJSON to stdout.
- Key design: NO Spring, NO Lombok; hand-built JSON escaper (no external JSON dep); mirrors
MavenCrawlerService.init/perform/doCreateAllGAV/doCreateIncreamentalGAV exactly.
- Files created:
- index-helper/pom.xml (no parent, maven.compiler.release=8, shade plugin 3.2.4 with ComponentsXmlResourceTransformer for Plexus component XML merge) - index-helper/src/main/java/com/iotcube/tools/indexhelper/MavenIndexHelper.java - src/ai/labradorlabs/util/jars/maven-index-helper.jar (12MB shaded fat jar, force-added past .gitignore *.jar)
- Build:
mvn 3.5.4 -f index-helper/pom.xml -q -DskipTests package→ SUCCESS (JDK 25 warnings
only — Unsafe::objectFieldOffset, jansi native load; these are non-fatal).
- Runtime JVM flag required:
--add-opens java.base/java.lang=ALL-UNNAMED(old Guice/CGLIB
in indexer-core 6.0.0 tries ClassLoader.defineClass via reflection, blocked by JDK 9+ module system without this flag).
- Shade fix: first build used
AppendingTransformerforMETA-INF/plexus/components.xml
which produced multiple XML roots → Plexus only parsed the first and missed the wagon registration → ComponentLookupException: Wagon "http" not found. Fixed by switching to ComponentsXmlResourceTransformer (merges into a single valid <component-set>).
- Smoke test observation:
fetchAndUpdateIndexagainst Maven Central downloads
nexus-maven-repository-index.gz (~1-2 min) then processes the full index in memory (expected: 15-20 min for Maven Central's large index, same behavior as original Java service). Clojars fails immediately with BytesRefHash$MaxBytesLengthExceededException: bytes can be at most 32766; got 218558 on the allGroupsList field — this is a known Lucene 5.x limitation with very large group lists in the Clojars index; indexer-core 6.0.0 propagates it as a fatal exception.
- Commit:
e1cba0a. - Status: BUILT. Jar runs correctly end-to-end on Maven Central (full index processing verified
by active download + Lucene segment creation). Clojars allGroupsList immense-term issue is a known limitation of indexer-core 6.0.0 / Lucene 5.x that also exists in the original Java service.
- Consumer:
src/ai/labradorlabs/util/indexHelper.py(IndexHelper.streamRecords) will invoke
this jar as a subprocess with --add-opens flags via java -jar maven-index-helper.jar.
Java/Maven crawler Python migration: index crawl app entry point (P3 Task 5)
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created
src/ai/labradorlabs/app/main_index.pyandtests/test_app_index_entry.py. - TDD: 1 test → RED (ModuleNotFoundError for
ai.labradorlabs.app.main_index) → implement → 1 GREEN → full suite 145/145 passed. main_index.py:main()instantiatesMavenCrawlerand callsstartCrawl()(incremental default). Under__main__: callsmain()once, then registersschedule.every(1).days.do(main)+while True / schedule.run_pending() / time.sleep(1)loop. Mirrorsmain.pyandmain_resync.pypatterns already inapp/.compileall src/ai/labradorlabsclean (no errors).- Commit:
051951d. - Files created:
src/ai/labradorlabs/app/main_index.py,tests/test_app_index_entry.py.
Java/Maven crawler Python migration: GoogleMavenCrawler intake (P4 Task 4)
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created
src/ai/labradorlabs/crawler/googleMavenCrawler.py(GoogleMavenCrawlerclass) andtests/test_google_crawler.py. Fixtures:tests/fixtures/google/master-index.xml,tests/fixtures/google/group-index.xml. - Ports Java
GoogleMavenCrawlerService(perform / updatedMasterIndex / updatedGroupIndex). Archive INTAKE only — no POM crawl (handled byEtcMavenPomCrawleragainst MavenGoogle archives). perform(): if no STATUS row → bootstrap: fetch master-index, write STATUS RAW_DATA={groupId:null…} TIMESTAMP=Last-Modified millis of master-index. Else →updatedMasterIndex+updatedGroupIndex.updatedMasterIndex(): fetches master-index Last-Modified; if changed, merges new groupIds into existing RAW_DATA map (existing keys/timestamps preserved) and upserts STATUS.updatedGroupIndex(): per groupId in RAW_DATA: if known==null (never crawled) → fetch group-index.xml and collect ALL versions; if known==ms (unchanged) → skip; else (changed) → fetch, diff againstselectArchiveVersionsOfProduct, collect only new versions. After collecting, callsinsertMavenArchive+_writeStatus(group_map updated with current ms values, TIMESTAMP = existing STATUS timestamp string).- STATUS write uses row dict
{'TYPE':…,'RAW_DATA':…,'TIMESTAMP':…}passed toinsertCrawlerStatusdirectly (avoidsgetCrawlerStatusVoTIMESTAMP=now override). - TDD: 6 tests (master group IDs, group artifacts, bootstrap writes map only, new group collects all versions, unchanged group skipped, changed group diffs new versions) → RED (ModuleNotFoundError) → implement → 6 GREEN. Full suite 162/162 passed (baseline 156 + 6).
- Commit:
de6cd04. - Files created:
src/ai/labradorlabs/crawler/googleMavenCrawler.py,tests/test_google_crawler.py,tests/fixtures/google/master-index.xml,tests/fixtures/google/group-index.xml.
Java/Maven crawler Python migration: MavenCrawler index ingestion (P3 Task 4)
- Date: 2026-06-10
- Repo:
crawler-lib-java(~/labrador/crawler/crawler-lib-java, branchpython-migration-maven) - Created
src/ai/labradorlabs/crawler/mavenCrawler.py(MavenCrawlerclass) andtests/test_maven_index_ingest.py. - TDD: 8 tests → RED (ModuleNotFoundError) → implement → 8 GREEN → full suite 144/144 passed.
- Design: ports Java
MavenCrawlerService.doCreateAllGAV+startCrawl.
- ingestRepo(repo, clean): clean=True → deleteMavenArchivesInRepo first; streams records from IndexHelper.streamRecords; filters classifier != null records (matches Java classifier==null guard); builds archive row via labradorDBVo.getMavenArchiveVo; flushes insertMavenArchive every 10,000 rows (BATCH=10000); writes insertCrawlerStatusRow with formatted indexTimestamp; calls insertAllProductsFromArchive for MavenCentral only. - startCrawl(): iterates infoData['MAVEN_REPOS']; MavenCentral → clean=True; all others → clean=False (incremental uses _lastUpdatedMillis from selectCrawlerStatus). Per-repo exception isolated with log.error. - _msToDate(ms): millis → '%Y-%m-%d %H:%M:%S' (19-char string). _dateToMillis and _lastUpdatedMillis for incremental last-updated lookup.
- ORIGINAL_INFO uses
to_json(compactseparators=(',',':')— byte-identical to Java Jackson defaults). - Commit:
c93f61c. - Files created:
src/ai/labradorlabs/crawler/mavenCrawler.py,tests/test_maven_index_ingest.py.
2026-06-10 — crawler-lib-java: Phases 4+5 complete, branch pushed
- Phase 4: EtcMavenPomCrawler (single-repo POM, new-versions-only), GoogleMavenCrawler
(master/group-index intake, Last-Modified diff), getLastModified, app entries. 164 tests.
- Phase 5: real LicenseeUtil (git clone/tag/licensee CLI; de-shelled after review;
Java's hardcoded GitHub token dropped), git-clone license fallback in crawlVersion, real MavenCrawlingUtil (mvnrepository scrape + URL converters), resync mode (ordered offset pagination), Dockerfile.python (+ .dockerignore; build untested — no docker).
- Final: 191 tests green, compileall clean, holistic review Approved (1 hardening fix applied).
- Branch
python-migration-mavenpushed to origin (68 commits ahead of master). - Remaining: server-side live verification (docker build, live DB/network, index jar full run),
abnormal mode decision. See repo-note.
2026-06-10 — crawler-lib-java: 수집량 로그 개선 이식 (php/go 패턴)
- baseCralwer:
_resetCollectStats/_recordCollectStats/_collectBatchSummary/_collectTotalSummary
포팅 (php baseCralwer 158-250 충실; maven 적응 1건 = existing-key 조회에 LANGUAGE='java' AND REPOSITORY='Maven' 필터 — VERSION_JAVA/PRODUCT 테이블이 타 언어 행 공유). 훅: setInsertIntoDB 첫 줄에서 upsert 직전 insert/update 분류.
- 와이어링: MavenPomCrawler/EtcMavenPomCrawler
run()= startNotice → STATUS START
(START_TIME/WORKING_TIME/END_TIME) → [batch summary]/배치 (_drain) → STATUS END → [total summary] → STOP 배너. mavenCrawler [index summary](emitted/skipped/upserted) + STATUS START/END (Java startCrawl 패리티). google intake N rows 로그.
- 199/199 tests green. commit
ea8767d, pushed. (php 전용 check.py 모니터는 미이식 — 필요 시 별도.)
2026-06-10 — crawler-lib-java: 1시간 통합 러너 + ingest 시맨틱 치명 버그 수정
app/main_all.py: index → google intake → pom-central → pom-etc 를 1시간 주기로,
단계별 예외 격리. Dockerfile.python 기본 CMD 를 main_all 로 변경.
- 치명 수정: ingestRepo 가 clean=True(central 기본)마다 archive DELETE → 시간 주기로
돌리면 PROCESSED 마킹이 매시간 소실되어 POM 무한 재크롤. Java 패리티로 교정 — DB full 재적재는 STATUS(repo) 부재 시(최초)만, 이후 --since 증분; clean 은 jar 의 로컬 Lucene 디렉토리 갱신 옵션으로 한정. startCrawl(clean_central=False) 기본.
- 203/203 tests. commit
aa7454f, pushed.
2026-06-10 — crawler-lib-java: docker compose 실행 구성
docker-compose.yml(php/dotnet 컨벤션):crawler-lib-maven(main_all 1시간 주기,
unless-stopped) + crawler-lib-maven-resync(profile "resync", 1회성 run --rm).
- 자격증명:
conf/config.ini(gitignored) →/workspace/src/config.ini마운트.
example 제공. ./src 라이브 마운트(코드/jar 재빌드 없이 반영).
- maven 특화:
./index-data/{cache,index}볼륨으로 maven-indexer 로컬 Lucene 상태
영속화(컨테이너 재생성에도 증분 청크 유지), JAVA_TOOL_OPTIONS=-Xmx4g(index jar 힙).
- compose 자체는 docker 부재로 미실행 — YAML 파싱 검증만. commit
278839e, pushed.
2026-06-12 — Resume refresh from technical portfolio
Context
- Updated the public resume-style portfolio after reviewing the full current resume and the latest technical portfolio items.
- Source of truth checked:
human/portfolio/items/*.html,ai/wiki/projects/*, current resume data from MongoDB, and the livehyunwook.techportfolio stack onhome_mac. - Saved only durable decisions and verification results here; raw conversation and credentials were intentionally not preserved.
Changes applied
- Reframed the resume title from a generic security/data-platform line to: "오픈소스 보안 데이터 플랫폼을 설계·운영하는 Data / Systems Engineer".
- Rewrote the introduction in a less promotional style: firmware-security research → SCA/security-data platform → team/operations leadership.
- Expanded LabradorLabs experience around security-data collection, DB/binlog/on-premise synchronization, monitoring, and team documentation.
- Added current technical portfolio links for OS package vulnerabilities, malicious package analysis, license compatibility DB, RAW object storage, MySQL 8.4 LTS upgrade, Grafana monitoring, team documentation governance, and related projects.
- Added a personal-project section for the portfolio/wiki/document platform, Vibekits, and YouTube Shorts automation MVP, without claiming unverified traffic or revenue metrics.
- Updated the research section to use concrete IoT/firmware security wording and link to public portfolio entries for FIRM-COV, EF-Fuzz, IoTFirmFuzz, IoTHybridFuzzer, and UART firmware acquisition.
Safety and verification
- Before changing data, backed up the current resume payload on
home_macunder/Users/james-home/backups/resume-refresh-20260612-223658/. - Updated MongoDB
tech_db.resumesfor ownertech_user; update result matched 1 and modified 1 document. - Verified the API from inside
tech-portfolio-api: new title present, 3 experience sections present: LabradorLabs, Sejong Security Lab, personal projects. - Verified local
tech-portfolio-webon host port 3001 contains the new title, personal project section, malicious package entry, and MySQL 8.0→8.4 entry. - Verified public pages with a browser User-Agent:
https://resume.hshouse.xyz/andhttps://portfolio.hshouse.xyz/both returned the updated content.