公开记录
软件健康报告模式 0.27.0 · 指标 1.13.0 · 2026-07-29 06:56 UTC

gejyn14 / kiwoom-cli

키움증권 REST API CLI — 주식 시세·계좌·주문·자동매매·미국주식을 터미널에서 (Kiwoom Securities CLI for Korean & US stocks)

Python自定义许可证★ 9 星标⑂ 1 复刻始于 2026年3月在 GitHub 上查看 ↗

gejyn14/kiwoom-cli 的健康指数为 100 分中的 64 分,处于「中等」区间。 其得分最高的类别是Engineering Quality(81/100),最低的是Community & Adoption(50/100)。 最近一次更新在 8 天前。 近期的大部分工作由 1 位贡献者完成。

64
总分 / 100
中等

软件健康指数

指标归入加权类别,统一采用 1–100 量表。总体分先取类别加权平均;当公开证据触发高风险司法辖区政策时,评级会按政策调整,并设置 49(有风险)的上限。AI 就绪度不计入总体分。

64
优秀85-100堪称典范;基本满足所有检验标准
良好70-84健康;仅有轻微不足
中等50-69可接受,但存在明显不足;建议进行审查
存在风险30-49存在重大薄弱环节;采用时应保持审慎
危急1-29问题严重(项目被弃置、仅有单一维护者、缺乏基本工程规范)
活力社区与采用可持续性与治理工程质量安全AI 就绪度

评分画像

每条轴代表一个类别。形状比平均值更重要——健康的对象会填满整个图形,而“一峰一谷”式画像意味着某一维度的优势正掩盖另一维度的风险。

所有权

Yu Jin An个人账户
4 关注者6 个公开仓库始于 2020年10月

该仓库由个人账户拥有。相较于组织支持的项目,单一所有者项目的延续性风险更高。

软件包生态系统

注册表软件包版本月下载量版本数最近发布标签
PyPIkiwoom-cli2.14.02,558498 天前kiwoomstocktradingclikoreafinance

按类别列示的指标

活力

项目是否仍有生命——是否仍在编写代码,是否仍在发布版本?

70良好 · 占总体的 22%
评分方式
28.8/36推送新近度 — 最近一次推送于 8 天前
3.5/36提交节奏 — 52 周中有 5 周有提交
18/18提交量 — 最近一年 417 次提交
10/10OpenSSF Scorecard:Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
所用输入
commits_last_year417
human_commit_share1
days_since_last_push8
active_weeks_last_year5

发布纪律

84良好
评分方式
27/27有发布版本 — 已发布 1 个发布版本
36/36发布时效 — 最近一次发布版本于 12 天前
12.6/27发布节奏 — 节奏未知(仅一次发布)
0/10OpenSSF Scorecard:Signed-Releases — 无数据
所用输入
releases_count1
latest_release_tagv2.5.0
releases_from_tags
days_since_latest_release12
mean_days_between_releases
已排除计分(无数据或不适用):OpenSSF Scorecard:Signed-Releases。 其余权重已重新归一化。

社区与采用

项目是否拥有用户、下载量与关注度,并具备欢迎贡献者参与的配置?

50中等 · 占总体的 18%
评分方式
14.6/60星标 — 9 个星标
0/25复刻 — 1 个复刻
0/15关注者 — 0 位关注者
所用输入
forks1
stars9
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

社区健康

86优秀
评分方式
22.5/22.5README
16.9/22.5许可证 — 存在许可证文件,但不是可识别的许可证
18/18CONTRIBUTING 指南
13.5/13.5行为准则
0/7.2议题模板
6.3/6.3PR 模板
所用输入
has_readme
has_license
has_contributing
has_issue_template
has_code_of_conduct
has_pull_request_template
评分方式
45.4/80月度下载量 — pypi 合计每月 2,558 次下载
0/20注册表被依赖数 — 该生态系统不报告此项
所用输入
packageskiwoom-cli
dependents
ecosystemspypi
total_downloads
monthly_downloads2,558
已排除计分(无数据或不适用):注册表被依赖数。 其余权重已重新归一化。

可持续性与治理

项目能否在其成员之外延续——巴士系数、响应能力、由谁支持,以及软件包的维护状况?

51中等 · 占总体的 24%
评分方式
9/54巴士系数 — 1 位贡献者贡献了半数提交
0.1/22.5提交分布 — 头号贡献者编写了 100% 的提交
2.7/13.5贡献者广度 — 2 位贡献者
0/10OpenSSF Scorecard:Contributors — project has 0 contributing companies or organizations -- score normalized to 0
所用输入
bus_factor1
contributors_sampled2
top_contributor_share0.995
评分方式
0/46.8议题解决 — 没有议题或无数据
36/38.3PR 接受 — 已裁定的 PR 中 32/34 已合并
0/15OpenSSF Scorecard:Code-Review — Found 0/4 approved changesets -- score normalized to 0
所用输入
merged_prs32
open_issues0
closed_issues0
issue_closed_ratio
closed_unmerged_prs2
已排除计分(无数据或不适用):议题解决。 其余权重已重新归一化。
评分方式
10/30所有权背书 — 个人(用户)账户
0/20已验证域名 — 不适用于个人账户
5/25所有者影响力 — gejyn14 有 4 位关注者
17.7/25既往记录 — 6 个公开仓库,账户约 5 年
所用输入
followers4
owner_typeUser
is_verified
owner_logingejyn14
public_repos6
account_age_days2,113
已排除计分(无数据或不适用):已验证域名。 其余权重已重新归一化。
评分方式
25/25已发布且可解析 — pypi 上有 1 个软件包
35/35发布时效 — 最近一次发布于 8 天前
20/20版本历史 — 49 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packageskiwoom-cli
ecosystemspypi
any_deprecated
min_days_since_publish8

工程质量

基础的工程与文档实践是否到位?

81良好 · 占总体的 20%

工程实践

68中等
评分方式
24/24CI 工作流 — 2 个工作流
24/24存在测试
0/16Linter 配置
0/9.6Pre-commit 钩子
0/6.4.editorconfig
20/20OpenSSF Scorecard:CI-Tests — 3 out of 3 merged PRs checked by a CI test -- score normalized to 10
所用输入
has_ci
has_tests
has_editorconfig
has_linter_config
has_precommit_config

文档

100优秀
评分方式
30/30README
25/25文档目录
15/15文档 / 主页站点 — https://github.com/gejyn14/kiwoom-cli/wiki
10/10仓库描述
10/10主题标签 — 14 个主题标签
10/10Wiki
所用输入
topicsai-agents, cli, kiwoom, korea, korean-stocks, python, quant, rest-api, samsung, skhynix, stock-trading, trading, algorithmic-trading, auto-trading
has_wiki
homepagehttps://github.com/gejyn14/kiwoom-cli/wiki
has_readme
has_docs_dir
has_description

安全

可见的安全与供应链实践是否稳固,且不存在未解决的高风险司法辖区暴露?

70良好 · 占总体的 16%

安全态势

63中等
评分方式
7.5/7.5Binary-Artifacts — no binaries found in the repo
2.2/7.5Branch-Protection — branch protection is not maximal on development and all release branches
2.5/2.5CI-Tests — 3 out of 3 merged PRs checked by a CI test -- score normalized to 10
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/4 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.2/2.5许可证 — license file detected
7.5/7.5Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
5/5Packaging — packaging workflow detected
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
5/5SAST — SAST tool is run on all commits
5/5Security-Policy — security policy file detected
0/7.5Signed-Releases — 无数据
7.5/7.5Token-Permissions — GitHub workflow tokens follow principle of least privilege
7.5/7.5Vulnerabilities — 0 existing vulnerabilities detected
所用输入
sourceopenssf_scorecard
checks_evaluated17
scorecard_versionv5.5.0
checks_inconclusive1
scorecard_aggregate6.4
已排除计分(无数据或不适用):signed_releases。 其余权重已重新归一化。
评分方式
35/35直接依赖不含已知公告 — 没有直接依赖携带已知公告
25/25间接依赖不含已知公告 — 没有间接依赖携带已知公告
0/40没有长期未处理的公告 — 没有公告带有发布日期
所用输入
sourceosv
advisories0
affected_packages0
assessed_packages23
unassessed_packages0
affected_by_severitynone
direct_affected_packages0
已排除计分(无数据或不适用):没有长期未处理的公告。 其余权重已重新归一化。 比对的是 pypi:kiwoom-cli@2.14.0 的运行时依赖闭包——安装已发布的软件包时真正被拉取进来的内容——共 23 个软件包。 未对可达性进行分析。

AI 就绪度

该仓库在多大程度上具备与 AI 编码代理协同开发与维护的条件?这是一枚独立的实验性徽章——权重为 0.0,因此单独呈现,不影响总体健康评分。

54中等 · 占总体的 0%
评分方式
45/45代理指令 — AGENTS.md
0/15机器可读文档(llms.txt)
40/40可读的提交历史 — 100 次人类提交中有 100 次说明了意图(结构化标题或解释性正文)
所用输入
has_llms_txt
legible_history_share1
agent_instruction_filesAGENTS.md
agent_instruction_max_bytes30,023
评分方式
0/18一条命令的引导启动
22/22自动化测试
0/11Lint / 格式化配置
0/11静态类型检查
0/10可复现环境
10/10已体现的代理实践 — 最近 100 次提交中有 64 次由代理编写或署名代理
0/8自动化维护 — 未观察到自动依赖更新
0/10OpenSSF Scorecard:Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
所用输入
has_nix
has_tests
lockfiles
has_dockerfile
typed_language
bootstrap_files
has_devcontainer
has_linter_config
typecheck_configs
agent_commit_share0.64
toolchain_manifests
dependency_bot_commit_share0
评分方式
0/45可类型检查的代码 — Python,未配置类型检查
50.8/55可控的文件大小 — 采样的 66 个源文件中有 5 个超过 60KB
所用输入
primary_languagePython
largest_source_bytes117,207
source_files_sampled66
oversized_source_files5

关键数据

9GitHub 星标
2贡献者
417最近 12 个月提交数
8距最近推送天数
1发布版本数
1巴士系数(bus factor)
0开放议题
PyPI软件包生态系统数

数据采集警告

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token

更多细节

OpenSSF Scorecard 6.4 / 10
6.4综合

来自开源项目 OpenSSF Scorecard 的独立、工具无关的安全评估。每项检查奖励的是安全实践本身,而非特定供应商的工具。Scorecard 无法判定的检查项标记为 不适用,并从安全评分中剔除(绝不按零分计)。Scorecard v5.5.0 · 2026-07-29 06:56 UTC

10Binary-Artifactsno binaries found in the repo
3Branch-Protectionbranch protection is not maximal on development and all release branches
10CI-Tests3 out of 3 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/4 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
9Licenselicense file detected
10Maintained30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
10Packagingpackaging workflow detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
10SASTSAST tool is run on all commits
10Security-Policysecurity policy file detected
不适用Signed-Releasesno releases found
10Token-PermissionsGitHub workflow tokens follow principle of least privilege
10Vulnerabilities0 existing vulnerabilities detected
直接依赖 7
注册表软件包版本约束清单文件
PyPIclick>=8.1pyproject.toml
PyPIhttpx>=0.27pyproject.toml
PyPIrich>=13.0pyproject.toml
PyPItomli>=2.0pyproject.toml
PyPItomli-w>=1.0pyproject.toml
PyPIkeyring>=25.0pyproject.toml
PyPIwebsockets>=12.0pyproject.toml
全部依赖 13

来自 GitHub 依赖图的完整解析依赖集合:6 个直接依赖与 7 个间接(传递)软件包。仓库提交锁文件时,传递闭包才是完整的。

注册表软件包版本关系
PyPIclick直接
PyPIhttpx直接
PyPIkeyring直接
PyPIrich直接
PyPItomli-w直接
PyPIwebsockets直接
PyPIhypothesis间接
PyPIopenpyxl间接
PyPIpytest间接
PyPIpytest-cov间接
PyPIpytest-httpx间接
PyPIruff间接
PyPIsetuptools间接
依赖安全公告 0

安装 pypi:kiwoom-cli@2.14.0 会引入 23 个包(直接与传递):其中 0 个存在已知公告,0 个为直接依赖。

没有已知公告影响已评估的依赖。

公告表示依赖图中记录的版本落入某条公告的受影响范围。可达性未经分析,且依赖图包含开发与测试的版本固定——某项发现可能只涉及工具链而非交付的软件。

