-
-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathpyproject.toml
More file actions
545 lines (514 loc) · 23.4 KB
/
Copy pathpyproject.toml
File metadata and controls
545 lines (514 loc) · 23.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
[project]
name = "dartlab"
version = "0.11.0"
description = "One stock code → full company story. Korean DART & US SEC EDGAR filings as structured Python data."
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">=3.12"
authors = [
{name = "eddmpython"}
]
keywords = [
"dart",
"edgar",
"sec",
"financial-statements",
"korea",
"disclosure",
"accounting",
"polars",
"panel",
"mcp",
"ai-analysis",
"annual-report",
"10-k",
"xbrl",
"전자공시",
"재무제표",
"사업보고서",
"공시분석",
"다트",
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Intended Audience :: Financial and Insurance Industry",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Office/Business :: Financial",
"Topic :: Office/Business :: Financial :: Accounting",
"Topic :: Office/Business :: Financial :: Investment",
"Topic :: Scientific/Engineering :: Information Analysis",
"Natural Language :: Korean",
"Natural Language :: English",
"Typing :: Typed",
]
dependencies = [
# core (pyodide 호환 - polars, numpy, bs4, lxml은 pyodide 빌트인)
# core
"beautifulsoup4>=4.14.3,<5",
"diff-match-patch>=20230430",
"openpyxl>=3.1.5,<4",
# C 확장 - 일반: PyPI, pyodide: 빌트인 (버전 무관)
"lxml>=6.0.2,<7; sys_platform != 'emscripten'",
"lxml; sys_platform == 'emscripten'",
"polars>=1.0.0,<2; sys_platform != 'emscripten'",
"polars; sys_platform == 'emscripten'",
# Windows는 시스템 IANA timezone DB를 제공하지 않는다. Polars가 UTC datetime을
# Python 값으로 내보낼 때 clean wheel 환경에서도 ZoneInfo("UTC")가 동작해야 한다.
"tzdata>=2025.2; sys_platform == 'win32'",
# M6: cross-company × multi-table aggregation 의 OOC god mode - Polars
# LazyFrame ↔ DuckDB relation Arrow zero-copy. 환경변수
# DARTLAB_CROSS_SCAN_ENGINE=duckdb 또는 large dataset 검출 시 자동 fallback.
"duckdb>=1.0,<2; sys_platform != 'emscripten'",
"cryptography>=46,<51; sys_platform != 'emscripten'",
"filelock>=3.20,<4; sys_platform != 'emscripten'",
"keyring>=25.7,<26; sys_platform != 'emscripten' and sys_platform != 'win32'",
"numpy>=1.26.0,<3; sys_platform != 'emscripten'",
"numpy; sys_platform == 'emscripten'",
# parquet fallback / pyodide bridge. pyodide 는 빌트인.
"pyarrow>=17,<26; sys_platform != 'emscripten'",
"pyarrow; sys_platform == 'emscripten'",
"zstandard>=0.23,<1; sys_platform != 'emscripten'",
# network
"httpx>=0.28.1,<1; sys_platform != 'emscripten'",
"httpx; sys_platform == 'emscripten'",
"huggingface-hub>=0.20.0,<2; sys_platform != 'emscripten'",
"rich>=14.3.3,<16; sys_platform != 'emscripten'",
"rich; sys_platform == 'emscripten'",
# ai providers (pyodide 불필요)
"openai>=1.0.0,<3; sys_platform != 'emscripten'",
# server (pyodide 불필요)
"fastapi>=0.135.1,<1; sys_platform != 'emscripten'",
"uvicorn[standard]>=0.30.0,<1; sys_platform != 'emscripten'",
"sse-starlette>=2.0.0,<4; sys_platform != 'emscripten'",
"mcp[cli]>=1.0,!=1.27.1,<2.0.1; sys_platform != 'emscripten'", # 1.27.1 Linux wheel 의 mcp.server namespace 회귀 - CI Fast 차단 (2026-05-17)
"qrcode>=7.0,<9; sys_platform != 'emscripten'",
"PyYAML>=6.0.0,<7; sys_platform != 'emscripten'",
# viz
"plotly>=5.0.0,<7; sys_platform != 'emscripten'",
# Universe local checkpoint canonical JSON codec
"msgspec>=0.21.1,<1; sys_platform != 'emscripten'",
]
[project.scripts]
dartlab = "dartlab.cli.main:main"
[project.entry-points."dartlab.plugins"]
[project.urls]
Homepage = "https://eddmpython.github.io/dartlab/"
Repository = "https://github.com/eddmpython/dartlab"
Documentation = "https://eddmpython.github.io/dartlab/docs/"
Issues = "https://github.com/eddmpython/dartlab/issues"
Changelog = "https://github.com/eddmpython/dartlab/releases"
Desktop = "https://github.com/eddmpython/dartlab-desktop/releases/latest/download/DartLab.exe"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/dartlab"]
# include 는 .gitignore 규칙을 override - CI 에서 python -m build 가 .gitignore 의
# 'data/' 루트-미지정 패턴에 걸려 src/dartlab/*/data/ 를 wheel 에서 빠뜨리는
# packaging 사고 방어. artifacts 만으로는 불충분함이 실증됨 (2026-04-19).
include = [
"src/dartlab/**/*.py",
"src/dartlab/**/*.md",
"src/dartlab/**/*.yaml",
"src/dartlab/**/*.yml",
"src/dartlab/**/*.json",
"src/dartlab/**/*.toml",
"src/dartlab/**/*.parquet",
"src/dartlab/**/py.typed",
]
artifacts = [
"src/dartlab/ui/build/**",
"src/dartlab/**/*.json",
"src/dartlab/**/*.toml",
"src/dartlab/**/*.parquet",
"src/dartlab/py.typed",
]
exclude = [
"**/_reference/**",
"**/_backup/**",
"src/dartlab/engines/edinet/**",
"src/dartlab/engines/esg/**",
"src/dartlab/engines/event/**",
"src/dartlab/engines/supply/**",
"src/dartlab/engines/watch/**",
]
[tool.hatch.build.targets.sdist]
# wheel include 와 동일 패턴으로 맞춰 산출물 drift 방지
include = [
"src/dartlab/**/*.py",
"src/dartlab/**/*.md",
"src/dartlab/**/*.yaml",
"src/dartlab/**/*.yml",
"src/dartlab/**/*.json",
"src/dartlab/**/*.toml",
"src/dartlab/**/*.parquet",
"src/dartlab/**/py.typed",
"src/dartlab/ui/build/**",
"README.md",
"LICENSE",
]
# ⚠ sdist 에도 artifacts 필수 (2026-07-26 사고 class). `python -m build` 는 sdist 를 먼저 굽고
# *그 sdist 를 풀어서* wheel 을 빌드한다. 그래서 sdist 에서 빠진 파일은 wheel 에서도 사라진다.
# `include` 는 positive filter 일 뿐이라 .gitignore(VCS) 배제를 이기지 못한다. .gitignore 에
# `src/dartlab/ui/build/` 가 추가된 순간(0.10.8~0.10.9) sdist 가 UI 를 통째로 떨궜고, PyPI wheel
# 0.10.9 의 dartlab/ui/** 가 0 개가 되어 데스크탑 런처가 "UI 빌드 파일(index.html) 없음" 으로
# 기동 불능이 됐다. VCS 배제를 이기는 유일한 키가 artifacts 다. wheel 타깃과 반드시 동기 유지.
artifacts = [
"src/dartlab/ui/build/**",
"src/dartlab/**/*.json",
"src/dartlab/**/*.toml",
"src/dartlab/**/*.parquet",
"src/dartlab/py.typed",
]
exclude = [
"**/_reference/**",
"**/_backup/**",
"src/dartlab/engines/edinet/**",
"src/dartlab/engines/esg/**",
"src/dartlab/engines/event/**",
"src/dartlab/engines/supply/**",
"src/dartlab/engines/watch/**",
]
[tool.ruff]
target-version = "py312"
line-length = 120
exclude = ["experiments", "*/_reference"]
[tool.ruff.lint]
select = ["E", "F", "I"]
# F401 = unused import - __init__.py 의 의도적 re-export 패턴이 dartlab 전반에 다수 (camelCase codemod 후 더 늘어남).
# 진짜 unused import 는 deptry / vulture 가 catch.
ignore = ["E402", "E501", "E741", "F841", "F401"]
[tool.pytest.ini_options]
testpaths = ["tests"]
# ⚠ `--import-mode=importlib` 는 rootdir 을 sys.path 에 넣지 않는다. 그래서 spawn 자식이
# target 을 unpickle 할 때 `tests` 패키지를 못 찾고 ModuleNotFoundError 로 죽는다.
# 로컬 `python -m pytest` 는 CWD 가 sys.path[0] 이라 우연히 통과했고 CI 는 콘솔 스크립트라
# 실패했다. multiprocessing 은 부모 sys.path 를 preparation data 로 자식에 전달하므로
# 여기서 한 번 넣으면 두 경로가 같아진다. 제거하면 spawn 회귀 5 건이 CI 에서만 되살아난다.
pythonpath = ["."]
python_files = ["test_*.py", "test*.py", "bench_*.py", "bench*.py"]
python_classes = ["Test*"]
python_functions = ["test_*", "test*"]
addopts = "-v --tb=short --import-mode=importlib"
asyncio_mode = "auto"
# Hypothesis ghostwriter draft 폴더는 자동 수집 제외 - 명시 호출 시에만 실행.
# (`tests/_drafts/` 안 fuzz draft 는 사용자 review 후 정식 위치로 이동.)
# pytest default (`*.egg .* CVS _darcs {arch} dist node_modules venv`) + `_drafts` 추가.
norecursedirs = ["*.egg", ".*", "CVS", "_darcs", "{arch}", "dist", "node_modules", "venv", "_drafts"]
markers = [
"requires_data: 로컬 parquet 데이터 필요 (CI에서 skip)",
"unit: 순수 로직/mock만 - 데이터 로드 없음, 병렬 안전",
"integration: Company 1개 로딩 필요 - 중간 무게",
"heavy: 대량 데이터 로드 - 단독 실행 필수",
"realData: 엔진별 실제 데이터 스모크 - HF parquet + 실제 파이프라인 실행 (schema drift/silent None 방어)",
"freshInstall: 빈 캐시 → 자동 다운로드 경로 검증 (외부 사용자 첫 호출 시나리오)",
"network: 외부 HTTP API 호출 (HF/DART/EDGAR/FRED) - transient 실패 재시도 대상",
"slow: 실행 30초 이상 - PR fast path 에서 제외",
# flaky 는 pytest-rerunfailures 가 설치된 환경에서 작동. 미설치 환경에선 무시.
# 미등록 시 PytestUnknownMarkWarning - 본 항목으로 warning 차단.
"flaky: transient 실패 자동 재시도 (pytest-rerunfailures)",
# ghostwriter 자동 생성 draft (사용자 review 전) - `tests/_drafts/` 안 한정.
"draft: Hypothesis ghostwriter 자동 draft - 정식 위치 이동 전 시범 검증",
# AI agent eval - LLM 호출 + judge 채점. 운영자 트리거 또는 nightly.
"eval: AI agent LLM 출력 회귀 (외부 API 호출, 비용 발생)",
# mutation testing 메타 마커.
"mutation: mutmut 대상 표시 - 잘못된 mutant 가 잡혀야 통과",
# metamorphic - 변환 후 보존 속성 검증 (수치 분석에 oracle 부재 시).
"metamorphic: 변환 후 속성 보존 (ranking shift, 단위 변환, 통화 환산)",
# vcr - record-replay 카세트 사용 (외부 HTTP mock).
"vcr: HTTP record-replay 카세트 사용 (네트워크 없이 회귀 검증)",
# Phase A - ETL 단계 검증 (parquet row group sort + IPC mirror 빌더).
"etl: ETL 산출물 검증 (parquet row group 메타, IPC mirror 동기성)",
# Phase B/D - 메모리 회귀 가드 (RSS 측정).
"memory: RSS 회귀 가드 - Company 1개 분석 시 peak RSS 검증",
# T3-3 - pytest-xdist serial 강제. Company / OOM 위험 테스트는 단일 worker 에서만.
"serial: pytest-xdist 병렬 제외 (Company 인스턴스 / Polars 힙 OOM 위험 / 전역 상태 변경)",
# 마스터 플랜 v2 트랙 7 PR-M5 - MCP 출시 자동 smoke. CI Fast 에 등록.
"mcpDogfood: MCP 출시 가드 - stdio + Streamable HTTP transport 통합 dispatch 검증",
]
# ════════════════════════════════════════════════════════════════════
# Mutation testing (mutmut) - Track 5
# ────────────────────────────────────────────────────────────────────
# 부분 적용: Polars/numpy native 호출 비중 큰 모듈은 mutant 의미 없음 (core/ 외 제외).
# 실행: uv run mutmut run # 약 30-60 분
# 결과: uv run mutmut results
# 목표: mutation score ≥ 80%
[tool.mutmut]
# T6-2 - paths 3 → 12 → 30 확장. 1.0.0 게이트 30 모듈 mutation score ≥ 80%.
paths_to_mutate = [
# core/ - 기존 + sprint 보강 (7)
"src/dartlab/core/formatting.py",
"src/dartlab/core/cache.py",
"src/dartlab/core/naming/__init__.py",
"src/dartlab/core/decimal.py",
"src/dartlab/core/random.py",
"src/dartlab/core/types.py",
"src/dartlab/core/utils.py",
"src/dartlab/core/dataAudit.py",
"src/dartlab/core/secrets.py",
"src/dartlab/core/credentialLifecycle.py",
"src/dartlab/core/plugins.py",
"src/dartlab/core/ratios.py",
# analysis/ - 재무 계산 (확장 4)
"src/dartlab/analysis/ratios.py",
"src/dartlab/analysis/cashflow.py",
"src/dartlab/analysis/financial/ratios.py",
"src/dartlab/analysis/growth.py",
# credit/ - Z-score + zone (3)
"src/dartlab/credit/altman.py",
"src/dartlab/credit/beneish.py",
"src/dartlab/credit/zone.py",
# quant/ - factor + portfolio (3)
"src/dartlab/quant/factors.py",
"src/dartlab/quant/foreignFlow.py",
"src/dartlab/quant/portfolio/mapping.py",
# macro/ - cycle + sector (2)
"src/dartlab/macro/cycle.py",
"src/dartlab/macro/sectorRotation.py",
# industry/ - peer + momentum (2)
"src/dartlab/industry/peerMatrix.py",
"src/dartlab/industry/sectorMomentum.py",
# scan/ - top recipe (3)
"src/dartlab/scan/foreignFlow.py",
"src/dartlab/scan/scanRatio.py",
"src/dartlab/scan/disclosure.py",
# synth/ - scenario (1)
"src/dartlab/synth/scenarioMatch.py",
# mappers / reference (1)
"src/dartlab/reference/mapper.py",
]
tests_dir = "tests/"
runner = "uv run python -X utf8 -m pytest -x -m unit --no-cov -q"
# CI 통합은 주간 cron - 매 PR fail gate 는 아님 (시간 비용 큼).
# 신규 path 가 존재하지 않는 경우 mutmut 가 skip - pyproject 가 ground truth.
[tool.coverage.run]
source = ["dartlab"]
# omit - coverage 90% 의 "90%" 는 "business logic 기준" 이 되도록 thin boundary /
# rendering / external-adapter / experimental 코드를 제외. 이 코드들은 realData
# smoke + wheel-smoke + integration 테스트로 간접 검증되거나 테스트 ROI 가 낮음.
omit = [
# 기존 omit
"src/dartlab/engines/ai/providers/*",
"src/dartlab/review/*",
# CLI - 얕은 argparse 래퍼. cli_e2e.py 가 핵심 경로 커버.
"src/dartlab/cli/commands/*",
"src/dartlab/cli/main.py",
"src/dartlab/cli/parser.py",
"src/dartlab/cli/rendering.py",
"src/dartlab/cli/services/*",
# 서버 API - HTTP 핸들러, realData end-to-end 로 간접 검증.
"src/dartlab/server/api/*",
"src/dartlab/server/web.py",
# 외부 API 클라이언트 - 네트워크 의존, 모킹 ROI 낮음.
"src/dartlab/providers/dart/openapi/*",
"src/dartlab/providers/edgar/openapi/*",
"src/dartlab/providers/edinet/openapi/*",
# 메신저 어댑터 - Slack/Telegram/Discord SDK 모킹 ROI 낮음.
"src/dartlab/channel/adapters/*",
"src/dartlab/channel/devtunnel.py",
# 시각화 - Plotly/차트 렌더링, snapshot 테스트 영역.
"src/dartlab/viz/charts/*",
"src/dartlab/viz/generators.py",
"src/dartlab/viz/plotly.py",
# 개발자 전용 - 사용자 미접근.
"src/dartlab/ai/audit/*",
"src/dartlab/mcp/*",
# 얕은 display/AI helper 래퍼 - great_tables/IPython 옵셔널.
"src/dartlab/viz/display/*",
"src/dartlab/viz/network.py",
# 실험/레거시 - 사용 여부 재평가 필요, 현재 0% 모듈.
"src/dartlab/analysis/financial/research/*",
"**/_reference/*",
"**/_backup/*",
]
[tool.coverage.report]
show_missing = true
skip_empty = true
exclude_lines = [
"pragma: no cover",
"if __name__",
"raise NotImplementedError",
]
# Coverage ratchet - omit 확대 후 추정 41.5% 기준 40 에서 시작.
# 경로 B (strategic omit + business logic 집중 테스트) 진행 중.
# 단계 계획: 40 → 50 → 60 → 70 → 80 → 90 (omitted-adjusted). PR 마다 하락 시 CI FAIL.
# CI 에서 `pytest --cov-fail-under` 플래그로 실제 강제.
fail_under = 40
[tool.pyright]
pythonVersion = "3.12"
typeCheckingMode = "basic"
include = ["src/dartlab"]
exclude = [
"src/dartlab/engines/ai/providers/**",
"ui/**",
"experiments/**",
]
reportMissingTypeStubs = false
reportUnknownParameterType = false
reportUnknownMemberType = false
reportUnknownVariableType = false
[tool.bandit]
exclude_dirs = ["experiments", "tests"]
skips = ["B101"]
[tool.deptry]
# 옵셔널 통합 의존성 - 사용자가 별도 설치할 때만 동작 (런타임 ImportError 가드 있음)
extend_exclude = [
"src/dartlab/.*/_reference/.*", # 학습/실험 코드, 런타임 미사용
]
[tool.deptry.per_rule_ignores]
DEP001 = [
# ── channel 어댑터 (외부 메신저 옵셔널) ──
"discord", "slack_bolt", "telegram",
# ── CLI 인터랙티브 옵셔널 ──
"prompt_toolkit",
# ── display 옵셔널 ──
"great_tables", "itables", "IPython",
# ── gather 옵셔널 ──
"FinanceDataReader", "tavily",
# ── _reference 학습/실험 ──
"agents", "owlready2", "rapidfuzz", "edgar",
# ── transitive deps (다른 패키지가 끌어옴) ──
"dotenv", # python-dotenv
"google", # google-genai
"yaml", # pyyaml
"bs4", # beautifulsoup4
"starlette", # fastapi가 끌어옴
"pydantic", # fastapi가 끌어옴
]
DEP002 = [
"beautifulsoup4", # bs4 직접 import
]
[dependency-groups]
dev = [
"build>=1.4.0",
"hatchling>=1.29.0",
"hypothesis>=6.100.0",
"import-linter>=2.0", # tests/audit/cycleScan.py + pyproject [tool.importlinter] - 레이어 단방향 강제 (P0 가드)
"libcst>=1.8.6",
"networkx[default]>=3.0", # scripts/build/buildIndustryMap.py - Kamada-Kawai+PageRank (default extra: scipy 포함)
"pillow>=12.3.0",
"pre-commit>=4.0.0",
"pyright>=1.1.0",
"pytest>=9.0.2",
"pytest-asyncio>=0.24.0",
"pytest-benchmark>=5.0.0",
"pytest-cov>=6.0.0",
"syrupy>=4.7.0", # CLI 출력 (rich Console export_text) snapshot 동결 - 본 SSOT 통합 acceptance test
"pandera[polars]>=0.29.0,!=0.32.0,<0.33", # raw finance/report/docs frame 계약. gather endpoint silent drift 차단. 0.32.0 만 배제: polars 백엔드가 pa.Field(isin=...) 빌트인 체크를 등록하지 않아 class 정의 시점에 KeyError. 0.32.1 에서 수정 확인(2026-07-10 실측).
"mutmut>=3.0.0", # mutation testing - "테스트가 깨진 코드 잡나" 측정. core/ 한정 (Polars native 모듈 제외).
"vcrpy>=6.0.0", # HTTP record-replay - providers/dart, providers/edgar 외부 호출 회귀.
"radon>=6.0.0",
"rope>=1.14.0",
"vulture>=2.0",
]
# ════════════════════════════════════════════════════════════════════
# Import-linter - 레이어 단방향 강제 + L2 양방향 cycle 차단 (P0 가드)
# ────────────────────────────────────────────────────────────────────
# 정책 SSOT: src/dartlab/skills/specs/operation/architecture.md
# 1. 상하 단방향: L0 ← L1 ← L1.5 ← L2 ← L2.5 dataHub ← L3 ← L4 (이 contract)
# 2. L2 동급 단방향 허용 (sibling import OK)
# 3. L2 양방향 순환 절대금지 (별도: tests/audit/cycleScan.py)
# ════════════════════════════════════════════════════════════════════
[tool.importlinter]
root_package = "dartlab"
# F4 강행 - contract 활성화 + 핵심 chain link 8 곳 importlib 변환.
# 잔존 위반 은 후속 0.10.x 에서 점진 inversion. 본 phase 는 가드 활성 + 기반 정리.
# CI fast 의 lint-imports step 은 continue-on-error(`|| pass`) - *의도된 advisory* (숨은 fail 아님).
# 레이어 단방향의 blocking SSOT = `tests/audit/dartlabGuard.py`(architecture-l0-l15 게이트, strict) +
# `cycleScan.py` + `tests/architecture/test_l2|l15_no_cross_import`(P1-7 에서 importlib 동적 import
# 탐지 추가). import-linter 는 transitive 가시화 보조 - 이중 가드라 advisory 유지가 정합.
# (debt-honesty P1-6 결정: blocking 전환 대신 advisory 명시 + stale ignore 정정)
# ── Contract 1: 상하 layer 단방향 (L2 는 exhaustive=false 로 제외, 별도 forbidden)
[[tool.importlinter.contracts]]
name = "Layered architecture (L0~L1.5 + L2.5 dataHub + L3 + L4)"
type = "layers"
layers = [
"dartlab.ai | dartlab.company",
"dartlab.story",
"dartlab.dataHub",
"dartlab.scan",
"dartlab.gather",
"dartlab.core",
]
exhaustive = false # L2 5 엔진은 이 contract 에서 무시 (sibling 단방향 허용)
ignore_imports = [] # 후속 정리 - 진행하며 한 줄씩 비움
# ── Contract 1b: L0~L1.5 완료 게이트 전용 (tests/architecture 와 동일 범위)
[[tool.importlinter.contracts]]
name = "L0-L1.5 architecture boundary"
type = "layers"
layers = [
"dartlab.scan | dartlab.frame | dartlab.synth | dartlab.reference",
"dartlab.gather | dartlab.providers",
"dartlab.core",
]
exhaustive = false
ignore_imports = []
# ── Contract 2: L2 가 상위 (L3/L4) import 금지 (direct only)
# allow_indirect_imports: true - direct 만 차단. transitive 는 root facade 정공법 D
# (dartlab/__init__.py 의 PEP 562 lazy + function-local import) 의 본질이라 도구 검사 외.
[[tool.importlinter.contracts]]
name = "L2 must not import L3/L4"
type = "forbidden"
source_modules = [
"dartlab.analysis",
"dartlab.credit",
"dartlab.macro",
"dartlab.quant",
"dartlab.industry",
]
forbidden_modules = [
"dartlab.story",
"dartlab.dataHub",
"dartlab.data",
"dartlab.ai",
"dartlab.company",
]
allow_indirect_imports = true
ignore_imports = []
# ── Contract 3: L2 가 하위 (L1.5/L1) 우회 import 금지 - L2 는 L0 만 직접 import (direct only)
[[tool.importlinter.contracts]]
name = "L2 must not import L1.5/L1"
type = "forbidden"
source_modules = [
"dartlab.analysis",
"dartlab.credit",
"dartlab.macro",
"dartlab.quant",
"dartlab.industry",
]
forbidden_modules = [
"dartlab.scan",
"dartlab.gather",
]
allow_indirect_imports = true
# 빌더/raw 데이터 접근 예외 - CLAUDE.md 표 "L1 직접 import 는 L1.5 에 없는 raw 가 필요할 때만 예외" 에 해당.
# industry/build 산업지도 빌더, quant alphas/factor/benchmark/screen/regime/signal 의 HF bulk + krx listing
# 등 raw 데이터 접근. frame/synth facade 확장 별도 트랙으로 점진 이전.
ignore_imports = [
"dartlab.industry.build.delta -> dartlab.scan.io.parquet",
"dartlab.industry.build.edges -> dartlab.scan.network.scanner",
"dartlab.industry.build.edges -> dartlab.scan.network.edges",
"dartlab.quant.alphas.bab -> dartlab.gather.bulkData.hfBulk",
"dartlab.quant.alphas.fundamentalMomentum -> dartlab.gather.bulkData.hfBulk",
"dartlab.quant.factor.build -> dartlab.gather.bulkData.hfBulk",
"dartlab.quant.factor.ranking -> dartlab.gather.bulkData.hfBulk",
"dartlab.quant.factor.value -> dartlab.gather.bulkData.hfBulk",
"dartlab.quant.regime.marketContext -> dartlab.gather.entry",
"dartlab.quant.screen._extendedCache -> dartlab.gather.entry",
"dartlab.quant.screen._extendedCache -> dartlab.gather.infra.http",
"dartlab.quant.screen._extendedCache -> dartlab.gather.sources.price",
"dartlab.quant.screen.preset -> dartlab.gather.krx.listing",
"dartlab.quant.signal.flow -> dartlab.gather.entry",
"dartlab.analysis.valuation.fitness -> dartlab.scan.builders.kr.extended",
# _signalsCorporate: Scan facade + invest-edge raw 접근 (L2→L1.5 합법, CLAUDE.md 허용).
# Contract 3(L2 는 L0 만 직접) 가 facade 접근까지 과엄격 flag - 기존 예외와 동류. (debt-honesty P1-6)
"dartlab.analysis.financial._signalsCorporate -> dartlab.scan",
"dartlab.analysis.financial._signalsCorporate -> dartlab.scan.network.edges",
]