@wooojin/forgen 0.4.0 → 0.4.3

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 (187) hide show
  1. package/.claude-plugin/plugin.json +5 -5
  2. package/CHANGELOG.md +194 -15
  3. package/CONTRIBUTING.md +2 -2
  4. package/README.ja.md +74 -9
  5. package/README.ko.md +77 -12
  6. package/README.md +127 -25
  7. package/README.zh.md +43 -9
  8. package/assets/README.md +86 -0
  9. package/assets/architecture.svg +100 -0
  10. package/assets/banner.png +0 -0
  11. package/assets/banner.svg +53 -0
  12. package/assets/demo/01-install.gif +0 -0
  13. package/assets/demo/01-install.tape +54 -0
  14. package/assets/demo/02-compound-learning.gif +0 -0
  15. package/assets/demo/02-compound-learning.tape +50 -0
  16. package/assets/demo/03-forge-personalization.gif +0 -0
  17. package/assets/demo/03-forge-personalization.tape +64 -0
  18. package/assets/demo/before-after.gif +0 -0
  19. package/assets/demo/before-after.tape +98 -0
  20. package/assets/demo-preview.svg +96 -0
  21. package/assets/icon.png +0 -0
  22. package/{hooks → assets/shared}/hook-registry.json +2 -1
  23. package/dist/checks/conclusion-verification-ratio.d.ts +37 -0
  24. package/dist/checks/conclusion-verification-ratio.js +86 -0
  25. package/dist/checks/fact-vs-agreement.d.ts +47 -0
  26. package/dist/checks/fact-vs-agreement.js +92 -0
  27. package/dist/checks/self-score-deflation.d.ts +38 -0
  28. package/dist/checks/self-score-deflation.js +108 -0
  29. package/dist/cli.js +98 -6
  30. package/dist/core/auto-compound-runner.js +137 -49
  31. package/dist/core/behavior-classifier.d.ts +28 -0
  32. package/dist/core/behavior-classifier.js +46 -0
  33. package/dist/core/dashboard.d.ts +7 -0
  34. package/dist/core/dashboard.js +41 -2
  35. package/dist/core/doctor.js +118 -5
  36. package/dist/core/extraction-notice.d.ts +18 -0
  37. package/dist/core/extraction-notice.js +64 -0
  38. package/dist/core/git-stats.d.ts +36 -0
  39. package/dist/core/git-stats.js +79 -0
  40. package/dist/core/harness.d.ts +1 -1
  41. package/dist/core/harness.js +27 -20
  42. package/dist/core/host-detect.d.ts +42 -0
  43. package/dist/core/host-detect.js +68 -0
  44. package/dist/core/init-cli.d.ts +26 -0
  45. package/dist/core/init-cli.js +104 -0
  46. package/dist/core/init.js +17 -0
  47. package/dist/core/inspect-cli.js +1 -2
  48. package/dist/core/installer.js +2 -2
  49. package/dist/core/migrate-cli.d.ts +11 -0
  50. package/dist/core/migrate-cli.js +53 -0
  51. package/dist/core/migrate-evidence-host.d.ts +36 -0
  52. package/dist/core/migrate-evidence-host.js +49 -0
  53. package/dist/core/paths.d.ts +8 -1
  54. package/dist/core/paths.js +11 -2
  55. package/dist/core/recall-cli.d.ts +26 -0
  56. package/dist/core/recall-cli.js +125 -0
  57. package/dist/core/recall-reference-detector.d.ts +43 -0
  58. package/dist/core/recall-reference-detector.js +65 -0
  59. package/dist/core/settings-injector.js +4 -2
  60. package/dist/core/spawn.d.ts +1 -1
  61. package/dist/core/spawn.js +4 -11
  62. package/dist/core/stats-cli.d.ts +21 -0
  63. package/dist/core/stats-cli.js +133 -10
  64. package/dist/core/trust-layer-intent.d.ts +35 -0
  65. package/dist/core/trust-layer-intent.js +30 -0
  66. package/dist/core/types.d.ts +1 -1
  67. package/dist/core/uninstall.js +2 -1
  68. package/dist/engine/compound-cli.js +1 -0
  69. package/dist/engine/compound-export.js +8 -3
  70. package/dist/engine/compound-extractor.js +7 -9
  71. package/dist/engine/learn-cli.js +5 -6
  72. package/dist/engine/lifecycle/bypass-detector.d.ts +6 -1
  73. package/dist/engine/lifecycle/bypass-detector.js +57 -5
  74. package/dist/engine/lifecycle/lifecycle-cli.js +4 -4
  75. package/dist/engine/lifecycle/meta-reclassifier.js +3 -3
  76. package/dist/engine/lifecycle/orchestrator.js +2 -2
  77. package/dist/engine/lifecycle/signals.js +6 -6
  78. package/dist/engine/meta-learning/session-quality-scorer.d.ts +1 -6
  79. package/dist/engine/meta-learning/session-quality-scorer.js +2 -21
  80. package/dist/engine/skill-promoter.js +3 -6
  81. package/dist/fgx.js +2 -1
  82. package/dist/forge/evidence-processor.js +12 -0
  83. package/dist/forge/onboarding.d.ts +3 -2
  84. package/dist/forge/onboarding.js +3 -2
  85. package/dist/hooks/context-guard.js +1 -1
  86. package/dist/hooks/dangerous-patterns.json +3 -3
  87. package/dist/hooks/db-guard.js +21 -5
  88. package/dist/hooks/forge-loop-progress.d.ts +9 -0
  89. package/dist/hooks/forge-loop-progress.js +38 -0
  90. package/dist/hooks/hook-registry.js +1 -1
  91. package/dist/hooks/hooks-generator.d.ts +15 -1
  92. package/dist/hooks/hooks-generator.js +18 -16
  93. package/dist/hooks/intent-classifier.js +1 -1
  94. package/dist/hooks/keyword-detector.js +2 -2
  95. package/dist/hooks/notepad-injector.js +1 -1
  96. package/dist/hooks/permission-handler.js +1 -1
  97. package/dist/hooks/post-tool-failure.js +1 -1
  98. package/dist/hooks/post-tool-use.d.ts +7 -1
  99. package/dist/hooks/post-tool-use.js +50 -23
  100. package/dist/hooks/pre-compact.js +2 -2
  101. package/dist/hooks/pre-tool-use.d.ts +7 -0
  102. package/dist/hooks/pre-tool-use.js +28 -10
  103. package/dist/hooks/rate-limiter.js +3 -3
  104. package/dist/hooks/secret-filter.js +1 -1
  105. package/dist/hooks/session-recovery.js +12 -1
  106. package/dist/hooks/shared/blocking-allowlist.d.ts +28 -0
  107. package/dist/hooks/shared/blocking-allowlist.js +38 -0
  108. package/dist/hooks/shared/command-parser.d.ts +44 -0
  109. package/dist/hooks/shared/command-parser.js +50 -0
  110. package/dist/hooks/shared/forge-loop-state.d.ts +36 -0
  111. package/dist/hooks/shared/forge-loop-state.js +116 -0
  112. package/dist/hooks/shared/hook-response.d.ts +30 -2
  113. package/dist/hooks/shared/hook-response.js +61 -3
  114. package/dist/hooks/skill-injector.js +2 -2
  115. package/dist/hooks/slop-detector.js +2 -2
  116. package/dist/hooks/solution-injector.d.ts +9 -0
  117. package/dist/hooks/solution-injector.js +48 -5
  118. package/dist/hooks/stop-guard.js +152 -13
  119. package/dist/hooks/subagent-tracker.js +1 -1
  120. package/dist/host/capabilities-claude.d.ts +8 -0
  121. package/dist/host/capabilities-claude.js +46 -0
  122. package/dist/host/capabilities-codex.d.ts +11 -0
  123. package/dist/host/capabilities-codex.js +50 -0
  124. package/dist/host/capabilities-registry.d.ts +11 -0
  125. package/dist/host/capabilities-registry.js +30 -0
  126. package/dist/host/codex-adapter.d.ts +8 -5
  127. package/dist/host/codex-adapter.js +10 -82
  128. package/dist/host/codex-output-parser.d.ts +39 -0
  129. package/dist/host/codex-output-parser.js +75 -0
  130. package/dist/host/exec-host.d.ts +54 -0
  131. package/dist/host/exec-host.js +92 -0
  132. package/dist/host/host-runtime.d.ts +37 -0
  133. package/dist/host/host-runtime.js +51 -0
  134. package/dist/host/install-claude.d.ts +35 -0
  135. package/dist/host/install-claude.js +238 -0
  136. package/dist/host/install-codex.d.ts +44 -0
  137. package/dist/host/install-codex.js +276 -0
  138. package/dist/host/install-orchestrator.d.ts +34 -0
  139. package/dist/host/install-orchestrator.js +126 -0
  140. package/dist/host/invoke-agent.d.ts +27 -0
  141. package/dist/host/invoke-agent.js +115 -0
  142. package/dist/host/parity-harness.d.ts +62 -0
  143. package/dist/host/parity-harness.js +283 -0
  144. package/dist/host/projection.d.ts +35 -0
  145. package/dist/host/projection.js +126 -0
  146. package/dist/i18n/index.js +3 -5
  147. package/dist/mcp/server.js +11 -0
  148. package/dist/mcp/tools.js +47 -0
  149. package/dist/services/session.d.ts +6 -3
  150. package/dist/services/session.js +33 -4
  151. package/dist/store/evidence-store.d.ts +1 -0
  152. package/dist/store/evidence-store.js +45 -3
  153. package/dist/store/host-mismatch.d.ts +42 -0
  154. package/dist/store/host-mismatch.js +65 -0
  155. package/dist/store/implicit-feedback-store.d.ts +59 -0
  156. package/dist/store/implicit-feedback-store.js +153 -0
  157. package/dist/store/profile-store.d.ts +29 -0
  158. package/dist/store/profile-store.js +53 -0
  159. package/dist/store/rule-store.js +8 -0
  160. package/dist/store/types.d.ts +13 -0
  161. package/hooks/hooks.json +6 -1
  162. package/package.json +7 -5
  163. package/plugin.json +4 -4
  164. package/scripts/postinstall.js +100 -25
  165. /package/{agents → assets/claude/agents}/analyst.md +0 -0
  166. /package/{agents → assets/claude/agents}/architect.md +0 -0
  167. /package/{agents → assets/claude/agents}/code-reviewer.md +0 -0
  168. /package/{agents → assets/claude/agents}/critic.md +0 -0
  169. /package/{agents → assets/claude/agents}/debugger.md +0 -0
  170. /package/{agents → assets/claude/agents}/designer.md +0 -0
  171. /package/{agents → assets/claude/agents}/executor.md +0 -0
  172. /package/{agents → assets/claude/agents}/explore.md +0 -0
  173. /package/{agents → assets/claude/agents}/git-master.md +0 -0
  174. /package/{agents → assets/claude/agents}/planner.md +0 -0
  175. /package/{agents → assets/claude/agents}/solution-evolver.md +0 -0
  176. /package/{agents → assets/claude/agents}/test-engineer.md +0 -0
  177. /package/{agents → assets/claude/agents}/verifier.md +0 -0
  178. /package/{commands → assets/claude/commands}/architecture-decision.md +0 -0
  179. /package/{commands → assets/claude/commands}/calibrate.md +0 -0
  180. /package/{commands → assets/claude/commands}/code-review.md +0 -0
  181. /package/{commands → assets/claude/commands}/compound.md +0 -0
  182. /package/{commands → assets/claude/commands}/deep-interview.md +0 -0
  183. /package/{commands → assets/claude/commands}/docker.md +0 -0
  184. /package/{commands → assets/claude/commands}/forge-loop.md +0 -0
  185. /package/{commands → assets/claude/commands}/learn.md +0 -0
  186. /package/{commands → assets/claude/commands}/retro.md +0 -0
  187. /package/{commands → assets/claude/commands}/ship.md +0 -0
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Forgen v0.4.1 — Implicit Feedback Store (TEST-5)
3
+ *
4
+ * `~/.forgen/state/implicit-feedback.jsonl` 의 append/read.
5
+ *
6
+ * TEST-5 / RC5: 누적된 엔트리들이 `type` 문자열만 가지고 category 없이 섞여 있어
7
+ * - drift_critical / drift_warning / revert_detected / repeated_edit / agent_* 가 한 스트림에 섞여
8
+ * - 집계/쿼리 시 카테고리 enum 부재로 휴리스틱 문자열 매칭에 의존
9
+ * - 스키마 검증이 없어 빈/잘못된 필드로 쓰여도 나중에 분석 불가
10
+ * 이 모듈은 category 필드를 **필수화**하고, 기존 레거시 라인은 read 시 `type→category`
11
+ * 백필로 보정한다. 새 write 는 category 없으면 drift/revert 계열은 **거부**한다.
12
+ */
13
+ import * as fs from 'node:fs';
14
+ import * as path from 'node:path';
15
+ import { STATE_DIR } from '../core/paths.js';
16
+ export const IMPLICIT_FEEDBACK_LOG = path.join(STATE_DIR, 'implicit-feedback.jsonl');
17
+ /** type → category 추론. 레거시 엔트리 마이그레이션과 호출지 기본값 계산에 공용. */
18
+ export function inferCategoryFromType(type) {
19
+ if (type === 'drift_critical' || type === 'drift_warning')
20
+ return 'drift';
21
+ if (type === 'revert_detected')
22
+ return 'revert';
23
+ if (type === 'repeated_edit')
24
+ return 'edit';
25
+ if (type.startsWith('agent_'))
26
+ return 'agent';
27
+ // H4: 양수 assist 신호 — 솔루션이 사용자에게 노출/참조되었음을 기록.
28
+ if (type === 'recommendation_surfaced' || type === 'recall_referenced')
29
+ return 'positive';
30
+ return null;
31
+ }
32
+ /**
33
+ * TEST-5 스키마 검증 — drift/revert 계열은 category 누락 시 쓰기 거부.
34
+ * agent/edit 은 fail-open (기존 호출지가 빠뜨려도 로깅 자체는 보존), 대신 inference
35
+ * 가 가능하면 자동 보정.
36
+ */
37
+ function validateAndNormalize(entry) {
38
+ if (!entry.type || !entry.at)
39
+ return null;
40
+ const inferred = inferCategoryFromType(entry.type);
41
+ const category = entry.category ?? inferred;
42
+ // drift/revert/positive 는 schema 강제: 명시든 추론이든 올바른 카테고리여야 함.
43
+ if (entry.type === 'drift_critical' || entry.type === 'drift_warning') {
44
+ if (category !== 'drift')
45
+ return null;
46
+ }
47
+ if (entry.type === 'revert_detected') {
48
+ if (category !== 'revert')
49
+ return null;
50
+ }
51
+ if (entry.type === 'recommendation_surfaced' || entry.type === 'recall_referenced') {
52
+ if (category !== 'positive')
53
+ return null;
54
+ }
55
+ if (!category)
56
+ return null;
57
+ return { ...entry, category };
58
+ }
59
+ /**
60
+ * TEST-5 메인 라이터. 내부에서 스키마 검증 후 append.
61
+ * drift/revert 스키마 위반 시 silent drop (hot path 에서 throw 금지).
62
+ * 반환값: 실제로 기록되었는지 (테스트 검증용).
63
+ */
64
+ export function appendImplicitFeedback(entry) {
65
+ const normalized = validateAndNormalize(entry);
66
+ if (!normalized)
67
+ return false;
68
+ try {
69
+ fs.mkdirSync(STATE_DIR, { recursive: true });
70
+ fs.appendFileSync(IMPLICIT_FEEDBACK_LOG, JSON.stringify(normalized) + '\n');
71
+ return true;
72
+ }
73
+ catch {
74
+ // fail-open: implicit feedback recording must not throw.
75
+ return false;
76
+ }
77
+ }
78
+ /**
79
+ * TEST-5 리더. 세션 필터링 + 레거시 라인에 대한 lazy 마이그레이션 (category 백필).
80
+ * 디스크 상 파일은 건드리지 않고 읽기 시점에만 category 를 보정한다 — atomic-write
81
+ * 없이 append-only 로그를 rewrite 하면 race 위험이 있기 때문.
82
+ * 영구 백필은 `migrateImplicitFeedbackLog()` 를 명시적으로 호출한다.
83
+ */
84
+ export function loadImplicitFeedback(sessionId) {
85
+ try {
86
+ if (!fs.existsSync(IMPLICIT_FEEDBACK_LOG))
87
+ return [];
88
+ const lines = fs.readFileSync(IMPLICIT_FEEDBACK_LOG, 'utf-8').split('\n').filter(Boolean);
89
+ const entries = [];
90
+ for (const line of lines) {
91
+ try {
92
+ const raw = JSON.parse(line);
93
+ if (raw.sessionId !== sessionId)
94
+ continue;
95
+ if (!raw.type || !raw.at)
96
+ continue;
97
+ const category = raw.category ?? inferCategoryFromType(raw.type);
98
+ if (!category)
99
+ continue;
100
+ entries.push({ ...raw, category });
101
+ }
102
+ catch {
103
+ /* skip malformed lines */
104
+ }
105
+ }
106
+ return entries;
107
+ }
108
+ catch {
109
+ return [];
110
+ }
111
+ }
112
+ /**
113
+ * 영구 마이그레이션 — 레거시 로그 파일을 읽어 category 백필 후 원자적으로 재기록.
114
+ * 마이그레이션 불가 라인 (type 도 category 도 없거나 inference 실패) 은 drop.
115
+ * 반환: { migrated: 백필된 라인 수, dropped: 버려진 라인 수 }
116
+ */
117
+ export function migrateImplicitFeedbackLog() {
118
+ if (!fs.existsSync(IMPLICIT_FEEDBACK_LOG))
119
+ return { migrated: 0, dropped: 0 };
120
+ const lines = fs.readFileSync(IMPLICIT_FEEDBACK_LOG, 'utf-8').split('\n').filter(Boolean);
121
+ const out = [];
122
+ let migrated = 0;
123
+ let dropped = 0;
124
+ for (const line of lines) {
125
+ try {
126
+ const raw = JSON.parse(line);
127
+ if (!raw.type || !raw.at) {
128
+ dropped++;
129
+ continue;
130
+ }
131
+ if (raw.category) {
132
+ out.push(JSON.stringify(raw));
133
+ continue;
134
+ }
135
+ const inferred = inferCategoryFromType(raw.type);
136
+ if (!inferred) {
137
+ dropped++;
138
+ continue;
139
+ }
140
+ const repaired = { ...raw, category: inferred };
141
+ out.push(JSON.stringify(repaired));
142
+ migrated++;
143
+ }
144
+ catch {
145
+ dropped++;
146
+ }
147
+ }
148
+ // atomic replace via temp file
149
+ const tmp = `${IMPLICIT_FEEDBACK_LOG}.migrate.${process.pid}`;
150
+ fs.writeFileSync(tmp, out.length > 0 ? out.join('\n') + '\n' : '');
151
+ fs.renameSync(tmp, IMPLICIT_FEEDBACK_LOG);
152
+ return { migrated, dropped };
153
+ }
@@ -19,3 +19,32 @@ export declare function saveProfile(profile: Profile): void;
19
19
  */