原始 JSON 报告 机器可读
{
  "data": {
    "repo": {
      "topics": [
        "ai-agents",
        "cli",
        "kiwoom",
        "korea",
        "korean-stocks",
        "python",
        "quant",
        "rest-api",
        "samsung",
        "skhynix",
        "stock-trading",
        "trading",
        "algorithmic-trading",
        "auto-trading"
      ],
      "is_fork": false,
      "size_kb": 2074,
      "has_wiki": true,
      "homepage": "https://github.com/gejyn14/kiwoom-cli/wiki",
      "languages": {
        "Shell": 4613,
        "Python": 1399322
      },
      "pushed_at": "2026-07-20T22:17:59Z",
      "created_at": "2026-03-30T15:23:22Z",
      "owner_type": "User",
      "updated_at": "2026-07-21T03:59:15Z",
      "description": "키움증권 REST API CLI — 주식 시세·계좌·주문·자동매매·미국주식을 터미널에서 (Kiwoom Securities CLI for Korean & US stocks)",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": null,
      "default_branch": "main",
      "license_spdx_raw": "NOASSERTION",
      "primary_language": "Python",
      "significant_languages": [
        "Python"
      ]
    },
    "owner": {
      "blog": null,
      "name": "Yu Jin An",
      "type": "User",
      "login": "gejyn14",
      "company": null,
      "location": "Seoul",
      "followers": 4,
      "avatar_url": "https://avatars.githubusercontent.com/u/72906303?v=4",
      "created_at": "2020-10-15T05:30:52Z",
      "is_verified": null,
      "public_repos": 6,
      "account_age_days": 2113
    },
    "license": {
      "state": "custom",
      "spdx_id": null,
      "raw_spdx": "NOASSERTION",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v2.5.0",
          "kind": "minor",
          "published_at": "2026-07-16T17:59:53Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "4effecade46d3bded40a418aae3316c5b3c82fe1",
          "body": "SECURITY.md가 v2.1에서 제거된 SecureStore(Fernet AES-128-CBC + HMAC +\nPBKDF2)와 비밀번호 계층을 아직 광고하고 있었고, 지원 버전 표에는 2.14.0을\n배포하면서 1.x만 적혀 있었다. 공개 도구에서 이것은 인증정보를 맡길지\n판단하는 바로 그 독자에게 보안 태세를 과장하는 셈이다.\n\n- SECURITY.md: 실제 아키텍처로 재작성(키체인이 암호화 경계, 앱 계층 없음),\n  지원 버전 2.x로 정정, 파일 권한·프로필 allowlist·raw API 게이트·Trusted\n  Pub\n[…]\n v2.14.0 (kwcli는 오늘 재확인 결과\n  v0.1.1 그대로라 비교 내용 유효)\n\nREADME 148개 예제를 Click 파서로 전수 검증(0 실패). 하네스가 심어둔 오류\n3종을 모두 잡는 것을 먼저 확인한 뒤 신뢰했다.\n\nCo-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: SECURITY.md의 죽은 보안 주장 제거 + v2.14.0 문서 정합 (#33)",
          "author_name": "Yu Jin An",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T15:03:14Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "69d5eb44b666d55c0cbe2b987dc624f48f5e325f",
          "body": "릴리스 체커가 잡은 유일한 실제 드리프트다. 워크북은 sdist/wheel에 포함되지\n않는 저장소 전용 산출물이라(pyproject의 include는 kiwoom_cli*만) PyPI의\nv2.14.0은 영향받지 않는다. 재릴리스 불필요.\n\n체커 자체의 구멍도 함께 드러났다: Excel 신선도 규칙은 commands/ 만 보는데\n이번 드리프트는 main.py의 --force에서 왔다. main.py가 config/auth/profile\n서브커맨드와 전역 옵션을 정의하므로 감시 대상에 들어가야 한다 — 지금은\nmain.py의 표면 변경이 초록 불과 함께 낡은 워크북을 내보낼 수 있다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: 명령 워크북 재생성 — auth logout --force 반영",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T14:53:47Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "19cd69e633f5ec6ea1238299a1c6f0c16d5ff6ad",
          "body": "* fix(config): resolve_profile이 문서화된 대로 CLI --profile을 1순위로 읽는다\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\n\n* fix(config): 프로필·도메인을 루트에서 한 번 해석해 공유한다\n\nTask 1이 이미 값을 맞춰놨으므로 새 테스트는 추가 시점에 통과한다.\n그래도 남기는 이유는 \"우연히 같음\"을 \"구조적으로 같음\"으로 바꾸는 것이\n이 커밋의 목적이기 때문이다. test_client_domain_and\n[…]\nthropic.com>\n\n* chore: opencode.json을 gitignore한다\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\n\n---------\n\nCo-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "v2.14.0: 확인하지 않은 것을 보고하던 세 곳을 고친다 (#32)",
          "author_name": "Yu Jin An",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T14:46:27Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "bd81e4beeb7b38e03d464f165e8add9ed12b5940",
          "body": "v2.13.0 — 스트리밍·견고성·정리 (Tranche D, 감사 최종)",
          "is_bot": false,
          "headline": "Merge pull request #31 from gejyn14/feature/v2.13.0-streaming",
          "author_name": "Yu Jin An",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T12:11:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ed98ab6abf34d3b0ab81f0e49a456b9b8598eac9",
          "body": "v2.13.0은 아직 태그도 PyPI 배포도 되지 않았고 PR #31이 열려 있으므로,\n별도 패치 릴리스를 내지 않고 같은 2.13.0 항목에 접어 넣는다. 버전 번호는\n그대로다.\n\n추가한 것:\n- Breaking 2건 (stream 등록 ack 종료코드, dashboard 관리종목 포함)\n- Fixed 3건 (등록 ack 미수신, WS 9001 심볼 훼손, dashboard/rank 목록 불일치)\n- Internal 2건 (자기참조 parametrize 8건 고정, ka10030 body 전 필드 단언)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): 후속 리뷰에서 나온 수정 5건을 2.13.0에 반영",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T12:09:42Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "89308064ae7f48ad6cb931651ae61d75b7eadda3",
          "body": "`parametrize(list(MAP.items()))`는 기대값을 검증 대상 상수에서 가져온다.\n상수를 어떻게 바꿔도 자기모순이 없어 항상 통과하므로 아무것도 막지 못한다\n(CLAUDE.md 경고 패턴). 23개 상수 29곳을 전수 조사했다.\n\n*** 스윕 방법론이 결론을 뒤집었다. 재현 시 주의할 것. ***\n각 상수의 **앞 두 항목**을 맞바꾸는 변이로 돌리면 24/24 CAUGHT, 구멍 0건이\n나온다. 이 리포의 코드북은 첫 키가 대개 CLI 기본값(all/kospi/week/limit)이고\n기본값은 \"default b\n[…]\nt(X.items())` 형태가 아니라 스윕의 탐색\n정규식에 걸리지 않는다. 수정 후 스윕을 다시 돌리면 그 상수들은 목록에서\n조용히 빠진 채 \"구멍 0건\"이 나온다 — 검증은 반드시 문제의 맞바꿈을 직접\n재적용하는 방식이어야 한다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: 자기참조 parametrize 8건을 리터럴로 고정한다",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T11:44:08Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "8bb4134efec83d0dc753ffb9bfb36dcd13cf9e73",
          "body": "`parametrize(list(ETF_RETURNS_PERIOD.items()))`는 기대값을 검증 대상\n상수에서 가져와 자기모순이 없다 — 상수를 어떻게 바꿔도 통과한다\n(CLAUDE.md가 경고하는 패턴).\n\n구멍이 실재했음을 실측으로 확인했다. 기본값(week)은 body 테스트가 우연히\n지키고 있었지만 나머지 세 값은 무방비였다. 전체 1988개 스위트 기준:\n  - year \"3\" -> \"9\"            : 1988 passed (미적발)\n  - month <-> six-months 맞바꿈 : 1988 passe\n[…]\n을 준다 — `--period month`가 6개월 수익률을 돌려줘도 어디에도 오류가\n남지 않는다.\n\n리터럴 고정 후 같은 변이 두 개가 각각 1건·2건으로 적발된다.\nimport가 불필요해진 것 자체가 상수 의존이 끊겼다는 증거다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(market): ETF 수익률 기간 코드를 리터럴로 고정한다",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T10:48:02Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "81b84f99724382a1e0536b9c3caceb0ecd0b4052",
          "body": "dashboard가 ka10030에 mang_stk_incls=\"1\"(exclude-managed)을 하드코딩해\n`market rank volume` 기본값 \"0\"(include-managed)과 갈려 있었다. 두 명령의\nbody는 이 필드 하나를 빼면 완전히 동일해서, 같은 \"당일 거래량 상위\"가\n서로 다른 목록으로 나왔다.\n\n관리종목은 상장폐지·자본잠식·감사의견 이슈로 거래량이 튀는 날 상위권에\n들어오는 종목이라, dashboard 쪽에서 조용히 빠지는 영향이 작지 않다.\n\nb4025f4(stex_tp)와 같은 처리: 리터럴 대\n[…]\nE)과 혼동하지 말라고 이미 경고하고 있다.\n\n테스트는 예외 키를 두지 않고 dash_body == rank_body 전체 동일로 강화했다.\n\nBREAKING CHANGE: dashboard의 거래량 상위 목록에 관리종목이 포함된다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(dashboard)!: 거래량 상위의 관리종목 포함 여부를 market rank volume과 일치시킨다",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T10:48:02Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "aa6c0995273369e059149cf41647164d9d338aa0",
          "body": "D1이 REG 실패 응답(return_code != 0)은 exit 2로 잡게 만들었지만, ack가\n아예 오지 않는 경우는 이월돼 있었다. LOGIN 성공 -> REG 전송 -> 서버가\n답 없이 종료하면 수신 루프가 그냥 break해 exit 0이 나갔다. json 모드는\nenvelope조차 없이 빈 stdout으로 끝나 에이전트에게는 \"성공, 이벤트 0건\"과\n구별되지 않았다 — 구독은 성사된 적이 없는데도.\n\n등록 ack 수신 여부를 추적해, 루프가 어떤 이유로 끝나든(소켓 정상 종료,\n--duration/--until 만료, P\n[…]\n마감에 도달했다면\n스트림은 시작조차 하지 않은 것이다.\n\nBREAKING CHANGE: 등록 ack 없이 끝나던 실행이 exit 0 -> 2. $?로 분기하는\n스크립트는 값이 달라진다. ack를 받은 뒤의 정상 종료는 그대로 0이다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(stream)!: 등록 ack 없이 끝난 스트림을 성공으로 보고하던 문제",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T05:38:06Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "2400ee023bcf59b94766107d708c9946f1d68508",
          "body": "normalize_ws_values의 \"9001\"(종목코드)이 선행 영문자를 무조건 벗겼다.\n국내 6자리 코드에서는 우연히 맞지만 그 외에는 값이 깨진다:\nNVDA -> VDA, TSLA -> SLA, F -> \"\", M04020000 -> 04020000.\n\nREST 경로(normalize_record의 stk_cd)는 이미 strip_kr_market_prefix로\n고쳐져 있었고(\"영문 1자 + 숫자 6자리\" 모양에만 적용) WS 경로만 옛 패턴이\n남아 있었다. 같은 헬퍼를 쓰게 해 접두사 규칙을 단일 소스로 만든다.\n\n두 경로가 같은 입력에 같은 symbol을 내는지 단언하는 테스트를 함께 넣어\n다시 갈라지지 않게 한다. 헬퍼의 \"무조건 스트립 금지\" 주석이 9001을 미해결\n버그로 가리키고 있던 것도 정정.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(normalize): WS 9001이 미국 티커·금현물 코드를 훼손하던 문제",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T05:37:54Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "4aed2f7d8d7309a934648c8ea11ee8370e50048a",
          "body": "`-f json`은 루트 옵션인데 describe 3건과 find 1건의 예제가 서브커맨드\n뒤에 두고 있었다. 그대로 실행하면 \"No such option: -f\"로 exit 1이다.\n이 문자열은 --help와 `kiwoom describe`가 에이전트에게 그대로 넘기는\n값이라 틀린 채로 두면 에이전트가 따라 쓴다. 같은 부류를 order.py에서\n30d6c36·da7a751로 이미 고쳤고 남은 3건이다.\n\n검증: 교정형 5건 전부 exit 0, 종전형(`describe --paths -f json`)은\nexit 1임을 네거티브 컨\n[…]\n sector 위치인자 9곳이\nSECTOR_CODE로, config prune-ledger 행 추가(232→233).\n워크북은 옵션 선언만 기록하므로 본문 룩업에는 구조적으로 눈이 멀다.\n실제로 `stock search --market`의 choices는 etn 동작 수정 전후가 같고\n`qty_ok`는 워크북에 0회 등장한다. 커버리지 근거로 쓰지 말 것.",
          "is_bot": false,
          "headline": "docs(main): describe·find 예제의 -f json 위치 교정 + 명령 워크북 재생성",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T05:18:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7cceea0c076baddbc3a598bc31cfaa6f3b4a0fcc",
          "body": "트랜치 D(스트리밍·견고성·정리) 마지막 태스크 — 2026-07-18 감사의 마지막\n트랜치다. 버전을 2.13.0으로 올리고 CHANGELOG [2.13.0] - 2026-07-20을\n확정했다.\n\nCHANGELOG는 breaking을 두 절로 나눴다. 커밋 제목에 `!`가 붙은 5건과,\n`fix(...)`로만 돼 있지만 종전에 동작하던 호출의 결과가 바뀌는 7건이다.\n후자를 묻어두지 않으려고 절 제목에 표시가 없다는 사실 자체를 적었다.\n\n--exchange는 \"받는 값 집합이 넓어지기만 했다\"와 \"기본값이 바뀌었다\"를\n따로 적었\n[…]\n\n검증: 1970 passed, ruff clean, `kiwoom --version`이 2.13.0(샌드박스 HOME +\nkeyring 스텁으로 실행). 실행 전후로 ~/.kiwoom의 파일 목록과 전체 md5가\n동일함을 확인했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "release: v2.13.0",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T05:09:37Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "1cd77412039068d61ea15af58b8d68636590e082",
          "body": "normalize._iso_datetime은 WS 시각 필드(20/21/908)에 무조건 +09:00을 붙인다.\n이 ID들은 미국 타입과도 공유되는데(FT의 21, FE의 20) 그 값이 KST인지\n현지(ET)인지 스펙으로 확정되지 않는다. 워크북 Response Example이 합성\n데이터라(FE의 20 == 51020, FT의 41과 FE의 27이 바이트 동일) 확증도 반증도\n불가능하다. 틀렸다면 13~14시간 어긋난다.\n\nnormalize.py의 코드 주석에는 이미 이 경고가 있지만, 녹화된 FT/FE 프레임을\nhistory로\n[…]\n자는 그 주석을 볼 일이 없다. AGENTS.md에도 남긴다.\n\n타임존은 추측하지 않는다 — 실제 US 프레임으로 확인되기 전까지 코드는 그대로\n둔다. 현재 stream 명령은 국내 소켓만 dial하므로 이 경로로는 도달하지 않는다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(agents): 미국 실시간 타입의 ts 타임존이 미검증임을 명시",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:57:18Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "a1fc9a789cab1beea80f9f14a55d88f784e900ea",
          "body": "_codebook_mappings()가 str->str만 모으므로 PROGRAM_MARKET_BY_EXCHANGE\n(str->dict, ka90005/ka90010의 mrkt_tp에 실배선)는 이 파일의 세 술어와 동치\n불변식 어디에도 잡히지 않는다. 지금은 test_market.py의 전송 body 고정이\n대신 막고 있지만, **새 중첩 코드북이 생기면 자동 커버리지가 0인 채로 조용히\n늘어난다**. 침묵이 위험이다.\n\n수집기를 재귀시키지 않고 인벤토리를 따로 고정한다. 재귀시키면\n`len(maps) == 172`부터 술어 카운트\n[…]\n집합 관계는 바깥 상수의 병합 위험과 의미가 달라 같은 축에서 셀 수 없다.\n\n두 방향으로 판별되는지 확인했다: 새 중첩 상수를 추가해도, 기존 상수의 바깥\n키를 바꿔도 실패한다. 중첩이 평평한 수집에 섞이지 않는지도 함께 고정한다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(constants): 중첩 코드북 인벤토리를 고정한다",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:56:52Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "84e34d83b5fc73dae66fb897f43aa24c9c700903",
          "body": "main.py의 prune-ledger는 LedgerLockBusy를 잡아 \"잠시 후 다시 시도하세요\"를\n안내한다. 그런데 POSIX의 _acquire는 LOCK_NB 없이 flock(LOCK_EX)이라 무한\n대기한다 — 동시에 주문이 도는 동안 이 명령은 그 핸들러에 도달하지 못하고\n사용자 프롬프트 앞에서 조용히 멈춘다. 재현: 잠금을 쥔 채 prune을 부르면\n20초가 지나도 끝나지 않는다(테스트가 그대로 매달렸다).\n\nlocked()에 blocking 인자를 추가하고 prune만 blocking=False로 잡는다.\nPOSIX\n[…]\n\n동시 주문이 정확성을 위해 기다리는 대신 LEDGER_BUSY로 흔들린다. 그 구분이\n실제로 유지되는지 별도 테스트로 고정했다(주문 경로는 잠금 해제까지 대기).\n\nAGENTS.md의 잠금 대기 설명도 두 경로를 구분하도록 고쳤다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(idempotency): prune-ledger의 잠금 대기를 논블로킹으로",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:55:37Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "5eb379a857a814e5d2df33cdaa5b57b925c089d4",
          "body": "ka40001의 etfobjt_idex_cd(Required=Y, Length 3)와 ka90008의 date\n(Required=Y, Length 8)가 default=\"\" 그대로 body에 실려 빈 문자열을 전송하고\n있었다. 두 워크북(docs/미국 REST API 문서.xlsx, docs/키움 REST API 문서.xlsx)\n의 Request 표가 일치한다.\n\n빈 문자열도 \"보내지 않음\"도 스펙 위반이지만, 빈 값을 명시적으로 주장하는\n대신 키를 빼면 서버가 자기 필수필드 오류로 답한다. 같은 파일의\nka10038/ka50080\n[…]\n이라\n무해하다. 나머지 8개 형제도 Required=N이라 그대로 둔다.\n\n옛 동작을 고정하던 테스트 2건은 값을 지정한 호출로 옮기고, 키 생략은\n옵션 선언이 아니라 전송 body에서 `\"key\" not in body`로 검증한다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(market)!: Required=Y 필드에 빈 문자열 대신 키를 생략",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:52:47Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "3d29fba253db66cdac9e1fc419a6c7526607e474",
          "body": "두 가지가 사실이 아니었다.\n\n1. \"어떤 병합이든 두 상수를 동치로 만든다\"는 참이 아니다. 한쪽만 넓히는\n   확장(A = {**A, **B} — \"빠진 키만 채우자\"는 자연스러운 수정)은 A != B로\n   남아 동치 불변식에 걸리지 않는다. 그런 확장 12쌍이 test_constant_drift.py\n   세 테스트를 전부 통과한다 (12개를 하나씩 심어 확인). 그중\n   ACCOUNT_EXCHANGE_WITH_SOR |= EXCHANGE_ALL은 계좌 거래소 값을 KRX/NXT/%\n   에서 1/2/3 체계로 갈아치우는 \n[…]\n 78, 세 집합이\n   서로소라 합집합 = 단순 합). 356은 어느 계산으로도 나오지 않는 숫자였다.\n   D7 리포트와 progress.md(둘 다 gitignore)에서도 함께 고쳤다.\n\n동작 변경 없음 — 독스트링만 고친다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(tests): 드리프트 테스트의 중심 주장과 해저드 쌍 수를 정정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:50:25Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "ced2c9df5f636a9ad23191baf6042003002dff38",
          "body": "FakeWebSocket.sent_trnms()가 trnm 외의 모든 키를 버려서, 핸드셰이크에 대해\n스위트가 사실상 아무것도 고정하지 않고 있었다. 실제로 확인한 것: LOGIN에서\ntoken을 streaming.py·watch.py 양쪽에서 통째로 빼고, watch가 종목 BOGUS의\n타입 0X를 grp_no=9/refresh=0으로 등록하게 바꾼 상태에서 기존 스위트\n1951건이 **전부 통과**한다.\n\nsent_frames()(파싱된 dict 그대로)를 추가하고 모듈별로 고정한다:\n- LOGIN 프레임 = {\"trnm\",\"t\n[…]\nClosed\n  쪽으로 새므로 SYSTEM 10건으로 건너뛰기 예산을 소진시켜야 도달한다\n- watch 수신 루프의 PING 에코 (핸드셰이크 안의 에코와 다른 자리다)\n- 양쪽 모듈의 KeyboardInterrupt → exit 0\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(stream,watch): WS 핸드셰이크 페이로드와 미검증 경로 고정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:45:57Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b4025f4f385e2e27ce9474255617fc1446e68b16",
          "body": "D7이 ka10030의 --exchange 기본값을 KRX(1)에서 통합(3)으로 옮겼을 때\ndashboard.py의 하드코딩 \"1\"만 남았다. 같은 \"당일 거래량 상위\"가 두 명령에서\n서로 다른 목록을 냈다. D7의 스윕이 market.py 밖의 유일한 호출자를 놓쳤다.\n\n전송 body 변화: dashboard의 ka10030 stex_tp \"1\" → \"3\" (통합).\n리터럴 대신 EXCHANGE_ALL[\"all\"]을 참조해 다음 이동 때 또 갈리지 않게 한다.\n\n두 명령의 ka10030 body를 전부 대조해 남은 차이도 확인했\n[…]\n 바꾸지\n않고, 대신 테스트에 명시적으로 고정했다 — 조용히 두면 다음 드리프트가\n\"원래 다르던 것\"에 섞여 안 보인다.\n\n테스트는 옵션 선언이 아니라 전송 body를 비교한다 (선언만 보면 커맨드 본문의\n하드코딩이 보이지 않는다).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(dashboard)!: 거래량 상위 조회 거래소를 market rank volume과 일치시킨다",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:41:57Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "da7a75113479386e68aec7ab59f25973350cbf67",
          "body": "`order validate`의 예제가 `-f json`을 서브커맨드 뒤에 붙여 두었다. -f는 루트\n옵션이라 그대로 치면 \"No such option: -f\"로 exit 1이다. 이 독스트링은\n--help와 `kiwoom describe`가 에이전트에게 그대로 내보내는 문자열이다.\n\n정정 예제에는 QTY 0의 의미도 함께 적었다 (국내=잔량 전부, 미국=항상 0).\n\nREADME/AGENTS/vs-official/main.py/order.py의 `kiwoom order ...` 예제 59개를\n가짜 클라이언트로 전부 실행해 확인했다. 하네스가 실제로 뭔가를 잡는지는\n음성 대조군 2건(수량 0 매수, 미국 정정 수량 5)을 심어 확인했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(order): --help 예제 중 실행되지 않는 것 정정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:40:03Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "30d6c36a923326ae04d5780e4272f168dfc0a1fe",
          "body": "kt10002/kt10008/kt50002의 Request 스펙은 mdfy_qty에 \"단위: 1주, '0' 입력 시\n잔량 전부 정정\"이라고 명시한다 (docs/미국 REST API 문서.xlsx의 해당 시트).\n부분체결 뒤 남은 알 수 없는 잔량을 재호가하는 문서화된 관용구이고, v2.12까지\nmdfy_qty=\"0\"이 실제로 전송됐다. D5가 넣은 수량 하한 가드가 기본값\nallow_zero=False로 세 정정 경로를 전부 exit 1로 막았다.\n\n세 정정 경로에만 allow_zero=True를 준다. 음수·비유한 값은 그대로 거\n[…]\n nonnegative), 한 CLI의 입력 정책이 API 스펙 비고를 뒤집을 근거는\n되지 못한다고 보고 스펙을 따른다. 구 워크북(키움 REST API 문서.xlsx)에는\nmdfy_qty 비고 칸이 아예 없다는 점도 함께 기록했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(order): 정정 수량 0(잔량 전부 정정)을 되살린다",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:40:03Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "656c7c207004bdd91564d7e8452eaa66d09e94ff",
          "body": "녹화 NDJSON의 ts는 시각만 담는다 (normalize._iso_datetime이 WS의 HHMMSS를\n\"09:30:15+09:00\"으로 렌더한다). 날짜는 파일명에만 있다. export가 그 값을\n그대로 쓰는 바람에 DEDUP_COLUMNS(ts, symbol, type, raw_json) + INSERT OR\nIGNORE가 날짜맹목이 됐다 — 여러 날을 한 번에 내보내면 날짜만 다른 별개\n이벤트가 조용히 한 건으로 접힌다 (재현: 이틀치 동일 내용 → 2건 수집,\n1행 저장). idx_events_symbol_ts도 같은 \n[…]\n)\n\n픽스처 맹목도 함께 고친다: data_setup은 이틀치를 담지만 시각이 전부 달라\n날짜 없이도 행이 구분됐다. 시각까지 같은 colliding_days를 추가하고, 범위\n필터가 시각이 아니라 날짜로 갈리는지도 별도로 고정한다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(history)!: export ts에 파일명 날짜를 결합하고 events를 실삽입 행수로",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T04:32:09Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "ff4f94d8c760b7a9d958e7298e6c176a24af4b38",
          "body": "멱등성 원장은 append-only라 파일도 lookup()의 선형 스캔도 무한히 자랐다.\n90일 지난 종결 키를 제거하는 prune()과 이를 부르는 수동 명령을 추가한다.\n\n트리거를 수동 명령 하나로 둔 이유:\n원장이 잘리면 lookup()이 None을 반환하고 그 결과는 이미 체결됐을 수 있는\n주문의 재전송이다. 실패의 대가가 \"파일이 크다\"와 비교가 안 된다. record()에서\n확률적으로 재작성하는 안은 그 위험을 주문 실행 도중에, 무작위로, 조용히\n터뜨린다 — 재현도 사후 추적도 불가능하다. 게다가 record()는 주\n[…]\n에 쓰고 fsync한 뒤 os.replace로 교체하고 디렉터리도\nfsync한다. 어느 시점에 죽어도 원장은 '이전 전체' 또는 '이후 전체'이고 잘린\n중간 상태가 없다. 전 구간을 기존 locked()로 감싼다 (재진입 불가라 주문\n경로에서는 호출 금지).\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "feat(idempotency): 원장 정리 명령 추가 (config prune-ledger)",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T02:20:17Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "fa099de1f055ace4e9a87b85f0ecc7c3716ba819",
          "body": "sector index만 --sector-code 옵션이라 도움말에 SECTOR_CODE가 나오는데,\n나머지 9개 명령은 위치인자라 파라미터 이름 INDS_CD가 그대로 노출됐다.\n한 그룹이 사용자에게 두 가지 어휘를 가르치는 셈이었다.\n\nmetavar만 바꾼다. 위치인자를 옵션으로 바꾸지는 않는다 — sector index는\n전체 업종을 반환해 기본값이 의미가 있으니 옵션이 맞고, 단일 업종 조회\n9개는 위치인자가 맞다. 틀린 건 표시되는 이름뿐이다.\n\n전송 body는 그대로다. HEAD와 현재의 sector 11개 호출 body를 캡처해\n동일함을 확인했고, 하네스가 눈멀지 않았는지는 inds_cd를 고정값으로\n바꾼 대조군이 검출되는 것으로 확인했다.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(market): sector 위치인자 metavar을 SECTOR_CODE로 통일",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T02:14:27Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "15a44ad81a487cc5f2182c81ad154d570f8b1db5",
          "body": "ka50080(gold chart-minute)의 upd_stkpc_tp와 ka90013(program stock-daily)의\namt_qty_tp/date는 스펙상 Required=N이다. 미지정은 \"빈 값을 명시했다\"가 아니라\n\"보내지 않는다\"인데, 세 자리 모두 default=\"\"를 그대로 body에 넣어 빈 문자열을\n전송하고 있었다. ka10038(rank broker-by-stock)의 선례대로 조건부로 키를 추가한다.\n\n전송 body 변화:\n- ka50080 기본: {stk_cd, tic_scope, upd_stkpc_t\n[…]\n enum을 씌우면 breaking).\n\nTranche E에서 전송 바이트 변경이라 미뤄둔 항목이다. E가 옛 동작을 고정해둔\n테스트 2건은 새 계약으로 옮기고, 키 생략은 옵션 선언이 아니라 전송 body에서\n`\"key\" not in body`로 검증한다.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(market)!: 선택 파라미터 미지정 시 빈 문자열 대신 키를 생략",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T02:14:09Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c2bd622365a013d95d8ba21a066404b18e0e1b5a",
          "body": "_sync_stocks의 루프가 [\"0\",\"10\",\"8\",\"3\"]이라 ka10099에 ETN 시장코드 \"60\"을\n한 번도 요청하지 않았다. 그런데 `stock search --market etn`은 Choice에\n광고돼 있고 _filter_map에도 ETN 라벨이 있어서, 사용자에게는 정상 옵션으로\n보이면서 **구조적으로 항상 0행**이었다 — 캐시에 ETN이 들어올 경로 자체가\n없었다.\n\nka10099 mrkt_tp 스펙은 \"60 : ETN\"을 문서화한다(docs/미국 REST API 문서.xlsx\n+ kwcli 0.1.1 번들 \n[…]\n12개 통과.\n\n범위 밖(별도 작업 필요): ka10099 스펙은 30(K-OTC), 50(코넥스), 70(손실제한\nETN), 80(금현물), 90(변동성 ETN), 2(인프라투융자), 4(뮤추얼펀드),\n5(신주인수권), 6(리츠), 7(신주인수권증서), 9(하이일드펀드)도 문서화하지만\n--market Choice에 대응 값이 없어 이번엔 건드리지 않았다.",
          "is_bot": false,
          "headline": "fix(stock): sync가 ETN(시장코드 60)을 조회하지 않아 --market etn이 항상 빈 결과",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T02:07:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ff4fed9641175dcd6f0fba77cd56c651f16e76b3",
          "body": "BREAKING: bare 호출의 결과 범위가 바뀐다. `market rank volume` 등 30개\n커맨드가 지금까지 KRX 단독(stex_tp=1)을 조회했는데 이제 통합(3)을\n조회한다. 사용자가 승인한 의도적 동작 변경이다. 종전 동작이 필요하면\n--exchange KRX를 명시할 것.\n\n받아들이는 값 집합은 넓어지기만 했다(breaking 아님):\n  23곳: {KRX, NXT}        -> {KRX, NXT, all}\n   7곳: {KRX, NXT, all}   -> 변화 없음\n  30곳 전부: 기본값 KRX ->\n[…]\n확인\n  - 30곳 × KRX/NXT/all 3값 전부 wire 코드 확인 (90 케이스)\n  - EXCHANGE_ALL(all=3) vs EXCHANGE_ALL_ZERO(all=0) 구분 고정 —\n    키 집합이 같아 병합해도 KeyError가 안 나는 유일한 잔여 해저드\nfalsify: 프로덕션 코드를 되돌리면 84개가 실패하고, 복원하면 전부 통과.",
          "is_bot": false,
          "headline": "feat(market)!: --exchange 30곳에 통합(3) 추가 + 기본값 all 전환",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T02:07:38Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6def1b2c888022220cd8041700f4bc628afe31e4",
          "body": "세 클로저 술어를 전부 양방향으로 계산하고, 그 결과를 테스트로 고정한다.\n\n관계 개수 (문자열->문자열 코드북 173개, 순서쌍 29,756개 전수):\n  술어1 strict (key,value) subset   : 120쌍 (단방향 120, 양방향 0)\n  술어2 keys 부분집합 + 값 상이      : 204쌍 (단방향 112, 양방향 46쌍)\n  술어3 부분 겹침 + 양쪽 배타 키     : 78쌍 (대칭)\n  값이 완전히 동일(원리상 고정 불가) : 120쌍\n\n술어3은 이전 세션이 \"인스턴스가 하나뿐\"이라며 미뤘던 것인데\n[…]\n\n\n하네스 주의: 병합 시 두 이름에 **각각 별도 dict 사본**을 줘야 한다. 같은\n객체로 alias하면 test_registry_covers_every_wired_mapping의 id() 기반\n룩업이 무너져 모든 병합이 코드북과 무관한 이유로 실패한다 — 실제로 이\n착각 때문에 476/476 \"전부 잡힘\"이라는 완전히 무의미한 결과가 한 번 나왔다.",
          "is_bot": false,
          "headline": "test(constants): 코드북 드리프트 테스트에 술어 3(부분 겹침) 추가",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T01:55:48Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1c52a01dd316ebd3db72cebf6ff45a4352823316",
          "body": "D5가 주문 경로에만 하한을 넣어 프리플라이트와 실주문이 어긋났다.\nvalidate는 qty=0과 음수 가격에 valid: true를 답하는데 실주문은 거부한다 —\n사전점검을 믿은 에이전트가 자금 이동 직전에 거부당한다. 프리플라이트의\n존재 이유가 실주문 결과 예측이므로 이 드리프트 자체가 결함이다.\n\n임계값을 두 벌 두지 않으려고 술어와 종료를 분리했다. validate_order_*가\nfail_input을 부르는 탓에 사전점검이 그대로 재사용할 수 없었으므로(프리플라이트는\nexit이 아니라 체크를 false로 만들어야 한다), \n[…]\n린 진단이\n된다. 가산적 변경이라 valid=all(checks.values())와 이름으로 읽는 소비자는\n그대로이고, exact-dict를 고정하던 AGENTS.md/README/테스트 2건을 갱신했다.\nprice_known 추가 때와 같은 방식이다.\n\n실패는 기존 계약대로 나타난다: VALIDATION_FAILED + exit 1, 새 오류 코드 없음.",
          "is_bot": false,
          "headline": "fix(order): validate 사전점검이 실주문과 같은 수량·가격 하한을 쓰도록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:59:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7e84226c856d4ca2aae6f5a21079616c40cd87e1",
          "body": "수량 0/음수, 음수 가격, NaN/Inf가 실제 전송되던 경로를 막는다. base에서\n측정한 결과 `ord_qty:\"0\"`, `ord_uv:\"-70000\"`, `cncl_qty:\"-5\"`,\n`fc_exmn_amt:\"-500000\"`이 그대로 나갔고, 미국 경로는 `ord_uv:\"nan\"`까지\n전송됐다. 국내 NaN/Inf는 int() 변환에서 ValueError/OverflowError로\nenvelope 없이 죽었다.\n\n_mutation.py에 공용 validate_order_qty/validate_order_price를 두고\n주\n[…]\n미 쓰는 계약을 그대로 따랐다. 정정단가도\n0 이하를 거부한다 — 정정에 시장가는 없다.\n\nkwcli에는 미국 API가 0건이라 US 3건은 kwcli가 침묵한다. 침묵을 허용으로\n읽지 않고 워크북 요청 스펙에 근거했다.\n\nbreaking: 전송되면 안 되는 값만 제거했고 정상 값 집합은 그대로다.\n자세한 before/after 측정표는 작업 리포트 참고.",
          "is_bot": false,
          "headline": "fix(order): 주문 수량·가격과 환전 금액에 하한 가드 추가",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:48:18Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e381cfa895e8f604522b172757dfb0648965ac61",
          "body": "save_config(cfg)가 keyring 이전 루프보다 먼저 실행됐다. cfg[\"profiles\"]의\n존재가 곧 `if \"profiles\" in cfg: return False` 가드의 마이그레이션 완료\n표식이므로, keyring 호출 중 예외가 나면 config.toml에는 이미 표식이 박힌\n채로 남는다. 다음 실행은 가드에 걸려 keyring 이전을 영영 재시도하지 않고\nbare appkey/secretkey/token이 고아로 남았다.\n\nsave_config를 keyring 루프 뒤로 옮겨, 자격증명이 실제로 옮겨간 뒤에\n[…]\n다. set 직후에 죽으면 bare 키가 남아\n다음 실행이 같은 값으로 다시 set하고 delete하며, config에는 아직 표식이\n없으므로 가드에 걸리지 않는다. 이 멱등성을 테스트로 고정했다.\n\ntoken 이전도 같은 루프로 합쳤다 (순서·동작 동일).\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(config): migrate_to_profiles가 부분 실패 시 자격증명을 고아로 남기던 문제",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:26:50Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "d7763e0f0d94ebaf1d2d44044e083157378f8c44",
          "body": "auth.load_token은 KIWOOM_TOKEN을 키체인보다 먼저 반환한다. revoke_token은\n그 env 토큰으로 폐기 요청을 보낸 뒤 무조건 auth.delete_token으로 키체인의\n{profile}:token을 지웠다 — 방금 폐기한 것과 다른, 아직 살아 있는 토큰을\n없애 영영 폐기할 수 없게 만들었다. main.py는 안내 문구만 출력했을 뿐\n삭제를 건너뛰지 않았고, 그 문구(\"해당 토큰이 계속 사용됩니다\")도 사실과\n달랐다. 폐기된 것은 바로 그 env 토큰이다.\n\n키체인 항목은 그것이 방금 폐기한 바로 그\n[…]\n고 무엇을 지우지 않았는지 정확히 출력한다.\njson 모드 envelope에도 두 필드가 실린다.\n\n테스트는 env/키체인 유무 4가지 조합 + 두 값이 같은 경우를 모두 고정한다.\nKIWOOM_TOKEN을 세팅하지 않는 테스트는 두 분기를 구분하지 못한다.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(auth): logout이 폐기하지 않은 키체인 토큰을 지우던 문제",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:25:20Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b9b8768a9212ac347a965ef41062d5d4bdb3ae37",
          "body": "--out에 없는 디렉터리를 주면 dest별로 제각각인 예외(sqlite\nOperationalError, csv FileNotFoundError)가 raw traceback으로 튀었고\njson 모드에서는 envelope 없이 죽었다. recorder.NdjsonRecorder.write와\n같은 패턴으로 상위 디렉터리를 만들고, 만들 수 없으면 fail_input으로\nINVALID_INPUT(exit 1)을 낸다. 파일을 쓰는 커맨드 중 여기만 빠져 있었다.\n\nsqlite는 csv/parquet과 달리 append인데 PK도 UNIQ\n[…]\n\n구버전 파일에서는 INSERT OR IGNORE가 그냥 INSERT로 퇴화한다. 조용히\n계속 중복되는 대신 스키마를 승격한다(rename → 새 DDL → INSERT OR\nIGNORE SELECT → drop). 이미 쌓인 중복은 이때 한 건으로 접힌다.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(history): export 상위 디렉터리 생성 + sqlite 재-export 중복 제거",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:21:43Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "27b330c1132f15182d4f2b593994f83d2c12d1f6",
          "body": "_save_stock_cache가 fetched_at을 기록만 하고 읽는 곳이 없어서 stocks.json이\n만료 없는 영구 캐시였다 — 신규 상장/폐지가 `stock sync`를 손으로 돌리기\n전까지 절대 반영되지 않는다. 필드를 지우는 대신 판정에 쓴다.\n\nTTL은 거래소 캐시(24h)보다 길게 잡았다. 상장/폐지는 하루 단위로 흔들리지\n않고, 만료 시 재동기화가 ka10099 4회 호출이라 대화형 `stock search`에서\n매일 물게 하기엔 비싸다. 이 값은 주문 body에 들어가지 않아 틀렸을 때의\n대가도 거래소 캐시보다 작다. 같은 이유로 도메인 분리도 하지 않는다 —\nKRX 상장 목록은 계좌·환경과 무관한 공개 참조 데이터다.\n\nfetched_at이 없거나 해석 불가면 신선도를 주장할 수 없으므로 만료 취급.\n\n'A005930' 라우팅 수정에 따라 stock 국내 분기에도 접두사 제거를 적용했다.",
          "is_bot": false,
          "headline": "fix(stock): 종목 리스트 캐시에 7일 TTL 적용 (fetched_at을 실제로 읽는다)",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:12:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "14524b65d2b82107c2375790cda3d2b6f2005bd5",
          "body": "잔고 응답은 종목코드를 시장구분 접두사와 함께 'A005930'으로 돌려준다.\nis_us_symbol이 이걸 6자리 숫자가 아니라고 보고 미국으로 판정해서,\n국내 분기는 stk_cd 필터를 조용히 빼먹고(전 종목 반환) 미국 분기는\n'A005930'을 ust21050에 보냈다. 감사가 지목한 orders pending 외에\npnl today / pnl by-period / orders executed / orderable margin-qty와\nstock·order의 국내 분기 전체가 같은 뿌리다.\n\n- normalize.strip_\n[…]\ne_record가 stk_cd를 접두사 없이 내보낸다. 원본은 data.raw에 남음\n- is_us_symbol과 국내 분기 body 구성 지점에 적용\n- order validate의 removeprefix(\"A\")는 죽은 코드가 아니었다 (kt00004 원본\n  응답을 보므로). 빼면 보유수량이 0으로 잡히는 걸 확인하고, 가드 있는\n  헬퍼로만 교체했다",
          "is_bot": false,
          "headline": "fix(symbol): 'A005930' 형태 국내 코드가 미국으로 라우팅되던 문제",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:12:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b18d65f466379322388288b87c27a3587ee19b71",
          "body": "모의투자에서 학습한 거래소가 실거래 주문의 stex_tp로 그대로 나가던 문제.\n캐시 파일이 us_exchanges.json 하나뿐이라 prod/mock이 같은 항목을 공유했다.\n\n- 파일명을 us_exchanges-<domain>.json으로 분리 (config.get_domain_key)\n- 항목 형식을 {\"exchange\":..., \"ts\":...}로 바꾸고 24시간 지나면 무시\n  (틀린 값이 영구화되지 않도록)\n- v2.12 이하의 평문 형식은 마이그레이션 없이 버린다 — ts가 없어 신선도를\n  알 수 없고 어느 도메인에서 학습했는지도 모른다\n- 캐시 파일도 0600으로 조인다\n\n프로필로는 나누지 않는다. 'NVDA가 나스닥 상장'은 계좌가 아니라 시장의\n사실이라 같은 도메인의 프로필끼리 공유해도 틀릴 여지가 없다.\n\n기존 캐시 테스트는 같은 도메인 안에서만 왕복해서 스코프 유무를 구분하지\n못했다. 도메인을 바꿔 교차로 읽는 테스트를 추가했다.",
          "is_bot": false,
          "headline": "fix(us): 거래소 캐시를 도메인별로 분리하고 24시간 TTL 적용",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:12:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fcd2cebdfddcc8259761456e4704c513d038720e",
          "body": "FT(미국주식 10호가)는 0D와 **같은 필드 ID 체계**를 쓴다 (21 시간, 41~50\n매도N호가, 51~60 매수N호가, 61~70/71~80 잔량). 따라서 직전 커밋의 0D 매핑이\nFT 필드도 이미 커버하고 있었고, 새 ID 매핑은 필요하지 않았다. 스펙 프레임을\nhandle_message에 통과시켜 확인했다. 남은 결함은 US 고유 요소 둘이다.\n\n- REALTIME_TYPES에 US 타입이 없어 모든 US 이벤트의 type_name이 \"?\"였다.\n  US_REALTIME_TYPES를 따로 두고 type_name_of\n[…]\n없는 상수만 늘었다.\n\n미검증으로 남긴 것: _iso_datetime은 HHMMSS에 무조건 +09:00을 붙이는데 이\nID들은 US 타입과 공유된다. FE에 \"현지 체결시간\"(51020)이 따로 있는 것은 KST를\n시사하지만, 워크북 예시가 합성 데이터라(FE의 20과 51020이 둘 다 \"215300\")\n값으로 확증할 수 없다. 주석에 위험으로 남겼다.",
          "is_bot": false,
          "headline": "fix(stream): FT 미국 호가 타입명·거래소코드 보존",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-20T00:00:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4294636dbf3175d605121af77142321702ee112a",
          "body": "주식호가잔량(0D) 프레임이 처음부터 끝까지 정규화되지 않았다. \"21\"(호가시간)이\n시각 ID로 등록돼 있지 않아 모든 0D 이벤트의 ts가 None이었고, 10단계 호가\nID 41~80은 어디에도 없어 숫자 ID 문자열 그대로 샜다. 숫자 분류에도 없어\n매도호가가 \"-20800\" 같은 방향지시자 부호를 단 채 음수 가격으로 보였다.\n\n필드 매핑은 docs/미국 REST API 문서.xlsx 시트 '주식호가잔량(0D)'과 kwcli\n0.1.1이 동봉한 kiwoom_api_spec.json의 같은 항목을 대조해 확정했다. 두 소스가\n[…]\n는 ts와 REST 필드명(sel_fpr_bid)을 손으로 넣어 이 버그를\n가리고 있던 test_history 픽스처 2건을 handle_message 실제 출력에서 파생하도록\n교체했다. 수량 분류를 고정하려던 첫 테스트는 스펙 값에 부호가 없어 ABS로\n바꿔도 통과하는 무력한 고정이었다 — 두 분류가 갈라지는 유일한 입력인\n부호 붙은 수량으로 다시 썼다.",
          "is_bot": false,
          "headline": "fix(stream): 0D 호가 필드 정규화 및 ts 매핑",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T23:28:01Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9d6c0d0a41a3ddd3de1061d4d22ae1a0fb934791",
          "body": "table 모드가 ConnectionRefusedError와 일반 예외에서 (0, None)을 반환해, 거부된\n연결과 예기치 못한 오류가 $?로 분기하는 쪽에는 성공한 스트림으로 보였다. json은\n이미 2였다. 양 모드를 2로 맞춘다.\n\n서버가 접속만 받고 LOGIN에 답하지 않은 채 끊으면 recv_ack의 recv()가\nConnectionClosedOK를 던져 바깥 핸들러로 새고 exit 0이 됐다. 인증되지 않은 세션은\n성공이 아니다. ack를 아예 못 받은 경우(이미 exit 3)와 관측적으로 같은 상황이라\n같은 종료 코드\n[…]\nose_exits_zero는 ConnectionClosed 핸들러를 고정하는 것처럼 보였지만\n프레임 큐 소진으로 StopAsyncIteration이 나 그 핸들러를 한 번도 실행하지 않았다.\n경로를 명시하는 이름으로 바꾸고 진짜 핸들러 고정을 따로 추가했다.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(stream,watch): WS 예외 핸들러 종료코드 정합·핸드셰이크 종료 감지·ack 대기 중 프레임 보존",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T23:13:11Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "9dcb845a1536d565823b7e94ddc7242856dc7d61",
          "body": "watch.py는 테스트가 없었고 어떤 실패 경로에서도 0이 아닌 종료 코드를 내지 않았다.\n\n- LOGIN/REG ack를 recv() 순번으로 집던 것을 streaming.recv_ack()로 교체.\n  prod는 LOGIN 응답 뒤에 SYSTEM 프레임을 보내지만 mock은 안 보내므로, 위치로\n  읽으면 SYSTEM을 ack로 오독해 return_code 부재 → -1 → 사유 공란 \"인증 실패\"\n  → exit 0으로 끝났다. mock 테스트만으로는 절대 드러나지 않는 경로다\n- 실패 종료 코드 부여: 토큰 없음·인증 실패\n[…]\n록 변경\n- -f json/csv는 fail_input으로 거부 (Rich TUI라 NDJSON 계약을 지킬 수 없는데\n  조용히 TUI를 띄워 stdout이 비었다). REST 조회 전에 거부한다\n\n테스트: tests/test_watch.py 신규 17건.\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(watch): 프레임 순서 가정 제거·종료코드·예외 처리",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T22:49:18Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "ae194a3a9d07a1a428881074adbc8e50a6b5a811",
          "body": "LOGIN 응답 핸들러가 SYSTEM 프레임의 키(code/message)를 읽고 있어 인증 실패를\n한 번도 감지하지 못했다. 실패한 인증에도 \"인증 성공\"을 출력하고 그대로 진행했다.\n실측(.superpowers/sdd/task-22-login-frame-evidence.md)상 LOGIN 응답 키는\nreturn_code/return_msg이고 return_code는 int다.\n\n- LOGIN ack를 return_code로 판정 (str(rc) != \"0\" 비교로 int/str 모두 처리)\n- 인증 실패의 table 모드 종료코\n[…]\n출력을 print_system_frame()으로 추출해 핸드셰이크·수신 루프가 공유\n\n테스트: 가짜 websocket(tests/fakes.py)으로 인증 실패/성공, 프레임 순서 교란,\n등록 실패, 토큰 없음을 table·json 양쪽에서 고정 (12건).\n\nCo-Authored-By: Claude Fable 5 <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(stream): WS 인증·등록 실패 감지 및 종료코드 교정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T22:45:52Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b0965fc042aac619d974ae3456260eef966360d1",
          "body": "docs: 한국어 문서에서 줄표 정리",
          "is_bot": false,
          "headline": "Merge pull request #30 from gejyn14/docs/korean-cleanup-v2.12",
          "author_name": "Yu Jin An",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T22:38:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e75f5d7eec4c12470372373e4c32e4ed03f22cea",
          "body": "앞 커밋에서 코드 펜스 안이라 남겨둔 4곳이다. 주석이라 동작에는 영향이 없다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(agents): bash 주석의 줄표도 콜론으로 정리",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T22:22:04Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "e27ee652bfee13ccf5ce673910dbd8b6bcb24b5f",
          "body": "문장 중간에 끼어든 줄표(—)를 마침표·쉼표·콜론으로 바꿨다. 영어 글의\nem dash 삽입구를 한국어 산문에 그대로 옮긴 자리라 읽기 흐름을 끊었다.\n정의 목록(`- \\`ok\\` — ...`)과 제목의 구분자 줄표는 콜론으로 통일했다.\n\n명령어·옵션·경로·API ID·수치·코드펜스는 건드리지 않았다.\nAGENTS.md 69 → 4(남은 4건은 bash 코드펜스 안 주석),\nCHANGELOG.md의 2.12.0 절 16 → 0.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: 한국어 문서 다듬기",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T22:20:38Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "d4c07075d45299cd3052238140bfdce5a52f4d8d",
          "body": "v2.12.0 — 옵션 human-readable 전환 (Tranche E)",
          "is_bot": false,
          "headline": "Merge pull request #29 from gejyn14/feature/v2.12.0-human-readable",
          "author_name": "Yu Jin An",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T21:09:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "69170ce77088188a1517c4fedf6c09b8b62c3db9",
          "body": "이중 철자 옵션의 human/legacy 판별이 \"둘 중 더 긴 쪽이 human\"이라는\n길이 휴리스틱이었고, 길이가 **같은** 경우만 막아 뒀다. legacy 쪽이 더\n긴 쌍(예: --type vs --trde-upper-tp)이 생기면 legacy를 human으로 잘못\n지목해 그 사이트에 정반대 선언 순서를 요구하게 되는데, 오늘은 그런\n쌍이 없어 조용히 통과한다.\n\nlegacy 스펠링 5개를 _LEGACY_SPELLINGS에 명시적으로 열거하고 human을\n\"나머지 하나\"로 정의한다. 등록되지 않은 새 쌍은 즉시 실패하며 등록을\n요구한다. 실제로 --type/--trde-upper-tp 쌍을 주입해 실패하는 것을\n확인했다.\n\nELW_BROKER_END_SKIP(제거된 이름)을 참조하던 docstring도 함께 정리했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(market): help 순서 판별을 휴리스틱에서 명시 목록으로 전환",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T21:01:45Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "5ad3a71430d61838c07e1e8526d6ebe64cce5e77",
          "body": "같은 결함 유형을 이미 두 번(2b58489, 12c8acb) 고쳤는데 세 건이 더\n남아 있었다. 워크북(docs/미국 REST API 문서.xlsx)으로 재확인한 결과\n셋 다 **값 대응**은 동일하지만 **문구**는 다르다 — 공유 자체는 계속\n정당하고, 정밀도 주장만 거짓이었다:\n\n- EXCLUDE_ENDED_ELW: 5개 시트가 동일하다고 했으나 실제 세 형태다\n  (ka30001/ka30002, ka30004, ka30009/ka30010)\n- ELW_RIGHT_TYPE_3DIGIT: ka30001/ka30004가 콜론 뒤\n[…]\n-> 12곳 (정의부 표와 불일치했다)\n- TRADER_ANALYSIS_PERIOD_5_120 \"제거한다\" 주석 정정 — 제거되지\n  않았고 두 곳이 쓰고 있다\n- dt 코드북 주석에서 내부 태스크 서술 제거, I2 예외 근거만 남김\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(constants): 거짓 'character-for-character 동일' 주장 3건 정정 + 죽은 상수 제거",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T21:01:37Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "c93b63979ffe6f0814faf2607eb0534d465d49fa",
          "body": "HumanChoice에 배선된 상수는 167개인데 레지스트리는 165개만 나열해\nMARKET_ALL과 TRADER_ANALYSIS_PERIOD_5_120이 human 이름 왕복 검증을\n전혀 받지 못하고 있었다.\n\nTRADER_ANALYSIS_PERIOD_5_120 쪽은 단순 누락이 아니라 같은 파일 안의\n두 주석이 서로 모순됐다 — 레지스트리 주석은 \"더 이상 쓰이지 않음\"이라\n했지만 개수 주석은 그 전환을 세고 있었다. 실제로는 ka10043(stock.py)/\nka10042(market.py) 두 곳에 배선돼 있으므로 레지스트리\n[…]\n했다.\n\n레지스트리가 손 관리라 또 어긋날 수 있으므로, 배선 집합과 목록을 집합\n비교하는 드리프트 가드를 추가한다(개수 비교는 한쪽이 늘고 다른 쪽이\n빠지면 통과하므로 집합으로 본다). 항목을 지우고 실제로 실패하는 것을\n확인했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(human-ux): 매핑 레지스트리 누락 2건 보강 + 드리프트 가드 추가",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T21:01:20Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "ff2e08884ddb93c319945375c72e99cbb1ead94a",
          "body": "기존 값 고정 테스트는 샘플한 키가 형제 상수와 값이 같은 키뿐이라\n상수를 통째로 형제와 합쳐도 전부 통과했다. ka10027의 세 상수\n(stk_cnd/pric_cnd/trde_prica_cnd)를 형제 값으로 병합해도 1552개가\n모두 통과하는 것을 확인했다 — 고정이 아무것도 고정하지 못했다.\n\n병합 mutation 하네스로 207개 폐쇄 관계를 전수 검사해 16개가 무방비인\n것을 확인하고, 전부 값이 갈리는 키로 고정했다(16 -> 1, 남은 하나는\nMARKET_KOSPI_KOSDAQ -> MARKET_SEARCH로 dead\n[…]\nE_NO_SOR\n\nEXCHANGE_TWO -> EXCHANGE_ALL은 값이 아니라 키 집합이 위험한 관계라\n(두 값은 동일) 키 집합 자체를 고정한다 — 병합 시 23개 사이트가\n스펙에 없는 stex_tp=\"3\"을 받아들이게 된다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test: 형제 상수와 값이 갈리는 키로 값 고정 재작성",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T20:48:38Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "4718a6589d7adfb16f6d880abe184ecf997bc788",
          "body": "전수 검증(base 6c76b21 vs HEAD의 Click 옵션 수용값 집합 비교)으로 확인한\n네 건의 과소보고를 고쳤습니다.\n\n- market rank 순매수~외국계기관 행 16 → 17\n  (net-buyer --period가 빠져 있었음)\n- 자유 텍스트였던 stock 옵션 목록에 open-change --volume-cond 추가\n  (8개라고 쓰고 7개만 나열하고 있었음). 같은 이유로 Non-breaking 절의\n  open-change 항목을 7개 → 3개로 정정\n- by-stock-total --trade는 이미 c\n[…]\n(\"1\"/\"5\") 명시\n- 내부 용어 제거(트랜치/Tranche B/Task 7b/34b/제약 8)\n- README 조사 오타, 숫자 코드 허용 범위 단정 완화,\n  kt10000 예제의 생략부호를 실제 실행 가능한 JSON으로 교체\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): v2.12.0 전환표 수치 교정 + breaking 범위 정정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T20:07:00Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "b29e4f28d4288c94d142bf324af2be36b0d85006",
          "body": "트랜치 E(human-readable 옵션 전환) 마지막 태스크 — 버전을 2.12.0으로\n올리고 CHANGELOG [Unreleased]를 [2.12.0] - 2026-07-20으로 확정했다.\n231개 명령어 기준으로 kiwoom_cli_commands.xlsx도 재생성했다(v2.11.0\n때는 61개 커밋만큼 밀려 있었다).",
          "is_bot": false,
          "headline": "release: v2.12.0",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T19:34:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8255caaf1dbb7be48abdfe7d8904b22d195f5886",
          "body": "명령 그룹별 전환 옵션 수를 표로 정리했다 — market 41개 커맨드/115개\n옵션(전부 자유 텍스트 이력이라 Breaking), stock 13개 커맨드/31개 옵션\n(Breaking 8 / Non-breaking 23). 숫자는 base(6c76b21)와 HEAD의\nmarket.py/stock.py를 옵션 단위로 직접 대조해 검증했다. --exchange/\n--market 값 확장, --vol-type/--vol-cnd 기본값 교정은 raw 코드를 human\n이름으로 바꾼 것과 성격이 달라 표에서 제외하고 각각 원래 있던 자리\n(Non-breaking 서술, Fixed)를 그대로 가리키게 했다.",
          "is_bot": false,
          "headline": "docs(changelog): 트랜치 E human-readable 전환 표 추가",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T19:34:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "89cfd0ce8a0644de93df5fafc21d86b383c75c60",
          "body": "AGENTS.md의 describe/find/api list 예제 8곳이 -f json을 서브커맨드 뒤에\n적어 그대로 실행하면 \"No such option '-f'\"로 깨졌다(-f/--format은 루트\n그룹 옵션이라 서브커맨드보다 앞에 와야 한다). --fields 예제 2곳도\nQTY 위치인자를 --qty 옵션으로 잘못 적어 같은 방식으로 깨졌다. 모든 예제를\n실제로 실행해 확인 후 고쳤다. README.md에도 같은 순서 버그가 하나 더\n있었다(describe --paths -f json).\n\nREADME.md의 human-readable 옵션 범위 안내도 갱신했다 — 이전엔 \"account/\nmarket 19개, stock은 범위 밖\"이라고 적혀 있었는데 트랜치 E에서 market\n115개 + stock 31개가 추가로 전환되어 더 이상 사실이 아니었다.",
          "is_bot": false,
          "headline": "docs(agents): describe/find/api list 예제의 전역 옵션 순서 버그 수정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T19:34:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ed2aaa05954fbdfb0a2e4fcbc57e04684d27dfa7",
          "body": "단일 사이트(rank new-highlow/stk_cnd)만 고정하던\ntest_rank_new_highlow_help_shows_human_name_first를 Click 트리를\n순회해 human/legacy 이중 철자 옵션 20곳을 전부 찾아 parametrize하는\n테스트로 교체. 나머지 19곳(volume-cond 3, price-cond 3, amount-cond,\nsector-code, stock-cond 11)은 선언 순서가 뒤바뀌어도 suite가 green을\n유지했던 커버리지 공백이었다.\n\nhuman/legacy 판별은\n[…]\n순서로\n역산하면 항상 참인 순환 검증이 되어 순서가 뒤바뀌어도 잡아내지 못한다.\n세 사이트(stock-cond/volume-cond/sector-code)에서 실제로 선언 순서를\n뒤집어 테스트가 각각 실패하는 것을 확인 후 원복했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(market): help-ordering 고정을 20개 사이트 전수 커버로 확장",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T19:03:54Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "0e70fa8d7249bce2743e70b44f4337c45a17c5d4",
          "body": "market.py의 --stk-cnd/--vol-cnd/--price-cnd/--amount-cnd/--inds-cd에\n--stock-cond/--volume-cond/--price-cond/--amount-cond/--sector-code\n별칭을 추가한다. 구 이름은 하위호환으로 남기고 --help에는 새 이름이\n대표로 뜬다.\n\naccount.py의 --exchange 3곳(kt00007/kt00009/kt00015)에 \"all\" 별칭을\n추가한다(dmst_stex_tp=\"%\" 그대로 전송, 사용자 결정 E-1 — 전송값을\n숫자코드\n[…]\n_ux.py의 HumanChoice 데코레이터 고정 개수를 216→219로\n갱신(트리 순회로 재도출)하고 매핑 레지스트리에 두 상수를 추가했다.\naccount.py/market.py에 구 이름·구 값·신 이름 회귀 테스트를 추가했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor(cli): API 약어 옵션명에 human 별칭 추가",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:44:44Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "9cb5e38dc60e90cb99e7e6a4338e7236225a076d",
          "body": "AMT_QTY_TP_1_2의 12곳 체크리스트가 34b 이전부터 이미 전부 stale이었다\n(market.py:776는 실제로 797, stock.py:1077은 실제로 1140 등). 줄번호는\n편집마다 바로 어긋나므로 api_id + 파일명만 남기고 걷어냈다 — api_id는\ngrep으로 바로 찾을 수 있고 편집에 영향받지 않는다. 같은 파일의 다른\n줄번호 참조 4곳(TRDE_TP_NET_BUY_BUY_SELL/AMT_QTY_TP_1_2/GOLD_PRICE_TYPE/\nTRADER_ANALYSIS_PERIOD_5_120 근처 주석)도 같은 이유로 정리했다.",
          "is_bot": false,
          "headline": "refactor(constants): 커플링 주석에서 줄번호 제거",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:36:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b848ac59adc28dc1788d0acfe6de1e2491c121c0",
          "body": "chart intraday-investor의 --market 값 확대(kospi/kosdaq만 받던 자리에 all\n추가)를 Non-breaking 섹션에 추가 — --exchange 스윕과 같은 종류의 수정.",
          "is_bot": false,
          "headline": "docs(changelog): ka10064 --market all 위드닝 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:35:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5306bfdb1dc4883029a71ae03f3b008aba1e35dd",
          "body": "click.Choice([\"kospi\",\"kosdaq\"])라 스펙의 000:전체(mrkt_tp)에 도달할 방법이\n없던 사전 존재 결함을 HumanChoice(MARKET_ALL)로 교체해 고쳤다. kospi/kosdaq\n전송값과 기본값은 그대로라 순수 확대(widening) — 리터럴 핀 테스트로 확인.\n\nka90003(program-top)의 --market은 스펙상 mrkt_tp가 P00101/P10102 두 값뿐이라\n건드리지 않았다(워크북 재확인 완료).",
          "is_bot": false,
          "headline": "fix(stock): chart intraday-investor --market 000:전체 값 커버리지 확대 (ka10064)",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:35:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6b8a7a15c4b741e95cb8e6a8d73ba4f2a183d2c6",
          "body": "Task 34b 6개 항목을 기존 Unreleased Non-breaking 섹션에 추가(새 헤딩\n없음). 전부 전환 전 click.Choice였던 자리라 breaking 없음.",
          "is_bot": false,
          "headline": "docs(changelog): stock program_top/chart_*/lending HumanChoice 전환 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:17:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4e17d0580a29795330e806aa157d5af85fa102be",
          "body": "Task 34b가 전환한 13개 옵션(program-top 2 + chart tick~year 6 +\nchart investor 3 + chart intraday-investor 2)만큼 순증. 개수는 트리\n순회 스크립트로 직접 재도출했다(추정 아님). PROGRAM_TOP_SIDE/\nCHART_ADJUSTED_PRICE를 매핑 레지스트리에 추가하고, lending trend/\nby-stock의 --all(all_tp)이 전환되지 않았음을 이름으로 못 박았다.",
          "is_bot": false,
          "headline": "test(human-ux): HumanChoice 데코레이터 고정 개수 202→215 + 매핑 레지스트리 갱신",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:17:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b780326d318cadb449620330208991dd89c27f6d",
          "body": "program-top/chart tick~year/chart investor/chart intraday-investor 13개\n옵션 전부에 기본 body 불변 테스트 + 하드코딩 리터럴 enum 테스트를 추가했다.\n\nPROGRAM_TOP_SIDE가 새로 만든 형제 상수 해저드 2건(NETSLMT_TP_NET_BUY_ONLY의\n진짜 부분집합, FOREIGN_PERIOD_SIDE의 진짜 부분집합)과 극성 해저드 5건\n(ELW_BROKER_SIDE 등)을 리터럴 거부/핀 테스트로 방어했다. 전부 상수를\n형제 값으로 바꿔치기해 테스트가 실제로 실패하는지 확인 후 복원했다\n(cp 백업, git checkout 미사용).",
          "is_bot": false,
          "headline": "test(stock): Task 34b 기본바디·enum·리터럴핀·형제상수 거부 테스트 추가",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:17:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9cb4ad72188faec358fa5b905e249e0fe058b1b5",
          "body": "…64) 숫자코드를 HumanChoice로 전환\n\nprogram-top(ka90003)의 --trade/--amount-qty, chart tick~year(ka10079~94)의\n--adjusted, chart investor(ka10060)/intraday-investor(ka10064)의\n--amount-qty/--trade/--unit 13개 옵션을 human-readable 이름으로 전환했다.\n전환 전 전부 click.Choice였고 값 집합이 줄지 않아 non-breaking(순수 확대)이다.\n\n신규 상수: PROGRAM\n[…]\n 있던 자리), INVESTOR_BY_STOCK_UNIT\n(3곳으로).\n\nchart tick/minute의 --range/--interval(tic_scope)은 값=라벨 자기서술적\n수량이라 전환하지 않았다. lending trend/by-stock의 --all(all_tp)은 스펙에\n값이 하나만 문서화돼 있어(반대값 불명) 미확인으로 raw 텍스트 유지.",
          "is_bot": false,
          "headline": "refactor(stock): program_top/chart_*(ka90003/ka10079~94/ka10060/ka100…",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:16:57Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3b22e81e1706b0b91f5f12165c3e5d510c399efc",
          "body": "trader-analysis --days가 raw로 되돌아갔다던 Breaking 항목을 지웠다\n(v2.11.0부터 이미 HumanChoice였던 걸 원복했을 뿐 이번 릴리스\n기준으로는 변화가 없다). 대신 net-buyer --period가 자유 텍스트에서\nHumanChoice로 좁아진 걸 새 Breaking 항목으로 적었고, Non-breaking\n절의 \"--period는 raw로 남겼다\"는 문장도 지금 상태에 맞게 고쳤다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): trader-analysis --days 원복 반영 + net-buyer --period 전환 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:00:08Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "c298f847fea75d0dc3094be98e02d41f705fcf27",
          "body": "trader-analysis(ka10043) --days를 v2.11.0에 배포된 HumanChoice로\n되돌린다. Task 34a는 이걸 I2 규칙(수량 프리셋 유지) 근거로 raw\n텍스트로 되돌렸는데, 그 결과 --days 999 같은 스펙 밖 값이 exit 0으로\n조용히 전송되게 됐다 — 이미 배포돼 검증되고 있던 걸 걷어내 회귀를\n만든 셈이라 반대 방향(HumanChoice 유지)이 맞다. net-buyer(ka10042)의\n--period는 동일 필드(TRADER_ANALYSIS_PERIOD_5_120, 워크북 값 동일\n확\n[…]\nsts/test_human_ux.py의 HumanChoice 데코레이터 고정 개수를\n200→202로 갱신(trader-analysis --days 원복 +net-buyer --period 전환)\n하고 두 옵션을 이름으로도 못 박았다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(stock,market): Task 34a 리뷰 대응 — trader-analysis/net-buyer 기간 원복+통일",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T18:00:02Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "cc41c153f677e24262a3fee337d61cdfeace9d0e",
          "body": "Unreleased의 기존 Fixed/Breaking/Non-breaking 섹션에 Task 34a 항목을\n추가했다(새 헤딩 없음) — open-change --volume-cond 기본값 fix,\n자유 텍스트 8곳의 breaking 좁힘, trader-analysis --days human 이름\n제거, by-stock-total --trade 값 집합 축소, 나머지 23개 옵션의\nnon-breaking 확대.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): stock daily_price~by_stock_total HumanChoice 전환 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T17:36:13Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "c2a8cf4965380a06ba110b0f222a7d40366c4828",
          "body": "Task 34a가 stock.py에 순증 30개(신규 31 - trader-analysis --days 되돌림\n1) HumanChoice 데코레이터를 추가했다. 개수는 트리 순회로 직접 재도출했다\n(추정 아님). 매핑 레지스트리에 신규 상수 24개를 추가하고\nTRADER_ANALYSIS_PERIOD_5_120(사용처 없어짐)을 제거했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(human-ux): HumanChoice 데코레이터 고정 개수 170→200 + 매핑 레지스트리 갱신",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T17:36:07Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "85362ac6408981cb52d38928d753bbebccdc03d3",
          "body": "daily_price~by_stock_total 13개 커맨드에 대해: 기본 호출 바디 고정\n테스트, human 이름->wire 값 enum 테스트, 극성 해저드 자리(TODAY_PREV_1_2,\nOPEN_CHANGE_SORT, OPEN_CHANGE_AMOUNT_CND, INSTANT_VOLUME_MARKET,\nVI_TRIGGER_SESSION, INVESTOR_DAILY_TRADE_SIDE, DAILY_BY_INVESTOR_TRADE_SIDE,\nINST_FOREIGN_PRICE_TYPE)의 리터럴 핀 테스트, 형제 상위집합 상수(\n[…]\n parametrize는 하드코딩 리터럴을 쓴다(매핑을\n순회해 기대값을 도출하는 자기참조 패턴 금지). 10개 형제 상수\n바꿔치기를 실제로 실행해 해당 테스트가 실패하는지 검증했다\n(cp 백업/복원, git checkout 미사용).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(stock): Task 34a 기본바디·enum·리터럴핀·형제상수 거부 테스트 추가",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T17:36:02Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "92e5128119a26ab295a4acebeaff64563e1fc9ee",
          "body": "…/11/44/45/58/59/61) 숫자코드를 HumanChoice로 전환\n\n7개 커맨드(daily-price/today-exec/today-volume/analysis price-cluster/\nanalysis open-change/analysis instant-volume/analysis vi-trigger/\nanalysis warrant/investor daily-trade/investor stock-institution/\ninvestor daily-by-investor/investor by-stock/investor by-\n[…]\nm 값이 다름 — 전부 별도 상수로 분리하고\n주석에 절대 합치지 말 것을 명시했다. AMT_QTY_TP_1_2/TRDE_TP_NET_BUY_BUY_SELL\n은 ka10059/ka10061로 확장(신규 상수 아님, 커플링 주석 갱신).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor(stock): daily_price~by_stock_total(ka10086/84/55/25/28/52/54…",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T17:35:51Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "868e6c7e8b5da8ff26d39312b8c59f0ee60167e3",
          "body": "브랜치 전체(base 6c76b21 → HEAD) 감사 결과, 전환한 115개 옵션은 전부\n베이스에서 type= 없는 StringParamType이었다. 아무 문자열이나 받던 자리를\n유한 집합으로 바꾼 것은 받아들이는 값의 집합이 줄어든 변경이므로\nBreaking이다. 다섯 청크(31a/31b/31c/32/33) 전부 Non-breaking으로 적혀\n있던 것을 바로잡는다.\n\n스펙 합법 호출은 하나도 깨지지 않으므로 코드 수정이 아니라 분류 정정이다.\n다섯 elw 커맨드를 3자리 권리구분 표 하나로 돌리던 스크립트가\nchange-ra\n[…]\nm stock-daily 미전환 근거를 HumanChoice에\n  빈 문자열 키가 없다는 실제 이유로 교체 + ka10038 dt 패턴 후속 큐잉\n- 청크마다 반복되던 Fixed/Breaking/Non-breaking을 한 벌로 병합\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): 자유 텍스트 좁힘 115개를 Breaking으로 재분류 + 헤딩 한 벌로 병합",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T17:09:18Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "daf0ce58d497f0c6bd930e84965f4e7f5ae56501",
          "body": "…ange 확대 고정\n\nAMT_QTY_TP_1_2는 이번 트랜치에서 5곳 → 7곳으로 확장됐는데, AMT_QTY_TP_0_1\n(amount=0,quantity=1)·SAME_NET_TRADE_SORT(quantity=1,amount=2)와 키가 같고\n값만 다른 극성 해저드를 갖는다. 두 dedicated 테스트가\nparametrize(list(AMT_QTY_TP_1_2.items())) 형태라 상수를 형제로 바꿔치기해도\n수집 시점에 바뀐 값을 그대로 가져와 자기참조로 통과했다.\n\n실측: 상수를 0/1 극성으로 치환하면 종전 형태는 2 passed, 리터럴로 바꾼\n형태는 정확히 실패한다.\n\netf all --exchange 확대도 함께 고정한다(all=3 신규, KRX=1/NXT=2 불변).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(market): ka90007/ka90008 --unit 극성 pin을 리터럴로 교체 + etf all --exch…",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T17:09:18Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "2f1b12f995ebeac78221393c80861ca6a306b551",
          "body": "워크북 ka40004 시트의 stex_tp가 \"1:KRX, 2:NXT, 3:통합\"으로 이미 넓힌\nka10030/ka90006/ka90007과 동일한데 KRX/NXT만 받고 있었다. 같은 한 줄\n패턴(click.Choice(list(EXCHANGE_ALL)) + EXCHANGE_ALL[stex_tp])을 적용한다.\n\n기존 KRX/NXT 호출과 기본값(KRX)은 전송값이 그대로인 순수 widening이다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(market): etf all(ka40004) --exchange를 all(통합)까지 확대",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T17:08:57Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "780c59fef39238c32be7e6db9430f2bd541852a2",
          "body": "…nge 3자리 정리 기록\n\nUnreleased 섹션에 21개 커맨드의 human-readable 이름 전환(non-breaking)과\nrank volume/program arbitrage-balance/program cumulative의 --exchange\nall(통합) widening을 기록.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): market etf·elw·gold·program HumanChoice 전환 + --excha…",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T16:45:34Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "9fa0a7a872c3c6bafd13df676b2a1a83052a185c",
          "body": "…/10,ka50079/80~83/91/92/101,ka90005~08/10/13) 숫자코드를 HumanChoice로 전환\n\nmarket etf/elw/gold/program 4개 그룹, 21개 커맨드의 숫자 API 코드 옵션을\nhuman-readable 이름으로 전환. 순수 표기 전환 - 전환된 옵션 전부 기본 호출\nbody가 종전과 동일함을 테스트로 고정했다(워크북·kwcli arguments.csv 이중\n확인, wire-value fix 없음).\n\nelw 그룹은 --right-type(권리구분)이 API마다 자릿수/EX 포함 \n[…]\nrity 해저드를 못 잡는다는\n것을 발견해, 해당 pin 테스트는 리터럴 문자열로 다시 작성했다.\n\ntest_human_ux.py의 HumanChoice 데코레이터 고정 개수를 145 → 170으로\n갱신(트리 순회 재도출, +25).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor(market): etf·elw·gold·program(ka40001/04,ka30001/02/04/05/09…",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T16:45:29Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "2b58489fc983e6dd2191a7d41d8857a50f39859c",
          "body": "두 시트는 같은 매핑이지만 표기까지 같지는 않다. ka10051은 \"금액:0, 수량:1\",\nka10131은 \"0:금액, 1:수량\"으로 라벨과 코드 순서가 반대다. 결론은 맞고 근거\n서술만 과장이었다. 이 규율은 주석을 믿을 수 있어야 성립하므로 정확도를 맞춘다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(comments): AMT_QTY_TP_0_1 주석의 \"character-for-character 동일\" 정정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T16:24:01Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "d479a1891323882ce81535fac9bde042af7f4160",
          "body": "Task 32 — market sector/theme 6개 커맨드의 순수 표기 전환을\nUnreleased에 기록. wire-value fix 없음.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): sector·theme HumanChoice 전환 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T16:16:32Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "40a9ff22e4d932367069abbaf00bb5b97e32823e",
          "body": "…1/ka90001) 숫자코드를 HumanChoice로 전환\n\nsector investor/current/stocks/daily/codes, theme groups 7개 옵션을\nhuman-readable 이름으로 전환. 순수 표기 전환이며 기본 호출 body는\n종전과 동일(wire-value fix 없음, 워크북·kwcli arguments.csv 이중 확인).\n\nsector current/stocks/daily(ka20001/02/09)는 워크북 문구가\ncharacter-for-character 동일해 SECTOR_PRICE_MA\n[…]\n↔ AMT_QTY_TP_1_2, THEME_LOOKUP_KIND\n↔ PRODUCT_TYPE 3건 모두 확인). test_human_ux.py의 HumanChoice 데코레이터\n고정 개수를 138 → 145로 갱신(트리 순회 재도출).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor(market): sector·theme(ka10051/ka20001/ka20002/ka20009/ka1010…",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T16:16:29Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "12c8acbaee221caa27b0123af2c96fb47766093e",
          "body": "주석은 api_id 2개가 공유한다고 적혀 있었지만 실제로는 5곳이다. 공유 범위를\n과소 기재하면, 한 곳을 분리한 뒤 \"이제 하나만 남았다\"고 오해해 나머지를\n조용히 깨뜨릴 수 있다. 다섯 자리를 파일·줄까지 명시했다.\n\nka90009 시트는 \"1:금액(천만), 2:수량(천)\"이라 character-for-character\n동일하다는 서술도 사실과 달랐다. 괄호 안이 응답 단위 주석이라 요청\n코드북은 같다는 점을 대신 적었다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(comments): AMT_QTY_TP_1_2의 공유 범위를 5곳 전부로 정정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T16:03:57Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "fee20359df3ac5b3747096b4961d4625dcfbb3b4",
          "body": "Unreleased에 Non-breaking 항목 추가. 순수 표기 전환이라 Fixed/Breaking\n섹션은 없다 - 일부 옵션이 이전엔 타입 제약 없는 자유 텍스트였다가\nHumanChoice로 좁혀진 점만 별도 문단으로 명시했다(매핑에 있는 값의 동작은\n불변).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): rank ka10042/62/65/98,ka90009 HumanChoice 전환 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T15:50:21Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "931a4101c737e4c7a3cdb5d145fc3a9a83e0e4a5",
          "body": "…자코드를 HumanChoice로 전환\n\nmarket rank 그룹의 마지막 5개 커맨드(net-buyer/same-net-trade/investor-top/\nafterhours-change/foreign-inst)의 숫자 API 코드 옵션을 HumanChoice로\n전환했다. 순수 표기 전환 - 다섯 커맨드 전부 기본 호출 body가 전환 전과\n동일함을 테스트로 고정했다(워크북·kwcli 이중 확인, wire-value fix 없음).\n\nnet-buyer(ka10042)의 date-type/pot-type/sort는 워크북으로 값이\n[…]\nield 부분집합) - 실제 상수를 형제 상위집합으로\n바꿔치기해 신규 테스트가 fail하는지 실측 확인했다.\n\ntest_human_ux.py의 HumanChoice 데코레이터 카운트를 트리 순회로 재도출해\n122->138로 갱신했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor(market): rank 순매수거래원순위~외국인기관매매상위(ka10042/62/65/98,ka90009) 숫…",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T15:50:15Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "60fac1c214d3f0523ccfce3aae92875eff94a340",
          "body": "리뷰어가 superset-closure 스크립트로 발견한 2개 미핀 해저드를 테스트로\n고정한다:\n\n- CREDIT_RATIO_CREDIT_CND(ka10033)는 EXPECTED_CHANGE_CREDIT_CND(ka10029)의\n  진짜 부분집합이었다(exclude-overlimit/short 누락) - rank credit-ratio\n  --credit exclude-overlimit|short 거부 테스트 추가.\n- FOREIGN_CONSECUTIVE_BASE_DATE(ka10035)는 PERIOD_TODAY_PREV_5_60\n\n[…]\nD, FOREIGN_CONSECUTIVE_SIDE, BROKER_TOP_QTY_TYPE,\nBROKER_TOP_SIDE, BROKER_TOP_PERIOD)는 기존 테스트가 이미 핀 고정하고\n있음을 동일한 방법으로 재확인(task-31b-report.md 참고).\n\npytest tests/ -q: 1093 passed (1089 + 4). ruff clean.",
          "is_bot": false,
          "headline": "test(market): Task 31b 리뷰 후속 - ka10033/ka10035 형제 상수 바꿔치기 핀 고정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T15:31:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2d63ab0686305452a775610f393af9c123041a86",
          "body": "ka10027 --vol-cnd(trde_qty_cnd) 기본값 \"0\"→\"0000\" 교정(전송값 변경, breaking)과\nka10027~39 9개 커맨드 28개 옵션의 HumanChoice 전환(비파괴적, 하위호환)을\nUnreleased 섹션에 기록한다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): rank ka10027~39 HumanChoice 전환 + --vol-cnd 기본값 교정 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T15:13:24Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "f7e0fca0015ac6d7a3350a1228615e1cbddb92b8",
          "body": "ka10030/ka10032/ka10038은 Tranche B에서 이미 전환돼 있어 건너뛰고, 나머지\n9개 커맨드(ka10027/29/31/33/34/35/36/37/39)의 코드 계열 옵션 28개를\nHumanChoice로 감쌌다. ka10034/36/37의 dt(기간)는 값이 완전히 동일해\nPERIOD_TODAY_PREV_5_60 하나로 수렴시켰다.\n\nka10027 --vol-cnd(trde_qty_cnd)는 부수적으로 와이어 값 결함도 고쳤다 —\n기존 기본값 raw \"0\"은 4자리 zero-pad 스펙(0000~1000) 어디에\n[…]\n하게 API별로 전부 분리했고(예: RANK_CHANGE_STK_CND vs\nEXPECTED_CHANGE_STK_CND), 그 중 일부는 값이 100% 동일해 어떤 테스트로도\n구분할 수 없다 — 이름 규약과 주석이 유일한 방어선이다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "refactor(market): rank 등락률상위~증권사별매매상위(ka10027~39) 숫자코드를 HumanChoice로 전환",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T15:12:08Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "4d7d124c18e82071fff3a5a0aea9d8d9524e8310",
          "body": "전송 바이트가 바뀌고 종전에 받던 입력을 거부하므로 Unreleased에 기록한다.\n기본값 8건은 의도가 보존되어 Fixed로, 전체 개념이 없는 ka10021/22/23의\n하한 부과와 --vol-type 0 거부는 Breaking으로 분리했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): rank ka10016~23 --vol-type 기본값 교정 + HumanChoice 전환 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T15:00:09Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "dd8bcb3ababd0caeff5c542ce8bd335ab3a36145",
          "body": "stk_cnd/sort_tp/가격조건 31개 자리가 \"정상 값이 맞게 나간다\"는 테스트만\n갖고 있어, 상수를 상위집합 형제로 바꿔치기해도 전부 통과했다. 기존\ntest_rank_vol_type_rejects_name_absent_from_that_api의 모양을 그대로 써서\n세 계열에 거부 테스트 36개를 추가한다.\n\n값 목록은 docs/미국 REST API 문서.xlsx의 각 api_id 시트에서 직접 확인했고\n8개 시트 모두 현재 상수와 일치했다.\n\n교차 바꿔치기 16건을 실제로 넣어 전부 실패하는 것을 확인했다. 바이트가\n동일한 클러스터(*_CREDIT_CND 5개, *_QTY_TYPE_5DIGIT 4개 등)는 테스트로\n구분이 불가능해 이름과 주석이 유일한 방어선으로 남는다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "test(market): rank ka10016~23 형제 상수 바꿔치기 거부 테스트 추가",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T15:00:09Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "d7a29ca413316c0a736845c4e30be9fd480ab1f2",
          "body": "8개 rank 커맨드의 trde_qty_tp 기본값이 raw \"0\"이었는데, 8개 API 어느\n스펙에도 \"0\"은 존재하지 않는다. 워크북 8개 시트와 kwcli arguments.csv를\n교차확인해 API별 스펙 값으로 교정하고 함께 HumanChoice로 전환했다.\n\n8개 전부 Required=Y라 ka10038 dt처럼 키를 생략하는 선택지는 없었다.\nka10016~20은 문서에 \"전체/장시작전\" 값이 있어 의도가 그대로 보존되고,\nka10021~23은 \"전체\" 개념 자체가 없어 사다리 최하단(필터를 가장 적게 거는\n값)을 골랐\n[…]\n86 passed. 8개 커맨드 각각 정확한 기본 body를 고정했고,\nmarket.py만 되돌려 41개가 실제로 실패하는 것을 확인한 뒤 복원했다.\ntest_human_ux 데코레이터 카운트는 트리 순회로 재도출해 86 -> 94.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(market): rank ka10016~23 --vol-type 기본값을 스펙 값으로 교정 + HumanChoice 전환",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T14:46:45Z",
          "body_truncated": true,
          "is_coding_agent": true
        },
        {
          "oid": "9d953532f06a9cc7016613afbe90b6ef0f2e6e37",
          "body": "ntl_tp/high_low_close_tp/stk_cnd/crd_cnd/updown_incls/updown_tp/sort_tp/\ntrde_gold_tp/high_low_tp/flu_tp/tm_tp/pric_cnd/trde_tp/rt_tp/pric_tp 등\n31개 옵션 자리를 신규 상수(_constants.py)와 HumanChoice로 감싸 사람이 읽는\n이름을 받게 했다. 전송값은 그대로 유지(표기만 변경) — 8개 커맨드 전부\n전환 전/후 기본 호출 body가 byte-identical함을 확인.\n\ntrde_qty_tp(--vo\n[…]\nk_cnd/crd_cnd도\nAPI별로 별도 상수로 유지(예: NEW_HIGH_LOW_STK_CND/NEAR_HIGHLOW_STK_CND/\nSURGE_STK_CND).\n\nmarket/human_ux 테스트 48개 추가(기본 body 불변 스모크 + human 옵션 변환 +\n레지스트리), HumanChoice 데코레이터 pinned count 55→86 갱신.",
          "is_bot": false,
          "headline": "refactor(market): rank 신고저가~거래량급증(ka10016~23) 숫자코드를 HumanChoice로 전환",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T14:37:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6c76b21926e695186d935c545b135a8b3de67ad5",
          "body": "v2.11.0 — API 파라미터 감사 (Tranche B)",
          "is_bot": false,
          "headline": "Merge pull request #28 from gejyn14/feature/v2.11.0-api-params",
          "author_name": "Yu Jin An",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T14:05:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "af79845f51267c233185f1366cfd8a6b7e1fb617",
          "body": "README의 금현물 매수 예제가 --type market을 쓰고 있었습니다. 금현물에는\n시장가가 없어 v0.2.1부터 동작하지 않던 예제입니다. 지정가로 바꾸고 이유를\n주석으로 남겼습니다.\n\nExcel은 v2.9.0 이후 갱신이 없어 61개 커밋만큼 밀려 있었습니다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs: 금현물 README 예제 교정 + 명령어 Excel 재생성",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T14:02:30Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "f7a53e2268b48a9c46010b1d0654ca3bd8682657",
          "body": "국내 지정가 주문의 ord_uv 가드 관련 서술을 교정했습니다. kwcli가 배포하는\norder_price_policies.csv에 국내 지정가 --price required가 명시돼 있어,\n\"근거가 없다\"는 기존 설명은 사실과 달랐습니다. 가드 자체는 다음 릴리스로\n미룹니다 — 서버가 빈 값을 거부하므로 잘못된 주문이 나가지는 않습니다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "release: v2.11.0",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:58:35Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "ca9a05ed6629c201736f89128c103902c713a0b5",
          "body": "두 맵을 서로 바꿔 끼워도 기존 891개 테스트가 전부 통과했다. 공유 키의\n전송값이 전부 같아 잘못된 값이 나가지는 않지만, 형제 쌍마다 분리를\n고정해 온 이 브랜치의 방식과 어긋나 비어 있던 자리다.\n\n유일하게 드러나는 차이인 ka10030 전용 200/300으로 양쪽을 고정했다.\nrank volume은 200k/300k를 받아 200/300을 보내야 하고, elw broker-top은\n같은 값을 exit 1로 거부해야 한다. 맵을 맞바꾸면 4개 전부 실패한다.",
          "is_bot": false,
          "headline": "test(market): VOLUME_RANK_QTY_TYPE↔ELW_BROKER_QTY_TYPE 분리 고정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:57:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5b2868a46163682aa1370a680049981d7b686bf7",
          "body": "ka10030 시트가 500 코드를 \"500만주이상\"으로 적어 이웃 항목들의 산술\n패턴과 어긋나는 탓에 500k라는 이름을 \"보수적 선택\"으로만 적어 뒀었다.\n키움 공식 kwcli의 maps/arguments.csv가 rankings today-volume의 같은\n필드를 9개 값 전부 우리와 동일하게(500k=500 포함) 매핑하고 있어 확인됐다.\n\nCHANGELOG에도 같은 유보 문구가 있어 함께 고쳤다.",
          "is_bot": false,
          "headline": "docs(comments): VOLUME_RANK_QTY_TYPE의 500k를 확인된 사실로 격상",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:56:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "984dd68b6f097548bcc53e751de628ab5e87164b",
          "body": "배포되는 주석에 task 번호와 docs/superpowers/plans/... 경로가 남아 있었다.\n그 플랜 저장소는 비공개라 읽는 사람에게 아무 의미가 없다.\n\n내용은 그대로 두고 참조만 걷어냈다. AMT_QTY_TP_0_1의 경우 사라진 C4 링크\n자리에 왜 합치면 안 되는지(키 집합이 같아 조용히 통과하고 극성만 뒤집힌\n값이 나간다)를 직접 적었다. 절대 합치지 말 것 경고는 전부 유지.",
          "is_bot": false,
          "headline": "docs(comments): 소스 주석의 내부 용어·비공개 경로 제거",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:55:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3901a3a1922c3b2bab0e4a0460a51fb91aea70ca",
          "body": "account/market/stock만 손으로 나열하다 보니 order 그룹이 통째로 빠져\n금현물 주문의 --order-type(GOLD_ORDER_TYPES) 2건이 집계에서 누락돼 있었다.\n루트 cli부터 내려가도록 바꿔 그룹이 새로 생겨도 자동으로 잡히게 했고,\n실제 측정값 55로 고정했다. 개수만 맞으면 통과하는 것을 막으려고 금현물\n두 건은 이름으로도 못 박았다.\n\n매핑 레지스트리 테스트에도 데코레이터에 물려 있는데 빠져 있던 13개를\n채웠다 (GOLD_ORDER_TYPES, CREDIT_MARKET, AMT_QTY_TP_0_1, AMT_QTY_TP_1_2,\nINTRADAY_INVESTOR, MIN_TIC_TP 등).",
          "is_bot": false,
          "headline": "test(human-ux): HumanChoice 고정 테스트를 CLI 트리 전체로 확장",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:54:46Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bedcd16af0275b3e76a37a668ae12a8868b0781c",
          "body": "\"dt 키를 빼고 빈 문자열인 채로 전송했다\"는 서술은 브랜치 중간 커밋\n11d1486의 상태였고 857fc98에서 고쳐져 릴리스된 적이 없다. fe289c7의\n실제 코드는 날짜와 함께 dt=\"1\"을 항상 보내 기간 조회가 이기고 있었다.\n결론(breaking)은 그대로 두고 이전 동작 설명만 고쳤다.",
          "is_bot": false,
          "headline": "docs(changelog): broker-by-stock --from/--to 항목의 이전 동작 서술 교정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:53:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "81f1193f6850af0d79261f4dc3caa98e0a94ea65",
          "body": "program time-trend/daily-trend의 --unit·--tick-type과 investor consecutive의\n--period·--net-type은 fe289c7 시점에 전부 type= 없는 자유 텍스트였다.\n매핑 밖 값이 이제 거부되므로 non-breaking이 아니라 breaking이다.\n\nNon-breaking 쪽에 꼬리말로 붙어 있던 축소 고백 두 군데를 걷어내고,\n기존 breaking 축소 목록에 항목 하나로 합쳤다.",
          "is_bot": false,
          "headline": "docs(changelog): 자유 텍스트→enum 축소 4건을 breaking으로 재분류",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:53:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eaf5c1ab764b3c575216c7dd05e6b2836b9719e2",
          "body": "이전 Choice 목록 [\"1\",\"2\"]에서 2는 실제로 amt_qty_tp=\"2\"로 전송됐지만\n새 AMT_QTY_TP_0_1에는 없는 값이라 이제 exit 1이다. 값 집합이 줄었으므로\n옵션이 이미 Choice였더라도 breaking이다.\n\nNon-breaking 쪽에서 \"숫자 코드는 그대로 동작한다\"고 단언하던 문장도\n스펙이 정의한 코드로 한정해 새 breaking 항목과 어긋나지 않게 고쳤다.",
          "is_bot": false,
          "headline": "docs(changelog): ka10131 --amount-qty 2 거부를 breaking으로 기재",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:52:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "44abb6a60b97947550246ac61e963e480a28ba4e",
          "body": "여집합으로 정의하는 방식은 실수를 막지 못하고 비대칭으로 만들 뿐이다.\n새 주문유형을 US_MARKET_TYPES에 추가하는 걸 잊으면 조용히 지정가\n계열로 분류돼 --price를 요구하게 된다 — 즉 시장가 계열 신규 유형이\n정당한 무가격 주문을 거부당하는 쪽으로만 실패한다. 주문 경로에서는\n이 방향이 의도한 선택(과잉 거부가 가격 누락 주문 전송보다 안전)이라는\n점을 주석에 명시했다. 정의 자체는 변경하지 않았다 — 동작 변화 없음.",
          "is_bot": false,
          "headline": "fix(us): US_LIMIT_TYPES 주석의 \"실수 불가능\" 과장 정정",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:29:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c2619ddf0b654ab33b2329a1b31d40c682277584",
          "body": "여섯 개 작업이 각자 이어붙인 Fixed/Breaking/Non-breaking 헤딩이 8번\n반복되던 것을 각 1개로 병합했다. 내용 변경 없이 순서만 정리했다.\n\n- Non-breaking인데 Breaking에 있던 두 항목을 이동:\n  credit inquiry/available의 CREDIT_MARKET 극성 설명(자체 결론이 이미\n  \"하위호환 깨짐 아님\"), program time/daily-trend의 --exchange all\n  추가(순수 확장)\n- bool(strt_dt or end_dt)를 버그로 서술한 Fixed\n[…]\n 확인)\" 제거,\n  영향받는 명령 목록에 --dry-run 추가(order buy NVDA 10 --type limit\n  --dry-run도 이제 exit 1)\n\nCLI로 직접 실행해 확인: --exchange all 추가는 기본 호출 body 불변,\ncredit 커맨드 둘 다 이전엔 옵션이 하나도 없었음, --dry-run이 가격\n가드보다 먼저 걸림.",
          "is_bot": false,
          "headline": "docs(changelog): Unreleased 섹션 정리 - 중복 헤딩 병합 + 오분류 항목 재배치",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:29:39Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "08724343c2bd03a4a0dad60613bd42a96864e220",
          "body": "Breaking: --price 없이 지정가 계열을 호출하던 스크립트는 이제 exit 1 —\n다만 그 호출은 원래 정상 동작한 적이 없었다(ord_uv=\"\" 전송). 국내 경로는\n스펙 근거 부족으로 의도적으로 그대로 두었다는 점도 함께 기록.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "docs(changelog): 미국주식 지정가 계열 --price 누락 가드 기록",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:15:45Z",
          "body_truncated": false,
          "is_coding_agent": true
        },
        {
          "oid": "25cbb53a6b383bbde82141649242f281a2d1251d",
          "body": "지정가 계열(limit/vwap-limit/twap-limit/loc/stop-limit)에서 --price 없이\n주문하면 ord_uv=\"\"로 조용히 전송되고 있었다. ust20000/ust20001 스펙:\n\"trde_tp가 00(지정가),30(LOC)...인 경우 필수 입력\". US_MARKET_TYPES의\n여집합으로 US_LIMIT_TYPES를 신설하고, _validate_us_type에 반대 방향 가드를\n추가했다. 국내(kt10000/kt10001)는 조건부 필수 문구가 스펙에 없어 대상에서\n제외했다.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>",
          "is_bot": false,
          "headline": "fix(us): 지정가 계열 주문의 --price 누락 가드 추가 (ust20000/ust20001)",
          "author_name": "Yu Jin",
          "author_login": "gejyn14",
          "committed_at": "2026-07-19T13:15:41Z",
          "body_truncated": false,
          "is_coding_agent": true
        }
      ],
      "releases_count": 1,
      "commits_last_year": 417,
      "latest_release_at": "2026-07-16T17:59:53Z",
      "latest_release_tag": "v2.5.0",
      "releases_from_tags": false,
      "days_since_last_push": 8,
      "active_weeks_last_year": 5,
      "days_since_latest_release": 12,
      "mean_days_between_releases": null
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": true,
      "health_percentage": 100,
      "has_issue_template": false,
      "has_code_of_conduct": true,
      "has_pull_request_template": true
    },
    "ecosystem": {
      "packages": [
        {
          "name": "kiwoom-cli",
          "exists": true,
          "license": "LicenseRef-kiwoom-cli-Source-Available-1.0",
          "keywords": [
            "kiwoom",
            "stock",
            "trading",
            "cli",
            "korea",
            "finance",
            "Development Status :: 5 - Production/Stable",
            "Environment :: Console",
            "Intended Audience :: Financial and Insurance Industry",
            "License :: Other/Proprietary License",
            "Programming Language :: Python :: 3.10",
            "Programming Language :: Python :: 3.11",
            "Programming Language :: Python :: 3.12",
            "Programming Language :: Python :: 3.13",
            "Topic :: Office/Business :: Financial :: Investment"
          ],
          "ecosystem": "pypi",
          "matches_repo": true,
          "registry_url": "https://pypi.org/project/kiwoom-cli/",
          "is_deprecated": false,
          "latest_version": "2.14.0",
          "repository_url": "https://github.com/gejyn14/kiwoom-cli",
          "versions_count": 49,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": 2558,
          "first_published_at": "2026-03-31T05:37:51.599264Z",
          "latest_published_at": "2026-07-20T14:48:06.203842Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 8
        }
      ]
    },
    "popularity": {
      "forks": 1,
      "stars": 9,
      "watchers": 0,
      "fork_history": {
        "days": [
          {
            "date": "2026-05-03",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 1,
        "total_forks": 1
      },
      "star_history": null,
      "open_issues_and_prs": 0
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [],
      "toolchain_manifests": [],
      "largest_source_bytes": 117207,
      "source_files_sampled": 66,
      "oversized_source_files": 5,
      "agent_instruction_files": [
        "AGENTS.md"
      ],
      "agent_instruction_max_bytes": 30023
    },
    "dependencies": {
      "manifests": [
        "pyproject.toml"
      ],
      "advisories": {
        "error": null,
        "scope": "published_package",
        "source": "osv",
        "findings": [],
        "collected": true,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 23,
        "malicious_count": 0,
        "assessed_package": "pypi:kiwoom-cli@2.14.0",
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "pypi"
      ],
      "dependencies": [
        {
          "name": "click",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=8.1"
        },
        {
          "name": "httpx",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=0.27"
        },
        {
          "name": "rich",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=13.0"
        },
        {
          "name": "tomli",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=2.0"
        },
        {
          "name": "tomli-w",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=1.0"
        },
        {
          "name": "keyring",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=25.0"
        },
        {
          "name": "websockets",
          "manifest": "pyproject.toml",
          "ecosystem": "pypi",
          "version_constraint": ">=12.0"
        }
      ],
      "all_dependencies": {
        "error": null,
        "source": "github-sbom",
        "packages": [
          {
            "name": "click",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "httpx",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "keyring",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "rich",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "tomli-w",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "websockets",
            "direct": true,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "hypothesis",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "openpyxl",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pytest",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-cov",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "pytest-httpx",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "ruff",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          },
          {
            "name": "setuptools",
            "direct": false,
            "version": null,
            "ecosystem": "pypi"
          }
        ],
        "collected": true,
        "truncated": false,
        "total_count": 13,
        "direct_count": 6,
        "indirect_count": 7
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 32,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 2
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "gejyn14",
          "commits": 415,
          "avatar_url": "https://avatars.githubusercontent.com/u/72906303?v=4"
        },
        {
          "type": "User",
          "login": "claude",
          "commits": 2,
          "avatar_url": "https://avatars.githubusercontent.com/u/81847?v=4"
        }
      ],
      "contributors_sampled": 2,
      "top_contributor_share": 0.995
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "publish.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 3,
            "reason": "branch protection is not maximal on development and all release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "3 out of 3 merged PRs checked by a CI test -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/4 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 9,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 10,
            "reason": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 0,
            "reason": "dependency not pinned by hash detected -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 10,
            "reason": "SAST tool is run on all commits",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 10,
            "reason": "security policy file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": null,
            "reason": "no releases found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 10,
            "reason": "GitHub workflow tokens follow principle of least privilege",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 10,
            "reason": "0 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "4effecade46d3bded40a418aae3316c5b3c82fe1",
        "ran_at": "2026-07-29T06:56:16Z",
        "aggregate_score": 6.4,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": true,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-22T17:52:52Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": "2026-07-20T15:03:15Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/gejyn14/kiwoom-cli",
    "host": "github.com",
    "name": "kiwoom-cli",
    "owner": "gejyn14"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 64,
      "inputs": {
        "security": 70,
        "vitality": 70,
        "community": 50,
        "governance": 51,
        "engineering": 81
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 70,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 60,
            "inputs": {
              "commits_last_year": 417,
              "human_commit_share": 1,
              "days_since_last_push": 8,
              "active_weeks_last_year": 5
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 8 days ago",
                "points": 28.8,
                "status": "partial",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 8
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "5/52 weeks with commits",
                "points": 3.5,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "417 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 417
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "good",
            "name": "Release discipline",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 84,
            "inputs": {
              "releases_count": 1,
              "latest_release_tag": "v2.5.0",
              "releases_from_tags": false,
              "days_since_latest_release": 12,
              "mean_days_between_releases": null
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "1 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 12 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 12
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "cadence unknown (single release)",
                "points": 12.6,
                "status": "partial",
                "details": [
                  {
                    "code": "release_cadence_unknown",
                    "params": {}
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "moderate",
        "name": "Community & Adoption",
        "value": 50,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 15,
            "inputs": {
              "forks": 1,
              "stars": 9,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "9 stars",
                "points": 14.6,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 9
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "1 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "excellent",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 86,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": true,
              "has_issue_template": false,
              "has_code_of_conduct": true,
              "has_pull_request_template": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file present, not a recognized license",
                "points": 16.9,
                "status": "partial",
                "details": [
                  {
                    "code": "license_custom",
                    "params": {}
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 18,
                "status": "met",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 13.5,
                "status": "met",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 6.3,
                "status": "met",
                "details": [],
                "max_points": 6.3
              }
            ]
          },
          {
            "key": "ecosystem_adoption",
            "band": "moderate",
            "name": "Ecosystem adoption (downloads)",
            "note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "registry_dependents"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 57,
            "inputs": {
              "packages": [
                "kiwoom-cli"
              ],
              "dependents": null,
              "ecosystems": "pypi",
              "total_downloads": null,
              "monthly_downloads": 2558
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "2,558 downloads/month across pypi",
                "points": 45.4,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 2558,
                      "ecosystems": "pypi"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 51,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 12,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 2,
              "top_contributor_share": 0.995
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0.1,
                "status": "partial",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "2 contributors",
                "points": 2.7,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 2
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "moderate",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 68,
            "inputs": {
              "merged_prs": 32,
              "open_issues": 0,
              "closed_issues": 0,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 2
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "no issues or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_issues_or_data",
                    "params": {}
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "32/34 decided PRs merged",
                "points": 36,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 32,
                      "decided": 34
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/4 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "at_risk",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 41,
            "inputs": {
              "followers": 4,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "gejyn14",
              "public_repos": 6,
              "account_age_days": 2113
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "4 followers of gejyn14",
                "points": 5,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 4,
                      "login": "gejyn14"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "6 public repos, account ~5 yr old",
                "points": 17.7,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 6
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 5
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "kiwoom-cli"
              ],
              "ecosystems": "pypi",
              "any_deprecated": false,
              "min_days_since_publish": 8
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on pypi",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "pypi"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 8 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 8
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "49 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 49
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "good",
        "name": "Engineering Quality",
        "value": 81,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 68,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": false,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "2 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 2
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "3 out of 3 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "excellent",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "topics": [
                "ai-agents",
                "cli",
                "kiwoom",
                "korea",
                "korean-stocks",
                "python",
                "quant",
                "rest-api",
                "samsung",
                "skhynix",
                "stock-trading",
                "trading",
                "algorithmic-trading",
                "auto-trading"
              ],
              "has_wiki": true,
              "homepage": "https://github.com/gejyn14/kiwoom-cli/wiki",
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": "https://github.com/gejyn14/kiwoom-cli/wiki",
                "points": 15,
                "status": "met",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": "14 topics",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "topics_count",
                    "params": {
                      "count": 14
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "good",
        "name": "Security",
        "value": 70,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "moderate",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 63,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 17,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 1,
              "scorecard_aggregate": 6.4
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection is not maximal on development and all release branches",
                "points": 2.2,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "3 out of 3 merged PRs checked by a CI test -- score normalized to 10",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/4 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.2,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is run on all commits",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "GitHub workflow tokens follow principle of least privilege",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "0 existing vulnerabilities detected",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "dependency_advisories",
            "band": "excellent",
            "name": "Dependency advisories",
            "note": "Excluded from scoring (no data or not applicable): No advisories left outstanding. Remaining weights renormalized. Matched the pypi:kiwoom-cli@2.14.0 runtime dependency closure — what installing the published package pulls in — 23 packages. Reachability is not analyzed.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "no_advisories_left_outstanding"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              },
              {
                "code": "advisories_scope_published",
                "params": {
                  "package": "pypi:kiwoom-cli@2.14.0",
                  "assessed": 23
                }
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "source": "osv",
              "advisories": 0,
              "affected_packages": 0,
              "assessed_packages": 23,
              "unassessed_packages": 0,
              "affected_by_severity": "none",
              "direct_affected_packages": 0
            },
            "components": [
              {
                "key": "direct_dependencies_free_of_known_advisories",
                "name": "Direct dependencies free of known advisories",
                "detail": "no direct dependency carries a known advisory",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "no_direct_advisories",
                    "params": {}
                  }
                ],
                "max_points": 35
              },
              {
                "key": "indirect_dependencies_free_of_known_advisories",
                "name": "Indirect dependencies free of known advisories",
                "detail": "no indirect dependency carries a known advisory",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "no_indirect_advisories",
                    "params": {}
                  }
                ],
                "max_points": 25
              },
              {
                "key": "no_advisories_left_outstanding",
                "name": "No advisories left outstanding",
                "detail": "no advisory carries a publication date",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "advisories_no_publication_date",
                    "params": {}
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "malicious_dependencies",
            "band": "excellent",
            "name": "Malicious dependencies",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "source": "osv",
              "meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
              "packages": [],
              "red_flag": false,
              "assessed_packages": 23,
              "malicious_packages": 0,
              "direct_malicious_packages": 0,
              "withdrawn_malicious_packages": 0,
              "installable_malicious_packages": 0
            },
            "components": [
              {
                "key": "no_dependency_reported_as_a_malicious_package",
                "name": "No dependency reported as a malicious package",
                "detail": "no dependency is reported as a malicious package",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "no_malicious_dependencies",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "excellent",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "review_only_matches": 0,
              "assessed_self_published_locations": 2
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 54,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "excellent",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 85,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 1,
              "agent_instruction_files": [
                "AGENTS.md"
              ],
              "agent_instruction_max_bytes": 30023
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "AGENTS.md",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "AGENTS.md"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "100 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 100,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "at_risk",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 32,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [],
              "has_dockerfile": false,
              "typed_language": false,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [],
              "agent_commit_share": 0.64,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "64 of the last 100 commits agent-authored or agent-credited",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "agent_authored_commits",
                    "params": {
                      "count": 64,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "moderate",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 51,
            "inputs": {
              "primary_language": "Python",
              "largest_source_bytes": 117207,
              "source_files_sampled": 66,
              "oversized_source_files": 5
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Python without a type-check config",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_typecheck_config_language",
                    "params": {
                      "language": "Python"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "5/66 source files over 60KB",
                "points": 50.8,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 66,
                      "oversized": 5
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-29T06:56:31.833875Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/g/gejyn14/kiwoom-cli.svg",
  "full_name": "gejyn14/kiwoom-cli",
  "license_state": "custom",
  "license_spdx": null
}

评分是信号,而非担保。 评分反映的是 GitHub 上公开可见的实践——不是代码审计,也不是安全保证。

缺失数据将被剔除并重新归一化权重,绝不按零分计。方法论已版本化并公开:指标 v1.13.0、模式 v0.27.0—— 完整方法论 · 指标知识库.

单项结果在整体记录中的位置: 汇总统计PyPI.