@wooojin/forgen 0.4.1 → 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.
- package/.claude-plugin/plugin.json +5 -5
- package/CHANGELOG.md +164 -15
- package/CONTRIBUTING.md +2 -2
- package/README.ja.md +17 -9
- package/README.ko.md +20 -12
- package/README.md +46 -12
- package/README.zh.md +17 -9
- package/assets/README.md +86 -0
- package/assets/architecture.svg +100 -0
- package/assets/banner.png +0 -0
- package/assets/banner.svg +53 -0
- package/assets/demo/01-install.gif +0 -0
- package/assets/demo/01-install.tape +54 -0
- package/assets/demo/02-compound-learning.gif +0 -0
- package/assets/demo/02-compound-learning.tape +50 -0
- package/assets/demo/03-forge-personalization.gif +0 -0
- package/assets/demo/03-forge-personalization.tape +64 -0
- package/assets/demo/before-after.gif +0 -0
- package/assets/demo/before-after.tape +98 -0
- package/assets/demo-preview.svg +96 -0
- package/assets/icon.png +0 -0
- package/{hooks → assets/shared}/hook-registry.json +2 -1
- package/dist/cli.js +78 -6
- package/dist/core/auto-compound-runner.js +62 -38
- package/dist/core/behavior-classifier.d.ts +28 -0
- package/dist/core/behavior-classifier.js +46 -0
- package/dist/core/dashboard.d.ts +7 -0
- package/dist/core/dashboard.js +32 -0
- package/dist/core/doctor.js +92 -0
- package/dist/core/git-stats.d.ts +36 -0
- package/dist/core/git-stats.js +79 -0
- package/dist/core/harness.d.ts +1 -1
- package/dist/core/harness.js +27 -20
- package/dist/core/host-detect.d.ts +42 -0
- package/dist/core/host-detect.js +68 -0
- package/dist/core/installer.js +2 -2
- package/dist/core/migrate-cli.d.ts +1 -0
- package/dist/core/migrate-cli.js +19 -0
- package/dist/core/migrate-evidence-host.d.ts +36 -0
- package/dist/core/migrate-evidence-host.js +49 -0
- package/dist/core/settings-injector.js +4 -2
- package/dist/core/spawn.d.ts +1 -1
- package/dist/core/spawn.js +4 -11
- package/dist/core/stats-cli.js +12 -0
- package/dist/core/trust-layer-intent.d.ts +35 -0
- package/dist/core/trust-layer-intent.js +30 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/engine/compound-extractor.js +7 -9
- package/dist/engine/learn-cli.js +4 -2
- package/dist/engine/lifecycle/bypass-detector.d.ts +6 -1
- package/dist/engine/lifecycle/bypass-detector.js +57 -5
- package/dist/fgx.js +2 -1
- package/dist/forge/evidence-processor.js +12 -0
- package/dist/forge/onboarding.d.ts +3 -2
- package/dist/forge/onboarding.js +3 -2
- package/dist/hooks/db-guard.js +3 -3
- package/dist/hooks/forge-loop-progress.d.ts +9 -0
- package/dist/hooks/forge-loop-progress.js +38 -0
- package/dist/hooks/hook-registry.js +1 -1
- package/dist/hooks/hooks-generator.d.ts +15 -1
- package/dist/hooks/hooks-generator.js +18 -16
- package/dist/hooks/keyword-detector.js +1 -1
- package/dist/hooks/post-tool-use.d.ts +1 -1
- package/dist/hooks/post-tool-use.js +13 -4
- package/dist/hooks/pre-compact.js +1 -1
- package/dist/hooks/pre-tool-use.js +4 -4
- package/dist/hooks/rate-limiter.js +2 -2
- package/dist/hooks/session-recovery.js +11 -0
- package/dist/hooks/shared/blocking-allowlist.d.ts +28 -0
- package/dist/hooks/shared/blocking-allowlist.js +38 -0
- package/dist/hooks/shared/forge-loop-state.d.ts +36 -0
- package/dist/hooks/shared/forge-loop-state.js +116 -0
- package/dist/hooks/shared/hook-response.d.ts +18 -0
- package/dist/hooks/shared/hook-response.js +31 -0
- package/dist/hooks/skill-injector.js +1 -1
- package/dist/hooks/stop-guard.js +15 -0
- package/dist/host/capabilities-claude.d.ts +8 -0
- package/dist/host/capabilities-claude.js +46 -0
- package/dist/host/capabilities-codex.d.ts +11 -0
- package/dist/host/capabilities-codex.js +50 -0
- package/dist/host/capabilities-registry.d.ts +11 -0
- package/dist/host/capabilities-registry.js +30 -0
- package/dist/host/codex-adapter.d.ts +8 -5
- package/dist/host/codex-adapter.js +10 -82
- package/dist/host/codex-output-parser.d.ts +39 -0
- package/dist/host/codex-output-parser.js +75 -0
- package/dist/host/exec-host.d.ts +54 -0
- package/dist/host/exec-host.js +92 -0
- package/dist/host/host-runtime.d.ts +37 -0
- package/dist/host/host-runtime.js +51 -0
- package/dist/host/install-claude.d.ts +35 -0
- package/dist/host/install-claude.js +238 -0
- package/dist/host/install-codex.d.ts +44 -0
- package/dist/host/install-codex.js +276 -0
- package/dist/host/install-orchestrator.d.ts +34 -0
- package/dist/host/install-orchestrator.js +126 -0
- package/dist/host/invoke-agent.d.ts +27 -0
- package/dist/host/invoke-agent.js +115 -0
- package/dist/host/parity-harness.d.ts +62 -0
- package/dist/host/parity-harness.js +283 -0
- package/dist/host/projection.d.ts +35 -0
- package/dist/host/projection.js +126 -0
- package/dist/mcp/server.js +11 -0
- package/dist/mcp/tools.js +47 -0
- package/dist/services/session.d.ts +6 -3
- package/dist/services/session.js +33 -4
- package/dist/store/evidence-store.d.ts +1 -0
- package/dist/store/evidence-store.js +34 -3
- package/dist/store/host-mismatch.d.ts +42 -0
- package/dist/store/host-mismatch.js +65 -0
- package/dist/store/profile-store.d.ts +29 -0
- package/dist/store/profile-store.js +53 -0
- package/dist/store/types.d.ts +13 -0
- package/hooks/hooks.json +6 -1
- package/package.json +6 -4
- package/plugin.json +4 -4
- package/scripts/postinstall.js +100 -25
- /package/{agents → assets/claude/agents}/analyst.md +0 -0
- /package/{agents → assets/claude/agents}/architect.md +0 -0
- /package/{agents → assets/claude/agents}/code-reviewer.md +0 -0
- /package/{agents → assets/claude/agents}/critic.md +0 -0
- /package/{agents → assets/claude/agents}/debugger.md +0 -0
- /package/{agents → assets/claude/agents}/designer.md +0 -0
- /package/{agents → assets/claude/agents}/executor.md +0 -0
- /package/{agents → assets/claude/agents}/explore.md +0 -0
- /package/{agents → assets/claude/agents}/git-master.md +0 -0
- /package/{agents → assets/claude/agents}/planner.md +0 -0
- /package/{agents → assets/claude/agents}/solution-evolver.md +0 -0
- /package/{agents → assets/claude/agents}/test-engineer.md +0 -0
- /package/{agents → assets/claude/agents}/verifier.md +0 -0
- /package/{commands → assets/claude/commands}/architecture-decision.md +0 -0
- /package/{commands → assets/claude/commands}/calibrate.md +0 -0
- /package/{commands → assets/claude/commands}/code-review.md +0 -0
- /package/{commands → assets/claude/commands}/compound.md +0 -0
- /package/{commands → assets/claude/commands}/deep-interview.md +0 -0
- /package/{commands → assets/claude/commands}/docker.md +0 -0
- /package/{commands → assets/claude/commands}/forge-loop.md +0 -0
- /package/{commands → assets/claude/commands}/learn.md +0 -0
- /package/{commands → assets/claude/commands}/retro.md +0 -0
- /package/{commands → assets/claude/commands}/ship.md +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-mismatch demote signal — Multi-Host Core Design §4.3 / §10 우선순위 5
|
|
3
|
+
*
|
|
4
|
+
* spec §12.2 / §18 결과: schema-level 등가성이 강하므로 *호스트별 가중치* 가 아니라
|
|
5
|
+
* *불일치 신호* 만 사용한다. 같은 솔루션/규칙이 한 host 에서만 자주 깨지면 그 host 한정으로
|
|
6
|
+
* demote 후보. 본 모듈은 그 신호를 *읽기만* 한다 — 실제 demote 적용은 lifecycle 트랙.
|
|
7
|
+
*
|
|
8
|
+
* 신호 정의 (Phase 1 단순 버전):
|
|
9
|
+
* - solution_failed_to_apply_count_by_host
|
|
10
|
+
* - block_acknowledged_then_revert_count_by_host
|
|
11
|
+
* - drift_event_count_by_host
|
|
12
|
+
* 위 3 종 metric 을 evidence_id → host 매핑으로 집계.
|
|
13
|
+
*/
|
|
14
|
+
export type HostId = 'claude' | 'codex';
|
|
15
|
+
export interface HostMismatchSummary {
|
|
16
|
+
readonly byHost: Record<HostId, number>;
|
|
17
|
+
readonly total: number;
|
|
18
|
+
/** ratio of host with most events to total (0..1). 0.5 = balanced, 1.0 = single-host signal. */
|
|
19
|
+
readonly skew: number;
|
|
20
|
+
/** the dominant host (or null if balanced). */
|
|
21
|
+
readonly dominantHost: HostId | null;
|
|
22
|
+
/** 본 신호가 lifecycle demote 를 권고할 정도로 강한지. 임계값은 1차 단순. */
|
|
23
|
+
readonly demoteRecommended: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 특정 솔루션/규칙 ID 에 대해 *부정적 evidence* (drift, revert, failure) 가 host 별로
|
|
27
|
+
* 어떻게 분포하는지 요약.
|
|
28
|
+
*
|
|
29
|
+
* 1차 구현은 evidence.summary 의 ID 매칭으로 단순 집계. 향후 evidence 가 명시적 ref 필드를
|
|
30
|
+
* 가지면 그쪽으로 전환.
|
|
31
|
+
*/
|
|
32
|
+
export declare function summarizeNegativeSignalsForRef(refId: string): HostMismatchSummary;
|
|
33
|
+
export declare function summarizeAllByHost(): {
|
|
34
|
+
claude: number;
|
|
35
|
+
codex: number;
|
|
36
|
+
total: number;
|
|
37
|
+
};
|
|
38
|
+
/** 테스트 노출용 — 임계값이 변경되면 회귀 즉시 감지. */
|
|
39
|
+
export declare const HOST_MISMATCH_TUNING: {
|
|
40
|
+
readonly DOMINANCE_THRESHOLD: 0.8;
|
|
41
|
+
readonly MIN_TOTAL_FOR_DEMOTE: 5;
|
|
42
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-mismatch demote signal — Multi-Host Core Design §4.3 / §10 우선순위 5
|
|
3
|
+
*
|
|
4
|
+
* spec §12.2 / §18 결과: schema-level 등가성이 강하므로 *호스트별 가중치* 가 아니라
|
|
5
|
+
* *불일치 신호* 만 사용한다. 같은 솔루션/규칙이 한 host 에서만 자주 깨지면 그 host 한정으로
|
|
6
|
+
* demote 후보. 본 모듈은 그 신호를 *읽기만* 한다 — 실제 demote 적용은 lifecycle 트랙.
|
|
7
|
+
*
|
|
8
|
+
* 신호 정의 (Phase 1 단순 버전):
|
|
9
|
+
* - solution_failed_to_apply_count_by_host
|
|
10
|
+
* - block_acknowledged_then_revert_count_by_host
|
|
11
|
+
* - drift_event_count_by_host
|
|
12
|
+
* 위 3 종 metric 을 evidence_id → host 매핑으로 집계.
|
|
13
|
+
*/
|
|
14
|
+
import { loadAllEvidence } from './evidence-store.js';
|
|
15
|
+
const DOMINANCE_THRESHOLD = 0.8; // 80% 이상이 한 host 에서 발생하면 dominant.
|
|
16
|
+
const MIN_TOTAL_FOR_DEMOTE = 5; // 너무 적은 표본은 무시.
|
|
17
|
+
function summarize(events) {
|
|
18
|
+
const byHost = { claude: 0, codex: 0 };
|
|
19
|
+
for (const e of events) {
|
|
20
|
+
const h = (e.host ?? 'claude');
|
|
21
|
+
byHost[h] += 1;
|
|
22
|
+
}
|
|
23
|
+
const total = byHost.claude + byHost.codex;
|
|
24
|
+
if (total === 0) {
|
|
25
|
+
return { byHost, total, skew: 0, dominantHost: null, demoteRecommended: false };
|
|
26
|
+
}
|
|
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);
|
|
31
|
+
const demoteRecommended = total >= MIN_TOTAL_FOR_DEMOTE && skew >= DOMINANCE_THRESHOLD;
|
|
32
|
+
return { byHost, total, skew, dominantHost: dominant, demoteRecommended };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 특정 솔루션/규칙 ID 에 대해 *부정적 evidence* (drift, revert, failure) 가 host 별로
|
|
36
|
+
* 어떻게 분포하는지 요약.
|
|
37
|
+
*
|
|
38
|
+
* 1차 구현은 evidence.summary 의 ID 매칭으로 단순 집계. 향후 evidence 가 명시적 ref 필드를
|
|
39
|
+
* 가지면 그쪽으로 전환.
|
|
40
|
+
*/
|
|
41
|
+
export function summarizeNegativeSignalsForRef(refId) {
|
|
42
|
+
const all = loadAllEvidence();
|
|
43
|
+
const matched = all.filter((e) => {
|
|
44
|
+
if (Array.isArray(e.candidate_rule_refs) && e.candidate_rule_refs.includes(refId))
|
|
45
|
+
return true;
|
|
46
|
+
if (typeof e.summary === 'string' && e.summary.includes(refId)) {
|
|
47
|
+
// 명시적 음수 키워드 (drift, revert, failed, block-revert) 가 있을 때만.
|
|
48
|
+
return /drift|revert|failed|regress/i.test(e.summary);
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
});
|
|
52
|
+
return summarize(matched);
|
|
53
|
+
}
|
|
54
|
+
export function summarizeAllByHost() {
|
|
55
|
+
const all = loadAllEvidence();
|
|
56
|
+
const r = { claude: 0, codex: 0 };
|
|
57
|
+
for (const e of all)
|
|
58
|
+
r[(e.host ?? 'claude')] += 1;
|
|
59
|
+
return { ...r, total: r.claude + r.codex };
|
|
60
|
+
}
|
|
61
|
+
/** 테스트 노출용 — 임계값이 변경되면 회귀 즉시 감지. */
|
|
62
|
+
export const HOST_MISMATCH_TUNING = {
|
|
63
|
+
DOMINANCE_THRESHOLD,
|
|
64
|
+
MIN_TOTAL_FOR_DEMOTE,
|
|
65
|
+
};
|
|
@@ -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
|
+
}
|
package/dist/store/types.d.ts
CHANGED
|
@@ -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,
|
|
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.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"preferGlobal": true,
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"types": "./dist/lib.d.ts",
|
|
@@ -47,12 +47,15 @@
|
|
|
47
47
|
],
|
|
48
48
|
"repository": {
|
|
49
49
|
"type": "git",
|
|
50
|
-
"url": "https://github.com/
|
|
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
|
-
"
|
|
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.
|
|
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/
|
|
11
|
-
"homepage": "https://github.com/
|
|
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"
|
package/scripts/postinstall.js
CHANGED
|
@@ -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', '
|
|
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, '
|
|
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
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
|
870
|
+
// ── 6. MCP 서버 ~/.claude.json 등록 ── (기존 사용자만)
|
|
820
871
|
let mcp = false;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
|
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
|