20
20
  export declare function profileExists(): boolean;
21
21
  export declare function isV1Profile(data: unknown): data is Profile;
22
+ /**
23
+ * D2 fix (2026-04-27): explicit_correction 누적 시 해당 축의 confidence 를 점진
24
+ * 상승시킨다. facet 값은 건드리지 않음 (회귀 위험 최소화) — confidence 가 score
25
+ * 집계 공식 (confidence × facet_avg + (1-confidence) × neutral_anchor) 의 가중치
26
+ * 라서, 사용자가 명시 교정을 누적한 축은 score 가 facet 평균을 더 강하게 반영.
27
+ *
28
+ * 자기증거: autonomy explicit_correction 6건이 score 를 못 움직였음 (facet 값
29
+ * 갱신 경로가 mismatch-detector 의 strong rule 승급에만 의존). 본 함수가 직접
30
+ * 경로를 추가.
31
+ *
32
+ * delta 기본 0.02 — 6건 누적 시 +0.12 → 0.45 → 0.57 (의미 있는 변동 가시화).
33
+ * clamp 0~1.
34
+ */
35
+ export declare function bumpAxisConfidence(axis: 'quality_safety' | 'autonomy' | 'judgment_philosophy' | 'communication_style', delta?: number): boolean;
36
+ /**
37
+ * feat/codex-support — default_host 영속화 헬퍼.
38
+ *
39
+ * fgx / forgen 무인자 실행 시 어느 host 를 spawn 할지 결정. 'ask' 면 매번 묻기.
40
+ * 미설정(undefined) 은 legacy 사용자 호환 — 'claude' 로 resolve.
41
+ */
42
+ export type DefaultHost = 'claude' | 'codex' | 'ask';
43
+ export declare function getDefaultHost(): DefaultHost | undefined;
44
+ export declare function setDefaultHost(host: DefaultHost): boolean;
45
+ /**
46
+ * Resolve effective host for runtime use.
47
+ * 우선순위: explicit override > profile.default_host > 'claude' fallback.
48
+ * 'ask' 는 caller 가 별도 처리 (interactive prompt).
49
+ */
50
+ export declare function resolveDefaultHost(override?: 'claude' | 'codex'): 'claude' | 'codex' | 'ask';
@@ -72,3 +72,56 @@ export function isV1Profile(data) {
72
72
  const p = data;
73
73
  return typeof p.model_version === 'string' && p.model_version.startsWith('2.');
74
74
  }
