leerness 1.11.0 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (7) hide show
  1. package/CHANGELOG.md +12827 -12723
  2. package/README.md +169 -169
  3. package/bin/leerness.js +19524 -19409
  4. package/lib/catalogs.js +485 -433
  5. package/lib/pure-utils.js +1407 -1337
  6. package/package.json +58 -58
  7. package/scripts/e2e.js +6204 -6204
package/lib/catalogs.js CHANGED
@@ -1,433 +1,485 @@
1
- // lib/catalogs.js — capability/adapter/reuse 정적 데이터 카탈로그 (순수 데이터, 부작용 0)
2
- // 1.9.295 (UR-0025 4단계): bin/harness.js 에서 비파괴 분리. selftest(CAPABILITY_SURFACE 6영역 + ADAPTERS + REUSE)가 동작 검증.
3
- // 런타임 변형 없음 — capabilitiesCmd/adapterCmd/_reuseDetect/reuseCheckCmd 소비처 모두 읽기 전용.
4
- 'use strict';
5
-
6
- const CAPABILITY_SURFACE = {
7
- filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', optOut: '핵심 동작 (백업으로 보호)' },
8
- network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', optOut: 'LEERNESS_OFFLINE=1' },
9
- childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in' },
10
- externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)' },
11
- automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)' },
12
- claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', optOut: 'leerness init . --no-auto-update' }
13
- };
14
- const POWERFUL_COMMANDS = [
15
- { cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)' },
16
- { cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신' },
17
- { cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)' },
18
- { cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release' },
19
- { cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)' },
20
- { cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)' },
21
- { cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도' }
22
- ];
23
- const ADAPTERS = {
24
- claude: { label: 'Anthropic Claude Code', keys: ['CLAUDE.md', '.claude/commands/handoff.md', '.claude/commands/session-close.md', '.claude/commands/audit.md', '.claude/commands/lazy-detect.md', '.claude/commands/update.md', '.claude/skills/leerness.md'], mcp: true },
25
- cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
26
- copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
27
- codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
28
- goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
29
- gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
30
- opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
31
- aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
32
- qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
33
- };
34
- const REUSE_CATEGORIES = [
35
- { key: 'auth', kw: ['auth', 'login', 'oauth', 'jwt', 'session', '인증', '로그인', '토큰'], candidates: 'auth.js(NextAuth), lucia, passport, jose(JWT)' },
36
- { key: 'http', kw: ['http client', 'fetch', 'api client', 'request', 'rest client', 'axios'], candidates: 'axios, ky, got, undici(내장 fetch)' },
37
- { key: 'date', kw: ['date', 'time', 'datetime', 'timezone', '날짜', '시간', '달력'], candidates: 'date-fns, dayjs, luxon, Temporal(표준)' },
38
- { key: 'validation', kw: ['validation', 'schema', 'validate', 'parse input', '검증', '유효성', '스키마'], candidates: 'zod, valibot, yup, joi' },
39
- { key: 'state', kw: ['state management', 'store', 'global state', '상태 관리', '스토어'], candidates: 'zustand, redux-toolkit, jotai, nanostores' },
40
- { key: 'ui', kw: ['ui component', 'design system', 'button', 'modal', 'component library', '컴포넌트', '디자인 시스템'], candidates: 'shadcn/ui, radix, MUI, Ark UI' },
41
- { key: 'markdown', kw: ['markdown', 'md parser', 'mdx', '마크다운'], candidates: 'marked, markdown-it, remark/unified' },
42
- { key: 'cli', kw: ['cli', 'command line', 'argv', 'arg parser', '명령행', '인자 파싱'], candidates: 'commander, yargs, clipanion, citty' },
43
- { key: 'db', kw: ['orm', 'database', 'sql', 'query builder', 'migration', 'db', '데이터베이스', '쿼리'], candidates: 'prisma, drizzle, kysely' },
44
- { key: 'test', kw: ['test', 'unit test', 'e2e', 'mock', '테스트', '목'], candidates: 'vitest, jest, playwright, node:test' },
45
- { key: 'pdf', kw: ['pdf', 'generate pdf', 'pdf 생성'], candidates: 'pdf-lib, pdfkit, puppeteer(렌더)' },
46
- { key: 'csv', kw: ['csv', 'excel', 'xlsx', 'spreadsheet', '스프레드시트'], candidates: 'papaparse, csv-parse, exceljs' },
47
- { key: 'queue', kw: ['queue', 'job', 'worker', 'cron', 'scheduler', '큐', '작업 스케줄'], candidates: 'bullmq, p-queue, node-cron, croner' },
48
- { key: 'i18n', kw: ['i18n', 'translation', 'localization', '국제화', '다국어'], candidates: 'i18next, lingui, format.js' },
49
- { key: 'logging', kw: ['log', 'logger', 'logging', '로깅'], candidates: 'pino, winston, consola' }
50
- ];
51
- const REUSE_CHECKLIST = [
52
- '라이선스: 프로젝트와 호환되는가 (MIT/Apache vs GPL 등)',
53
- '유지보수: 최근 커밋/릴리스가 활발한가, 이슈 응답이 있는가',
54
- '보안: 알려진 취약점이 없는가 (npm audit / advisory)',
55
- '적합성: 요구사항의 80%+ 를 충족하는가 (과한 의존성/기능 과잉 아닌가)',
56
- '통합 비용: 학습+통합 비용 < 직접 구현 비용인가',
57
- '제어: 핵심 로직이면 외부 의존 리스크를 감수할 가치가 있는가'
58
- ];
59
-
60
- // 1.9.333 (UR-0025 심층): 플랫폼/API 제약 기본 catalog (순수 데이터) — constraints 서브시스템 핵심 데이터.
61
- const _DEFAULT_PLATFORM_CONSTRAINTS = {
62
- version: '1.9.208',
63
- platforms: {
64
- stripe: {
65
- aliases: ['stripe', 'stripe api', 'payment', '결제'],
66
- docs: 'https://stripe.com/docs/rate-limits',
67
- constraints: [
68
- { kind: 'rate-limit', detail: 'read: 100 req/s, write: 100 req/s (live mode), test mode: 25 req/s' },
69
- { kind: 'idempotency', detail: 'Idempotency-Key 헤더 24h 유지 — 중복 결제 방지 필수' },
70
- { kind: 'webhook', detail: 'webhook 서명 검증 필수 (Stripe-Signature header + endpoint secret)' }
71
- ]
72
- },
73
- openai: {
74
- aliases: ['openai', 'gpt', 'chatgpt', 'gpt-4', 'gpt-3'],
75
- docs: 'https://platform.openai.com/docs/guides/rate-limits',
76
- constraints: [
77
- { kind: 'rate-limit', detail: 'tier-based: Free 3 RPM / Tier 1 500 RPM / Tier 5 10,000 RPM' },
78
- { kind: 'token-limit', detail: 'TPM (tokens/min) 별도 — 큰 입력 시 RPM 도달 전 차단 가능' },
79
- { kind: 'cost', detail: 'gpt-4: $30/$60 per 1M input/output tokens — 대량 호출 전 비용 추정 필수' }
80
- ]
81
- },
82
- anthropic: {
83
- aliases: ['anthropic', 'claude', 'claude api', 'sonnet', 'opus', 'haiku'],
84
- docs: 'https://docs.anthropic.com/claude/reference/rate-limits',
85
- constraints: [
86
- { kind: 'rate-limit', detail: 'tier-based: Free 5 RPM / Tier 1 50 RPM / Tier 4 4,000 RPM' },
87
- { kind: 'context-window', detail: 'claude-sonnet 200K context, claude-opus 200K, 1M tier 별도' },
88
- { kind: 'cost', detail: 'sonnet: $3/$15 per 1M tokens (1M context tier 2x)' }
89
- ]
90
- },
91
- github: {
92
- aliases: ['github', 'github api', 'gh api', 'octokit'],
93
- docs: 'https://docs.github.com/en/rest/rate-limit',
94
- constraints: [
95
- { kind: 'rate-limit', detail: 'authenticated: 5,000 req/hr, unauthenticated: 60 req/hr' },
96
- { kind: 'rate-limit', detail: 'search API: 30 req/min (authenticated)' },
97
- { kind: 'secondary', detail: 'secondary rate limit — concurrent + content creation 별도 가드' }
98
- ]
99
- },
100
- discord: {
101
- aliases: ['discord', 'discord api', 'discord bot'],
102
- docs: 'https://discord.com/developers/docs/topics/rate-limits',
103
- constraints: [
104
- { kind: 'rate-limit', detail: 'global: 50 req/s, per-route 별도' },
105
- { kind: 'invalid', detail: '10,000 invalid req/10min → 1h ban 위험' }
106
- ]
107
- },
108
- twitter: {
109
- aliases: ['twitter', 'twitter api', 'x api', 'x.com api'],
110
- docs: 'https://developer.twitter.com/en/docs/twitter-api/rate-limits',
111
- constraints: [
112
- { kind: 'rate-limit', detail: 'tier-based: Free 1,500 posts/month, Basic 50,000 posts/month' },
113
- { kind: 'auth', detail: 'OAuth 2.0 PKCE 필수 (user context), App-only는 별도 endpoint' }
114
- ]
115
- }
116
- }
117
- };
118
-
119
- // 1.9.333 패턴 적용: intent 도메인 기본 catalog (순수 데이터) — intent-domain 서브시스템 핵심 데이터.
120
- const _DEFAULT_DOMAIN_CATALOG = {
121
- version: '1.9.213',
122
- domains: {
123
- game: {
124
- aliases: ['게임', 'game', 'unity', 'unreal', 'godot', 'phaser', 'gamedev'],
125
- components: [
126
- { key: 'map', desc: '맵/타일 시스템 + 영역/스폰' },
127
- { key: 'character', desc: '캐릭터/스프라이트 + 애니메이션 상태머신' },
128
- { key: 'gameLoop', desc: '게임 루프 (tick/render/update)' },
129
- { key: 'collision', desc: '충돌 감지 (AABB/SAT/grid)' },
130
- { key: 'camera', desc: '카메라 follow + 영역 제한' },
131
- { key: 'hud', desc: 'HUD/UI (HP/score/inventory)' },
132
- { key: 'audio', desc: '사운드 매니저 (BGM/SFX)' },
133
- { key: 'save', desc: '저장/로드 (slot 시스템)' },
134
- { key: 'menu', desc: '메뉴 (메인/일시정지/설정)' },
135
- { key: 'input', desc: '입력 핸들러 (키보드/패드/터치)' }
136
- ]
137
- },
138
- web: {
139
- aliases: ['웹', 'web', 'website', 'webapp', 'nextjs', 'react', 'vue', 'svelte', 'frontend'],
140
- components: [
141
- { key: 'routing', desc: '라우팅 (path/dynamic/nested)' },
142
- { key: 'state', desc: '상태 관리 (context/redux/zustand)' },
143
- { key: 'auth', desc: '인증 (OAuth/JWT/session)' },
144
- { key: 'api', desc: 'API 클라이언트 (fetch/axios/tanstack-query)' },
145
- { key: 'db', desc: 'DB 연동 (ORM/migration/seed)' },
146
- { key: 'ui', desc: 'UI 컴포넌트 라이브러리' },
147
- { key: 'test', desc: '테스트 (unit/e2e/visual)' },
148
- { key: 'deploy', desc: '배포 (Vercel/Netlify/Cloudflare)' }
149
- ]
150
- },
151
- api: {
152
- aliases: ['api', 'rest', 'graphql', 'endpoint', 'backend', 'server'],
153
- components: [
154
- { key: 'endpoint', desc: '엔드포인트 라우팅 + HTTP method' },
155
- { key: 'auth', desc: '인증/인가 (API key/OAuth/JWT)' },
156
- { key: 'rate-limit', desc: 'rate limit (RPS/RPM/token bucket)' },
157
- { key: 'validation', desc: '입력 검증 (zod/joi/yup)' },
158
- { key: 'error', desc: '에러 핸들링 + 응답 형식' },
159
- { key: 'logging', desc: '로깅 + 모니터링 (structured logs)' },
160
- { key: 'docs', desc: 'API 문서 (OpenAPI/Swagger)' }
161
- ]
162
- },
163
- cli: {
164
- aliases: ['cli', 'command-line', 'tool', 'utility', 'shell'],
165
- components: [
166
- { key: 'argParser', desc: '인자 파싱 (yargs/commander/clipanion)' },
167
- { key: 'help', desc: 'help / man / examples 텍스트' },
168
- { key: 'config', desc: '설정 파일 + env 변수' },
169
- { key: 'output', desc: '출력 (TTY 색상/JSON/quiet)' },
170
- { key: 'error', desc: '에러 처리 + exit code 규약' },
171
- { key: 'completion', desc: 'shell completion (bash/zsh/fish)' }
172
- ]
173
- },
174
- data: {
175
- aliases: ['data', 'pipeline', 'etl', 'analytics', 'ingest'],
176
- components: [
177
- { key: 'ingest', desc: '데이터 수집 (file/API/stream)' },
178
- { key: 'transform', desc: '변환 (cleaning/normalization/joining)' },
179
- { key: 'storage', desc: '저장소 (parquet/db/blob)' },
180
- { key: 'query', desc: '쿼리/분석 (SQL/aggregations)' },
181
- { key: 'validation', desc: '데이터 검증 (schema/contracts)' },
182
- { key: 'lineage', desc: '데이터 lineage 추적' }
183
- ]
184
- }
185
- }
186
- };
187
-
188
- // 1.9.335 (UR-0025 심층): LSP 정규식 fallback 언어별 심볼 패턴 catalog (harness 에서 분리)
189
- // 6개 언어 (JS/TS / Python / Go / Rust / Java) — LSP 미설치 시 regex 심볼 추출에 사용.
190
- const _LSP_LANG_PATTERNS = {
191
- javascript: [
192
- { re: /^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/, kind: 'function' },
193
- { re: /^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/, kind: 'class' },
194
- { re: /^\s*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/, kind: 'interface' },
195
- { re: /^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?(?:function|\()/, kind: 'function' },
196
- { re: /^\s*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/, kind: 'type' },
197
- { re: /^\s*(?:export\s+)?enum\s+([A-Za-z_$][\w$]*)/, kind: 'enum' }
198
- ],
199
- python: [
200
- { re: /^\s*async\s+def\s+([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
201
- { re: /^\s*def\s+([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
202
- { re: /^\s*class\s+([A-Za-z_][\w]*)\s*[(:]/, kind: 'class' }
203
- ],
204
- go: [
205
- { re: /^\s*func\s+(?:\([^)]*\)\s+)?([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
206
- { re: /^\s*type\s+([A-Za-z_][\w]*)\s+struct\b/, kind: 'struct' },
207
- { re: /^\s*type\s+([A-Za-z_][\w]*)\s+interface\b/, kind: 'interface' },
208
- { re: /^\s*type\s+([A-Za-z_][\w]*)\s+[A-Za-z]/, kind: 'type' }
209
- ],
210
- rust: [
211
- { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)/, kind: 'function' },
212
- { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?struct\s+([A-Za-z_][\w]*)/, kind: 'struct' },
213
- { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?enum\s+([A-Za-z_][\w]*)/, kind: 'enum' },
214
- { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?trait\s+([A-Za-z_][\w]*)/, kind: 'trait' },
215
- { re: /^\s*impl\s+(?:[^{]+\s+for\s+)?([A-Za-z_][\w]*)/, kind: 'impl' },
216
- { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?type\s+([A-Za-z_][\w]*)\s*=/, kind: 'type' }
217
- ],
218
- java: [
219
- { re: /^\s*(?:public|private|protected)?\s*(?:final\s+)?(?:abstract\s+)?class\s+([A-Za-z_][\w]*)/, kind: 'class' },
220
- { re: /^\s*(?:public|private|protected)?\s*(?:abstract\s+)?interface\s+([A-Za-z_][\w]*)/, kind: 'interface' },
221
- { re: /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?enum\s+([A-Za-z_][\w]*)/, kind: 'enum' },
222
- // method: visibility + return type + name( (heuristic — 첫 번째 ( 매칭, 키워드 필터)
223
- { re: /^\s*(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:[A-Za-z_<>,\s\[\]]+\s+)?([A-Za-z_][\w]*)\s*\(/, kind: 'method' }
224
- ]
225
- };
226
-
227
- // 1.9.26: optimism-check — evidence의 외부 동작 주장 vs 실제 코드 호출 흔적 불일치 감지
228
- // 사용자 요청 (1.9.26): "API 연동/작업 요청 시 실제로 일어나지 않았는데 일어난 것처럼 표시하는 낙관적 결과 방지"
229
- //
230
- // 패턴 (한국어 + 영어):
231
- // evidence에 "API 호출" / "HTTP 200|201" / "POST /" / "응답 확인" → 코드에 fetch/http.request/axios 흔적 없으면 의심
232
- // evidence에 "DB 저장" / "insert N건" / "DB에" → db.*/pg.*/mysql.*/mongoose.*/prisma.* 없으면 의심
233
- // evidence에 "이메일 발송" / "메일 전송" → sendMail/nodemailer/smtp 없으면 의심
234
- // 1.9.27: 패턴 카탈로그 확장 (5 → 10) + URL/메서드 단위 매핑 추가
235
- const OPTIMISM_PATTERNS = [
236
- { kind: 'API', evidenceRe: /(API\s*호출|HTTP\s*\d{3}|POST\s*\/|GET\s*\/|PUT\s*\/|DELETE\s*\/|fetch|REST 응답|응답 확인|endpoint|엔드포인트)/i,
237
- codeRe: /\b(fetch\s*\(|http\.request|https\.request|axios\.|got\.|undici|node-fetch)/i,
238
- label: 'API/HTTP 호출' },
239
- { kind: 'DB', evidenceRe: /(DB에?\s*저장|insert\s+\d+|데이터베이스|SQL\s*(INSERT|UPDATE|DELETE)|migration|마이그레이션 적용)/i,
240
- codeRe: /\b(db\.|pg\.|pool\.|mysql\.|mongoose\.|prisma\.|sequelize|knex|sqlite3|MongoClient|createConnection)/i,
241
- label: 'DB 호출' },
242
- { kind: 'Email', evidenceRe: /(이메일[^.\n]{0,30}(발송|전송|보냈|보냄|완료)|메일[^.\n]{0,30}(발송|전송|보냈|보냄)|sendMail|smtp\s*(전송|발송))/i,
243
- codeRe: /\b(sendMail|nodemailer|smtp|@sendgrid|mailgun|aws-sdk\/ses|resend\.)/i,
244
- label: '이메일 전송' },
245
- { kind: 'Webhook', evidenceRe: /(웹훅\s*(호출|전송|발송)|webhook\s+(sent|posted|triggered))/i,
246
- codeRe: /\b(fetch\s*\(|http\.request|axios\.)/i,
247
- label: '웹훅' },
248
- { kind: 'Payment', evidenceRe: /(결제\s*(완료|성공|승인|취소)|payment\s+(processed|charged)|stripe 결제|toss\s*결제|카카오페이|네이버페이|kakaopay|nicepay|iamport 결제|페이팔|paypal)/i,
249
- codeRe: /\b(stripe|toss|@stripe|tosspayments|iamport|kakao|nicepay|naverpay|paypal-rest-sdk|@paypal)/i,
250
- label: '결제' },
251
- // 1.9.27 신규 카테고리
252
- { kind: 'FileIO', evidenceRe: /(파일[^.\n]{0,20}(생성|저장|작성|기록)|\d+개[^.\n]{0,20}파일|디스크[^.\n]{0,20}저장|로그 파일 작성)/i,
253
- codeRe: /\b(fs\.write|fs\.appendFile|writeFileSync|appendFileSync|fs\/promises|fs\.createWriteStream)/i,
254
- label: '파일 I/O 쓰기' },
255
- { kind: 'Queue', evidenceRe: /(메시지\s*큐|발행\s*완료|publish\s*(완료|성공)|RabbitMQ|Kafka|SQS|Redis Pub|이벤트 발행)/i,
256
- codeRe: /\b(amqp|kafkajs|rabbit|redis\.(publish|xadd)|@aws-sdk\/client-sqs|bull|bullmq)/i,
257
- label: '메시지 큐 발행' },
258
- { kind: 'Cache', evidenceRe: /(Redis[^.\n]{0,20}(저장|set|get)|캐시[^.\n]{0,20}(저장|기록|적중)|memcache)/i,
259
- codeRe: /\b(redis\.|ioredis|memcached|node-cache|@upstash\/redis|connect-redis)/i,
260
- label: '캐시 저장' },
261
- { kind: 'Notify', evidenceRe: /(슬랙\s*(알림|발송|전송)|Slack\s+(notification|sent|posted)|Discord\s+(알림|발송|webhook)|푸시 알림 전송)/i,
262
- codeRe: /\b(@slack\/web-api|slack-webhook|discord\.js|discord-webhook|@discordjs|firebase\/messaging|expo-notifications)/i,
263
- label: '슬랙/Discord 알림' },
264
- { kind: 'Storage', evidenceRe: /(S3\s*(업로드|저장)|GCS\s*업로드|Azure Blob|클라우드 스토리지 업로드|object storage 저장)/i,
265
- codeRe: /\b(@aws-sdk\/client-s3|aws-sdk[^a-z]|@google-cloud\/storage|@azure\/storage-blob|aws-s3)/i,
266
- label: '클라우드 스토리지' }
267
- ];
268
-
269
-
270
- // 1.9.337 (UR-0025 심층): 리뷰 페르소나 catalog (5종: security/performance/ux/testing/docs) — harness 에서 분리.
271
- const BUILT_IN_PERSONAS = {
272
- security: {
273
- id: 'security',
274
- name: '보안 엔지니어 (10년차)',
275
- description: 'OWASP Top 10, CWE, RFC, 한국 개인정보보호법/게임산업법 정통',
276
- body: `너는 **10년 경력의 시니어 보안 엔지니어**다. OWASP Top 10 2021, CWE, RFC 7235/6454, CORS 보안, secret 관리에 정통하며, 한국 금융사·카카오·네이버 등 대형 IT 기업의 보안 감사 경험이 있다. 코드를 볼 때 **위협 모델링**과 **공격 표면(attack surface)** 을 자동으로 시각화한다.
277
-
278
- 검토 영역: 입력 검증 / 인증·인가 / CORS / 시크릿/로그 노출 / DoS / 데이터 노출 / 의존성 attack surface / 한국 시장 특화 (개인정보보호법, 결제 정보)
279
- 보고에 포함: 위협 모델 / CWE ID 매핑 / 실 공격 시나리오 1건 (HTTP 페이로드) / P0/P1/P2 우선순위 / OWASP Top 10 2021 매핑`
280
- },
281
- performance: {
282
- id: 'performance',
283
- name: '성능 최적화 전문가 (V8 내부)',
284
- description: 'V8 엔진 (Ignition/TurboFan, hidden class), Node.js 이벤트 루프, libuv 정통',
285
- body: `너는 **V8 엔진 내부 (Ignition, TurboFan, hidden class)와 Node.js 이벤트 루프, libuv에 정통한 성능 최적화 전문가**다. Linux perf, node --prof, clinic.js, autocannon, FlameGraph 활용 경험이 풍부하다. 메모리 압박(GC pressure), CPU bound vs I/O bound 구분, hot path 식별이 직관이다.
286
-
287
- 검토 영역: Hot path 식별 / hidden class 안정성 / 메모리 할당 패턴 / 정규식 컴파일 / JSON.parse/stringify 비용 / 이벤트 루프 블로킹 / 라우트 매칭 복잡도
288
- 보고에 포함: 성능 프로필 요약 (RPS/latency 추정) / Hot path Top 5 / 비효율 표 (영향 high/med/low) / 벤치 시나리오 (autocannon 명령) / 권장 우선순위 (당장/부하증가/마이크로)`
289
- },
290
- ux: {
291
- id: 'ux',
292
- name: '한국어 UX 라이터 + DX 컨설턴트',
293
- description: '카카오/네이버/토스/라인 마이크로카피, API 디자인 (Stripe/GitHub/Google) 정통',
294
- body: `너는 **한국 사용자 대상 게임/SaaS 제품의 UX 라이터 + DX(Developer Experience) 컨설턴트**다. 카카오, 네이버, 토스, 라인의 한국어 마이크로카피 가이드라인을 숙지하고 있으며, 클라이언트 개발자의 API 통합 경험을 잘 안다. 에러 메시지, HTTP status, 응답 본문 일관성이 직관이다.
295
-
296
- 검토 영역: 한국어 에러 메시지 톤 / HTTP status 적절성 (400/404/422/409) / 응답 본문 일관성 / 한국어/영문 혼재 / 누락 정보 (rate limit, request id, version) / 클라이언트 SDK 친화성
297
- 보고에 포함: UX/DX 점수 (1-10) / 발견 이슈 표 / Before/After 메시지 5건 / SDK 친화성 점수 (1-5) / 권장 로드맵 (이번 PR / 1주 / 분기)`
298
- },
299
- testing: {
300
- id: 'testing',
301
- name: '테스트 엔지니어 (TDD + property-based)',
302
- description: 'TDD, property-based testing (fast-check), AAA 패턴, fuzz, mutation testing 정통',
303
- body: `너는 **TDD와 property-based testing (fast-check) 에 정통한 테스트 엔지니어**다. AAA 패턴, given/when/then, fuzz testing, mutation testing, contract testing 경험이 있다. 테스트 커버리지보다 **테스트 품질**과 **회귀 방어** 가치를 더 중시한다.
304
-
305
- 검토 영역: 테스트 누락 분기 / edge case / mocking 과다 / AAA 패턴 위반 / async 테스트 결함 (race) / property 후보 / 회귀 가능성
306
- 보고에 포함: 누락 테스트 목록 + 우선순위 / fast-check property 후보 3건 / 기존 테스트 약점 / 권장 회귀 시나리오`
307
- },
308
- docs: {
309
- id: 'docs',
310
- name: '기술 문서 작성자 (한국어)',
311
- description: 'README, API 문서, 사용 가이드 작성. Stripe Docs / Google Cloud / 카카오 dev 가이드 정통',
312
- body: `너는 **한국어 기술 문서 작성에 정통한 테크니컬 라이터**다. Stripe Docs, Google Cloud, AWS, 카카오 개발자 가이드 톤을 잘 안다. README 첫 60초 경험, 점진적 공개 (progressive disclosure), 코드 예시의 즉시 실행 가능성을 중시한다.
313
-
314
- 검토 영역: 60초 시작 가능성 / 예시 코드 정확성 / 누락된 사전 요구사항 / 한국어 자연스러움 / 시각적 균형 (이모지/표/코드블록) / 한국어/영문 혼재 / 다음 단계 명시
315
- 보고에 포함: 사용자 페르소나별 평가 (입문자/실무자/전문가) / 60초 안 첫 결과 가능 여부 / 누락 정보 / 권장 개선 표`
316
- }
317
- };
318
-
319
-
320
- // 1.9.338 (UR-0025 심층): i18n 문자열 catalog (ko/en) — harness 에서 분리. 조회는 순수 _translate(STRINGS, key, lang).
321
- const STRINGS = {
322
- // 설치 가이드 prompt
323
- 'install.lang.title': { ko: '설치 언어를 선택하세요', en: 'Select install language' },
324
- 'install.lang.auto': { ko: '자동 감지', en: 'Auto detect' },
325
- 'install.lang.auto.desc': { ko: '디렉토리/파일 + 시스템(OS) 언어 자동 판별 (한국어/영어)', en: 'Auto-detect from dir/files + system (OS) locale (KO/EN)' },
326
- 'install.lang.sysNotice': { ko: '시스템 언어 감지', en: 'System language detected' },
327
- 'install.lang.ko': { ko: '한국어', en: 'Korean' },
328
- 'install.lang.ko.desc': { ko: '모든 인스트럭션을 한국어로 생성', en: 'All instructions in Korean' },
329
- 'install.lang.en': { ko: 'English', en: 'English' },
330
- 'install.lang.en.desc': { ko: '모든 인스트럭션을 영어로 생성', en: 'All instructions in English' },
331
- 'install.agents.title': { ko: 'CLI 에이전트 활성화 (복수 선택, Space 토글) — sub-agent 위임용',
332
- en: 'Enable CLI agents (multi-select, Space toggle) — for sub-agent dispatch' },
333
- 'install.agents.none': { ko: '선택 안함 (나중에 setup-agents)', en: 'None (later setup-agents)' },
334
- 'install.complete': { ko: '✓ 설치 완료', en: '✓ Install complete' },
335
- // REPL agent 모드
336
- 'repl.welcome.title': { ko: 'leerness REPL agent', en: 'leerness REPL agent' },
337
- 'repl.welcome.subtitle': { ko: 'Tab provider · Shift+Tab model · :help · /slash', en: 'Tab provider · Shift+Tab model · :help · /slash' },
338
- 'repl.welcome.start': { ko: '시작하려면 메시지를 입력하세요', en: 'Type a message to start' },
339
- // 공통
340
- 'common.cancel': { ko: '취소됨', en: 'Cancelled' },
341
- 'common.confirm': { ko: '확인', en: 'Confirm' },
342
- 'common.ready': { ko: '준비 완료', en: 'Ready' }
343
- };
344
-
345
-
346
- // 1.9.341 (UR-0025 심층): 내장 스킬 catalog (9종) — harness 에서 분리. _loadSkillCatalog 의 builtin fallback.
347
- const BUILTIN_CATALOG = {
348
- 'office': { displayNameKo: '마이크로소프트 오피스 자동화 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Word/Excel/PowerPoint 문서 자동화', '템플릿 기반 문서 생성', '표/차트/요약 문서화', '민감정보 제외 규칙 적용'] },
349
- 'commerce-api': { displayNameKo: '커머스 API 연동 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['쿠팡·롯데온·스마트스토어 API 연동 설계', '주문/상품/매출 동기화', '환경변수 기반 인증 분리', '레이트리밋/재시도/오류 처리'] },
350
- 'crawling': { displayNameKo: '크롤링·브라우저 자동화 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Playwright 기반 자동화', '다운로드/로그인 세션 처리', '스크린샷 기반 실패 진단', '약관/권한/차단 위험 점검'] },
351
- 'firebase': { displayNameKo: 'Firebase·Cloud Functions 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Firebase Functions 배포 구조', '환경변수/시크릿 분리', '권한/IAM 점검', '로컬 에뮬레이터 검증'] },
352
- 'ads-analytics': { displayNameKo: '광고·GA4 분석 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['GA4 이벤트/전환 점검', '광고 데이터 수집 구조화', '소스/매체 분석', '리포트 자동화'] },
353
- 'appstore-review': { displayNameKo: '앱스토어 심사 대응 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['심사 문구 분석', '개인정보 라벨 점검', '리젝 대응 초안', '웹뷰/앱 데이터 수집 구분'] },
354
- 'ai-verified-skill-publisher': { displayNameKo: 'AI 검증 스킬 업로드·라이브러리화 스킬', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['검증된 스킬 정규화', '민감정보 스캔', 'AI 검증 메타데이터 작성', 'npm/git 업로드 dry-run 및 실행 게이트'] },
355
- 'feature-implementation': { displayNameKo: '기능 구현 표준 스킬', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['feature-contracts 작성', '재사용 우선 검사', '테스트 증거 수집', '핸드오프 트리거'] },
356
- // 1.9.11: 기본 내장 — 로드맵 자동 생성 스킬
357
- 'project-roadmap-generator': { displayNameKo: '프로젝트 로드맵 자동 생성 스킬', version: '0.2.0', lastUpdated: '2026-05-12', verification: 'passed', capabilities: ['leerness .harness/* 통합 파싱 (plan/progress/skills/rules/decisions/handoff/current-state)', '좌→우 수평 트리 + 상하 중앙정렬 SVG', '7개 상태 색상 (완료/진행/보류/검토/예정/미완료/오류)', 'design-system + CSS variables 자동 주입', '화이트보드 panning/zoom + 더블클릭 reset', '단일 HTML 출력 (외부 의존성 0)'] }
358
- };
359
-
360
- // 1.9.342 (UR-0025 심층): roadmap.html 상태 라벨/색상 맵 (status → ko 라벨 / hex 색상) — harness 에서 분리.
361
- const ROADMAP_STATUS_LABEL = { done: '완료', 'in-progress': '진행', 'on-hold': '보류', waiting: '검토', incomplete: '미완료', planned: '예정', blocked: '오류', dropped: '취소', skill: '스킬', rule: '룰', meta: '프로젝트' };
362
- const ROADMAP_STATUS_COLOR = { done: '#16a34a', 'in-progress': '#2563eb', 'on-hold': '#6b7280', waiting: '#eab308', incomplete: '#f97316', planned: '#94a3b8', blocked: '#dc2626', dropped: '#9ca3af', skill: '#8b5cf6', rule: '#06b6d4', meta: '#0f172a' };
363
-
364
-
365
- // 1.9.343 (UR-0025 심층): 시크릿 값 스캔 정규식 catalog (13종) — harness 에서 분리. scan secrets 의 값 탐지. (_isSecretKey 키이름 휴리스틱과 보안 응집)
366
- const SECRET_PATTERNS = [
367
- { name: 'AWS Access Key', re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g }, // ASIA=임시 자격증명 추가
368
- { name: 'GitHub token', re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g }, // ghp_/gho_/ghu_/ghs_/ghr_ 통합
369
- { name: 'GitHub fine-grained PAT', re: /\bgithub_pat_[A-Za-z0-9_]{40,}\b/g },
370
- { name: 'OpenAI project/service key', re: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}/g }, // modern (하이픈/언더스코어 포함)
371
- { name: 'OpenAI API key', re: /\bsk-[A-Za-z0-9]{32,}\b/g }, // legacy
372
- { name: 'Anthropic API key', re: /\bsk-ant-[A-Za-z0-9_-]{20,}/g }, // _ 포함 + 후행 \b 제거 (실제 키 호환)
373
- { name: 'Stripe secret key', re: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\b/g },
374
- { name: 'npm token', re: /\bnpm_[A-Za-z0-9]{36}\b/g },
375
- { name: 'Google API key', re: /\bAIza[0-9A-Za-z_\-]{35}\b/g },
376
- { name: 'Google OAuth token', re: /\bya29\.[A-Za-z0-9_-]{20,}/g },
377
- { name: 'Slack token', re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
378
- { name: 'Generic private key', re: /-----BEGIN (?:RSA |OPENSSH |EC |DSA |PGP )?PRIVATE KEY-----/g },
379
- { name: 'Hardcoded password assignment', re: /\b(?:password|passwd|pwd|secret|api_key|apikey)\s*[:=]\s*["']([^"'\s]{6,})["']/gi, valueGroup: 1 }, // 1.9.365 CV-6: valueGroup → placeholder 오탐 억제
380
- // 1.9.365 (외부리뷰 CV-6/UR-0081): unquoted 자격증명 (KEY=secret123) FN 보강. 값이 시크릿스러운지(_looksSecretLike) + placeholder 아닌지 후처리. 코드 var-ref(점 포함)는 charset 으로 제외.
381
- { name: 'Hardcoded credential (unquoted)', re: /\b(?:password|passwd|pwd|secret|secret_key|api_key|apikey|access_key|access_token|auth_token)\s*=\s*([A-Za-z0-9_\-/+]{8,})(?=[\s;,)]|$)/gi, valueGroup: 1, requireSecretLike: true },
382
- // 1.9.350 (UR-0060 외부리뷰 3모델): 누락 패턴 보강
383
- { name: 'GitLab PAT', re: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
384
- { name: 'JWT', re: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
385
- { name: 'DB connection string (embedded password)', re: /\b(?:postgres|postgresql|mysql|mongodb(?:\+srv)?|redis|amqp):\/\/[^:\s/@]+:[^@\s/]+@/gi },
386
- { name: 'SendGrid API key', re: /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b/g },
387
- { name: 'AWS Secret Access Key (context)', re: /\baws[^\n]{0,40}?(?:secret_access_key|secret_key|secret)[^\n]{0,12}?["']?[A-Za-z0-9/+]{40}["']?/gi },
388
- { name: 'Hardcoded Bearer token', re: /\bBearer\s+[A-Za-z0-9_\-.=]{20,}/g },
389
- ];
390
-
391
- // 1.9.367/1.9.368 (UR-0025): 데이터/인덱스 파일은 migration-preserved 블록 없이 overwrite (누적 방지) — harness 에서 분리.
392
- const MERGE_OVERWRITE_FILES = new Set([
393
- '.harness/skill-index.md',
394
- '.harness/manifest.json',
395
- '.harness/skills-lock.json',
396
- '.harness/HARNESS_VERSION',
397
- '.harness/LANGUAGE',
398
- '.harness/context-routing.md'
399
- ]);
400
-
401
- // 1.9.380 (UR-0025): 워크스페이스 필수 파일 — verify / migrate audit / migrate apply 단일출처 (harness 3중 중복 제거).
402
- const REQUIRED_WORKSPACE_FILES = ['.harness/plan.md', '.harness/progress-tracker.md', '.harness/guideline.md', '.harness/protected-files.md', '.harness/design-system.md', '.harness/anti-lazy-work-policy.md', '.harness/session-handoff.md', '.harness/current-state.md', 'AGENTS.md'];
403
-
404
- // 1.9.381 (UR-0025): 키워드 추출 stopwords — handoff 자동회수 / lessons 키워드 추출 단일출처 (harness 2중 중복 제거).
405
- const KEYWORD_STOPWORDS = new Set([
406
- '이런', '저런', '하다', '하고', '있는', '하지', '에서',
407
- '작업', '구현', '추가', '진행', '수정', '변경', '검토', '확인',
408
- '프로젝트', '관리', '기능', '시스템', '코드', '파일', '버전', '정리', '계획',
409
- 'next', 'action', 'task', 'todo', 'work'
410
- ]);
411
-
412
- // 1.9.369 (UR-0025): init --minimal 시 제외하는 키 — 코어 워크플로(handoff/verify/audit/session close)가 요구하지 않는 파일만. harness 에서 분리.
413
- const MINIMAL_SKIP_KEYS = new Set([
414
- '.cursor/rules/leerness.mdc', '.github/copilot-instructions.md',
415
- '.harness/project-brief.md', '.harness/task-type-map.md', '.harness/architecture.md', '.harness/context-map.md',
416
- '.harness/guardrails.md', '.harness/feature-contracts.md', '.harness/feature-graph.md',
417
- '.harness/testing-strategy.md', '.harness/review-checklist.md', '.harness/release-checklist.md',
418
- '.harness/session-close-policy.md', '.harness/language-policy.md', '.harness/test-evidence-policy.md',
419
- '.harness/AX_PLAN_GUIDE.md', '.harness/AX_MIGRATION_GUIDE.md', '.harness/AX_NEW_PROJECT_GUIDE.md', '.harness/AX_SKILL_LIBRARY_GUIDE.md',
420
- '.harness/skill-index.md',
421
- '.harness/templates/end-of-session-report.md', '.harness/templates/decision.md', '.harness/templates/task-row.md',
422
- '.claude/skills/leerness.md'
423
- ]);
424
-
425
- // 1.9.344 (UR-0025 심층): skill discover GitHub preset catalog (vercel/anthropic) — harness 에서 분리.
426
- const SKILL_CATALOG_PRESETS = {
427
- 'vercel': { owner: 'vercel-labs', repo: 'agent-skills', branch: 'main', path: 'skills',
428
- homepage: 'https://github.com/vercel-labs/agent-skills' },
429
- 'anthropic': { owner: 'anthropics', repo: 'skills', branch: 'main', path: 'skills',
430
- homepage: 'https://github.com/anthropics/skills' }
431
- };
432
-
433
- module.exports = { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS };
1
+ // lib/catalogs.js — capability/adapter/reuse 정적 데이터 카탈로그 (순수 데이터, 부작용 0)
2
+ // 1.9.295 (UR-0025 4단계): bin/harness.js 에서 비파괴 분리. selftest(CAPABILITY_SURFACE 6영역 + ADAPTERS + REUSE)가 동작 검증.
3
+ // 런타임 변형 없음 — capabilitiesCmd/adapterCmd/_reuseDetect/reuseCheckCmd 소비처 모두 읽기 전용.
4
+ 'use strict';
5
+
6
+ const CAPABILITY_SURFACE = {
7
+ filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', optOut: '핵심 동작 (백업으로 보호)' },
8
+ network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', optOut: 'LEERNESS_OFFLINE=1' },
9
+ childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in' },
10
+ externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)' },
11
+ automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)' },
12
+ claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', optOut: 'leerness init . --no-auto-update' }
13
+ };
14
+ const POWERFUL_COMMANDS = [
15
+ { cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)' },
16
+ { cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신' },
17
+ { cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)' },
18
+ { cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release' },
19
+ { cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)' },
20
+ { cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)' },
21
+ { cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도' }
22
+ ];
23
+ const ADAPTERS = {
24
+ claude: { label: 'Anthropic Claude Code', keys: ['CLAUDE.md', '.claude/commands/handoff.md', '.claude/commands/session-close.md', '.claude/commands/audit.md', '.claude/commands/lazy-detect.md', '.claude/commands/update.md', '.claude/skills/leerness.md'], mcp: true },
25
+ cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
26
+ copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
27
+ codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
28
+ goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
29
+ gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
30
+ opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
31
+ aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
32
+ qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
33
+ };
34
+ const REUSE_CATEGORIES = [
35
+ { key: 'auth', kw: ['auth', 'login', 'oauth', 'jwt', 'session', '인증', '로그인', '토큰'], candidates: 'auth.js(NextAuth), lucia, passport, jose(JWT)' },
36
+ { key: 'http', kw: ['http client', 'fetch', 'api client', 'request', 'rest client', 'axios'], candidates: 'axios, ky, got, undici(내장 fetch)' },
37
+ { key: 'date', kw: ['date', 'time', 'datetime', 'timezone', '날짜', '시간', '달력'], candidates: 'date-fns, dayjs, luxon, Temporal(표준)' },
38
+ { key: 'validation', kw: ['validation', 'schema', 'validate', 'parse input', '검증', '유효성', '스키마'], candidates: 'zod, valibot, yup, joi' },
39
+ { key: 'state', kw: ['state management', 'store', 'global state', '상태 관리', '스토어'], candidates: 'zustand, redux-toolkit, jotai, nanostores' },
40
+ { key: 'ui', kw: ['ui component', 'design system', 'button', 'modal', 'component library', '컴포넌트', '디자인 시스템'], candidates: 'shadcn/ui, radix, MUI, Ark UI' },
41
+ { key: 'markdown', kw: ['markdown', 'md parser', 'mdx', '마크다운'], candidates: 'marked, markdown-it, remark/unified' },
42
+ { key: 'cli', kw: ['cli', 'command line', 'argv', 'arg parser', '명령행', '인자 파싱'], candidates: 'commander, yargs, clipanion, citty' },
43
+ { key: 'db', kw: ['orm', 'database', 'sql', 'query builder', 'migration', 'db', '데이터베이스', '쿼리'], candidates: 'prisma, drizzle, kysely' },
44
+ { key: 'test', kw: ['test', 'unit test', 'e2e', 'mock', '테스트', '목'], candidates: 'vitest, jest, playwright, node:test' },
45
+ { key: 'pdf', kw: ['pdf', 'generate pdf', 'pdf 생성'], candidates: 'pdf-lib, pdfkit, puppeteer(렌더)' },
46
+ { key: 'csv', kw: ['csv', 'excel', 'xlsx', 'spreadsheet', '스프레드시트'], candidates: 'papaparse, csv-parse, exceljs' },
47
+ { key: 'queue', kw: ['queue', 'job', 'worker', 'cron', 'scheduler', '큐', '작업 스케줄'], candidates: 'bullmq, p-queue, node-cron, croner' },
48
+ { key: 'i18n', kw: ['i18n', 'translation', 'localization', '국제화', '다국어'], candidates: 'i18next, lingui, format.js' },
49
+ { key: 'logging', kw: ['log', 'logger', 'logging', '로깅'], candidates: 'pino, winston, consola' }
50
+ ];
51
+ const REUSE_CHECKLIST = [
52
+ '라이선스: 프로젝트와 호환되는가 (MIT/Apache vs GPL 등)',
53
+ '유지보수: 최근 커밋/릴리스가 활발한가, 이슈 응답이 있는가',
54
+ '보안: 알려진 취약점이 없는가 (npm audit / advisory)',
55
+ '적합성: 요구사항의 80%+ 를 충족하는가 (과한 의존성/기능 과잉 아닌가)',
56
+ '통합 비용: 학습+통합 비용 < 직접 구현 비용인가',
57
+ '제어: 핵심 로직이면 외부 의존 리스크를 감수할 가치가 있는가'
58
+ ];
59
+
60
+ // 1.9.333 (UR-0025 심층): 플랫폼/API 제약 기본 catalog (순수 데이터) — constraints 서브시스템 핵심 데이터.
61
+ const _DEFAULT_PLATFORM_CONSTRAINTS = {
62
+ version: '1.9.208',
63
+ platforms: {
64
+ stripe: {
65
+ aliases: ['stripe', 'stripe api', 'payment', '결제'],
66
+ docs: 'https://stripe.com/docs/rate-limits',
67
+ constraints: [
68
+ { kind: 'rate-limit', detail: 'read: 100 req/s, write: 100 req/s (live mode), test mode: 25 req/s' },
69
+ { kind: 'idempotency', detail: 'Idempotency-Key 헤더 24h 유지 — 중복 결제 방지 필수' },
70
+ { kind: 'webhook', detail: 'webhook 서명 검증 필수 (Stripe-Signature header + endpoint secret)' }
71
+ ]
72
+ },
73
+ openai: {
74
+ aliases: ['openai', 'gpt', 'chatgpt', 'gpt-4', 'gpt-3'],
75
+ docs: 'https://platform.openai.com/docs/guides/rate-limits',
76
+ constraints: [
77
+ { kind: 'rate-limit', detail: 'tier-based: Free 3 RPM / Tier 1 500 RPM / Tier 5 10,000 RPM' },
78
+ { kind: 'token-limit', detail: 'TPM (tokens/min) 별도 — 큰 입력 시 RPM 도달 전 차단 가능' },
79
+ { kind: 'cost', detail: 'gpt-4: $30/$60 per 1M input/output tokens — 대량 호출 전 비용 추정 필수' }
80
+ ]
81
+ },
82
+ anthropic: {
83
+ aliases: ['anthropic', 'claude', 'claude api', 'sonnet', 'opus', 'haiku'],
84
+ docs: 'https://docs.anthropic.com/claude/reference/rate-limits',
85
+ constraints: [
86
+ { kind: 'rate-limit', detail: 'tier-based: Free 5 RPM / Tier 1 50 RPM / Tier 4 4,000 RPM' },
87
+ { kind: 'context-window', detail: 'claude-sonnet 200K context, claude-opus 200K, 1M tier 별도' },
88
+ { kind: 'cost', detail: 'sonnet: $3/$15 per 1M tokens (1M context tier 2x)' }
89
+ ]
90
+ },
91
+ github: {
92
+ aliases: ['github', 'github api', 'gh api', 'octokit'],
93
+ docs: 'https://docs.github.com/en/rest/rate-limit',
94
+ constraints: [
95
+ { kind: 'rate-limit', detail: 'authenticated: 5,000 req/hr, unauthenticated: 60 req/hr' },
96
+ { kind: 'rate-limit', detail: 'search API: 30 req/min (authenticated)' },
97
+ { kind: 'secondary', detail: 'secondary rate limit — concurrent + content creation 별도 가드' }
98
+ ]
99
+ },
100
+ discord: {
101
+ aliases: ['discord', 'discord api', 'discord bot'],
102
+ docs: 'https://discord.com/developers/docs/topics/rate-limits',
103
+ constraints: [
104
+ { kind: 'rate-limit', detail: 'global: 50 req/s, per-route 별도' },
105
+ { kind: 'invalid', detail: '10,000 invalid req/10min → 1h ban 위험' }
106
+ ]
107
+ },
108
+ twitter: {
109
+ aliases: ['twitter', 'twitter api', 'x api', 'x.com api'],
110
+ docs: 'https://developer.twitter.com/en/docs/twitter-api/rate-limits',
111
+ constraints: [
112
+ { kind: 'rate-limit', detail: 'tier-based: Free 1,500 posts/month, Basic 50,000 posts/month' },
113
+ { kind: 'auth', detail: 'OAuth 2.0 PKCE 필수 (user context), App-only는 별도 endpoint' }
114
+ ]
115
+ }
116
+ }
117
+ };
118
+
119
+ // 1.9.333 패턴 적용: intent 도메인 기본 catalog (순수 데이터) — intent-domain 서브시스템 핵심 데이터.
120
+ const _DEFAULT_DOMAIN_CATALOG = {
121
+ version: '1.9.213',
122
+ domains: {
123
+ game: {
124
+ aliases: ['게임', 'game', 'unity', 'unreal', 'godot', 'phaser', 'gamedev'],
125
+ components: [
126
+ { key: 'map', desc: '맵/타일 시스템 + 영역/스폰' },
127
+ { key: 'character', desc: '캐릭터/스프라이트 + 애니메이션 상태머신' },
128
+ { key: 'gameLoop', desc: '게임 루프 (tick/render/update)' },
129
+ { key: 'collision', desc: '충돌 감지 (AABB/SAT/grid)' },
130
+ { key: 'camera', desc: '카메라 follow + 영역 제한' },
131
+ { key: 'hud', desc: 'HUD/UI (HP/score/inventory)' },
132
+ { key: 'audio', desc: '사운드 매니저 (BGM/SFX)' },
133
+ { key: 'save', desc: '저장/로드 (slot 시스템)' },
134
+ { key: 'menu', desc: '메뉴 (메인/일시정지/설정)' },
135
+ { key: 'input', desc: '입력 핸들러 (키보드/패드/터치)' }
136
+ ]
137
+ },
138
+ web: {
139
+ aliases: ['웹', 'web', 'website', 'webapp', 'nextjs', 'react', 'vue', 'svelte', 'frontend'],
140
+ components: [
141
+ { key: 'routing', desc: '라우팅 (path/dynamic/nested)' },
142
+ { key: 'state', desc: '상태 관리 (context/redux/zustand)' },
143
+ { key: 'auth', desc: '인증 (OAuth/JWT/session)' },
144
+ { key: 'api', desc: 'API 클라이언트 (fetch/axios/tanstack-query)' },
145
+ { key: 'db', desc: 'DB 연동 (ORM/migration/seed)' },
146
+ { key: 'ui', desc: 'UI 컴포넌트 라이브러리' },
147
+ { key: 'test', desc: '테스트 (unit/e2e/visual)' },
148
+ { key: 'deploy', desc: '배포 (Vercel/Netlify/Cloudflare)' }
149
+ ]
150
+ },
151
+ api: {
152
+ aliases: ['api', 'rest', 'graphql', 'endpoint', 'backend', 'server'],
153
+ components: [
154
+ { key: 'endpoint', desc: '엔드포인트 라우팅 + HTTP method' },
155
+ { key: 'auth', desc: '인증/인가 (API key/OAuth/JWT)' },
156
+ { key: 'rate-limit', desc: 'rate limit (RPS/RPM/token bucket)' },
157
+ { key: 'validation', desc: '입력 검증 (zod/joi/yup)' },
158
+ { key: 'error', desc: '에러 핸들링 + 응답 형식' },
159
+ { key: 'logging', desc: '로깅 + 모니터링 (structured logs)' },
160
+ { key: 'docs', desc: 'API 문서 (OpenAPI/Swagger)' }
161
+ ]
162
+ },
163
+ cli: {
164
+ aliases: ['cli', 'command-line', 'tool', 'utility', 'shell'],
165
+ components: [
166
+ { key: 'argParser', desc: '인자 파싱 (yargs/commander/clipanion)' },
167
+ { key: 'help', desc: 'help / man / examples 텍스트' },
168
+ { key: 'config', desc: '설정 파일 + env 변수' },
169
+ { key: 'output', desc: '출력 (TTY 색상/JSON/quiet)' },
170
+ { key: 'error', desc: '에러 처리 + exit code 규약' },
171
+ { key: 'completion', desc: 'shell completion (bash/zsh/fish)' }
172
+ ]
173
+ },
174
+ data: {
175
+ aliases: ['data', 'pipeline', 'etl', 'analytics', 'ingest'],
176
+ components: [
177
+ { key: 'ingest', desc: '데이터 수집 (file/API/stream)' },
178
+ { key: 'transform', desc: '변환 (cleaning/normalization/joining)' },
179
+ { key: 'storage', desc: '저장소 (parquet/db/blob)' },
180
+ { key: 'query', desc: '쿼리/분석 (SQL/aggregations)' },
181
+ { key: 'validation', desc: '데이터 검증 (schema/contracts)' },
182
+ { key: 'lineage', desc: '데이터 lineage 추적' }
183
+ ]
184
+ }
185
+ }
186
+ };
187
+
188
+ // 1.9.335 (UR-0025 심층): LSP 정규식 fallback 언어별 심볼 패턴 catalog (harness 에서 분리)
189
+ // 6개 언어 (JS/TS / Python / Go / Rust / Java) — LSP 미설치 시 regex 심볼 추출에 사용.
190
+ const _LSP_LANG_PATTERNS = {
191
+ javascript: [
192
+ { re: /^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/, kind: 'function' },
193
+ { re: /^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/, kind: 'class' },
194
+ { re: /^\s*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/, kind: 'interface' },
195
+ { re: /^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?(?:function|\()/, kind: 'function' },
196
+ { re: /^\s*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/, kind: 'type' },
197
+ { re: /^\s*(?:export\s+)?enum\s+([A-Za-z_$][\w$]*)/, kind: 'enum' }
198
+ ],
199
+ python: [
200
+ { re: /^\s*async\s+def\s+([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
201
+ { re: /^\s*def\s+([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
202
+ { re: /^\s*class\s+([A-Za-z_][\w]*)\s*[(:]/, kind: 'class' }
203
+ ],
204
+ go: [
205
+ { re: /^\s*func\s+(?:\([^)]*\)\s+)?([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
206
+ { re: /^\s*type\s+([A-Za-z_][\w]*)\s+struct\b/, kind: 'struct' },
207
+ { re: /^\s*type\s+([A-Za-z_][\w]*)\s+interface\b/, kind: 'interface' },
208
+ { re: /^\s*type\s+([A-Za-z_][\w]*)\s+[A-Za-z]/, kind: 'type' }
209
+ ],
210
+ rust: [
211
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)/, kind: 'function' },
212
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?struct\s+([A-Za-z_][\w]*)/, kind: 'struct' },
213
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?enum\s+([A-Za-z_][\w]*)/, kind: 'enum' },
214
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?trait\s+([A-Za-z_][\w]*)/, kind: 'trait' },
215
+ { re: /^\s*impl\s+(?:[^{]+\s+for\s+)?([A-Za-z_][\w]*)/, kind: 'impl' },
216
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?type\s+([A-Za-z_][\w]*)\s*=/, kind: 'type' }
217
+ ],
218
+ java: [
219
+ { re: /^\s*(?:public|private|protected)?\s*(?:final\s+)?(?:abstract\s+)?class\s+([A-Za-z_][\w]*)/, kind: 'class' },
220
+ { re: /^\s*(?:public|private|protected)?\s*(?:abstract\s+)?interface\s+([A-Za-z_][\w]*)/, kind: 'interface' },
221
+ { re: /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?enum\s+([A-Za-z_][\w]*)/, kind: 'enum' },
222
+ // method: visibility + return type + name( (heuristic — 첫 번째 ( 매칭, 키워드 필터)
223
+ { re: /^\s*(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:[A-Za-z_<>,\s\[\]]+\s+)?([A-Za-z_][\w]*)\s*\(/, kind: 'method' }
224
+ ]
225
+ };
226
+
227
+ // 1.9.26: optimism-check — evidence의 외부 동작 주장 vs 실제 코드 호출 흔적 불일치 감지
228
+ // 사용자 요청 (1.9.26): "API 연동/작업 요청 시 실제로 일어나지 않았는데 일어난 것처럼 표시하는 낙관적 결과 방지"
229
+ //
230
+ // 패턴 (한국어 + 영어):
231
+ // evidence에 "API 호출" / "HTTP 200|201" / "POST /" / "응답 확인" → 코드에 fetch/http.request/axios 흔적 없으면 의심
232
+ // evidence에 "DB 저장" / "insert N건" / "DB에" → db.*/pg.*/mysql.*/mongoose.*/prisma.* 없으면 의심
233
+ // evidence에 "이메일 발송" / "메일 전송" → sendMail/nodemailer/smtp 없으면 의심
234
+ // 1.9.27: 패턴 카탈로그 확장 (5 → 10) + URL/메서드 단위 매핑 추가
235
+ const OPTIMISM_PATTERNS = [
236
+ { kind: 'API', evidenceRe: /(API\s*호출|HTTP\s*\d{3}|POST\s*\/|GET\s*\/|PUT\s*\/|DELETE\s*\/|fetch|REST 응답|응답 확인|endpoint|엔드포인트)/i,
237
+ codeRe: /\b(fetch\s*\(|http\.request|https\.request|axios\.|got\.|undici|node-fetch)/i,
238
+ label: 'API/HTTP 호출' },
239
+ { kind: 'DB', evidenceRe: /(DB에?\s*저장|insert\s+\d+|데이터베이스|SQL\s*(INSERT|UPDATE|DELETE)|migration|마이그레이션 적용)/i,
240
+ codeRe: /\b(db\.|pg\.|pool\.|mysql\.|mongoose\.|prisma\.|sequelize|knex|sqlite3|MongoClient|createConnection)/i,
241
+ label: 'DB 호출' },
242
+ { kind: 'Email', evidenceRe: /(이메일[^.\n]{0,30}(발송|전송|보냈|보냄|완료)|메일[^.\n]{0,30}(발송|전송|보냈|보냄)|sendMail|smtp\s*(전송|발송))/i,
243
+ codeRe: /\b(sendMail|nodemailer|smtp|@sendgrid|mailgun|aws-sdk\/ses|resend\.)/i,
244
+ label: '이메일 전송' },
245
+ { kind: 'Webhook', evidenceRe: /(웹훅\s*(호출|전송|발송)|webhook\s+(sent|posted|triggered))/i,
246
+ codeRe: /\b(fetch\s*\(|http\.request|axios\.)/i,
247
+ label: '웹훅' },
248
+ { kind: 'Payment', evidenceRe: /(결제\s*(완료|성공|승인|취소)|payment\s+(processed|charged)|stripe 결제|toss\s*결제|카카오페이|네이버페이|kakaopay|nicepay|iamport 결제|페이팔|paypal)/i,
249
+ codeRe: /\b(stripe|toss|@stripe|tosspayments|iamport|kakao|nicepay|naverpay|paypal-rest-sdk|@paypal)/i,
250
+ label: '결제' },
251
+ // 1.9.27 신규 카테고리
252
+ { kind: 'FileIO', evidenceRe: /(파일[^.\n]{0,20}(생성|저장|작성|기록)|\d+개[^.\n]{0,20}파일|디스크[^.\n]{0,20}저장|로그 파일 작성)/i,
253
+ codeRe: /\b(fs\.write|fs\.appendFile|writeFileSync|appendFileSync|fs\/promises|fs\.createWriteStream)/i,
254
+ label: '파일 I/O 쓰기' },
255
+ { kind: 'Queue', evidenceRe: /(메시지\s*큐|발행\s*완료|publish\s*(완료|성공)|RabbitMQ|Kafka|SQS|Redis Pub|이벤트 발행)/i,
256
+ codeRe: /\b(amqp|kafkajs|rabbit|redis\.(publish|xadd)|@aws-sdk\/client-sqs|bull|bullmq)/i,
257
+ label: '메시지 큐 발행' },
258
+ { kind: 'Cache', evidenceRe: /(Redis[^.\n]{0,20}(저장|set|get)|캐시[^.\n]{0,20}(저장|기록|적중)|memcache)/i,
259
+ codeRe: /\b(redis\.|ioredis|memcached|node-cache|@upstash\/redis|connect-redis)/i,
260
+ label: '캐시 저장' },
261
+ { kind: 'Notify', evidenceRe: /(슬랙\s*(알림|발송|전송)|Slack\s+(notification|sent|posted)|Discord\s+(알림|발송|webhook)|푸시 알림 전송)/i,
262
+ codeRe: /\b(@slack\/web-api|slack-webhook|discord\.js|discord-webhook|@discordjs|firebase\/messaging|expo-notifications)/i,
263
+ label: '슬랙/Discord 알림' },
264
+ { kind: 'Storage', evidenceRe: /(S3\s*(업로드|저장)|GCS\s*업로드|Azure Blob|클라우드 스토리지 업로드|object storage 저장)/i,
265
+ codeRe: /\b(@aws-sdk\/client-s3|aws-sdk[^a-z]|@google-cloud\/storage|@azure\/storage-blob|aws-s3)/i,
266
+ label: '클라우드 스토리지' }
267
+ ];
268
+
269
+
270
+ // 1.9.337 (UR-0025 심층): 리뷰 페르소나 catalog (5종: security/performance/ux/testing/docs) — harness 에서 분리.
271
+ const BUILT_IN_PERSONAS = {
272
+ security: {
273
+ id: 'security',
274
+ name: '보안 엔지니어 (10년차)',
275
+ description: 'OWASP Top 10, CWE, RFC, 한국 개인정보보호법/게임산업법 정통',
276
+ body: `너는 **10년 경력의 시니어 보안 엔지니어**다. OWASP Top 10 2021, CWE, RFC 7235/6454, CORS 보안, secret 관리에 정통하며, 한국 금융사·카카오·네이버 등 대형 IT 기업의 보안 감사 경험이 있다. 코드를 볼 때 **위협 모델링**과 **공격 표면(attack surface)** 을 자동으로 시각화한다.
277
+
278
+ 검토 영역: 입력 검증 / 인증·인가 / CORS / 시크릿/로그 노출 / DoS / 데이터 노출 / 의존성 attack surface / 한국 시장 특화 (개인정보보호법, 결제 정보)
279
+ 보고에 포함: 위협 모델 / CWE ID 매핑 / 실 공격 시나리오 1건 (HTTP 페이로드) / P0/P1/P2 우선순위 / OWASP Top 10 2021 매핑`
280
+ },
281
+ performance: {
282
+ id: 'performance',
283
+ name: '성능 최적화 전문가 (V8 내부)',
284
+ description: 'V8 엔진 (Ignition/TurboFan, hidden class), Node.js 이벤트 루프, libuv 정통',
285
+ body: `너는 **V8 엔진 내부 (Ignition, TurboFan, hidden class)와 Node.js 이벤트 루프, libuv에 정통한 성능 최적화 전문가**다. Linux perf, node --prof, clinic.js, autocannon, FlameGraph 활용 경험이 풍부하다. 메모리 압박(GC pressure), CPU bound vs I/O bound 구분, hot path 식별이 직관이다.
286
+
287
+ 검토 영역: Hot path 식별 / hidden class 안정성 / 메모리 할당 패턴 / 정규식 컴파일 / JSON.parse/stringify 비용 / 이벤트 루프 블로킹 / 라우트 매칭 복잡도
288
+ 보고에 포함: 성능 프로필 요약 (RPS/latency 추정) / Hot path Top 5 / 비효율 표 (영향 high/med/low) / 벤치 시나리오 (autocannon 명령) / 권장 우선순위 (당장/부하증가/마이크로)`
289
+ },
290
+ ux: {
291
+ id: 'ux',
292
+ name: '한국어 UX 라이터 + DX 컨설턴트',
293
+ description: '카카오/네이버/토스/라인 마이크로카피, API 디자인 (Stripe/GitHub/Google) 정통',
294
+ body: `너는 **한국 사용자 대상 게임/SaaS 제품의 UX 라이터 + DX(Developer Experience) 컨설턴트**다. 카카오, 네이버, 토스, 라인의 한국어 마이크로카피 가이드라인을 숙지하고 있으며, 클라이언트 개발자의 API 통합 경험을 잘 안다. 에러 메시지, HTTP status, 응답 본문 일관성이 직관이다.
295
+
296
+ 검토 영역: 한국어 에러 메시지 톤 / HTTP status 적절성 (400/404/422/409) / 응답 본문 일관성 / 한국어/영문 혼재 / 누락 정보 (rate limit, request id, version) / 클라이언트 SDK 친화성
297
+ 보고에 포함: UX/DX 점수 (1-10) / 발견 이슈 표 / Before/After 메시지 5건 / SDK 친화성 점수 (1-5) / 권장 로드맵 (이번 PR / 1주 / 분기)`
298
+ },
299
+ testing: {
300
+ id: 'testing',
301
+ name: '테스트 엔지니어 (TDD + property-based)',
302
+ description: 'TDD, property-based testing (fast-check), AAA 패턴, fuzz, mutation testing 정통',
303
+ body: `너는 **TDD와 property-based testing (fast-check) 에 정통한 테스트 엔지니어**다. AAA 패턴, given/when/then, fuzz testing, mutation testing, contract testing 경험이 있다. 테스트 커버리지보다 **테스트 품질**과 **회귀 방어** 가치를 더 중시한다.
304
+
305
+ 검토 영역: 테스트 누락 분기 / edge case / mocking 과다 / AAA 패턴 위반 / async 테스트 결함 (race) / property 후보 / 회귀 가능성
306
+ 보고에 포함: 누락 테스트 목록 + 우선순위 / fast-check property 후보 3건 / 기존 테스트 약점 / 권장 회귀 시나리오`
307
+ },
308
+ docs: {
309
+ id: 'docs',
310
+ name: '기술 문서 작성자 (한국어)',
311
+ description: 'README, API 문서, 사용 가이드 작성. Stripe Docs / Google Cloud / 카카오 dev 가이드 정통',
312
+ body: `너는 **한국어 기술 문서 작성에 정통한 테크니컬 라이터**다. Stripe Docs, Google Cloud, AWS, 카카오 개발자 가이드 톤을 잘 안다. README 첫 60초 경험, 점진적 공개 (progressive disclosure), 코드 예시의 즉시 실행 가능성을 중시한다.
313
+
314
+ 검토 영역: 60초 시작 가능성 / 예시 코드 정확성 / 누락된 사전 요구사항 / 한국어 자연스러움 / 시각적 균형 (이모지/표/코드블록) / 한국어/영문 혼재 / 다음 단계 명시
315
+ 보고에 포함: 사용자 페르소나별 평가 (입문자/실무자/전문가) / 60초 안 첫 결과 가능 여부 / 누락 정보 / 권장 개선 표`
316
+ }
317
+ };
318
+
319
+
320
+ // 1.9.338 (UR-0025 심층): i18n 문자열 catalog (ko/en) — harness 에서 분리. 조회는 순수 _translate(STRINGS, key, lang).
321
+ const STRINGS = {
322
+ // 설치 가이드 prompt
323
+ 'install.lang.title': { ko: '설치 언어를 선택하세요', en: 'Select install language' },
324
+ 'install.lang.auto': { ko: '자동 감지', en: 'Auto detect' },
325
+ 'install.lang.auto.desc': { ko: '디렉토리/파일 + 시스템(OS) 언어 자동 판별 (한국어/영어)', en: 'Auto-detect from dir/files + system (OS) locale (KO/EN)' },
326
+ 'install.lang.sysNotice': { ko: '시스템 언어 감지', en: 'System language detected' },
327
+ 'install.lang.ko': { ko: '한국어', en: 'Korean' },
328
+ 'install.lang.ko.desc': { ko: '모든 인스트럭션을 한국어로 생성', en: 'All instructions in Korean' },
329
+ 'install.lang.en': { ko: 'English', en: 'English' },
330
+ 'install.lang.en.desc': { ko: '모든 인스트럭션을 영어로 생성', en: 'All instructions in English' },
331
+ 'install.agents.title': { ko: 'CLI 에이전트 활성화 (복수 선택, Space 토글) — sub-agent 위임용',
332
+ en: 'Enable CLI agents (multi-select, Space toggle) — for sub-agent dispatch' },
333
+ 'install.agents.none': { ko: '선택 안함 (나중에 setup-agents)', en: 'None (later setup-agents)' },
334
+ 'install.complete': { ko: '✓ 설치 완료', en: '✓ Install complete' },
335
+ // REPL agent 모드
336
+ 'repl.welcome.title': { ko: 'leerness REPL agent', en: 'leerness REPL agent' },
337
+ 'repl.welcome.subtitle': { ko: 'Tab provider · Shift+Tab model · :help · /slash', en: 'Tab provider · Shift+Tab model · :help · /slash' },
338
+ 'repl.welcome.start': { ko: '시작하려면 메시지를 입력하세요', en: 'Type a message to start' },
339
+ // 공통
340
+ 'common.cancel': { ko: '취소됨', en: 'Cancelled' },
341
+ 'common.confirm': { ko: '확인', en: 'Confirm' },
342
+ 'common.ready': { ko: '준비 완료', en: 'Ready' }
343
+ };
344
+
345
+
346
+ // 1.9.341 (UR-0025 심층): 내장 스킬 catalog (9종) — harness 에서 분리. _loadSkillCatalog 의 builtin fallback.
347
+ const BUILTIN_CATALOG = {
348
+ 'office': { displayNameKo: '마이크로소프트 오피스 자동화 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Word/Excel/PowerPoint 문서 자동화', '템플릿 기반 문서 생성', '표/차트/요약 문서화', '민감정보 제외 규칙 적용'] },
349
+ 'commerce-api': { displayNameKo: '커머스 API 연동 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['쿠팡·롯데온·스마트스토어 API 연동 설계', '주문/상품/매출 동기화', '환경변수 기반 인증 분리', '레이트리밋/재시도/오류 처리'] },
350
+ 'crawling': { displayNameKo: '크롤링·브라우저 자동화 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Playwright 기반 자동화', '다운로드/로그인 세션 처리', '스크린샷 기반 실패 진단', '약관/권한/차단 위험 점검'] },
351
+ 'firebase': { displayNameKo: 'Firebase·Cloud Functions 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['Firebase Functions 배포 구조', '환경변수/시크릿 분리', '권한/IAM 점검', '로컬 에뮬레이터 검증'] },
352
+ 'ads-analytics': { displayNameKo: '광고·GA4 분석 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['GA4 이벤트/전환 점검', '광고 데이터 수집 구조화', '소스/매체 분석', '리포트 자동화'] },
353
+ 'appstore-review': { displayNameKo: '앱스토어 심사 대응 스킬 라이브러리', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['심사 문구 분석', '개인정보 라벨 점검', '리젝 대응 초안', '웹뷰/앱 데이터 수집 구분'] },
354
+ 'ai-verified-skill-publisher': { displayNameKo: 'AI 검증 스킬 업로드·라이브러리화 스킬', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['검증된 스킬 정규화', '민감정보 스캔', 'AI 검증 메타데이터 작성', 'npm/git 업로드 dry-run 및 실행 게이트'] },
355
+ 'feature-implementation': { displayNameKo: '기능 구현 표준 스킬', version: '1.0.0', lastUpdated: '2026-05-08', verification: 'passed', capabilities: ['feature-contracts 작성', '재사용 우선 검사', '테스트 증거 수집', '핸드오프 트리거'] },
356
+ // 1.9.11: 기본 내장 — 로드맵 자동 생성 스킬
357
+ 'project-roadmap-generator': { displayNameKo: '프로젝트 로드맵 자동 생성 스킬', version: '0.2.0', lastUpdated: '2026-05-12', verification: 'passed', capabilities: ['leerness .harness/* 통합 파싱 (plan/progress/skills/rules/decisions/handoff/current-state)', '좌→우 수평 트리 + 상하 중앙정렬 SVG', '7개 상태 색상 (완료/진행/보류/검토/예정/미완료/오류)', 'design-system + CSS variables 자동 주입', '화이트보드 panning/zoom + 더블클릭 reset', '단일 HTML 출력 (외부 의존성 0)'] }
358
+ };
359
+
360
+ // 1.9.342 (UR-0025 심층): roadmap.html 상태 라벨/색상 맵 (status → ko 라벨 / hex 색상) — harness 에서 분리.
361
+ const ROADMAP_STATUS_LABEL = { done: '완료', 'in-progress': '진행', 'on-hold': '보류', waiting: '검토', incomplete: '미완료', planned: '예정', blocked: '오류', dropped: '취소', skill: '스킬', rule: '룰', meta: '프로젝트' };
362
+ const ROADMAP_STATUS_COLOR = { done: '#16a34a', 'in-progress': '#2563eb', 'on-hold': '#6b7280', waiting: '#eab308', incomplete: '#f97316', planned: '#94a3b8', blocked: '#dc2626', dropped: '#9ca3af', skill: '#8b5cf6', rule: '#06b6d4', meta: '#0f172a' };
363
+
364
+
365
+ // 1.9.343 (UR-0025 심층): 시크릿 값 스캔 정규식 catalog (13종) — harness 에서 분리. scan secrets 의 값 탐지. (_isSecretKey 키이름 휴리스틱과 보안 응집)
366
+ const SECRET_PATTERNS = [
367
+ { name: 'AWS Access Key', re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g }, // ASIA=임시 자격증명 추가
368
+ { name: 'GitHub token', re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g }, // ghp_/gho_/ghu_/ghs_/ghr_ 통합
369
+ { name: 'GitHub fine-grained PAT', re: /\bgithub_pat_[A-Za-z0-9_]{40,}\b/g },
370
+ { name: 'OpenAI project/service key', re: /\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}/g }, // modern (하이픈/언더스코어 포함)
371
+ { name: 'OpenAI API key', re: /\bsk-[A-Za-z0-9]{32,}\b/g }, // legacy
372
+ { name: 'Anthropic API key', re: /\bsk-ant-[A-Za-z0-9_-]{20,}/g }, // _ 포함 + 후행 \b 제거 (실제 키 호환)
373
+ { name: 'Stripe secret key', re: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\b/g },
374
+ { name: 'npm token', re: /\bnpm_[A-Za-z0-9]{36}\b/g },
375
+ { name: 'Google API key', re: /\bAIza[0-9A-Za-z_\-]{35}\b/g },
376
+ { name: 'Google OAuth token', re: /\bya29\.[A-Za-z0-9_-]{20,}/g },
377
+ { name: 'Slack token', re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
378
+ { name: 'Generic private key', re: /-----BEGIN (?:RSA |OPENSSH |EC |DSA |PGP )?PRIVATE KEY-----/g },
379
+ { name: 'Hardcoded password assignment', re: /\b(?:password|passwd|pwd|secret|api_key|apikey)\s*[:=]\s*["']([^"'\s]{6,})["']/gi, valueGroup: 1 }, // 1.9.365 CV-6: valueGroup → placeholder 오탐 억제
380
+ // 1.9.365 (외부리뷰 CV-6/UR-0081): unquoted 자격증명 (KEY=secret123) FN 보강. 값이 시크릿스러운지(_looksSecretLike) + placeholder 아닌지 후처리. 코드 var-ref(점 포함)는 charset 으로 제외.
381
+ { name: 'Hardcoded credential (unquoted)', re: /\b(?:password|passwd|pwd|secret|secret_key|api_key|apikey|access_key|access_token|auth_token)\s*=\s*([A-Za-z0-9_\-/+]{8,})(?=[\s;,)]|$)/gi, valueGroup: 1, requireSecretLike: true },
382
+ // 1.9.350 (UR-0060 외부리뷰 3모델): 누락 패턴 보강
383
+ { name: 'GitLab PAT', re: /\bglpat-[A-Za-z0-9_-]{20,}\b/g },
384
+ { name: 'JWT', re: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
385
+ { name: 'DB connection string (embedded password)', re: /\b(?:postgres|postgresql|mysql|mongodb(?:\+srv)?|redis|amqp):\/\/[^:\s/@]+:[^@\s/]+@/gi },
386
+ { name: 'SendGrid API key', re: /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b/g },
387
+ { name: 'AWS Secret Access Key (context)', re: /\baws[^\n]{0,40}?(?:secret_access_key|secret_key|secret)[^\n]{0,12}?["']?[A-Za-z0-9/+]{40}["']?/gi },
388
+ { name: 'Hardcoded Bearer token', re: /\bBearer\s+[A-Za-z0-9_\-.=]{20,}/g },
389
+ ];
390
+
391
+ // 1.9.367/1.9.368 (UR-0025): 데이터/인덱스 파일은 migration-preserved 블록 없이 overwrite (누적 방지) — harness 에서 분리.
392
+ const MERGE_OVERWRITE_FILES = new Set([
393
+ '.harness/skill-index.md',
394
+ '.harness/manifest.json',
395
+ '.harness/skills-lock.json',
396
+ '.harness/HARNESS_VERSION',
397
+ '.harness/LANGUAGE',
398
+ '.harness/context-routing.md'
399
+ ]);
400
+
401
+ // 1.9.380 (UR-0025): 워크스페이스 필수 파일 — verify / migrate audit / migrate apply 단일출처 (harness 3중 중복 제거).
402
+ const REQUIRED_WORKSPACE_FILES = ['.harness/plan.md', '.harness/progress-tracker.md', '.harness/guideline.md', '.harness/protected-files.md', '.harness/design-system.md', '.harness/anti-lazy-work-policy.md', '.harness/session-handoff.md', '.harness/current-state.md', 'AGENTS.md'];
403
+
404
+ // 1.9.381 (UR-0025): 키워드 추출 stopwords — handoff 자동회수 / lessons 키워드 추출 단일출처 (harness 2중 중복 제거).
405
+ const KEYWORD_STOPWORDS = new Set([
406
+ '이런', '저런', '하다', '하고', '있는', '하지', '에서',
407
+ '작업', '구현', '추가', '진행', '수정', '변경', '검토', '확인',
408
+ '프로젝트', '관리', '기능', '시스템', '코드', '파일', '버전', '정리', '계획',
409
+ 'next', 'action', 'task', 'todo', 'work'
410
+ ]);
411
+
412
+ // 1.9.369 (UR-0025): init --minimal 시 제외하는 키 — 코어 워크플로(handoff/verify/audit/session close)가 요구하지 않는 파일만. harness 에서 분리.
413
+ const MINIMAL_SKIP_KEYS = new Set([
414
+ '.cursor/rules/leerness.mdc', '.github/copilot-instructions.md',
415
+ '.harness/project-brief.md', '.harness/task-type-map.md', '.harness/architecture.md', '.harness/context-map.md',
416
+ '.harness/guardrails.md', '.harness/feature-contracts.md', '.harness/feature-graph.md',
417
+ '.harness/testing-strategy.md', '.harness/review-checklist.md', '.harness/release-checklist.md',
418
+ '.harness/session-close-policy.md', '.harness/language-policy.md', '.harness/test-evidence-policy.md',
419
+ '.harness/AX_PLAN_GUIDE.md', '.harness/AX_MIGRATION_GUIDE.md', '.harness/AX_NEW_PROJECT_GUIDE.md', '.harness/AX_SKILL_LIBRARY_GUIDE.md',
420
+ '.harness/skill-index.md',
421
+ '.harness/templates/end-of-session-report.md', '.harness/templates/decision.md', '.harness/templates/task-row.md',
422
+ '.claude/skills/leerness.md'
423
+ ]);
424
+
425
+ // 1.9.344 (UR-0025 심층): skill discover GitHub preset catalog (vercel/anthropic) — harness 에서 분리.
426
+ const SKILL_CATALOG_PRESETS = {
427
+ 'vercel': { owner: 'vercel-labs', repo: 'agent-skills', branch: 'main', path: 'skills',
428
+ homepage: 'https://github.com/vercel-labs/agent-skills' },
429
+ 'anthropic': { owner: 'anthropics', repo: 'skills', branch: 'main', path: 'skills',
430
+ homepage: 'https://github.com/anthropics/skills' }
431
+ };
432
+
433
+ // 1.11.4 (UR-0007): 큐레이션 도구 용어집 카탈로그 비개발자용 설명. constraints 카탈로그 패턴(aliases+merge-loadable).
434
+ // 바이브 코딩 시 package.json/requirements 의존성을 plain-ko/plain-en 으로 풀어줌(무LLM·0deps·손작성 고품질).
435
+ const _TOOL_CATALOG = {
436
+ version: '1.0.0',
437
+ tools: {
438
+ react: { aliases: ['react', 'react-dom', 'reactjs'], category: 'frontend', plainKo: '화면(UI)을 재사용 가능한 조각(컴포넌트)으로 나눠 만드는 웹 화면 라이브러리.', plainEn: 'A library for building web UIs from reusable components.', docs: 'https://react.dev' },
439
+ vue: { aliases: ['vue', 'vuejs', '@vue/runtime-core'], category: 'frontend', plainKo: 'React 대안의 웹 화면 프레임워크 — 배우기 쉬운 편.', plainEn: 'A progressive framework for building web UIs (a React alternative).' },
440
+ svelte: { aliases: ['svelte', 'sveltekit', '@sveltejs/kit'], category: 'frontend', plainKo: '빌드 시 최적화해 가볍고 빠른 웹 화면 프레임워크.', plainEn: 'A compiler-based UI framework that ships minimal JS.' },
441
+ next: { aliases: ['next', 'nextjs'], category: 'fullstack', plainKo: 'React 기반으로 페이지·서버 기능까지 한 번에 만드는 풀스택 프레임워크.', plainEn: 'A React framework adding routing, server rendering, and backend routes.' },
442
+ tailwind: { aliases: ['tailwindcss', 'tailwind'], category: 'frontend', plainKo: 'class 이름만으로 빠르게 디자인하는 CSS 유틸리티 도구.', plainEn: 'A utility-first CSS framework you style via class names.' },
443
+ astro: { aliases: ['astro'], category: 'frontend', plainKo: '내용 위주 사이트를 빠르게 만드는 웹 프레임워크(기본 JS 최소).', plainEn: 'A content-focused web framework that ships zero JS by default.' },
444
+ express: { aliases: ['express', 'expressjs'], category: 'backend', plainKo: '웹 서버에서 주소(라우트)별 요청을 처리하는 가장 흔한 Node 서버 틀.', plainEn: 'A minimal Node.js framework for handling web server routes.' },
445
+ fastify: { aliases: ['fastify'], category: 'backend', plainKo: 'Express 대안의 빠른 Node 웹 서버 틀.', plainEn: 'A fast, low-overhead Node.js web framework.' },
446
+ nestjs: { aliases: ['@nestjs/core', 'nestjs', 'nest'], category: 'backend', plainKo: '구조가 잡힌(엔터프라이즈형) Node 백엔드 프레임워크.', plainEn: 'A structured, opinionated Node.js backend framework.' },
447
+ fastapi: { aliases: ['fastapi'], category: 'backend', plainKo: '파이썬으로 빠르게 API 서버를 만드는 현대적 프레임워크.', plainEn: 'A modern, fast Python framework for building APIs.' },
448
+ django: { aliases: ['django'], category: 'backend', plainKo: '관리자·인증까지 다 갖춘 파이썬 웹 프레임워크(배터리 포함).', plainEn: 'A batteries-included Python web framework.' },
449
+ flask: { aliases: ['flask'], category: 'backend', plainKo: '가볍고 단순한 파이썬 웹 서버 프레임워크.', plainEn: 'A lightweight, minimal Python web framework.' },
450
+ postgres: { aliases: ['pg', 'postgres', 'postgresql', 'node-postgres', 'psycopg2'], category: 'database', plainKo: '데이터를 표(테이블) 형태로 안전하게 저장하는 관계형 데이터베이스.', plainEn: 'A relational database that stores data in tables.' },
451
+ mysql: { aliases: ['mysql', 'mysql2', 'mariadb'], category: 'database', plainKo: '가장 널리 쓰이는 관계형 데이터베이스 중 하나.', plainEn: 'A widely-used relational database.' },
452
+ mongodb: { aliases: ['mongodb', 'mongoose'], category: 'database', plainKo: '표 대신 문서(JSON 비슷) 형태로 저장하는 NoSQL 데이터베이스.', plainEn: 'A NoSQL database that stores JSON-like documents.' },
453
+ redis: { aliases: ['redis', 'ioredis'], category: 'database', plainKo: '아주 빠른 임시 저장소(캐시) — 자주 쓰는 값을 메모리에 보관.', plainEn: 'A fast in-memory store used for caching and queues.' },
454
+ sqlite: { aliases: ['sqlite', 'sqlite3', 'better-sqlite3'], category: 'database', plainKo: '파일 하나로 동작하는 가벼운 데이터베이스(서버 불필요).', plainEn: 'A lightweight file-based database (no server needed).' },
455
+ prisma: { aliases: ['prisma', '@prisma/client'], category: 'database', plainKo: '코드에서 데이터베이스를 타입 안전하게 다루는 도구(ORM).', plainEn: 'A type-safe ORM for querying your database from code.' },
456
+ drizzle: { aliases: ['drizzle-orm', 'drizzle'], category: 'database', plainKo: '가벼운 타입 안전 SQL ORM.', plainEn: 'A lightweight, type-safe SQL ORM.' },
457
+ supabase: { aliases: ['@supabase/supabase-js', 'supabase'], category: 'backend', plainKo: 'DB·인증·저장소를 한 번에 주는 오픈소스 백엔드 서비스(Firebase 대안).', plainEn: 'An open-source backend (DB + auth + storage), a Firebase alternative.' },
458
+ firebase: { aliases: ['firebase', 'firebase-admin'], category: 'backend', plainKo: '구글의 DB·인증·호스팅 백엔드 서비스.', plainEn: "Google's backend service for DB, auth, and hosting." },
459
+ stripe: { aliases: ['stripe', '@stripe/stripe-js'], category: 'payments', plainKo: '신용카드 결제를 대신 처리해 주는 결제 서비스.', plainEn: 'A payments service that handles credit-card checkout.' },
460
+ zod: { aliases: ['zod'], category: 'validation', plainKo: '들어온 데이터가 올바른 형식인지 검사·보장하는 스키마 검증기.', plainEn: 'A schema validator that checks data is the right shape.' },
461
+ yup: { aliases: ['yup'], category: 'validation', plainKo: '폼/데이터 형식 검증 라이브러리.', plainEn: 'A schema validation library for forms/data.' },
462
+ jsonwebtoken:{ aliases: ['jsonwebtoken', 'jose'], category: 'auth', plainKo: '로그인 증명 토큰(JWT)을 만들고 검증하는 도구.', plainEn: 'Creates and verifies login proof tokens (JWT).' },
463
+ passport: { aliases: ['passport'], category: 'auth', plainKo: 'Node 서버의 로그인/인증 처리 미들웨어.', plainEn: 'Authentication middleware for Node servers.' },
464
+ bcrypt: { aliases: ['bcrypt', 'bcryptjs'], category: 'auth', plainKo: '비밀번호를 안전하게 암호화(해시)해 저장하는 도구.', plainEn: 'Securely hashes passwords for storage.' },
465
+ axios: { aliases: ['axios', 'node-fetch', 'got'], category: 'http', plainKo: '다른 서버에 요청을 보내는(HTTP 호출) 도구.', plainEn: 'A client for making HTTP requests to other servers.' },
466
+ graphql: { aliases: ['graphql', 'apollo-server', '@apollo/client'], category: 'api', plainKo: '필요한 데이터만 골라 받는 API 질의 언어/방식.', plainEn: 'A query language/runtime for APIs that fetch exactly what you ask.' },
467
+ socketio: { aliases: ['socket.io', 'ws'], category: 'realtime', plainKo: '서버-브라우저 실시간 양방향 통신(채팅 등) 도구.', plainEn: 'Real-time two-way communication (e.g. chat) between server and browser.' },
468
+ jest: { aliases: ['jest', 'vitest', 'mocha'], category: 'testing', plainKo: '코드가 의도대로 동작하는지 자동으로 확인하는 테스트 도구.', plainEn: 'A testing framework that auto-checks your code behaves correctly.' },
469
+ playwright: { aliases: ['playwright', '@playwright/test', 'puppeteer', 'cypress'], category: 'testing', plainKo: '실제 브라우저를 자동 조작해 화면을 테스트하는 도구.', plainEn: 'Drives a real browser to test your UI end-to-end.' },
470
+ eslint: { aliases: ['eslint', 'prettier'], category: 'tooling', plainKo: '코드 스타일·실수를 자동 점검/정리하는 도구.', plainEn: 'Lints and formats code to catch mistakes and enforce style.' },
471
+ typescript: { aliases: ['typescript', 'ts-node'], category: 'language', plainKo: 'JavaScript에 타입(자료형) 검사를 더해 실수를 줄이는 언어.', plainEn: 'JavaScript with static types to catch errors early.' },
472
+ webpack: { aliases: ['webpack', 'vite', 'rollup', 'esbuild', 'parcel'], category: 'build', plainKo: '여러 코드 파일을 브라우저용으로 묶고 최적화하는 빌드 도구.', plainEn: 'Bundles and optimizes your code files for the browser.' },
473
+ docker: { aliases: ['docker', 'dockerfile', 'docker-compose'], category: 'infra', plainKo: '앱과 실행 환경을 통째로 상자(컨테이너)에 담아 어디서나 똑같이 실행.', plainEn: 'Packages an app + its environment into a portable container.' },
474
+ kubernetes: { aliases: ['kubernetes', 'k8s', 'helm'], category: 'infra', plainKo: '여러 컨테이너를 자동으로 배치·관리하는 오케스트레이션 도구.', plainEn: 'Orchestrates and manages many containers automatically.' },
475
+ openai: { aliases: ['openai'], category: 'ai', plainKo: 'OpenAI(GPT) AI 모델을 코드에서 호출하는 라이브러리.', plainEn: 'A client to call OpenAI (GPT) AI models from code.' },
476
+ anthropic: { aliases: ['@anthropic-ai/sdk', 'anthropic'], category: 'ai', plainKo: 'Anthropic(Claude) AI 모델을 코드에서 호출하는 라이브러리.', plainEn: 'A client to call Anthropic (Claude) AI models from code.' },
477
+ langchain: { aliases: ['langchain', '@langchain/core'], category: 'ai', plainKo: 'AI 모델·도구를 엮어 앱을 만드는 프레임워크.', plainEn: 'A framework for chaining LLMs and tools into apps.' },
478
+ dotenv: { aliases: ['dotenv', 'python-dotenv'], category: 'config', plainKo: '비밀키 등 설정값을 .env 파일에서 읽어오는 도구.', plainEn: 'Loads config/secret values from a .env file.' },
479
+ lodash: { aliases: ['lodash', 'ramda'], category: 'utility', plainKo: '배열·객체 다루기 등 자주 쓰는 유틸 함수 모음.', plainEn: 'A utility library of common array/object helpers.' },
480
+ dayjs: { aliases: ['dayjs', 'date-fns', 'moment', 'luxon'], category: 'utility', plainKo: '날짜·시간을 다루기 쉽게 해 주는 라이브러리.', plainEn: 'A library for parsing and formatting dates/times.' },
481
+ pandas: { aliases: ['pandas', 'numpy'], category: 'data', plainKo: '파이썬으로 표 형태 데이터를 분석·가공하는 라이브러리.', plainEn: 'Python libraries for tabular data analysis and numerics.' }
482
+ }
483
+ };
484
+
485
+ module.exports = { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _TOOL_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS };