@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,32 @@
1
+ import type { SolutionFrontmatter, SolutionStatus } from './solution-format.js';
2
+ export interface LifecycleResult {
3
+ promoted: string[];
4
+ demoted: string[];
5
+ retired: string[];
6
+ contradictions: string[];
7
+ }
8
+ /** Get the next promotion status */
9
+ export declare function nextStatus(current: SolutionStatus): SolutionStatus | null;
10
+ /** Get confidence for a status level.
11
+ * Spacing: 0.25 between levels for meaningful differentiation in matching scores.
12
+ * Previous: 0.3/0.6/0.8/0.85 had only 0.05 gap between verified and mature. */
13
+ export declare function statusConfidence(status: SolutionStatus): number;
14
+ /** Check promotion eligibility */
15
+ export declare function checkPromotion(fm: SolutionFrontmatter): boolean;
16
+ /** Check if solution should be demoted due to confidence-status mismatch */
17
+ export declare function checkConfidenceDemotion(fm: SolutionFrontmatter): SolutionStatus | null;
18
+ /** Check if solution identifiers still exist in codebase (staleness detection) */
19
+ export declare function checkIdentifierStaleness(fm: SolutionFrontmatter, cwd: string): boolean;
20
+ /** Check if solution is stale (status-specific inactivity threshold) */
21
+ export declare function isStale(fm: SolutionFrontmatter): boolean;
22
+ /**
23
+ * Update a solution file with new frontmatter.
24
+ * PR2b: solution-writer.mutateSolutionFile로 통합. lock + fresh re-read + atomic write.
25
+ */
26
+ export declare function updateSolutionFile(filePath: string, updates: Partial<SolutionFrontmatter>): boolean;
27
+ /** Run lifecycle check on all solutions */
28
+ export declare function runLifecycleCheck(sessionId?: string): LifecycleResult;
29
+ /** Detect contradictions between solutions */
30
+ export declare function detectContradictions(dirs: string[]): string[];
31
+ /** Manual verify command: immediately promote to verified */
32
+ export declare function verifySolution(solutionName: string): boolean;
@@ -0,0 +1,305 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { parseFrontmatterOnly } from './solution-format.js';
5
+ import { mutateSolutionFile } from './solution-writer.js';
6
+ import { createLogger } from '../core/logger.js';
7
+ const log = createLogger('compound-lifecycle');
8
+ import { ME_SOLUTIONS, ME_RULES } from '../core/paths.js';
9
+ /** Circuit breaker negative thresholds by status */
10
+ const CIRCUIT_BREAKER_THRESHOLDS = {
11
+ experiment: 2,
12
+ candidate: 3,
13
+ verified: 4,
14
+ };
15
+ /** Minimum age (ms) before promotion is allowed */
16
+ const MIN_AGE_FOR_PROMOTION = {
17
+ experiment: 7 * 24 * 60 * 60 * 1000, // 7 days
18
+ candidate: 14 * 24 * 60 * 60 * 1000, // 14 days
19
+ verified: 7 * 24 * 60 * 60 * 1000, // 7 days (prevents instant mature)
20
+ };
21
+ /** Confidence-status consistency thresholds */
22
+ const STATUS_CONFIDENCE_MIN = {
23
+ mature: 0.75,
24
+ verified: 0.5,
25
+ candidate: 0.2,
26
+ experiment: 0.05,
27
+ retired: 0,
28
+ };
29
+ /** Get the next promotion status */
30
+ export function nextStatus(current) {
31
+ switch (current) {
32
+ case 'experiment': return 'candidate';
33
+ case 'candidate': return 'verified';
34
+ case 'verified': return 'mature';
35
+ default: return null;
36
+ }
37
+ }
38
+ /** Get confidence for a status level.
39
+ * Spacing: 0.25 between levels for meaningful differentiation in matching scores.
40
+ * Previous: 0.3/0.6/0.8/0.85 had only 0.05 gap between verified and mature. */
41
+ export function statusConfidence(status) {
42
+ switch (status) {
43
+ case 'experiment': return 0.3;
44
+ case 'candidate': return 0.55;
45
+ case 'verified': return 0.75;
46
+ case 'mature': return 0.90;
47
+ case 'retired': return 0;
48
+ }
49
+ }
50
+ /** Check promotion eligibility */
51
+ export function checkPromotion(fm) {
52
+ const ev = fm.evidence;
53
+ switch (fm.status) {
54
+ case 'experiment':
55
+ // A: reflected >= 3 AND negative == 0 AND sessions >= 3 (Beta(4,1) → P(rate>0.5)=0.94)
56
+ // B: reExtracted >= 2 AND negative == 0 AND reflected >= 1 (prevents trivial re-extraction)
57
+ return (ev.negative === 0) && ((ev.reflected >= 3 && ev.sessions >= 3) ||
58
+ (ev.reExtracted >= 2 && ev.reflected >= 1));
59
+ case 'candidate':
60
+ // A: reflected >= 4 AND negative == 0 AND sessions >= 3
61
+ // B: reExtracted >= 2 AND negative == 0
62
+ return (ev.negative === 0) && ((ev.reflected >= 4 && ev.sessions >= 3) ||
63
+ (ev.reExtracted >= 2));
64
+ case 'verified':
65
+ // reflected >= 8, negative <= 1, sessions >= 5
66
+ return ev.reflected >= 8 && ev.negative <= 1 && ev.sessions >= 5;
67
+ default:
68
+ return false;
69
+ }
70
+ }
71
+ /** Check if solution should be demoted due to confidence-status mismatch */
72
+ export function checkConfidenceDemotion(fm) {
73
+ if (fm.status === 'retired')
74
+ return null;
75
+ // Check from highest to lowest
76
+ if (fm.status === 'mature' && fm.confidence < STATUS_CONFIDENCE_MIN.mature)
77
+ return 'verified';
78
+ if (fm.status === 'verified' && fm.confidence < STATUS_CONFIDENCE_MIN.verified)
79
+ return 'candidate';
80
+ if (fm.status === 'candidate' && fm.confidence < STATUS_CONFIDENCE_MIN.candidate)
81
+ return 'experiment';
82
+ if (fm.status === 'experiment' && fm.confidence < STATUS_CONFIDENCE_MIN.experiment)
83
+ return 'retired';
84
+ return null;
85
+ }
86
+ /** Check if solution identifiers still exist in codebase (staleness detection) */
87
+ export function checkIdentifierStaleness(fm, cwd) {
88
+ if (fm.identifiers.length === 0)
89
+ return false; // no identifiers to check
90
+ try {
91
+ const validIds = fm.identifiers.slice(0, 5).filter(id => id.length >= 6);
92
+ // All identifiers were too short — nothing to grep, treat as stale (matches original behavior)
93
+ if (validIds.length === 0)
94
+ return true;
95
+ // Escape regex metacharacters and join with OR for a single grep call
96
+ // (previously: one execFileSync per identifier — up to 15s worst case)
97
+ const pattern = validIds.map(id => id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
98
+ execFileSync('grep', [
99
+ '-r', '-E',
100
+ '--include=*.ts', '--include=*.tsx',
101
+ '--include=*.js', '--include=*.jsx',
102
+ '--exclude-dir=node_modules',
103
+ '--exclude-dir=dist',
104
+ '--exclude-dir=.git',
105
+ '-l', '-m', '1', pattern, '.',
106
+ ], { cwd, encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
107
+ return false; // grep exit 0 = at least one identifier found = not stale
108
+ }
109
+ catch (e) {
110
+ // grep exit 1 = no matches found = stale
111
+ // Other errors (timeout, ENOENT) = don't penalize — same as original outer catch behavior
112
+ const status = e.status;
113
+ return status === 1;
114
+ }
115
+ }
116
+ /** Status-specific staleness thresholds (days).
117
+ * experiment decays faster, mature gets longer grace period. */
118
+ const STALENESS_DAYS = {
119
+ experiment: 60,
120
+ candidate: 90,
121
+ verified: 120,
122
+ mature: 120,
123
+ };
124
+ /** Check if solution is stale (status-specific inactivity threshold) */
125
+ export function isStale(fm) {
126
+ if (fm.status === 'retired')
127
+ return false;
128
+ const staleDays = STALENESS_DAYS[fm.status] ?? 90;
129
+ const ninetyDaysMs = staleDays * 24 * 60 * 60 * 1000;
130
+ if (fm.evidence.injected === 0) {
131
+ // Never injected — check age
132
+ const created = new Date(fm.created).getTime();
133
+ const age = Date.now() - created;
134
+ return age > ninetyDaysMs;
135
+ }
136
+ // Has been injected — check last update
137
+ const updated = new Date(fm.updated).getTime();
138
+ const age = Date.now() - updated;
139
+ return age > ninetyDaysMs;
140
+ }
141
+ /**
142
+ * Update a solution file with new frontmatter.
143
+ * PR2b: solution-writer.mutateSolutionFile로 통합. lock + fresh re-read + atomic write.
144
+ */
145
+ export function updateSolutionFile(filePath, updates) {
146
+ return mutateSolutionFile(filePath, sol => {
147
+ sol.frontmatter = {
148
+ ...sol.frontmatter,
149
+ ...updates,
150
+ };
151
+ return true;
152
+ });
153
+ }
154
+ /** Run lifecycle check on all solutions */
155
+ export function runLifecycleCheck(sessionId = 'system') {
156
+ const result = { promoted: [], demoted: [], retired: [], contradictions: [] };
157
+ const dirs = [ME_SOLUTIONS, ME_RULES];
158
+ for (const dir of dirs) {
159
+ if (!fs.existsSync(dir))
160
+ continue;
161
+ let files;
162
+ try {
163
+ files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
164
+ }
165
+ catch {
166
+ continue;
167
+ }
168
+ for (const file of files) {
169
+ const filePath = path.join(dir, file);
170
+ try {
171
+ const content = fs.readFileSync(filePath, 'utf-8');
172
+ const fm = parseFrontmatterOnly(content);
173
+ if (!fm || fm.status === 'retired')
174
+ continue;
175
+ // 1. Check stale
176
+ if (isStale(fm)) {
177
+ if (updateSolutionFile(filePath, { status: 'retired', confidence: 0 })) {
178
+ result.retired.push(fm.name);
179
+ }
180
+ continue;
181
+ }
182
+ // 2. Check confidence-status consistency
183
+ const demoteTo = checkConfidenceDemotion(fm);
184
+ if (demoteTo) {
185
+ if (updateSolutionFile(filePath, { status: demoteTo, confidence: statusConfidence(demoteTo) })) {
186
+ result.demoted.push(`${fm.name}: ${fm.status} → ${demoteTo}`);
187
+ }
188
+ continue;
189
+ }
190
+ // 3. Circuit breaker BEFORE promotion — negative evidence takes priority
191
+ const cbThreshold = CIRCUIT_BREAKER_THRESHOLDS[fm.status];
192
+ if (cbThreshold !== undefined && fm.evidence.negative >= cbThreshold) {
193
+ if (updateSolutionFile(filePath, { status: 'retired', confidence: 0 })) {
194
+ result.retired.push(`${fm.name} (circuit-breaker:${fm.status})`);
195
+ }
196
+ continue;
197
+ }
198
+ // 4. Check promotion FIRST (with minimum age gate based on updated timestamp)
199
+ // Promotion must run before identifier staleness to give solutions a chance
200
+ // to be promoted before being penalized for stale identifiers.
201
+ if (checkPromotion(fm)) {
202
+ const minAgeMs = MIN_AGE_FOR_PROMOTION[fm.status] ?? 0;
203
+ const ageMs = Date.now() - new Date(fm.updated || fm.created).getTime();
204
+ if (ageMs >= minAgeMs) {
205
+ const next = nextStatus(fm.status);
206
+ if (next) {
207
+ if (updateSolutionFile(filePath, { status: next, confidence: statusConfidence(next) })) {
208
+ result.promoted.push(`${fm.name}: ${fm.status} → ${next}`);
209
+ }
210
+ }
211
+ continue;
212
+ }
213
+ }
214
+ // 5. Identifier staleness — code references no longer exist
215
+ if (fm.identifiers.length > 0) {
216
+ const effectiveCwd = process.env.FORGEN_CWD ?? process.env.COMPOUND_CWD ?? process.cwd();
217
+ if (checkIdentifierStaleness(fm, effectiveCwd)) {
218
+ const newConf = Math.max(0, fm.confidence - 0.20);
219
+ if (updateSolutionFile(filePath, { confidence: newConf })) {
220
+ result.demoted.push(`${fm.name}: identifier-stale (confidence → ${newConf})`);
221
+ }
222
+ }
223
+ }
224
+ }
225
+ catch (e) {
226
+ log.debug(`lifecycle check failed: ${file}`, e);
227
+ }
228
+ }
229
+ }
230
+ // 5. Contradiction detection
231
+ result.contradictions = detectContradictions(dirs);
232
+ return result;
233
+ }
234
+ /** Detect contradictions between solutions */
235
+ export function detectContradictions(dirs) {
236
+ const contradictions = [];
237
+ const solutions = [];
238
+ for (const dir of dirs) {
239
+ if (!fs.existsSync(dir))
240
+ continue;
241
+ try {
242
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
243
+ for (const file of files) {
244
+ const content = fs.readFileSync(path.join(dir, file), 'utf-8');
245
+ const fm = parseFrontmatterOnly(content);
246
+ if (!fm || fm.status === 'retired')
247
+ continue;
248
+ solutions.push({ name: fm.name, tags: fm.tags, identifiers: fm.identifiers });
249
+ }
250
+ }
251
+ catch { /* 솔루션 파일 파싱 실패 무시 — 중복 감지는 best-effort */ }
252
+ }
253
+ // Pre-build tag Sets for O(1) lookup — avoids O(m²) per pair
254
+ const tagSets = solutions.map(s => new Set(s.tags));
255
+ // Pairwise comparison
256
+ for (let i = 0; i < solutions.length; i++) {
257
+ for (let j = i + 1; j < solutions.length; j++) {
258
+ const a = solutions[i];
259
+ const b = solutions[j];
260
+ // Tags overlap > 70%
261
+ const overlap = a.tags.filter(t => tagSets[j].has(t));
262
+ const overlapRatio = overlap.length / Math.max(a.tags.length, b.tags.length, 1);
263
+ if (overlapRatio < 0.7)
264
+ continue;
265
+ // Identifiers completely different
266
+ const idOverlap = a.identifiers.filter(id => b.identifiers.includes(id));
267
+ if (idOverlap.length === 0 && a.identifiers.length > 0 && b.identifiers.length > 0) {
268
+ contradictions.push(`${a.name} vs ${b.name} (tags ${(overlapRatio * 100).toFixed(0)}% overlap, identifiers disjoint)`);
269
+ }
270
+ }
271
+ }
272
+ return contradictions;
273
+ }
274
+ /** Manual verify command: immediately promote to verified */
275
+ export function verifySolution(solutionName) {
276
+ const dirs = [ME_SOLUTIONS, ME_RULES];
277
+ for (const dir of dirs) {
278
+ if (!fs.existsSync(dir))
279
+ continue;
280
+ try {
281
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
282
+ for (const file of files) {
283
+ const filePath = path.join(dir, file);
284
+ // PR2c-4 (security L-1): symlink을 통한 임의 파일 read 차단.
285
+ // mutateSolutionByName과 일관성 유지.
286
+ try {
287
+ if (fs.lstatSync(filePath).isSymbolicLink())
288
+ continue;
289
+ }
290
+ catch {
291
+ continue;
292
+ }
293
+ const content = fs.readFileSync(filePath, 'utf-8');
294
+ const fm = parseFrontmatterOnly(content);
295
+ if (!fm || fm.name !== solutionName)
296
+ continue;
297
+ if (fm.status === 'verified' || fm.status === 'mature')
298
+ return true; // already verified
299
+ return updateSolutionFile(filePath, { status: 'verified', confidence: 0.8 });
300
+ }
301
+ }
302
+ catch { /* 솔루션 파일 읽기/업데이트 실패 무시 — false 반환으로 재시도 가능 */ }
303
+ }
304
+ return false;
305
+ }
@@ -0,0 +1,32 @@
1
+ export interface CompoundInsight {
2
+ id: string;
3
+ type: 'solution' | 'rule' | 'convention' | 'pattern';
4
+ title: string;
5
+ content: string;
6
+ scope: 'me' | 'team';
7
+ /** 자동 분류: personal (개인 스타일) vs team (공통 패턴) */
8
+ classification: 'personal' | 'team';
9
+ /** 분류 근거 */
10
+ reason: string;
11
+ source: 'session' | 'manual';
12
+ }
13
+ /** 키워드 기반으로 인사이트를 개인/팀으로 자동 분류 */
14
+ export declare function classifyInsight(title: string, content: string): {
15
+ classification: 'personal' | 'team';
16
+ reason: string;
17
+ };
18
+ /**
19
+ * Compound Loop — 이미 추출된 인사이트를 저장
20
+ */
21
+ export declare function runCompoundLoop(cwd: string, insights: CompoundInsight[]): Promise<{
22
+ saved: string[];
23
+ skipped: string[];
24
+ }>;
25
+ /** 팀 제안으로 저장 (.compound/proposals/) */
26
+ export declare function saveTeamProposals(insights: CompoundInsight[], cwd: string): void;
27
+ /** .compound/proposals/ 에서 제안 파일 로드 */
28
+ export declare function loadProposals(proposalsDir: string): CompoundInsight[];
29
+ /** 제안 파일 정리 */
30
+ export declare function cleanProposals(proposalsDir: string): void;
31
+ /** CLI 핸들러: forgen compound */
32
+ export declare function handleCompound(args: string[]): Promise<void>;