@wooojin/forgen 0.1.0

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 (268) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/CHANGELOG.md +353 -0
  3. package/CONTRIBUTING.md +98 -0
  4. package/LICENSE +21 -0
  5. package/README.ja.md +469 -0
  6. package/README.ko.md +469 -0
  7. package/README.md +483 -0
  8. package/README.zh.md +469 -0
  9. package/agents/analyst.md +98 -0
  10. package/agents/architect.md +62 -0
  11. package/agents/code-reviewer.md +120 -0
  12. package/agents/code-simplifier.md +197 -0
  13. package/agents/critic.md +70 -0
  14. package/agents/debugger.md +117 -0
  15. package/agents/designer.md +131 -0
  16. package/agents/executor.md +54 -0
  17. package/agents/explore.md +145 -0
  18. package/agents/git-master.md +212 -0
  19. package/agents/performance-reviewer.md +172 -0
  20. package/agents/planner.md +29 -0
  21. package/agents/qa-tester.md +158 -0
  22. package/agents/refactoring-expert.md +168 -0
  23. package/agents/scientist.md +144 -0
  24. package/agents/security-reviewer.md +137 -0
  25. package/agents/test-engineer.md +153 -0
  26. package/agents/verifier.md +133 -0
  27. package/agents/writer.md +184 -0
  28. package/commands/api-design.md +268 -0
  29. package/commands/architecture-decision.md +314 -0
  30. package/commands/ci-cd.md +270 -0
  31. package/commands/code-review.md +233 -0
  32. package/commands/compound.md +117 -0
  33. package/commands/database.md +263 -0
  34. package/commands/debug-detective.md +99 -0
  35. package/commands/docker.md +274 -0
  36. package/commands/documentation.md +276 -0
  37. package/commands/ecomode.md +51 -0
  38. package/commands/frontend.md +271 -0
  39. package/commands/git-master.md +90 -0
  40. package/commands/incident-response.md +292 -0
  41. package/commands/migrate.md +101 -0
  42. package/commands/performance.md +288 -0
  43. package/commands/refactor.md +105 -0
  44. package/commands/security-review.md +288 -0
  45. package/commands/tdd.md +183 -0
  46. package/commands/testing-strategy.md +265 -0
  47. package/dist/cli.d.ts +2 -0
  48. package/dist/cli.js +295 -0
  49. package/dist/core/auto-compound-runner.d.ts +12 -0
  50. package/dist/core/auto-compound-runner.js +460 -0
  51. package/dist/core/config-hooks.d.ts +10 -0
  52. package/dist/core/config-hooks.js +112 -0
  53. package/dist/core/config-injector.d.ts +50 -0
  54. package/dist/core/config-injector.js +455 -0
  55. package/dist/core/doctor.d.ts +1 -0
  56. package/dist/core/doctor.js +163 -0
  57. package/dist/core/errors.d.ts +81 -0
  58. package/dist/core/errors.js +133 -0
  59. package/dist/core/global-config.d.ts +43 -0
  60. package/dist/core/global-config.js +25 -0
  61. package/dist/core/harness.d.ts +24 -0
  62. package/dist/core/harness.js +621 -0
  63. package/dist/core/init.d.ts +7 -0
  64. package/dist/core/init.js +37 -0
  65. package/dist/core/inspect-cli.d.ts +7 -0
  66. package/dist/core/inspect-cli.js +47 -0
  67. package/dist/core/legacy-detector.d.ts +33 -0
  68. package/dist/core/legacy-detector.js +66 -0
  69. package/dist/core/logger.d.ts +34 -0
  70. package/dist/core/logger.js +121 -0
  71. package/dist/core/mcp-config.d.ts +44 -0
  72. package/dist/core/mcp-config.js +177 -0
  73. package/dist/core/notepad.d.ts +31 -0
  74. package/dist/core/notepad.js +88 -0
  75. package/dist/core/paths.d.ts +85 -0
  76. package/dist/core/paths.js +101 -0
  77. package/dist/core/plugin-detector.d.ts +44 -0
  78. package/dist/core/plugin-detector.js +226 -0
  79. package/dist/core/runtime-detector.d.ts +8 -0
  80. package/dist/core/runtime-detector.js +49 -0
  81. package/dist/core/scope-resolver.d.ts +8 -0
  82. package/dist/core/scope-resolver.js +45 -0
  83. package/dist/core/session-logger.d.ts +6 -0
  84. package/dist/core/session-logger.js +111 -0
  85. package/dist/core/session-store.d.ts +28 -0
  86. package/dist/core/session-store.js +218 -0
  87. package/dist/core/settings-lock.d.ts +18 -0
  88. package/dist/core/settings-lock.js +125 -0
  89. package/dist/core/spawn.d.ts +3 -0
  90. package/dist/core/spawn.js +135 -0
  91. package/dist/core/types.d.ts +108 -0
  92. package/dist/core/types.js +1 -0
  93. package/dist/core/uninstall.d.ts +4 -0
  94. package/dist/core/uninstall.js +307 -0
  95. package/dist/core/v1-bootstrap.d.ts +26 -0
  96. package/dist/core/v1-bootstrap.js +155 -0
  97. package/dist/engine/compound-cli.d.ts +24 -0
  98. package/dist/engine/compound-cli.js +250 -0
  99. package/dist/engine/compound-extractor.d.ts +68 -0
  100. package/dist/engine/compound-extractor.js +860 -0
  101. package/dist/engine/compound-lifecycle.d.ts +32 -0
  102. package/dist/engine/compound-lifecycle.js +305 -0
  103. package/dist/engine/compound-loop.d.ts +32 -0
  104. package/dist/engine/compound-loop.js +511 -0
  105. package/dist/engine/match-eval-log.d.ts +139 -0
  106. package/dist/engine/match-eval-log.js +270 -0
  107. package/dist/engine/phrase-blocklist.d.ts +119 -0
  108. package/dist/engine/phrase-blocklist.js +208 -0
  109. package/dist/engine/skill-promoter.d.ts +20 -0
  110. package/dist/engine/skill-promoter.js +115 -0
  111. package/dist/engine/solution-format.d.ts +160 -0
  112. package/dist/engine/solution-format.js +432 -0
  113. package/dist/engine/solution-index.d.ts +13 -0
  114. package/dist/engine/solution-index.js +252 -0
  115. package/dist/engine/solution-matcher.d.ts +364 -0
  116. package/dist/engine/solution-matcher.js +656 -0
  117. package/dist/engine/solution-writer.d.ts +76 -0
  118. package/dist/engine/solution-writer.js +157 -0
  119. package/dist/engine/term-matcher.d.ts +81 -0
  120. package/dist/engine/term-matcher.js +268 -0
  121. package/dist/engine/term-normalizer.d.ts +116 -0
  122. package/dist/engine/term-normalizer.js +171 -0
  123. package/dist/fgx.d.ts +6 -0
  124. package/dist/fgx.js +42 -0
  125. package/dist/forge/cli.d.ts +11 -0
  126. package/dist/forge/cli.js +100 -0
  127. package/dist/forge/evidence-processor.d.ts +21 -0
  128. package/dist/forge/evidence-processor.js +87 -0
  129. package/dist/forge/mismatch-detector.d.ts +44 -0
  130. package/dist/forge/mismatch-detector.js +83 -0
  131. package/dist/forge/onboarding-cli.d.ts +6 -0
  132. package/dist/forge/onboarding-cli.js +89 -0
  133. package/dist/forge/onboarding.d.ts +25 -0
  134. package/dist/forge/onboarding.js +122 -0
  135. package/dist/hooks/compound-reflection.d.ts +45 -0
  136. package/dist/hooks/compound-reflection.js +82 -0
  137. package/dist/hooks/context-guard.d.ts +24 -0
  138. package/dist/hooks/context-guard.js +156 -0
  139. package/dist/hooks/dangerous-patterns.json +18 -0
  140. package/dist/hooks/db-guard.d.ts +17 -0
  141. package/dist/hooks/db-guard.js +105 -0
  142. package/dist/hooks/hook-config.d.ts +29 -0
  143. package/dist/hooks/hook-config.js +92 -0
  144. package/dist/hooks/hook-registry.d.ts +43 -0
  145. package/dist/hooks/hook-registry.js +31 -0
  146. package/dist/hooks/hooks-generator.d.ts +49 -0
  147. package/dist/hooks/hooks-generator.js +99 -0
  148. package/dist/hooks/intent-classifier.d.ts +12 -0
  149. package/dist/hooks/intent-classifier.js +62 -0
  150. package/dist/hooks/keyword-detector.d.ts +25 -0
  151. package/dist/hooks/keyword-detector.js +389 -0
  152. package/dist/hooks/notepad-injector.d.ts +18 -0
  153. package/dist/hooks/notepad-injector.js +51 -0
  154. package/dist/hooks/permission-handler.d.ts +14 -0
  155. package/dist/hooks/permission-handler.js +114 -0
  156. package/dist/hooks/post-tool-failure.d.ts +11 -0
  157. package/dist/hooks/post-tool-failure.js +118 -0
  158. package/dist/hooks/post-tool-handlers.d.ts +17 -0
  159. package/dist/hooks/post-tool-handlers.js +115 -0
  160. package/dist/hooks/post-tool-use.d.ts +29 -0
  161. package/dist/hooks/post-tool-use.js +151 -0
  162. package/dist/hooks/pre-compact.d.ts +10 -0
  163. package/dist/hooks/pre-compact.js +165 -0
  164. package/dist/hooks/pre-tool-use.d.ts +31 -0
  165. package/dist/hooks/pre-tool-use.js +325 -0
  166. package/dist/hooks/prompt-injection-filter.d.ts +56 -0
  167. package/dist/hooks/prompt-injection-filter.js +287 -0
  168. package/dist/hooks/rate-limiter.d.ts +21 -0
  169. package/dist/hooks/rate-limiter.js +86 -0
  170. package/dist/hooks/secret-filter.d.ts +14 -0
  171. package/dist/hooks/secret-filter.js +65 -0
  172. package/dist/hooks/session-recovery.d.ts +27 -0
  173. package/dist/hooks/session-recovery.js +406 -0
  174. package/dist/hooks/shared/atomic-write.d.ts +41 -0
  175. package/dist/hooks/shared/atomic-write.js +148 -0
  176. package/dist/hooks/shared/context-budget.d.ts +37 -0
  177. package/dist/hooks/shared/context-budget.js +45 -0
  178. package/dist/hooks/shared/file-lock.d.ts +56 -0
  179. package/dist/hooks/shared/file-lock.js +253 -0
  180. package/dist/hooks/shared/hook-response.d.ts +33 -0
  181. package/dist/hooks/shared/hook-response.js +62 -0
  182. package/dist/hooks/shared/injection-caps.d.ts +39 -0
  183. package/dist/hooks/shared/injection-caps.js +52 -0
  184. package/dist/hooks/shared/plugin-signal.d.ts +23 -0
  185. package/dist/hooks/shared/plugin-signal.js +104 -0
  186. package/dist/hooks/shared/read-stdin.d.ts +8 -0
  187. package/dist/hooks/shared/read-stdin.js +63 -0
  188. package/dist/hooks/shared/sanitize-id.d.ts +7 -0
  189. package/dist/hooks/shared/sanitize-id.js +9 -0
  190. package/dist/hooks/shared/sanitize.d.ts +7 -0
  191. package/dist/hooks/shared/sanitize.js +22 -0
  192. package/dist/hooks/skill-injector.d.ts +38 -0
  193. package/dist/hooks/skill-injector.js +285 -0
  194. package/dist/hooks/slop-detector.d.ts +18 -0
  195. package/dist/hooks/slop-detector.js +93 -0
  196. package/dist/hooks/solution-injector.d.ts +58 -0
  197. package/dist/hooks/solution-injector.js +436 -0
  198. package/dist/hooks/subagent-tracker.d.ts +10 -0
  199. package/dist/hooks/subagent-tracker.js +90 -0
  200. package/dist/i18n/index.d.ts +43 -0
  201. package/dist/i18n/index.js +224 -0
  202. package/dist/lib.d.ts +14 -0
  203. package/dist/lib.js +14 -0
  204. package/dist/mcp/server.d.ts +8 -0
  205. package/dist/mcp/server.js +40 -0
  206. package/dist/mcp/solution-reader.d.ts +90 -0
  207. package/dist/mcp/solution-reader.js +273 -0
  208. package/dist/mcp/tools.d.ts +16 -0
  209. package/dist/mcp/tools.js +302 -0
  210. package/dist/preset/facet-catalog.d.ts +17 -0
  211. package/dist/preset/facet-catalog.js +46 -0
  212. package/dist/preset/preset-manager.d.ts +31 -0
  213. package/dist/preset/preset-manager.js +111 -0
  214. package/dist/renderer/inspect-renderer.d.ts +11 -0
  215. package/dist/renderer/inspect-renderer.js +123 -0
  216. package/dist/renderer/rule-renderer.d.ts +18 -0
  217. package/dist/renderer/rule-renderer.js +159 -0
  218. package/dist/store/evidence-store.d.ts +23 -0
  219. package/dist/store/evidence-store.js +58 -0
  220. package/dist/store/profile-store.d.ts +12 -0
  221. package/dist/store/profile-store.js +53 -0
  222. package/dist/store/recommendation-store.d.ts +22 -0
  223. package/dist/store/recommendation-store.js +64 -0
  224. package/dist/store/rule-store.d.ts +22 -0
  225. package/dist/store/rule-store.js +62 -0
  226. package/dist/store/session-state-store.d.ts +11 -0
  227. package/dist/store/session-state-store.js +44 -0
  228. package/dist/store/types.d.ts +159 -0
  229. package/dist/store/types.js +7 -0
  230. package/hooks/hook-registry.json +21 -0
  231. package/hooks/hooks.json +185 -0
  232. package/package.json +89 -0
  233. package/plugin.json +20 -0
  234. package/scripts/postinstall.js +826 -0
  235. package/skills/api-design/SKILL.md +262 -0
  236. package/skills/architecture-decision/SKILL.md +309 -0
  237. package/skills/ci-cd/SKILL.md +264 -0
  238. package/skills/code-review/SKILL.md +228 -0
  239. package/skills/compound/SKILL.md +101 -0
  240. package/skills/database/SKILL.md +257 -0
  241. package/skills/debug-detective/SKILL.md +95 -0
  242. package/skills/docker/SKILL.md +268 -0
  243. package/skills/documentation/SKILL.md +270 -0
  244. package/skills/ecomode/SKILL.md +46 -0
  245. package/skills/frontend/SKILL.md +265 -0
  246. package/skills/git-master/SKILL.md +86 -0
  247. package/skills/incident-response/SKILL.md +286 -0
  248. package/skills/migrate/SKILL.md +96 -0
  249. package/skills/performance/SKILL.md +282 -0
  250. package/skills/refactor/SKILL.md +100 -0
  251. package/skills/security-review/SKILL.md +282 -0
  252. package/skills/tdd/SKILL.md +178 -0
  253. package/skills/testing-strategy/SKILL.md +260 -0
  254. package/starter-pack/solutions/starter-api-error-responses.md +37 -0
  255. package/starter-pack/solutions/starter-async-patterns.md +40 -0
  256. package/starter-pack/solutions/starter-caching-strategy.md +40 -0
  257. package/starter-pack/solutions/starter-code-review-checklist.md +39 -0
  258. package/starter-pack/solutions/starter-debugging-systematic.md +40 -0
  259. package/starter-pack/solutions/starter-dependency-injection.md +40 -0
  260. package/starter-pack/solutions/starter-error-handling-patterns.md +38 -0
  261. package/starter-pack/solutions/starter-git-atomic-commits.md +36 -0
  262. package/starter-pack/solutions/starter-input-validation.md +40 -0
  263. package/starter-pack/solutions/starter-n-plus-one-queries.md +37 -0
  264. package/starter-pack/solutions/starter-refactor-safely.md +38 -0
  265. package/starter-pack/solutions/starter-secret-management.md +37 -0
  266. package/starter-pack/solutions/starter-separation-of-concerns.md +36 -0
  267. package/starter-pack/solutions/starter-tdd-red-green-refactor.md +40 -0
  268. package/starter-pack/solutions/starter-typescript-strict-types.md +39 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Forgen — Skill Promoter