75
+ /**
76
+ * D2 fix (2026-04-27): explicit_correction 누적 시 해당 축의 confidence 를 점진
77
+ * 상승시킨다. facet 값은 건드리지 않음 (회귀 위험 최소화) — confidence 가 score
78
+ * 집계 공식 (confidence × facet_avg + (1-confidence) × neutral_anchor) 의 가중치
79
+ * 라서, 사용자가 명시 교정을 누적한 축은 score 가 facet 평균을 더 강하게 반영.
80
+ *
81
+ * 자기증거: autonomy explicit_correction 6건이 score 를 못 움직였음 (facet 값
82
+ * 갱신 경로가 mismatch-detector 의 strong rule 승급에만 의존). 본 함수가 직접
83
+ * 경로를 추가.
84
+ *
85
+ * delta 기본 0.02 — 6건 누적 시 +0.12 → 0.45 → 0.57 (의미 있는 변동 가시화).
86
+ * clamp 0~1.
87
+ */
88
+ export function bumpAxisConfidence(axis, delta = 0.02) {
89
+ const profile = loadProfile();
90
+ if (!profile)
91
+ return false;
92
+ const target = profile.axes[axis];
93
+ if (!target || typeof target.confidence !== 'number')
94
+ return false;
95
+ const next = Math.max(0, Math.min(1, target.confidence + delta));
96
+ if (next === target.confidence)
97
+ return false;
98
+ target.confidence = next;
99
+ saveProfile(profile);
100
+ return true;
101
+ }
102
+ export function getDefaultHost() {
103
+ const profile = loadProfile();
104
+ return profile?.default_host;
105
+ }
106
+ export function setDefaultHost(host) {
107
+ const profile = loadProfile();
108
+ if (!profile)
109
+ return false;
110
+ profile.default_host = host;
111
+ profile.metadata.updated_at = new Date().toISOString();
112
+ saveProfile(profile);
113
+ return true;
114
+ }
115
+ /**
116
+ * Resolve effective host for runtime use.
117
+ * 우선순위: explicit override > profile.default_host > 'claude' fallback.
118
+ * 'ask' 는 caller 가 별도 처리 (interactive prompt).
119
+ */
120
+ export function resolveDefaultHost(override) {
121
+ if (override)
122
+ return override;
123
+ const stored = getDefaultHost();
124
+ if (stored === undefined)
125
+ return 'claude';
126
+ return stored;
127
+ }
@@ -33,6 +33,14 @@ export function createRule(params) {
33
33
  }
