@wooojin/forgen 0.4.13 → 0.5.5

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 (166) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +261 -0
  3. package/README.ja.md +16 -12
  4. package/README.ko.md +12 -8
  5. package/README.md +90 -27
  6. package/README.zh.md +16 -12
  7. package/assets/claude/commands/forge-loop.md +7 -1
  8. package/assets/claude/commands/ship.md +18 -1
  9. package/assets/opencode/forgen.ts +52 -0
  10. package/assets/shared/hook-registry.json +210 -22
  11. package/dist/checks/_shared/meta-guard-dispatch.d.ts +7 -0
  12. package/dist/checks/_shared/meta-guard-dispatch.js +12 -2
  13. package/dist/checks/_shared/model-profile.d.ts +25 -0
  14. package/dist/checks/_shared/model-profile.js +63 -0
  15. package/dist/cli.js +99 -153
  16. package/dist/core/auto-compound-runner.d.ts +0 -11
  17. package/dist/core/auto-compound-runner.js +199 -92
  18. package/dist/core/changelog-cli.d.ts +4 -1
  19. package/dist/core/changelog-cli.js +8 -7
  20. package/dist/core/compound-consent.d.ts +15 -0
  21. package/dist/core/compound-consent.js +48 -0
  22. package/dist/core/compound-sweep-cli.d.ts +60 -0
  23. package/dist/core/compound-sweep-cli.js +374 -0
  24. package/dist/core/config-injector.d.ts +16 -1
  25. package/dist/core/config-injector.js +47 -28
  26. package/dist/core/dashboard-cli.js +40 -16
  27. package/dist/core/dashboard.d.ts +3 -4
  28. package/dist/core/dashboard.js +15 -34
  29. package/dist/core/dev-cli.d.ts +13 -0
  30. package/dist/core/dev-cli.js +70 -0
  31. package/dist/core/doctor.d.ts +16 -5
  32. package/dist/core/doctor.js +190 -176
  33. package/dist/core/drift-score.d.ts +2 -0
  34. package/dist/core/drift-score.js +9 -1
  35. package/dist/core/harness.js +115 -43
  36. package/dist/core/health-cli.d.ts +2 -0
  37. package/dist/core/health-cli.js +6 -1
  38. package/dist/core/host-detect.d.ts +3 -1
  39. package/dist/core/host-detect.js +26 -1
  40. package/dist/core/migrate-cli.js +15 -0
  41. package/dist/core/migrate-evidence-host.d.ts +2 -1
  42. package/dist/core/migrate-tenetx.d.ts +50 -0
  43. package/dist/core/migrate-tenetx.js +262 -0
  44. package/dist/core/probe-workflow-cli.d.ts +3 -3
  45. package/dist/core/probe-workflow-cli.js +13 -13
  46. package/dist/core/recall-cli.js +1 -1
  47. package/dist/core/regress-map-cli.js +1 -1
  48. package/dist/core/rendered-rules-manifest.d.ts +29 -0
  49. package/dist/core/rendered-rules-manifest.js +60 -0
  50. package/dist/core/session-store.js +14 -3
  51. package/dist/core/settings-injector.d.ts +3 -0
  52. package/dist/core/settings-injector.js +12 -16
  53. package/dist/core/spawn.d.ts +37 -1
  54. package/dist/core/spawn.js +116 -9
  55. package/dist/core/state-gc.js +1 -0
  56. package/dist/core/status-cli.d.ts +20 -0
  57. package/dist/core/status-cli.js +100 -0
  58. package/dist/core/statusline-cli.d.ts +7 -0
  59. package/dist/core/statusline-cli.js +63 -19
  60. package/dist/core/transcript-summary.d.ts +18 -0
  61. package/dist/core/transcript-summary.js +81 -0
  62. package/dist/core/trust-layer-intent.d.ts +21 -1
  63. package/dist/core/trust-layer-intent.js +7 -0
  64. package/dist/core/types.d.ts +3 -2
  65. package/dist/core/uninstall.js +12 -0
  66. package/dist/core/usage-telemetry.d.ts +7 -1
  67. package/dist/core/usage-telemetry.js +7 -9
  68. package/dist/core/v1-bootstrap.js +1 -1
  69. package/dist/core/watch-cli.js +1 -1
  70. package/dist/engine/compound-extractor.js +10 -0
  71. package/dist/engine/compound-loop.js +55 -7
  72. package/dist/engine/compound-share.d.ts +85 -0
  73. package/dist/engine/compound-share.js +606 -0
  74. package/dist/engine/correction-cluster-runner.d.ts +38 -0
  75. package/dist/engine/correction-cluster-runner.js +188 -0
  76. package/dist/engine/correction-clustering.d.ts +80 -0
  77. package/dist/engine/correction-clustering.js +167 -0
  78. package/dist/engine/enforce-classifier.d.ts +10 -1
  79. package/dist/engine/enforce-classifier.js +111 -22
  80. package/dist/engine/extraction-session.js +10 -3
  81. package/dist/engine/private-filter.d.ts +36 -0
  82. package/dist/engine/private-filter.js +100 -0
  83. package/dist/engine/ranking-pipeline.js +4 -2
  84. package/dist/engine/relevance-gate.d.ts +12 -0
  85. package/dist/engine/relevance-gate.js +12 -0
  86. package/dist/engine/roi-demotion.d.ts +79 -0
  87. package/dist/engine/roi-demotion.js +159 -0
  88. package/dist/engine/solution-format.d.ts +1 -0
  89. package/dist/engine/solution-format.js +26 -0
  90. package/dist/engine/solution-matcher.js +10 -1
  91. package/dist/fgx.js +7 -6
  92. package/dist/forge/cli.js +8 -2
  93. package/dist/hooks/compound-reflection.js +6 -1
  94. package/dist/hooks/context-guard.d.ts +16 -1
  95. package/dist/hooks/context-guard.js +99 -49
  96. package/dist/hooks/hook-config.js +5 -0
  97. package/dist/hooks/hook-registry.d.ts +6 -1
  98. package/dist/hooks/hooks-generator.js +3 -0
  99. package/dist/hooks/post-tool-use.js +2 -3
  100. package/dist/hooks/pre-compact.js +14 -0
  101. package/dist/hooks/pre-tool-use.js +5 -1
  102. package/dist/hooks/session-end.d.ts +34 -0
  103. package/dist/hooks/session-end.js +100 -0
  104. package/dist/hooks/session-recovery.js +18 -1
  105. package/dist/hooks/shared/hook-timing.js +6 -1
  106. package/dist/hooks/shared/stop-triggers.d.ts +29 -2
  107. package/dist/hooks/shared/stop-triggers.js +35 -2
  108. package/dist/hooks/solution-injector.d.ts +28 -0
  109. package/dist/hooks/solution-injector.js +126 -28
  110. package/dist/hooks/stop-guard.js +5 -2
  111. package/dist/hooks/subagent-stop-guard.js +4 -1
  112. package/dist/host/capabilities-claude.js +1 -0
  113. package/dist/host/capabilities-codex.js +1 -0
  114. package/dist/host/capabilities-opencode.d.ts +26 -0
  115. package/dist/host/capabilities-opencode.js +78 -0
  116. package/dist/host/capabilities-registry.d.ts +7 -0
  117. package/dist/host/capabilities-registry.js +14 -0
  118. package/dist/host/codex-rules-context.d.ts +25 -0
  119. package/dist/host/codex-rules-context.js +62 -0
  120. package/dist/host/exec-host.d.ts +14 -3
  121. package/dist/host/exec-host.js +15 -2
  122. package/dist/host/host-binding.d.ts +27 -0
  123. package/dist/host/host-binding.js +11 -0
  124. package/dist/host/host-runtime.js +18 -0
  125. package/dist/host/install-codex.d.ts +66 -0
  126. package/dist/host/install-codex.js +293 -25
  127. package/dist/host/install-opencode.d.ts +38 -0
  128. package/dist/host/install-opencode.js +148 -0
  129. package/dist/host/install-orchestrator.d.ts +4 -1
  130. package/dist/host/install-orchestrator.js +30 -1
  131. package/dist/host/invoke-agent.d.ts +3 -2
  132. package/dist/host/invoke-agent.js +1 -0
  133. package/dist/host/opencode/context-cli.d.ts +15 -0
  134. package/dist/host/opencode/context-cli.js +25 -0
  135. package/dist/host/opencode/guard-cli.d.ts +13 -0
  136. package/dist/host/opencode/guard-cli.js +39 -0
  137. package/dist/host/opencode/plugin/forgen.d.ts +31 -0
  138. package/dist/host/opencode/plugin/forgen.js +60 -0
  139. package/dist/host/opencode/translate.d.ts +49 -0
  140. package/dist/host/opencode/translate.js +96 -0
  141. package/dist/host/parity-harness.d.ts +10 -2
  142. package/dist/host/parity-harness.js +7 -3
  143. package/dist/host/projection.d.ts +37 -3
  144. package/dist/host/projection.js +104 -42
  145. package/dist/mcp/tools.js +17 -4
  146. package/dist/store/evidence-store.d.ts +2 -6
  147. package/dist/store/evidence-store.js +164 -46
  148. package/dist/store/host-mismatch.d.ts +2 -1
  149. package/dist/store/host-mismatch.js +8 -8
  150. package/dist/store/profile-store.d.ts +3 -2
  151. package/dist/store/rule-store.d.ts +41 -0
  152. package/dist/store/rule-store.js +77 -2
  153. package/dist/store/types.d.ts +9 -2
  154. package/hooks/hooks.json +13 -1
  155. package/package.json +3 -2
  156. package/plugin.json +2 -2
  157. package/skills/architecture-decision/SKILL.md +18 -0
  158. package/skills/calibrate/SKILL.md +18 -0
  159. package/skills/code-review/SKILL.md +17 -0
  160. package/skills/compound/SKILL.md +17 -0
  161. package/skills/deep-interview/SKILL.md +7 -0
  162. package/skills/docker/SKILL.md +18 -0
  163. package/skills/forge-loop/SKILL.md +30 -1
  164. package/skills/learn/SKILL.md +15 -0
  165. package/skills/retro/SKILL.md +16 -0
  166. package/skills/ship/SKILL.md +36 -1
