@wooojin/forgen 0.4.1 → 0.4.4
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 +267 -15
- package/CONTRIBUTING.md +2 -2
- package/README.ja.md +17 -9
- package/README.ko.md +34 -12
- package/README.md +65 -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/{commands → assets/claude/commands}/calibrate.md +4 -3
- package/{commands → assets/claude/commands}/retro.md +2 -2
- 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/checks/_shared/text-sanitizer.d.ts +21 -0
- package/dist/checks/_shared/text-sanitizer.js +60 -0
- package/dist/checks/dangerous-response-pattern.d.ts +32 -0
- package/dist/checks/dangerous-response-pattern.js +65 -0
- package/dist/checks/fact-vs-agreement.js +25 -1
- package/dist/cli.js +78 -6
- package/dist/core/auto-compound-runner.js +90 -39
- 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 +57 -25
- 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 +51 -0
- package/dist/renderer/rule-renderer.d.ts +1 -1
- package/dist/renderer/rule-renderer.js +73 -1
- package/dist/services/session.d.ts +6 -3
- package/dist/services/session.js +33 -4
- package/dist/store/compound-usage-store.d.ts +28 -0
- package/dist/store/compound-usage-store.js +59 -0
- 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/skills/calibrate/SKILL.md +4 -3
- package/skills/retro/SKILL.md +2 -2
- /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}/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}/ship.md +0 -0
package/dist/services/session.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* - launch context(런타임 + 정제된 args)를 단일 타입으로 통일
|
|
11
11
|
* - CLI/fgx에서 수집한 런타임 값을 Harness, Spawn, Hook Generator에 일관되게 전달
|
|
12
12
|
*/
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
const localRequire = createRequire(import.meta.url);
|
|
13
15
|
/** 런타임 정규화: 외부 문자열을 내부 enum으로 변환 */
|
|
14
16
|
function parseRuntime(raw) {
|
|
15
17
|
if (!raw)
|
|
@@ -24,17 +26,44 @@ function parseRuntime(raw) {
|
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
const DEFAULT_RUNTIME = 'claude';
|
|
29
|
+
/**
|
|
30
|
+
* profile.default_host 를 읽어 runtime 결정.
|
|
31
|
+
* 'ask' 면 별도 prompt 책임 — 본 함수는 default 'claude' 로 fallback (caller 가 --ask 처리).
|
|
32
|
+
* profile-store import 가 cycle 위험이라 require 로 lazy.
|
|
33
|
+
*/
|
|
34
|
+
function readProfileDefaultRuntime() {
|
|
35
|
+
try {
|
|
36
|
+
const mod = localRequire('../store/profile-store.js');
|
|
37
|
+
const stored = mod.getDefaultHost?.();
|
|
38
|
+
if (stored === 'claude' || stored === 'codex')
|
|
39
|
+
return stored;
|
|
40
|
+
return null; // 'ask' 또는 미설정
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
27
46
|
/**
|
|
28
47
|
* CLI 인자를 파싱해 런타임 결정 + 런타임 플래그 제거
|
|
29
|
-
*
|
|
30
|
-
*
|
|
48
|
+
* 우선순위 (높음→낮음):
|
|
49
|
+
* 1. --runtime <claude|codex> flag
|
|
50
|
+
* 2. FORGEN_RUNTIME env
|
|
51
|
+
* 3. profile.default_host (P1-4)
|
|
52
|
+
* 4. 'claude' fallback (legacy 호환)
|
|
31
53
|
*/
|
|
32
54
|
export function resolveLaunchContext(args) {
|
|
33
55
|
const runtimeFromEnv = parseRuntime(process.env.FORGEN_RUNTIME);
|
|
56
|
+
const runtimeFromProfile = runtimeFromEnv ? null : readProfileDefaultRuntime();
|
|
57
|
+
const initial = runtimeFromEnv ?? runtimeFromProfile ?? DEFAULT_RUNTIME;
|
|
58
|
+
const initialSource = runtimeFromEnv
|
|
59
|
+
? 'env'
|
|
60
|
+
: runtimeFromProfile
|
|
61
|
+
? 'profile'
|
|
62
|
+
: 'default';
|
|
34
63
|
const result = {
|
|
35
|
-
runtime:
|
|
64
|
+
runtime: initial,
|
|
36
65
|
args: [],
|
|
37
|
-
runtimeSource:
|
|
66
|
+
runtimeSource: initialSource,
|
|
38
67
|
};
|
|
39
68
|
for (let i = 0; i < args.length; i += 1) {
|
|
40
69
|
const arg = args[i];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — compound usage signal store (Pathfinder D11 fix, MVP).
|
|
3
|
+
*
|
|
4
|
+
* 배경 (Deep Interview 2026-04-30 Round 5):
|
|
5
|
+
* verified compound 23개, candidate 8개 — 그러나 mature 0. retro 회고에서
|
|
6
|
+
* "활용률 측정 불가 — compound-search 호출 카운터 미수집" 확인. 즉 사용자가
|
|
7
|
+
* compound 를 reuse 했다는 신호 자체가 잡히지 않아서 mature 승격 입력이 없음.
|
|
8
|
+
*
|
|
9
|
+
* 원칙(user-mirror, Round 4 Contrarian): forgen 자기 학습이 아니라 *사용자
|
|
10
|
+
* reuse* 가 mature 의 권위 종착점. 따라서 compound-read 호출이 있을 때마다
|
|
11
|
+
* "이 패턴이 사용됐다" 신호를 한 줄 기록.
|
|
12
|
+
*
|
|
13
|
+
* MVP 스코프: 신호 *수집*만. 승격 정책(예: 5회 reuse → mature) 은 다음 사이클.
|
|
14
|
+
* 기록만 잘 쌓이면 임계 설정·승격 로직은 위에 얹기 쉬움.
|
|
15
|
+
*
|
|
16
|
+
* 데이터: append-only JSONL at ~/.forgen/state/compound-usage.jsonl
|
|
17
|
+
* 각 라인: {"at": ISO, "name": "<solution-slug>", "via": "mcp|cli|hook"}
|
|
18
|
+
* crash-safe: 단순 fs.appendFileSync — 동시 append 는 OS 가 atomic 보장 (POSIX <PIPE_BUF).
|
|
19
|
+
*/
|
|
20
|
+
export declare const COMPOUND_USAGE_LOG: string;
|
|
21
|
+
export interface UsageEntry {
|
|
22
|
+
at: string;
|
|
23
|
+
name: string;
|
|
24
|
+
via: 'mcp' | 'cli' | 'hook';
|
|
25
|
+
}
|
|
26
|
+
export declare function recordUsage(name: string, via?: UsageEntry['via']): void;
|
|
27
|
+
/** 단순 카운터 — 승격 정책에서 호출. MVP 에선 미사용. */
|
|
28
|
+
export declare function readUsageCounts(): Map<string, number>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — compound usage signal store (Pathfinder D11 fix, MVP).
|
|
3
|
+
*
|
|
4
|
+
* 배경 (Deep Interview 2026-04-30 Round 5):
|
|
5
|
+
* verified compound 23개, candidate 8개 — 그러나 mature 0. retro 회고에서
|
|
6
|
+
* "활용률 측정 불가 — compound-search 호출 카운터 미수집" 확인. 즉 사용자가
|
|
7
|
+
* compound 를 reuse 했다는 신호 자체가 잡히지 않아서 mature 승격 입력이 없음.
|
|
8
|
+
*
|
|
9
|
+
* 원칙(user-mirror, Round 4 Contrarian): forgen 자기 학습이 아니라 *사용자
|
|
10
|
+
* reuse* 가 mature 의 권위 종착점. 따라서 compound-read 호출이 있을 때마다
|
|
11
|
+
* "이 패턴이 사용됐다" 신호를 한 줄 기록.
|
|
12
|
+
*
|
|
13
|
+
* MVP 스코프: 신호 *수집*만. 승격 정책(예: 5회 reuse → mature) 은 다음 사이클.
|
|
14
|
+
* 기록만 잘 쌓이면 임계 설정·승격 로직은 위에 얹기 쉬움.
|
|
15
|
+
*
|
|
16
|
+
* 데이터: append-only JSONL at ~/.forgen/state/compound-usage.jsonl
|
|
17
|
+
* 각 라인: {"at": ISO, "name": "<solution-slug>", "via": "mcp|cli|hook"}
|
|
18
|
+
* crash-safe: 단순 fs.appendFileSync — 동시 append 는 OS 가 atomic 보장 (POSIX <PIPE_BUF).
|
|
19
|
+
*/
|
|
20
|
+
import * as fs from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import { STATE_DIR } from '../core/paths.js';
|
|
23
|
+
export const COMPOUND_USAGE_LOG = path.join(STATE_DIR, 'compound-usage.jsonl');
|
|
24
|
+
export function recordUsage(name, via = 'mcp') {
|
|
25
|
+
if (!name)
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
29
|
+
const entry = { at: new Date().toISOString(), name, via };
|
|
30
|
+
fs.appendFileSync(COMPOUND_USAGE_LOG, JSON.stringify(entry) + '\n');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// fail-open: 신호 수집 실패가 사용자 경험을 방해하면 안 됨
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** 단순 카운터 — 승격 정책에서 호출. MVP 에선 미사용. */
|
|
37
|
+
export function readUsageCounts() {
|
|
38
|
+
const counts = new Map();
|
|
39
|
+
if (!fs.existsSync(COMPOUND_USAGE_LOG))
|
|
40
|
+
return counts;
|
|
41
|
+
try {
|
|
42
|
+
const lines = fs.readFileSync(COMPOUND_USAGE_LOG, 'utf-8').split('\n').filter(Boolean);
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
try {
|
|
45
|
+
const entry = JSON.parse(line);
|
|
46
|
+
if (typeof entry.name === 'string') {
|
|
47
|
+
counts.set(entry.name, (counts.get(entry.name) ?? 0) + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// skip malformed
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// fail-open
|
|
57
|
+
}
|
|
58
|
+
return counts;
|
|
59
|
+
}
|
|
@@ -14,6 +14,7 @@ export declare function createEvidence(params: {
|
|
|
14
14
|
candidate_rule_refs?: string[];
|
|
15
15
|
confidence: number;
|
|
16
16
|
raw_payload?: Record<string, unknown>;
|
|
17
|
+
host?: 'claude' | 'codex';
|
|
17
18
|
}): Evidence;
|
|
18
19
|
export declare function saveEvidence(evidence: Evidence): void;
|
|
19
20
|
/**
|
|
@@ -17,6 +17,24 @@ import { appendLifecycleEvents } from '../engine/lifecycle/meta-reclassifier.js'
|
|
|
17
17
|
function evidencePath(evidenceId) {
|
|
18
18
|
return path.join(ME_BEHAVIOR, `${evidenceId}.json`);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* 현재 세션이 어느 host 에서 실행되는지 추론 (Multi-Host §4.2).
|
|
22
|
+
* 1) explicit `params.host`
|
|
23
|
+
* 2) env var `FORGEN_HOST` (e2e 격리용)
|
|
24
|
+
* 3) `runtime` env hint
|
|
25
|
+
* 4) Codex CLI 흔적 (`CODEX_HOME` 또는 `CODEX_SANDBOX_NETWORK_DISABLED`)
|
|
26
|
+
* 5) default 'claude' (1원칙)
|
|
27
|
+
*/
|
|
28
|
+
function detectHost(explicit) {
|
|
29
|
+
if (explicit)
|
|
30
|
+
return explicit;
|
|
31
|
+
const fromEnv = process.env.FORGEN_HOST;
|
|
32
|
+
if (fromEnv === 'claude' || fromEnv === 'codex')
|
|
33
|
+
return fromEnv;
|
|
34
|
+
if (process.env.CODEX_HOME || process.env.CODEX_SANDBOX_NETWORK_DISABLED)
|
|
35
|
+
return 'codex';
|
|
36
|
+
return 'claude';
|
|
37
|
+
}
|
|
20
38
|
export function createEvidence(params) {
|
|
21
39
|
return {
|
|
22
40
|
evidence_id: crypto.randomUUID(),
|
|
@@ -29,6 +47,7 @@ export function createEvidence(params) {
|
|
|
29
47
|
candidate_rule_refs: params.candidate_rule_refs ?? [],
|
|
30
48
|
confidence: params.confidence,
|
|
31
49
|
raw_payload: params.raw_payload ?? {},
|
|
50
|
+
host: detectHost(params.host),
|
|
32
51
|
};
|
|
33
52
|
}
|
|
34
53
|
/** TEST-4 / RC4: behavior_observation 의 summary 가 의미있는 내용을 담아야 분석 가능. */
|
|
@@ -84,8 +103,19 @@ export function appendEvidence(evidence) {
|
|
|
84
103
|
return { saved: true, t1_events: 0 };
|
|
85
104
|
}
|
|
86
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* 기존 evidence 에 host 필드가 없으면 'claude' 로 backfill (Multi-Host §4.2 마이그레이션 정책).
|
|
108
|
+
* 새 multi-host 도입 이전 데이터는 모두 Claude 에서 발생했음 — 이 backfill 은 무손실.
|
|
109
|
+
*/
|
|
110
|
+
function backfillHost(ev) {
|
|
111
|
+
if (!ev)
|
|
112
|
+
return ev;
|
|
113
|
+
if (ev.host === 'claude' || ev.host === 'codex')
|
|
114
|
+
return ev;
|
|
115
|
+
return { ...ev, host: 'claude' };
|
|
116
|
+
}
|
|
87
117
|
export function loadEvidence(evidenceId) {
|
|
88
|
-
return safeReadJSON(evidencePath(evidenceId), null);
|
|
118
|
+
return backfillHost(safeReadJSON(evidencePath(evidenceId), null));
|
|
89
119
|
}
|
|
90
120
|
export function loadAllEvidence() {
|
|
91
121
|
if (!fs.existsSync(ME_BEHAVIOR))
|
|
@@ -95,8 +125,9 @@ export function loadAllEvidence() {
|
|
|
95
125
|
if (!file.endsWith('.json'))
|
|
96
126
|
continue;
|
|
97
127
|
const ev = safeReadJSON(path.join(ME_BEHAVIOR, file), null);
|
|
98
|
-
|
|
99
|
-
|
|
128
|
+
const filled = backfillHost(ev);
|
|
129
|
+
if (filled)
|
|
130
|
+
items.push(filled);
|
|
100
131
|
}
|
|
101
132
|
return items;
|
|
102
133
|
}
|
|
@@ -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.4",
|
|
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.4",
|
|
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"
|