34
34
  export function saveRule(rule) {
35
35
  rule.updated_at = new Date().toISOString();
36
+ // v0.4.1 audit-trail 불변식: rule 저장 시 lifecycle state 가 null/undefined 이면
37
+ // active phase 기본값 주입. 이전에는 old rule 파일이 lifecycle 없이 존재해 쌤 이후
38
+ // audit trail (phase/violation_count/meta_promotions) 추적 불가 — 이번 세션에서
39
+ // suppressed rule 의 lifecycle=null 발견. initLifecycle 은 기존 값이 있으면 normalize,
40
+ // 없으면 phase='active' + counters=0 초기화.
41
+ if (!rule.lifecycle) {
42
+ rule.lifecycle = initLifecycle(rule);
43
+ }
36
44
  atomicWriteJSON(rulePath(rule.rule_id), rule, { pretty: true });
37
45
  }
38
46
  /**
@@ -123,6 +123,12 @@ export interface Evidence {
123
123
  candidate_rule_refs: string[];
124
124
  confidence: number;
125
125
  raw_payload: Record<string, unknown>;
126
+ /**
127
+ * Multi-Host Core Design §4.2 / §10 우선순위 5.
128
+ * evidence 가 어느 host 에서 발생했는지 태그. 미지정 시 'claude' 로 backfill (기존 데이터 호환).
129
+ * core 의 학습 로직은 이 필드를 *호스트별 가중치* 가 아니라 *불일치 demote 신호* 로만 사용한다.
130
+ */
131
+ host?: 'claude' | 'codex';
126
132
  }