@@ -15,19 +15,19 @@ import { loadAllEvidence } from './evidence-store.js';
15
15
  const DOMINANCE_THRESHOLD = 0.8; // 80% 이상이 한 host 에서 발생하면 dominant.
16
16
  const MIN_TOTAL_FOR_DEMOTE = 5; // 너무 적은 표본은 무시.
17
17
  function summarize(events) {
18
- const byHost = { claude: 0, codex: 0 };
18
+ const byHost = { claude: 0, codex: 0, opencode: 0 };
19
19
  for (const e of events) {
20
20
  const h = (e.host ?? 'claude');
21
21
  byHost[h] += 1;
22
22
  }
23
- const total = byHost.claude + byHost.codex;
23
+ // host-agnostic 집계 — 새 host 추가 시 이 로직은 수정 불필요.
24
+ const entries = Object.entries(byHost);
25
+ const total = entries.reduce((s, [, n]) => s + n, 0);
24
26
  if (total === 0) {
25
27
  return { byHost, total, skew: 0, dominantHost: null, demoteRecommended: false };
26
28
  }
27
- const claudeShare = byHost.claude / total;
28
- const codexShare = byHost.codex / total;
29
- const dominant = claudeShare >= codexShare ? 'claude' : 'codex';
30
- const skew = Math.max(claudeShare, codexShare);
29
+ const [dominant, dominantCount] = entries.reduce((max, cur) => (cur[1] > max[1] ? cur : max));
30
+ const skew = dominantCount / total;
31
31
  const demoteRecommended = total >= MIN_TOTAL_FOR_DEMOTE && skew >= DOMINANCE_THRESHOLD;
32
32
  return { byHost, total, skew, dominantHost: dominant, demoteRecommended };
33
33
  }