3
+ *
4
+ * verified/mature 솔루션을 .forgen/me/skills/ 스킬로 승격.
5
+ * 솔루션(선언적 지식) → 스킬(절차적 지식) 변환.
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import * as os from 'node:os';
10
+ import { parseSolutionV3 } from './solution-format.js';
11
+ import { createLogger } from '../core/logger.js';
12
+ const log = createLogger('skill-promoter');
13
+ const FORGEN_HOME = path.join(os.homedir(), '.forgen');
14
+ const ME_SOLUTIONS = path.join(FORGEN_HOME, 'me', 'solutions');
15
+ const ME_SKILLS = path.join(FORGEN_HOME, 'me', 'skills');
16
+ // Claude Code가 자동 인식하는 글로벌 스킬 경로
17
+ const CLAUDE_SKILLS = path.join(os.homedir(), '.claude', 'skills');
18
+ // 일반적인 태그 제외 (트리거로 부적합)
19
+ const GENERIC_TAGS = new Set([
20
+ 'typescript', 'javascript', 'react', 'node', 'error', 'fix', 'code',
21
+ 'pattern', 'solution', 'decision', 'troubleshoot', 'project', 'file',
22
+ ]);
23
+ /** 솔루션을 스킬로 승격 */
24
+ export function promoteSolution(solutionName, triggers) {
25
+ // 1. 솔루션 찾기
26
+ const solPath = path.join(ME_SOLUTIONS, `${solutionName}.md`);
27
+ if (!fs.existsSync(solPath)) {
28
+ return { success: false, reason: `Solution not found: ${solutionName}` };
29
+ }
30
+ const content = fs.readFileSync(solPath, 'utf-8');
31
+ const parsed = parseSolutionV3(content);
32
+ if (!parsed) {
33
+ return { success: false, reason: `Failed to parse solution: ${solutionName}` };
34
+ }
35
+ // 2. 자격 검증: verified 이상만
36
+ if (!['verified', 'mature'].includes(parsed.frontmatter.status)) {
37
+ return {
38
+ success: false,
39
+ reason: `Only verified/mature solutions can be promoted. Current: ${parsed.frontmatter.status}. Use 'forgen compound --verify ${solutionName}' first.`,
40
+ };
41
+ }
42
+ // 3. 중복 체크
43
+ const skillPath = path.join(ME_SKILLS, `${solutionName}.md`);
44
+ if (fs.existsSync(skillPath)) {
45
+ return { success: false, reason: `Skill already exists: ${solutionName}` };
46
+ }
47
+ // 4. 트리거 결정
48
+ const effectiveTriggers = triggers ?? deriveTriggersFromTags(parsed.frontmatter.tags);
49
+ if (effectiveTriggers.length === 0) {
50
+ return { success: false, reason: 'No triggers could be derived. Provide --trigger manually.' };
51
+ }
52
+ // 5. 스킬 파일 생성
53
+ const description = parsed.context
54
+ ? parsed.context.split('\n')[0].slice(0, 100)
55
+ : `${solutionName} 패턴 적용`;
56
+ const skillContent = [
57
+ '---',
58
+ `name: ${solutionName}`,
59
+ `description: ${description}`,
60
+ 'triggers:',
61
+ ...effectiveTriggers.map(t => ` - "${t}"`),
62
+ `promoted_from: solution/${solutionName}`,
63
+ `promoted_at: "${new Date().toISOString().split('T')[0]}"`,
64
+ 'status: candidate',
65
+ 'usage_count: 0',
66
+ '---',
67
+ '',
68
+ '<Purpose>',
69
+ parsed.context || `${solutionName} 패턴을 적용합니다.`,
70
+ '</Purpose>',
71
+ '',
72
+ '<Steps>',
73
+ parsed.content,
74
+ '</Steps>',
75
+ ].join('\n');
76
+ fs.mkdirSync(ME_SKILLS, { recursive: true });
77
+ fs.writeFileSync(skillPath, skillContent);
78
+ // Claude Code 네이티브 스킬로도 등록 (~/.claude/skills/{name}/SKILL.md)
79
+ const claudeSkillDir = path.join(CLAUDE_SKILLS, solutionName);
80
+ fs.mkdirSync(claudeSkillDir, { recursive: true });
81
+ fs.writeFileSync(path.join(claudeSkillDir, 'SKILL.md'), skillContent);
82
+ log.debug(`스킬 승격 완료: ${solutionName} → ${skillPath} + ${claudeSkillDir}`);
83
+ return { success: true, skillPath };
84
+ }
85
+ /** 태그에서 트리거 키워드 자동 추출 */
86
+ function deriveTriggersFromTags(tags) {
87
+ return tags
88
+ .filter(t => t.length >= 3 && !GENERIC_TAGS.has(t.toLowerCase()))
89
+ .slice(0, 3);
90
+ }
91
+ /** 스킬 목록 조회 */
92
+ export function listSkills() {
93
+ if (!fs.existsSync(ME_SKILLS))
94
+ return [];
95
+ return fs.readdirSync(ME_SKILLS)
96
+ .filter(f => f.endsWith('.md'))
97
+ .map(f => {
98
+ try {
99
+ const content = fs.readFileSync(path.join(ME_SKILLS, f), 'utf-8');
100
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
101
+ if (!fmMatch)
102
+ return null;
103
+ const fm = fmMatch[1];
104
+ const name = fm.match(/name:\s*(.+)/)?.[1]?.trim() ?? f.replace('.md', '');
105
+ const status = fm.match(/status:\s*(.+)/)?.[1]?.trim() ?? 'unknown';
106
+ const promotedFrom = fm.match(/promoted_from:\s*(.+)/)?.[1]?.trim();
107
+ const triggers = [...fm.matchAll(/- "(.+)"/g)].map(m => m[1]);
108
+ return { name, status, promotedFrom, triggers };
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ })
114
+ .filter((s) => s !== null);
115
+ }
@@ -0,0 +1,160 @@
1
+ export type SolutionStatus = 'experiment' | 'candidate' | 'verified' | 'mature' | 'retired';
2
+ export type SolutionType = 'pattern' | 'solution' | 'decision' | 'troubleshoot' | 'anti-pattern' | 'convention';
3
+ export interface SolutionEvidence {
4
+ injected: number;
5
+ reflected: number;
6
+ negative: number;
7
+ sessions: number;
8
+ reExtracted: number;
9
+ }
10
+ export interface SolutionFrontmatter {
11
+ name: string;
12
+ version: number;
13
+ status: SolutionStatus;
14
+ confidence: number;
15
+ type: SolutionType;
16
+ scope: 'me' | 'team' | 'project';
17
+ tags: string[];
18
+ identifiers: string[];
19
+ evidence: SolutionEvidence;
20
+ created: string;
21
+ updated: string;
22
+ supersedes: string | null;
23
+ extractedBy: 'auto' | 'manual';
24
+ }
25
+ export interface SolutionV3 {
26
+ frontmatter: SolutionFrontmatter;
27
+ context: string;
28
+ content: string;
29
+ filePath?: string;
30
+ }
31
+ export interface SolutionIndexEntry {
32
+ name: string;
33
+ status: SolutionStatus;
34
+ confidence: number;
35
+ type: SolutionType;
36
+ scope: 'me' | 'team' | 'project';
37
+ tags: string[];
38
+ /**
39
+ * Pre-expanded tag set, computed at index build time via the term normalizer.
40
+ * Contains `tags` plus every related match term from the canonical families
41
+ * they belong to.
42
+ *
43
+ * T2 scope (current): populated as forward-looking metadata for T3's
44
+ * ranking-decision log (which records raw + normalized query/solution
45
+ * terms for offline explainability) and T4's BM25 term-frequency stats.
46
+ * **NOT consumed by the current matcher** — `rankCandidates` and
47
+ * `calculateRelevance` still use raw `tags` for intersection to preserve
48
+ * the Round 3 baseline (bidirectional expansion would inflate recall 5-10×
49
+ * and invalidate fixture metrics). A future PR that uses `normalizedTags`
50
+ * in scoring must update `ROUND3_BASELINE` in the same commit.
51
+ *
52
+ * Not persisted — recomputed on every index build. Safe to regenerate whenever
53
+ * `DEFAULT_MATCH_TERMS` changes.
54
+ */
55
+ normalizedTags: string[];
56
+ identifiers: string[];
57
+ filePath: string;
58
+ }
59
+ export declare const DEFAULT_EVIDENCE: SolutionEvidence;
60
+ export declare function slugify(text: string): string;
61
+ /** Runtime type guard for SolutionFrontmatter */
62
+ export declare function validateFrontmatter(fm: unknown): fm is SolutionFrontmatter;
63
+ /** Parse YAML frontmatter from solution file content */
64
+ export declare function parseFrontmatterOnly(content: string): SolutionFrontmatter | null;
65
+ /** Parse a full V3 solution file into its components */
66
+ export declare function parseSolutionV3(content: string): SolutionV3 | null;
67
+ /** Serialize a SolutionV3 to a markdown string with YAML frontmatter */
68
+ export declare function serializeSolutionV3(solution: SolutionV3): string;
69
+ /** Check if content is in V3 format (YAML frontmatter) */
70
+ export declare function isV3Format(content: string): boolean;
71
+ /** Check if content is in V1 format (# Title + > Type: pattern) */
72
+ export declare function isV1Format(content: string): boolean;
73
+ /** 한국어 일반 조사/어미 — strip 대상 (긴 것부터 매칭)
74
+ *
75
+ * term-matcher에서 재사용 가능하도록 export — 매칭 시점과 추출 시점의 stripping
76
+ * 규칙을 단일 source of truth로 유지해 한국어 stem 비교 정합성 보장.
77
+ *
78
+ * 주의: 이 리스트는 **추출 시점에도 적용**되므로 1글자 suffix를 추가할 때
79
+ * `집중`→`집`, `시도`→`시` 같은 한자어 명사가 깨지지 않도록 극도로 보수적으로
80
+ * 유지한다. 동사 활용형(`리팩토링중`, `배포시`)처럼 매칭 전용 suffix가 필요하면
81
+ * term-matcher의 `KO_VERBAL_SUFFIXES`에 따로 둔다.
82
+ */
83
+ export declare const KO_SUFFIXES: string[];
84
+ export declare function stripKoSuffix(word: string): string;
85
+ /**
86
+ * Extract tags from text.
87
+ * Korean 2-char words preserved (e.g. "에러", "배포"), stopwords filtered.
88
+ * English words require 3+ chars, stopwords filtered.
89
+ * Tags capped at MAX_TAGS, ranked by frequency.
90
+ *
91
+ * NOTE on hyphens: this function strips `-` to a space (`api-key` query token
92
+ * becomes `api` and `key` separately). Solution-side compound tags are
93
+ * recovered downstream by `expandCompoundTags`, and query-side bigram
94
+ * recovery is done by `expandQueryBigrams`. Both ship as part of R4-T1
95
+ * (compound-tag tokenizer fix) — see `docs/plans/2026-04-08-t4-bm25-skip-adr.md`
96
+ * "Round 4 candidates" section for the rationale. Changing this regex
97
+ * directly was considered but rejected: it would silently shift the index
98
+ * representation of every existing solution, requiring an index rebuild and
99
+ * a fresh `ROUND3_BASELINE` measurement on every downstream PR.
100
+ */
101
+ export declare function extractTags(text: string): string[];
102
+ /**
103
+ * Expand a solution tag list with hyphen-split alternatives.
104
+ *
105
+ * Each input tag is preserved verbatim, and any tag containing `-` also
106
+ * contributes its parts (length ≥ 3 each) as additional tags. The output
107
+ * is deduplicated.
108
+ *
109
+ * Examples:
110
+ * - `['api-key', 'security']` → `['api-key', 'api', 'key', 'security']`
111
+ * - `['code-review', 'quality']` → `['code-review', 'code', 'review', 'quality']`
112
+ * - `['n+1', 'database']` → `['n+1', 'database']` (no hyphen, n+1 unchanged)
113
+ * - `['red-green-refactor']` → `['red-green-refactor', 'red', 'green', 'refactor']`
114
+ * - `['typescript']` → `['typescript']` (no hyphen, no expansion)
115
+ *
116
+ * Korean compound tags (`API에러`, `테스트주도개발`) are preserved verbatim
117
+ * because they contain no `-`. The expansion is intentionally English-
118
+ * compound-aware only — Korean compound recovery is not in scope for R4-T1
119
+ * (the existing `term-normalizer` family expansion handles Korean ↔ English
120
+ * cross-mapping).
121
+ *
122
+ * The output ordering is insertion order: original tags first, then split
123
+ * parts in left-to-right order. Stable across runs (Set + Array dedup).
124
+ */
125
+ export declare function expandCompoundTags(tags: readonly string[]): string[];
126
+ /**
127
+ * Expand a query tag list with adjacent-token bigram alternatives.
128
+ *
129
+ * For each adjacent (a, b) pair where both tokens are length ≥ 3, the
130
+ * function adds:
131
+ * - `a-b` (hyphen-joined form, e.g. `api-key`)
132
+ * - `ab` (concatenated form, e.g. `apikey`)
133
+ * - `a-b'` (singular stem of b, only if b ends in `s` and length > 3)
134
+ * - `ab'` (concatenated singular stem)
135
+ *
136
+ * Examples:
137
+ * - `['api', 'keys']` → `['api', 'keys', 'api-key', 'apikey', 'api-keys', 'apikeys']`
138
+ * - `['code', 'review']` → `['code', 'review', 'code-review', 'codereview']`
139
+ * - `['red', 'green', 'refactor']` → `[..., 'red-green', 'redgreen', 'green-refactor', 'greenrefactor']`
140
+ *
141
+ * Plural→singular stem is intentionally minimal: only `s`-suffix removal,
142
+ * no `es`/`ies` handling. The cost-benefit is asymmetric — `apis → api`
143
+ * is the highest-value case and is handled correctly; `classes → classe`
144
+ * is wrong but doesn't matter because no solution tag is `classe`.
145
+ *
146
+ * Why both `-` and concatenated forms: solution tag conventions vary
147
+ * across packs (`api-key` vs `apikey`), and this expansion is cheap.
148
+ * The downstream intersection check is O(M) per solution where M = expanded
149
+ * query tag count, so even doubling the query tag count is well within
150
+ * the matcher's hot-path budget for the corpus sizes Forgen targets
151
+ * (N ≤ 200 solutions).
152
+ *
153
+ * Korean tokens (`/[가-힣]/`) are passed through verbatim: bigram
154
+ * concatenation of Korean compound words is meaningless because the
155
+ * boundary is lexical, not whitespace-driven (`디버깅` is one word, not
156
+ * two adjacent tokens). Only ASCII-letter pairs participate.
157
+ */
158
+ export declare function expandQueryBigrams(tags: readonly string[]): string[];
159
+ /** Migrate a V1-format solution file to V3 format */
160
+ export declare function migrateV1toV3(content: string, filePath: string): string;