127
133
  export interface QualityFacets {
128
134
  verification_depth: number;
@@ -175,6 +181,13 @@ export interface Profile {
175
181
  last_onboarding_at: string;
176
182
  last_reclassification_at: string | null;
177
183
  };
184
+ /**
185
+ * feat/codex-support — 사용자가 fgx/forgen 무인자 실행 시 spawn 할 host.
186
+ * - 'claude' / 'codex': 명시 default
187
+ * - 'ask': 매번 묻기 (interactive prompt)
188
+ * - undefined: legacy / 미설정 → 'claude' fallback (마이그레이션 호환)
189
+ */
190
+ default_host?: 'claude' | 'codex' | 'ask';
178
191
  }
179
192
  export type RecommendationSource = 'onboarding' | 'mismatch_recommendation';
180
193
  export type RecommendationStatus = 'proposed' | 'accepted' | 'archived';
package/hooks/hooks.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Forgen harness hooks (auto-generated, 20/20 active)",
2
+ "description": "Forgen harness hooks (auto-generated, 21/21 active)",
3
3
  "hooks": {
4
4
  "UserPromptSubmit": [
5
5
  {
@@ -34,6 +34,11 @@
34
34
  "type": "command",
35
35
  "command": "node \"${CLAUDE_PLUGIN_ROOT}/dist/hooks/skill-injector.js\"",
36
36
  "timeout": 5
37
+ },
38
+ {
39
+ "type": "command",
40
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/dist/hooks/forge-loop-progress.js\"",
41
+ "timeout": 2
37
42
  }
38
43
  ]
39
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooojin/forgen",
3
- "version": "0.4.0",
3
+ "version": "0.4.3",
4
4
  "preferGlobal": true,
5
5
  "main": "dist/lib.js",
6
6
  "types": "./dist/lib.d.ts",
@@ -30,7 +30,7 @@
30
30
  "postinstall": "node scripts/postinstall.js",
31
31
  "prepare": "npm run build",
32
32
  "prepublishOnly": "npm test",
33
- "prepack": "node scripts/prepack-hooks.cjs"
33
+ "prepack": "npm run build && node scripts/prepack-hooks.cjs"
34
34
  },