@@ -53,10 +53,10 @@ export function summarizeNegativeSignalsForRef(refId) {
53
53
  }
54
54
  export function summarizeAllByHost() {
55
55
  const all = loadAllEvidence();
56
- const r = { claude: 0, codex: 0 };
56
+ const r = { claude: 0, codex: 0, opencode: 0 };
57
57
  for (const e of all)
58
58
  r[(e.host ?? 'claude')] += 1;
59
- return { ...r, total: r.claude + r.codex };
59
+ return { ...r, total: r.claude + r.codex + r.opencode };
60
60
  }
61
61
  /** 테스트 노출용 — 임계값이 변경되면 회귀 즉시 감지. */
62
62
  export const HOST_MISMATCH_TUNING = {
@@ -5,6 +5,7 @@
5
5
  * Authoritative schema: docs/plans/2026-04-03-forgen-data-model-storage-spec.md §2
6
6
  */
7
7
  import type { Profile, QualityPack, AutonomyPack, JudgmentPack, CommunicationPack, TrustPolicy } from './types.js';
8
+ import type { HostId } from '../core/trust-layer-intent.js';
8
9
  export declare function createProfile(userId: string, qualityPack: QualityPack, autonomyPack: AutonomyPack, trustPolicy: TrustPolicy, trustSource: Profile['trust_preferences']['source'], judgmentPack?: JudgmentPack, communicationPack?: CommunicationPack): Profile;
9
10
  export declare function loadProfile(): Profile | null;
10
11
  export declare function loadProfileRaw(): unknown;
@@ -50,7 +51,7 @@ export declare function bumpAxisConfidence(axis: 'quality_safety' | 'autonomy' |
50
51
  * fgx / forgen 무인자 실행 시 어느 host 를 spawn 할지 결정. 'ask' 면 매번 묻기.
51
52
  * 미설정(undefined) 은 legacy 사용자 호환 — 'claude' 로 resolve.
52
53
  */
53
- export type DefaultHost = 'claude' | 'codex' | 'ask';
54
+ export type DefaultHost = HostId | 'ask';
54
55
  export declare function getDefaultHost(): DefaultHost | undefined;
55
56
  export declare function setDefaultHost(host: DefaultHost): boolean;
56
57
  /**
@@ -58,4 +59,4 @@ export declare function setDefaultHost(host: DefaultHost): boolean;
58
59
  * 우선순위: explicit override > profile.default_host > 'claude' fallback.
59
60
  * 'ask' 는 caller 가 별도 처리 (interactive prompt).
60
61
  */
61
- export declare function resolveDefaultHost(override?: 'claude' | 'codex'): 'claude' | 'codex' | 'ask';
62
+ export declare function resolveDefaultHost(override?: HostId): DefaultHost;
@@ -33,6 +33,23 @@ export declare function appendRule(rule: Rule): Promise<{
33
33
  export declare function loadRule(ruleId: string): Rule | null;
34
34
  export declare function loadAllRules(): Rule[];
35
35
  export declare function loadActiveRules(): Rule[];
36
+ /**
37
+ * render_key(+source, +scope)로 기존 rule을 찾는다 — status 무관(active/suppressed/removed/
38
+ * superseded 전부 포함). ADR-013 채굴 룰 재생성 결함 수정(RCA): promoteSessionCandidates가
39
+ * loadActiveRules() 로만 dedup 하다 보니 TTL/cap 로 은퇴(removed)된 render_key가 재차 채굴되면
40
+ * "없음" 으로 오판해 매번 새 rule_id + created_at=now 로 재생성 — 이게 6,701개 파일 중
41
+ * 6,525개가 removed 로 죽어 쌓인 근본 원인. 이 함수로 upsert identity 를 render_key 로
42
+ * 고정한다: 있으면 재활성화, 없을 때만 신규 생성.
43
+ *
44
+ * scope 필터가 필요한 이유: processCorrection()(evidence-processor.ts)이 fix-now/avoid-this
45
+ * 에 대해 scope:'session' 임시 rule 을 만들 때 *같은 render_key 공식*(`${axis}.${target-slug}`)
46
+ * 을 쓴다. scope 를 안 걸면 promoteSessionCandidates 가 그 임시 rule 을 "기존 me rule" 로
47
+ * 오인해 재활성화해버려 영구 승격이 아예 안 되는 회귀가 생긴다(실측: 회귀 테스트로 발견).
48
+ *
49
+ * 동일 render_key 매칭이 여럿(레거시 데이터 등 이례적 상황)이면 active 우선, 그다음
50
+ * updated_at 최신순으로 하나만 반환.
51
+ */
52
+ export declare function findRuleByRenderKey(renderKey: string, source?: RuleSource, scope?: RuleScope): Rule | null;
36
53
  /**
37
54
  * ADR-002 Meta signal — rule 들이 프롬프트에 inject 되었음을 기록.
38
55
  * rule.lifecycle.inject_count +1, last_inject_at = now.
@@ -45,4 +62,28 @@ export declare function updateRuleStatus(ruleId: string, status: RuleStatus): bo
45
62
  * 현재 세션 ID와 다른 scope:'session' 규칙을 비활성화.
46
63
  * 이전 세션의 임시 규칙이 새 세션에서 영향을 미치지 않도록 정리.
47
64
  */
65
+ /** status:'removed' rule 파일 정리 결과. */
66
+ export interface PruneResult {
67
+ /** retention 기간을 지나 삭제 대상인 rule_id (dry-run/apply 공통). */
68
+ candidates: string[];
69
+ /** apply=true 일 때 실제 unlink 된 rule_id (dry-run 이면 항상 빈 배열). */
70
+ deleted: string[];
71
+ }
72
+ /** removed 룰 보관 기간 기본값 — 7일. TTL 로 은퇴된 지 이만큼 지나면 prune 대상. */
73
+ export declare const DEFAULT_PRUNE_RETENTION_MS: number;
74
+ /**
75
+ * status:'removed' 파일을 retention 기간이 지난 것만 실제 unlink.
76
+ * RCA: retire(updateRuleStatus)는 status flip만 하고 파일을 지우지 않아 6,525개 죽은
77
+ * 파일이 ~/.forgen/me/rules 에 쌓여 loadAllRules() linear scan 비용을 키우고
78
+ * cross-process race 창을 넓혔다. status 전이 시각(updated_at) 기준으로 retention을
79
+ * 재는 이유: removed 직후 즉시 지우면 (a) findRuleByRenderKey 가 재활성화할 기회를
80
+ * 없애고 (b) 오탐 은퇴를 되돌릴 유예가 사라진다.
81
+ *
82
+ * 기본 dry-run(apply=false) — 프로덕션 데이터 삭제는 호출측이 명시적으로 apply:true
83
+ * 를 넘길 때만. (compound-sweep-cli 의 `--prune-removed`는 `--apply` 없으면 dry-run.)
84
+ */
85
+ export declare function pruneRemovedRules(opts?: {
86
+ retentionMs?: number;
87
+ apply?: boolean;
88
+ }): PruneResult;
48
89
  export declare function cleanupStaleSessionRules(_currentSessionId: string): number;
@@ -160,6 +160,37 @@ function resolveProjectRulesDir() {
160
160
  export function loadActiveRules() {
161
161
  return loadAllRules().filter(r => r.status === 'active');
162
162
  }
163
+ /**
164
+ * render_key(+source, +scope)로 기존 rule을 찾는다 — status 무관(active/suppressed/removed/
165
+ * superseded 전부 포함). ADR-013 채굴 룰 재생성 결함 수정(RCA): promoteSessionCandidates가
166
+ * loadActiveRules() 로만 dedup 하다 보니 TTL/cap 로 은퇴(removed)된 render_key가 재차 채굴되면
167
+ * "없음" 으로 오판해 매번 새 rule_id + created_at=now 로 재생성 — 이게 6,701개 파일 중
168
+ * 6,525개가 removed 로 죽어 쌓인 근본 원인. 이 함수로 upsert identity 를 render_key 로
169
+ * 고정한다: 있으면 재활성화, 없을 때만 신규 생성.
170
+ *
171
+ * scope 필터가 필요한 이유: processCorrection()(evidence-processor.ts)이 fix-now/avoid-this
172
+ * 에 대해 scope:'session' 임시 rule 을 만들 때 *같은 render_key 공식*(`${axis}.${target-slug}`)
173
+ * 을 쓴다. scope 를 안 걸면 promoteSessionCandidates 가 그 임시 rule 을 "기존 me rule" 로
174
+ * 오인해 재활성화해버려 영구 승격이 아예 안 되는 회귀가 생긴다(실측: 회귀 테스트로 발견).
175
+ *
176
+ * 동일 render_key 매칭이 여럿(레거시 데이터 등 이례적 상황)이면 active 우선, 그다음
177
+ * updated_at 최신순으로 하나만 반환.
178
+ */
179
+ export function findRuleByRenderKey(renderKey, source, scope) {
180
+ const matches = loadAllRules().filter((r) => r.render_key === renderKey
181
+ && (source === undefined || r.source === source)
182
+ && (scope === undefined || r.scope === scope));
183
+ if (matches.length === 0)
184
+ return null;
185
+ matches.sort((a, b) => {
186
+ if (a.status === 'active' && b.status !== 'active')
187
+ return -1;
188
+ if (b.status === 'active' && a.status !== 'active')
189
+ return 1;
190
+ return b.updated_at.localeCompare(a.updated_at);
191
+ });
192
+ return matches[0];
193
+ }
163
194
  /**
164
195
  * ADR-002 Meta signal — rule 들이 프롬프트에 inject 되었음을 기록.
165
196
  * rule.lifecycle.inject_count +1, last_inject_at = now.
@@ -188,10 +219,54 @@ export function updateRuleStatus(ruleId, status) {
188
219
  saveRule(rule);
189
220
  return true;
190
221
  }
222
+ /** removed 룰 보관 기간 기본값 — 7일. TTL 로 은퇴된 지 이만큼 지나면 prune 대상. */
223
+ export const DEFAULT_PRUNE_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
191
224
  /**
192
- * 현재 세션 ID와 다른 scope:'session' 규칙을 비활성화.
193
- * 이전 세션의 임시 규칙이 새 세션에서 영향을 미치지 않도록 정리.
225
+ * status:'removed' 파일을 retention 기간이 지난 것만 실제 unlink.
226
+ * RCA: retire(updateRuleStatus)는 status flip만 하고 파일을 지우지 않아 6,525개 죽은
227
+ * 파일이 ~/.forgen/me/rules 에 쌓여 loadAllRules() linear scan 비용을 키우고
228
+ * cross-process race 창을 넓혔다. status 전이 시각(updated_at) 기준으로 retention을
229
+ * 재는 이유: removed 직후 즉시 지우면 (a) findRuleByRenderKey 가 재활성화할 기회를
230
+ * 없애고 (b) 오탐 은퇴를 되돌릴 유예가 사라진다.
231
+ *
232
+ * 기본 dry-run(apply=false) — 프로덕션 데이터 삭제는 호출측이 명시적으로 apply:true
233
+ * 를 넘길 때만. (compound-sweep-cli 의 `--prune-removed`는 `--apply` 없으면 dry-run.)
194
234
  */
235
+ export function pruneRemovedRules(opts = {}) {
236
+ const retentionMs = opts.retentionMs ?? DEFAULT_PRUNE_RETENTION_MS;
237
+ const apply = opts.apply ?? false;
238
+ const now = Date.now();
239
+ const candidates = [];
240
+ const deleted = [];
241
+ if (!fs.existsSync(ME_RULES))
242
+ return { candidates, deleted };
243
+ for (const file of fs.readdirSync(ME_RULES)) {
244
+ if (!file.endsWith('.json'))
245
+ continue;
246
+ const filePath = path.join(ME_RULES, file);
247
+ const rule = safeReadJSON(filePath, null);
248
+ if (rule?.status !== 'removed')
249
+ continue;
250
+ // 안전 불변식: prune 은 auto-mined advisory churn(결함1의 폭주 산출물)만 삭제한다.
251
+ // 영구·명시 룰(L1 하드룰, source:'explicit_correction')은 status:'removed' 여도
252
+ // 파일을 지우지 않는다 — 회귀 시 이력/복구 근거가 되고, 애초에 이 함수의 대상이 아니다.
253
+ const isAutoMinedChurn = rule.source === 'behavior_inference' || rule.render_key?.startsWith('auto:');
254
+ if (!isAutoMinedChurn)
255
+ continue;
256
+ const age = now - Date.parse(rule.updated_at);
257
+ if (!Number.isFinite(age) || age < retentionMs)
258
+ continue;
259
+ candidates.push(rule.rule_id);
260
+ if (apply) {
261
+ try {
262
+ fs.unlinkSync(filePath);
263
+ deleted.push(rule.rule_id);
264
+ }
265
+ catch { /* best-effort — 다음 prune 에서 재시도 */ }
266
+ }
267
+ }
268
+ return { candidates, deleted };
269
+ }
195
270
  export function cleanupStaleSessionRules(_currentSessionId) {
196
271
  if (!fs.existsSync(ME_RULES))
197
272
  return 0;
@@ -4,6 +4,7 @@
4
4
  * Authoritative source: docs/plans/2026-04-03-forgen-data-model-storage-spec.md
5
5
  * Runtime contracts: docs/plans/2026-04-03-forgen-component-interface-design.md
6
6
  */
7
+ import type { HostId } from '../core/trust-layer-intent.js';
7
8
  export type QualityPack = '보수형' | '균형형' | '속도형';
8
9
  export type AutonomyPack = '확인 우선형' | '균형형' | '자율 실행형';
9
10
  export type JudgmentPack = '최소변경형' | '균형형' | '구조적접근형';
@@ -110,6 +111,12 @@ export interface Rule {
110
111
  * ADR-002 §Data Model.
111
112
  */
112
113
  lifecycle?: LifecycleState;
114
+ /**
115
+ * W3-2 (교정 클러스터링): 이 rule 이 클러스터 통합으로 superseded 되었다면, 통합 결과
116
+ * rule 의 rule_id. `forgen rules --unmerge-cluster` 로 복원 시 이 링크로 원본을 되살린다.
117
+ * 통합 rule 쪽에는 설정되지 않는다(원본에만). ADR-010 W3-2.
118
+ */
119
+ clustered_into?: string;
113
120
  }
114
121
  export type EvidenceType = 'explicit_correction' | 'behavior_observation' | 'session_summary';
115
122
  export interface Evidence {
@@ -128,7 +135,7 @@ export interface Evidence {
128
135
  * evidence 가 어느 host 에서 발생했는지 태그. 미지정 시 'claude' 로 backfill (기존 데이터 호환).
129
136
  * core 의 학습 로직은 이 필드를 *호스트별 가중치* 가 아니라 *불일치 demote 신호* 로만 사용한다.
130
137
  */
131
- host?: 'claude' | 'codex';
138
+ host?: HostId;
132
139
  }
133
140
  export interface QualityFacets {
134
141
  verification_depth: number;
@@ -187,7 +194,7 @@ export interface Profile {
187
194
  * - 'ask': 매번 묻기 (interactive prompt)
188
195
  * - undefined: legacy / 미설정 → 'claude' fallback (마이그레이션 호환)
189
196
  */
190
- default_host?: 'claude' | 'codex' | 'ask';
197
+ default_host?: HostId | 'ask';
191
198
  }
192
199
  export type RecommendationSource = 'onboarding' | 'mismatch_recommendation';
193
200
  export type RecommendationStatus = 'proposed' | 'accepted' | 'archived';
package/hooks/hooks.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Forgen harness hooks (auto-generated, 22/22 active)",
2
+ "description": "Forgen harness hooks (auto-generated, 23/23 active)",
3
3
  "hooks": {
4
4
  "UserPromptSubmit": [
5
5
  {
@@ -195,6 +195,18 @@
195
195
  }
196
196
  ]
197
197
  }
198
+ ],
199
+ "SessionEnd": [
200
+ {
201
+ "matcher": "*",
202
+ "hooks": [
203
+ {
204
+ "type": "command",
205
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/dist/hooks/session-end.js\"",
206
+ "timeout": 3
207
+ }
208
+ ]
209
+ }
198
210
  ]
199
211
  }
200
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooojin/forgen",
3
- "version": "0.4.13",
3
+ "version": "0.5.5",
4
4
  "preferGlobal": true,
5
5
  "main": "dist/lib.js",
6
6
  "types": "./dist/lib.d.ts",
@@ -79,7 +79,7 @@
79
79
  "devDependencies": {
80
80
  "@biomejs/biome": "^2.4.16",
81
81
  "@types/js-yaml": "^4.0.9",
82
- "@types/node": "^25.9.1",
82
+ "@types/node": "^26.1.1",
83
83
  "@vitest/coverage-v8": "^4.1.7",
84
84
  "typescript": "^5.9.3",
85
85
  "vitest": "^4.1.7"
@@ -87,6 +87,7 @@
87
87
  "dependencies": {
88
88
  "@modelcontextprotocol/sdk": "^1.28.0",
89
89
  "js-yaml": "^4.1.1",
90
+ "jsonc-parser": "^3.3.1",
90
91
  "zod": "^4.4.3"
91
92
  },
92
93
  "peerDependencies": {
package/plugin.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "$schema": "https://claude.ai/schemas/claude-plugin.json",
3
3
  "name": "forgen",
4
- "version": "0.4.13",
4
+ "version": "0.5.5",
5
5
  "description": "Claude Code harness — the more you use Claude, the better it gets",
6
6
  "author": {
7
7
  "name": "jang-ujin",
8
- "url": "https://github.com/wooo-jin"
8
+ "url": "https://github.com/forgen-team"
9
9
  },
10
10
  "repository": "https://github.com/forgen-team/forgen",
11
11
  "homepage": "https://github.com/forgen-team/forgen",
@@ -1,6 +1,24 @@
1
1
  ---
2
2
  name: architecture-decision
3
3
  description: This skill should be used when the user asks to "adr,ch-architecture decision,아키텍처 결정,설계 결정,기술 결정". ADR 생성 -- 대안 평가, 가중 트레이드오프 매트릭스, 결정 라이프사이클 관리
4
+ argument-hint: "[결정 주제]"
5
+ model: opus
6
+ allowed-tools:
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ - Bash
11
+ - Write
12
+ - Edit
13
+ - mcp__forgen-compound__compound-search
14
+ - mcp__forgen-compound__compound-read
15
+ - mcp__forgen-compound__compound-list
16
+ triggers:
17
+ - "adr"
18
+ - "ch-architecture decision"
19
+ - "아키텍처 결정"
20
+ - "설계 결정"
21
+ - "기술 결정"
4
22
  ---
5
23
 
6
24
  <Purpose>
@@ -1,6 +1,24 @@
1
1
  ---
2
2
  name: calibrate
3
3
  description: This skill should be used when the user asks to "calibrate, 캘리브레이트, 프로필 보정, 프로필 조정, 프로필 확인". 축적된 evidence(교정 기록)를 분석하여 4축 프로필 조정을 제안합니다.
4
+ argument-hint: "[기간: 7d/30d/90d]"
5
+ model: opus
6
+ allowed-tools:
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ - Bash
11
+ - mcp__forgen-compound__compound-search
12
+ - mcp__forgen-compound__compound-read
13
+ - mcp__forgen-compound__profile-read
14
+ - mcp__forgen-compound__correction-record
15
+ triggers:
16
+ - "calibrate"
17
+ - "캘리브레이트"
18
+ - "프로필 보정"
19
+ - "프로필 조정"
20
+ - "프로필 확인"
21
+ - "프로필 업데이트"
4
22
  ---
5
23
 
6
24
  <Purpose>
@@ -1,6 +1,23 @@
1
1
  ---
2
2
  name: code-review
3
3
  description: This skill should be used when the user asks to "code review,코드 리뷰,리뷰해줘,review this". 신뢰도 보정 기반 체계적 코드 리뷰 — compound 이력 연동, auto-fix, 20개 체크리스트.
4
+ argument-hint: "[file, PR number, or git range]"
5
+ model: opus
6
+ allowed-tools:
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ - Bash
11
+ - Edit
12
+ - Write
13
+ - Agent
14
+ triggers:
15
+ - "code review"
16
+ - "코드 리뷰"
17
+ - "리뷰해줘"
18
+ - "review this"
19
+ - "리뷰"
20
+ - "review"
4
21
  ---
5
22
 
6
23
  <Purpose>
@@ -1,6 +1,23 @@
1
1
  ---
2
2
  name: compound
3
3
  description: This skill should be used when the user asks to "복리화,compound,패턴 추출,솔루션 축적,what did we learn". Compound Engineering — extract reusable patterns from this session's work
4
+ argument-hint: "[solution description]"
5
+ model: inherit
6
+ allowed-tools:
7
+ - Bash
8
+ - Read
9
+ - Write
10
+ - mcp__forgen-compound__compound-search
11
+ - mcp__forgen-compound__compound-read
12
+ - mcp__forgen-compound__compound-list
13
+ - mcp__forgen-compound__compound-stats
14
+ triggers:
15
+ - "복리화"
16
+ - "compound"
17
+ - "패턴 추출"
18
+ - "솔루션 축적"
19
+ - "what did we learn"
20
+ - "배운 것 정리"
4
21
  ---
5
22
 
6
23
  <Purpose>
@@ -1,6 +1,13 @@
1
1
  ---
2
2
  name: deep-interview
3
3
  description: This skill should be used when the user asks to "deep-interview,딥인터뷰,심층인터뷰,deep interview". Deep requirement interview with weighted dimension scoring, challenge modes, and ontology tracking
4
+ argument-hint: "[project or feature description]"
5
+ triggers:
6
+ - "deep-interview"
7
+ - "딥인터뷰"
8
+ - "심층인터뷰"
9
+ - "deep interview"
10
+ - "요구사항 인터뷰"
4
11
  ---
5
12
 
6
13
  <Purpose>
@@ -1,6 +1,24 @@
1
1
  ---
2
2
  name: docker
3
3
  description: This skill should be used when the user asks to "docker,container,컨테이너,dockerfile,도커,docker-compose". Docker 컨테이너화 -- Dockerfile 최적화, Compose 구성, 보안 강화
4
+ argument-hint: "[컨테이너화 대상]"
5
+ model: sonnet
6
+ allowed-tools:
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ - Bash
11
+ - Write
12
+ - Edit
13
+ - mcp__forgen-compound__compound-search
14
+ - mcp__forgen-compound__compound-read
15
+ triggers:
16
+ - "docker"
17
+ - "container"
18
+ - "컨테이너"
19
+ - "dockerfile"
20
+ - "도커"
21
+ - "docker-compose"
4
22
  ---
5
23
 
6
24
  <Purpose>
@@ -1,6 +1,29 @@
1
1
  ---
2
2
  name: forge-loop
3
3
  description: This skill should be used when the user asks to "forge-loop, 포지루프, 끝까지, don't stop, goal, 목표, goal lock, scope lock". 작업을 PRD(User Story)로 분해 + 모든 수용 기준 충족까지 반복 실행. `--goal-only` 플래그로 PRD/수용기준 박제만 (실행 사이클 없이) 가능 — goal-locking pattern lightweight 진입점.
4
+ argument-hint: "[task description] [--goal-only]"
5
+ model: inherit
6
+ allowed-tools:
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ - Bash
11
+ - Agent
12
+ - Edit
13
+ - Write
14
+ triggers:
15
+ - "forge-loop"
16
+ - "포지루프"
17
+ - "끝까지"
18
+ - "don't stop"
19
+ - "완료될 때까지"
20
+ - "루프로 실행"
21
+ - "goal"
22
+ - "목표"
23
+ - "goal lock"
24
+ - "scope lock"
25
+ - "completion criteria"
26
+ - "수용 기준"
4
27
  ---
5
28
 
6
29
  <Purpose>
@@ -65,13 +88,19 @@ PRD 확정 직후 **반드시** `~/.forgen/state/forge-loop.json`에 저장:
65
88
  mkdir -p ~/.forgen/state
66
89
  cat > ~/.forgen/state/forge-loop.json <<EOF
67
90
  {"active":true,"startedAt":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","stories":[
68
- {"id":"US-001","title":"...","passes":false,"attempts":0}
91
+ {"id":"US-001","title":"...","passes":false,"attempts":0,"acceptanceCriteria":["..."]}
69
92
  ]}
70
93
  EOF
71
94
  ```
72
95
 
73
96
  이 파일이 있어야 Claude가 중간에 멈추지 않도록 Stop 훅이 차단합니다.
97
+ 소유 세션은 최초 차단 시점에 Stop 훅이 자동 귀속하므로(`sessionId` 필드) 이
98
+ 파일을 쓸 때 세션 ID를 직접 넣을 필요는 없습니다 — 단, 귀속된 세션과 다른
99
+ 세션에서는 이 루프가 차단하지 않습니다. 24시간 이상 갱신이 없으면 자동
100
+ 해제(1회성 안내 포함)되며, 연속 30회 차단 시에도 안전 상한으로 자동 해제됩니다.
74
101
  스토리 완료 시 `passes: true`로 업데이트. 전체 완료는 Stop 훅이 자동 처리.
102
+ `acceptanceCriteria[0]`은 차단 메시지에 `AC1:`로 노출되므로 있으면 첫 항목을
103
+ 구체적이고 검증 가능한 문장으로 작성하세요 (없어도 정상 동작).
75
104
 
76
105
  ### goal-only 모드 — Phase 1 종료 분기
77
106
 
@@ -1,6 +1,21 @@
1
1
  ---
2
2
  name: learn
3
3
  description: This skill should be used when the user asks to "learn, 학습 관리, compound 정리, 솔루션 정리". Compound 지식의 관리 인터페이스 — 검색, 통계, 가지치기, 내보내기/가져오기.
4
+ argument-hint: "[search {query}|stats|prune|export|import {path}]"
5
+ model: inherit
6
+ allowed-tools:
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ - Bash
11
+ triggers:
12
+ - "learn"
13
+ - "학습 관리"
14
+ - "compound 정리"
15
+ - "솔루션 정리"
16
+ - "compound 관리"
17
+ - "솔루션 관리"
18
+ - "지식 관리"
4
19
  ---
5
20
 
6
21
  <Purpose>
@@ -1,6 +1,22 @@
1
1
  ---
2
2
  name: retro
3
3
  description: This skill should be used when the user asks to "retro, 회고, retrospective, 돌아보기". Git 메트릭 + compound 건강도 + 학습 추세를 교차 분석하는 회고 리포트.
4
+ argument-hint: "[7d|14d|30d] [compare]"
5
+ model: inherit
6
+ allowed-tools:
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ - Bash
11
+ triggers:
12
+ - "retro"
13
+ - "회고"
14
+ - "retrospective"
15
+ - "돌아보기"
16
+ - "주간 회고"
17
+ - "월간 회고"
18
+ - "주간회고"
19
+ - "월간회고"
4
20
  ---
5
21
 
6
22
  <Purpose>
@@ -1,6 +1,24 @@
1
1
  ---
2
2
  name: ship
3
3
  description: This skill should be used when the user asks to "ship, 배포, 릴리스, release". 비대화형 자동 릴리스 파이프라인 — 테스트, 리뷰, 버전, CHANGELOG, PR을 원커맨드로.
4
+ argument-hint: "[patch|minor|major]"
5
+ model: inherit
6
+ disable-model-invocation: true
7
+ allowed-tools:
8
+ - Read
9
+ - Grep
10
+ - Glob
11
+ - Bash
12
+ - Agent
13
+ - Edit
14
+ - Write
15
+ triggers:
16
+ - "ship"
17
+ - "배포"
18
+ - "릴리스"
19
+ - "release"
20
+ - "릴리즈"
21
+ - "배포해줘"
4
22
  ---
5
23
 
6
24
  <Purpose>
@@ -148,10 +166,27 @@ npm version {patch|minor|major} --no-git-tag-version
148
166
 
149
167
  커밋을 주제별 그룹핑 -> CHANGELOG.md 상단에 추가.
150
168
 
169
+ ## Step 6.5: Smoke 증거 생성 (forgen 레포 한정)
170
+
171
+ forgen 레포 자체를 ship 할 때는 릴리스 커밋 전에 smoke 증거를 생성한다
172
+ (ADR-010 W0-2 — CI self-gate 가 릴리스 커밋에서 smoke-report 를 요구).
173
+
174
+ ```bash
175
+ # forgen 레포에서만. vitest 는 Step 3 에서 이미 돌았지만 게이트 증거는
176
+ # smoke.cjs 산출물만 인정 — 전체 실행 (실제 프로세스 산출물 원칙).
177
+ if [ -f scripts/smoke.cjs ]; then
178
+ node scripts/smoke.cjs || ABORT
179
+ fi
180
+ ```
181
+
182
+ - 실패 -> ABORT ("smoke 증거 생성 실패 — 게이트 통과 불가")
183
+
151
184
  ## Step 7: 릴리스 커밋
152
185
 
153
186
  ```bash
154
- git add package.json CHANGELOG.md
187
+ git add package.json package-lock.json CHANGELOG.md
188
+ # forgen 레포: smoke 증거 포함
189
+ [ -f .forgen-release/smoke-report.json ] && git add .forgen-release/smoke-report.json
155
190
  git commit -m "release: v{version}"
156
191
  ```
157
192