35
35
  "author": "jang-ujin",
36
36
  "license": "MIT",
@@ -47,12 +47,15 @@
47
47
  ],
48
48
  "repository": {
49
49
  "type": "git",
50
- "url": "https://github.com/wooo-jin/forgen.git"
50
+ "url": "https://github.com/forgen-team/forgen.git"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=20.0.0"
54
54
  },
55
55
  "type": "module",
56
+ "workspaces": [
57
+ "packages/*"
58
+ ],
56
59
  "bin": {
57
60
  "forgen": "./dist/cli.js",
58
61
  "fgx": "./dist/fgx.js",
@@ -60,8 +63,7 @@
60
63
  },
61
64
  "files": [
62
65
  "dist/",
63
- "agents/",
64
- "commands/",
66
+ "assets/",
65
67
  "skills/",
66
68
  "starter-pack/",
67
69
  "scripts/postinstall.js",
package/plugin.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "$schema": "https://claude.ai/schemas/claude-plugin.json",
3
3
  "name": "forgen",
4
- "version": "0.4.0",
4
+ "version": "0.4.3",
5
5
  "description": "Claude Code harness — the more you use Claude, the better it gets",
6
6
  "author": {
7
7
  "name": "jang-ujin",
8
8
  "url": "https://github.com/wooo-jin"
9
9
  },
10
- "repository": "https://github.com/wooo-jin/forgen",
11
- "homepage": "https://github.com/wooo-jin/forgen",
10
+ "repository": "https://github.com/forgen-team/forgen",
11
+ "homepage": "https://github.com/forgen-team/forgen",
12
12
  "license": "MIT",
13
13
  "keywords": [
14
14
  "claude-code",
@@ -17,7 +17,7 @@
17
17
  "forge"
18
18
  ],
19
19
  "skills": "./skills/",
20
- "agents": "agents/",
20
+ "agents": "assets/claude/agents/",
21
21
  "statusLine": {
22
22
  "type": "command",
23
23
  "command": "forgen me"
@@ -89,7 +89,7 @@ function fixOwnership(...paths) {
89
89
  const HOME = resolveHome();
90
90
 
91
91
  // ── Paths ──
92
- const SKILLS_DIR = join(PKG_ROOT, 'commands');
92
+ const SKILLS_DIR = join(PKG_ROOT, 'assets', 'claude', 'commands');
93
93
  const DIST_HOOKS = join(PKG_ROOT, 'dist', 'hooks');
94
94
  const COMMANDS_DIR = join(HOME, '.claude', 'commands', 'forgen');
95
95
  const CLAUDE_DIR = join(HOME, '.claude');
@@ -214,7 +214,7 @@ function registerPlugin() {
214
214
  if (!linked) {
215
215
  // 2차: 필수 디렉토리 복사
216
216
  mkdirSync(CACHE_DIR, { recursive: true });
217
- const copyDirs = ['.claude-plugin', 'hooks', 'skills', 'commands', 'agents'];
217
+ const copyDirs = ['.claude-plugin', 'hooks', 'skills', 'assets'];
218
218
  for (const dir of copyDirs) {
219
219
  const src = join(PKG_ROOT, dir);
220
220
  if (existsSync(src)) {
@@ -330,7 +330,7 @@ function detectPluginConflicts() {
330
330
  * Claude Code 플러그인은 skills/{name}/SKILL.md 구조로 스킬을 인식.
331
331
  */
332
332
  function generateSkillsDir() {
333
- const skillsSrc = join(PKG_ROOT, 'commands');
333
+ const skillsSrc = join(PKG_ROOT, 'assets', 'claude', 'commands');
334
334
  const skillsDst = join(PKG_ROOT, 'skills');
335
335
  if (!existsSync(skillsSrc)) return;
336
336
 
@@ -381,7 +381,7 @@ function generateSkillsDir() {
381
381
  */
382
382
  let HOOK_REGISTRY = [];
383
383
  try {
384
- HOOK_REGISTRY = JSON.parse(readFileSync(join(PKG_ROOT, 'hooks', 'hook-registry.json'), 'utf-8'));
384
+ HOOK_REGISTRY = JSON.parse(readFileSync(join(PKG_ROOT, 'assets', 'shared', 'hook-registry.json'), 'utf-8'));
385
385
  } catch {
386
386
  console.warn('[forgen] hook-registry.json not found, skipping hook generation');
387
387
  }
@@ -738,6 +738,49 @@ function main() {
738
738
  migrateLegacyStorage();
739
739
  ensureDirectories();
740
740
 
741
+ // ── 0. 기존 forgen entry 감지 (마이그레이션 모드 분기) ──
742
+ //
743
+ // feat/codex-support (P1-6, 2026-04-27): postinstall 이 호스트 인젝션을 *자동*
744
+ // 으로 하던 v0.4.x 동작을 변경. 새로운 정책:
745
+ // - 기존 사용자 (settings.json 에 forgen hook entry 있음): 그대로 갱신 (행동
746
+ // 변화 없음 — 마이그레이션 안전). default_host 자동 설정 안내 banner 출력.
747
+ // - 신규 사용자 (forgen entry 없음): host 인젝션 자동 안 함. "Run `forgen
748
+ // install`" banner 만 출력. 사용자가 어느 host (Claude/Codex/Both) 에 등록할지
749
+ // 명시 선택.
750
+ //
751
+ // 사용자 의도와 무관한 Claude bias (postinstall 이 ~/.claude/ 자동 인젝션) 제거.
752
+ let isExistingForgenUser = false;
753
+ if (existsSync(SETTINGS_PATH)) {
754
+ try {
755
+ const probe = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
756
+ const hooks = probe?.hooks ?? {};
757
+ for (const arr of Object.values(hooks)) {
758
+ if (!Array.isArray(arr)) continue;
759
+ for (const grp of arr) {
760
+ if (!grp?.hooks) continue;
761
+ for (const h of grp.hooks) {
762
+ const cmd = typeof h?.command === 'string' ? h.command : '';
763
+ // P1-6b detection: forgen hook command path 식별. cross-platform (POSIX + Windows)
764
+ // 및 다양한 install 위치 (npm global, plugin cache, dev) 커버.
765
+ if (
766
+ cmd.includes('CLAUDE_PLUGIN_ROOT') ||
767
+ cmd.includes('/forgen-local/forgen/') ||
768
+ cmd.includes('\\forgen-local\\forgen\\') ||
769
+ cmd.includes('/forgen/dist/hooks/') ||
770
+ cmd.includes('\\forgen\\dist\\hooks\\') ||
771
+ cmd.includes('/@wooojin/forgen/') ||
772
+ cmd.includes('\\@wooojin\\forgen\\')
773
+ ) {
774
+ isExistingForgenUser = true;
775
+ }
776
+ }
777
+ if (isExistingForgenUser) break;
778
+ }
779
+ if (isExistingForgenUser) break;
780
+ }
781
+ } catch { /* settings.json 손상 시 fall-through, 단계 1 의 corrupt handling 이 처리 */ }
782
+ }
783
+
741
784
  // ── 1. settings.json 한 번 읽기 ──
742
785
  //
743
786
  // Audit finding #2/#10 (2026-04-21): prior `catch { settings = {} }`
@@ -765,12 +808,16 @@ function main() {
765
808
  }
766
809
 
767
810
  // ── 2. 플러그인 등록 (installed_plugins.json + skills) ──
811
+ // P1-6b (옵션 Y): 신규 사용자에게는 자동 install 안 함. 기존 forgen 사용자만 갱신.
812
+ // 사용자 host 선택 권한 보장 (1원칙 = Claude bias 제거).
768
813
  let plugin = false;
769
- try {
770
- plugin = registerPlugin();
771
- if (plugin) applyPluginSettings(settings);
772
- } catch (err) {
773
- console.error(`[forgen] plugin registration failed: ${err?.message ?? err}`);
814
+ if (isExistingForgenUser) {
815
+ try {
816
+ plugin = registerPlugin();
817
+ if (plugin) applyPluginSettings(settings);
818
+ } catch (err) {
819
+ console.error(`[forgen] plugin registration failed: ${err?.message ?? err}`);
820
+ }
774
821
  }
775
822
 
776
823
  // ── 3. hooks.json 동적 생성 ──
@@ -800,29 +847,35 @@ function main() {
800
847
  }
801
848
  }
802
849
 
803
- // ── 4. 슬래시 커맨드 설치 ──
850
+ // ── 4. 슬래시 커맨드 설치 ── (기존 사용자만)
804
851
  let commands = 0;
805
- try {
806
- commands = installSlashCommands();
807
- } catch (err) {
808
- console.error(`[forgen] slash commands failed: ${err?.message ?? err}`);
852
+ if (isExistingForgenUser) {
853
+ try {
854
+ commands = installSlashCommands();
855
+ } catch (err) {
856
+ console.error(`[forgen] slash commands failed: ${err?.message ?? err}`);
857
+ }
809
858
  }
810
859
 
811
- // ── 5. settings에 훅 설정 적용 ──
860
+ // ── 5. settings에 훅 설정 적용 ── (기존 사용자만)
812
861
  let hooks = false;
813
- try {
814
- hooks = applyHookSettings(settings);
815
- } catch (err) {
816
- console.error(`[forgen] hooks settings failed: ${err?.message ?? err}`);
862
+ if (isExistingForgenUser) {
863
+ try {
864
+ hooks = applyHookSettings(settings);
865
+ } catch (err) {
866
+ console.error(`[forgen] hooks settings failed: ${err?.message ?? err}`);
867
+ }
817
868
  }
818
869
 
819
- // ── 6. MCP 서버를 ~/.claude.json 등록 (settings.json이 아닌 올바른 경로) ──
870
+ // ── 6. MCP 서버 ~/.claude.json 등록 ── (기존 사용자만)
820
871
  let mcp = false;
821
- try {
822
- mcp = applyMcpToClaudeJson();
823
- cleanLegacyMcpFromSettings(settings);
824
- } catch (err) {
825
- console.error(`[forgen] MCP server registration failed: ${err?.message ?? err}`);
872
+ if (isExistingForgenUser) {
873
+ try {
874
+ mcp = applyMcpToClaudeJson();
875
+ cleanLegacyMcpFromSettings(settings);
876
+ } catch (err) {
877
+ console.error(`[forgen] MCP server registration failed: ${err?.message ?? err}`);
878
+ }
826
879
  }
827
880
 
828
881
  // ── 7. settings.json 한 번 쓰기 (atomic) ──
@@ -863,6 +916,28 @@ function main() {
863
916
  if (parts.length > 0) {
864
917
  console.log(`[forgen] Installed: ${parts.join(', ')} → ${HOME}`);
865
918
  }
919
+
920
+ // First-run banner (신규 사용자 안내) — feat/codex-support P1-6
921
+ if (!isExistingForgenUser) {
922
+ console.log('');
923
+ console.log(' ╭─ forgen — multi-host support ─────────────────────────────╮');
924
+ console.log(' │ binaries installed: forgen, fgx, forgen-mcp │');
925
+ console.log(' │ │');
926
+ console.log(' │ Next: register forgen on a host (Claude or Codex): │');
927
+ console.log(' │ $ forgen install (interactive 3-choice) │');
928
+ console.log(' │ $ forgen install claude (Claude only) │');
929
+ console.log(' │ $ forgen install codex (Codex only) │');
930
+ console.log(' │ $ forgen install both (둘 다) │');
931
+ console.log(' ╰────────────────────────────────────────────────────────────╯');
932
+ console.log('');
933
+ } else {
934
+ // 기존 사용자: default_host 안내
935
+ console.log('');
936
+ console.log(' [forgen] Multi-host support added. Existing forgen entry preserved.');
937
+ console.log(' Optional: `forgen config default-host {claude|codex|ask}` to set fgx default.');
938
+ console.log(' Optional: `forgen install codex` to register on Codex too.');
939
+ console.log('');
940
+ }
866
941
  }
867
942
 
868
943
  try {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes