@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.
- package/.claude-plugin/plugin.json +5 -5
- package/CHANGELOG.md +194 -15
- package/CONTRIBUTING.md +2 -2
- package/README.ja.md +74 -9
- package/README.ko.md +77 -12
- package/README.md +127 -25
- package/README.zh.md +43 -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/checks/conclusion-verification-ratio.d.ts +37 -0
- package/dist/checks/conclusion-verification-ratio.js +86 -0
- package/dist/checks/fact-vs-agreement.d.ts +47 -0
- package/dist/checks/fact-vs-agreement.js +92 -0
- package/dist/checks/self-score-deflation.d.ts +38 -0
- package/dist/checks/self-score-deflation.js +108 -0
- package/dist/cli.js +98 -6
- package/dist/core/auto-compound-runner.js +137 -49
- 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 +41 -2
- package/dist/core/doctor.js +118 -5
- package/dist/core/extraction-notice.d.ts +18 -0
- package/dist/core/extraction-notice.js +64 -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/init-cli.d.ts +26 -0
- package/dist/core/init-cli.js +104 -0
- package/dist/core/init.js +17 -0
- package/dist/core/inspect-cli.js +1 -2
- package/dist/core/installer.js +2 -2
- package/dist/core/migrate-cli.d.ts +11 -0
- package/dist/core/migrate-cli.js +53 -0
- package/dist/core/migrate-evidence-host.d.ts +36 -0
- package/dist/core/migrate-evidence-host.js +49 -0
- package/dist/core/paths.d.ts +8 -1
- package/dist/core/paths.js +11 -2
- package/dist/core/recall-cli.d.ts +26 -0
- package/dist/core/recall-cli.js +125 -0
- package/dist/core/recall-reference-detector.d.ts +43 -0
- package/dist/core/recall-reference-detector.js +65 -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.d.ts +21 -0
- package/dist/core/stats-cli.js +133 -10
- 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/core/uninstall.js +2 -1
- package/dist/engine/compound-cli.js +1 -0
- package/dist/engine/compound-export.js +8 -3
- package/dist/engine/compound-extractor.js +7 -9
- package/dist/engine/learn-cli.js +5 -6
- package/dist/engine/lifecycle/bypass-detector.d.ts +6 -1
- package/dist/engine/lifecycle/bypass-detector.js +57 -5
- package/dist/engine/lifecycle/lifecycle-cli.js +4 -4
- package/dist/engine/lifecycle/meta-reclassifier.js +3 -3
- package/dist/engine/lifecycle/orchestrator.js +2 -2
- package/dist/engine/lifecycle/signals.js +6 -6
- package/dist/engine/meta-learning/session-quality-scorer.d.ts +1 -6
- package/dist/engine/meta-learning/session-quality-scorer.js +2 -21
- package/dist/engine/skill-promoter.js +3 -6
- 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/context-guard.js +1 -1
- package/dist/hooks/dangerous-patterns.json +3 -3
- package/dist/hooks/db-guard.js +21 -5
- 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/intent-classifier.js +1 -1
- package/dist/hooks/keyword-detector.js +2 -2
- package/dist/hooks/notepad-injector.js +1 -1
- package/dist/hooks/permission-handler.js +1 -1
- package/dist/hooks/post-tool-failure.js +1 -1
- package/dist/hooks/post-tool-use.d.ts +7 -1
- package/dist/hooks/post-tool-use.js +50 -23
- package/dist/hooks/pre-compact.js +2 -2
- package/dist/hooks/pre-tool-use.d.ts +7 -0
- package/dist/hooks/pre-tool-use.js +28 -10
- package/dist/hooks/rate-limiter.js +3 -3
- package/dist/hooks/secret-filter.js +1 -1
- package/dist/hooks/session-recovery.js +12 -1
- package/dist/hooks/shared/blocking-allowlist.d.ts +28 -0
- package/dist/hooks/shared/blocking-allowlist.js +38 -0
- package/dist/hooks/shared/command-parser.d.ts +44 -0
- package/dist/hooks/shared/command-parser.js +50 -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 +30 -2
- package/dist/hooks/shared/hook-response.js +61 -3
- package/dist/hooks/skill-injector.js +2 -2
- package/dist/hooks/slop-detector.js +2 -2
- package/dist/hooks/solution-injector.d.ts +9 -0
- package/dist/hooks/solution-injector.js +48 -5
- package/dist/hooks/stop-guard.js +152 -13
- package/dist/hooks/subagent-tracker.js +1 -1
- 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/i18n/index.js +3 -5
- 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 +45 -3
- package/dist/store/host-mismatch.d.ts +42 -0
- package/dist/store/host-mismatch.js +65 -0
- package/dist/store/implicit-feedback-store.d.ts +59 -0
- package/dist/store/implicit-feedback-store.js +153 -0
- package/dist/store/profile-store.d.ts +29 -0
- package/dist/store/profile-store.js +53 -0
- package/dist/store/rule-store.js +8 -0
- package/dist/store/types.d.ts +13 -0
- package/hooks/hooks.json +6 -1
- package/package.json +7 -5
- 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,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen v0.4.1 — TEST-2: 자가 점수 인플레이션 가드
|
|
3
|
+
*
|
|
4
|
+
* Claude 가 자신의 작업 품질/확신도/완성도를 **숫자**로 상향 선언하면서 해당
|
|
5
|
+
* 턴(또는 세션)에 측정 도구 호출이 0 건이면 block. TEST-1 (사실 vs 합의) 보다
|
|
6
|
+
* 강한 신호 — 구체적 숫자 인플레이션은 합의-기반 자기-아부(sycophancy)의
|
|
7
|
+
* 가장 또렷한 표식.
|
|
8
|
+
*
|
|
9
|
+
* 배경 (RC2): v0.4.0 self-interview 에서 "8/10", "신뢰도 90%", "0.85 → 0.95"
|
|
10
|
+
* 같은 자가 점수가 턴마다 올라갔지만 `npm test` / `curl` / `Read` 등 실제
|
|
11
|
+
* 측정 호출은 0건. TEST-1 이 서술체 사실 주장을 잡았다면, TEST-2 는 **숫자**
|
|
12
|
+
* 점수의 인플레이션에 초점을 맞춘다.
|
|
13
|
+
*
|
|
14
|
+
* 순수 함수 — Stop hook block 경로에 붙는다.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 측정성 도구 — **숫자 점수**를 뒷받침할 수 있는 실 **실행** 범주.
|
|
18
|
+
*
|
|
19
|
+
* v0.4.1 coverage fix (2026-04-24 buyer-day1 R4 관찰): 이전에는 Read/Edit/Write/
|
|
20
|
+
* Grep/Glob 도 측정으로 간주했으나, 파일 "읽기/수정" 은 "신뢰도 95/100" 같은
|
|
21
|
+
* 수치 판정을 뒷받침 못 함. Read 1회면 minMeasurements=1 충족되어 block 회피.
|
|
22
|
+
* 실제 구매자 시나리오: Claude 가 자가평가 전에 대상 파일 한 번 Read 하면
|
|
23
|
+
* TEST-2 무력화 — 가드의 본 의도 훼손.
|
|
24
|
+
*
|
|
25
|
+
* 새 기준: **실행 결과** 만 측정 — `Bash` (npm test / curl / node --check 등)
|
|
26
|
+
* 와 `NotebookEdit` (cell 실행). 읽기 전용 도구는 수치 점수의 근거가 될 수 없음.
|
|
27
|
+
*/
|
|
28
|
+
const MEASUREMENT_TOOLS = new Set([
|
|
29
|
+
'Bash',
|
|
30
|
+
'NotebookEdit',
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* "자가 점수" 신호 — 숫자 + 품질/완성도/확신도 컨텍스트.
|
|
34
|
+
* - "신뢰도 90%", "품질 점수 85/100", "확신도 0.9", "8/10", "90점"
|
|
35
|
+
* - "0.7 → 0.9" 같은 증감 표기
|
|
36
|
+
*
|
|
37
|
+
* 이 regex 들은 *숫자 그 자체* 만 매칭하지 않고 품질-관련 명사와 같이 나타날 때만
|
|
38
|
+
* 매칭하도록 좁힘 (false positive 방지).
|
|
39
|
+
*/
|
|
40
|
+
const SELF_SCORE_PATTERNS = [
|
|
41
|
+
// "신뢰도 90%" / "quality 85%" / "확신도 0.9"
|
|
42
|
+
/(신뢰도|확신도|완성도|품질|자신감|confidence|quality|completeness)[\s::]*(\d+(?:\.\d+)?)\s*(%|점|\/\s*\d+|\/100|\/10)?/gi,
|
|
43
|
+
// "0.85 → 0.95" / "7 -> 9" score delta
|
|
44
|
+
/(\d+(?:\.\d+)?)\s*(?:→|->|–>|~>)\s*(\d+(?:\.\d+)?)/g,
|
|
45
|
+
// "8/10", "85/100" — 단독 분수 점수 (앞뒤 품질 컨텍스트 확인은 하지 않지만 보수적 매칭)
|
|
46
|
+
/\b(\d+(?:\.\d+)?)\s*\/\s*(10|100)\b/g,
|
|
47
|
+
// 별 점수 "⭐⭐⭐⭐" 4개 이상
|
|
48
|
+
/⭐{4,}/g,
|
|
49
|
+
];
|
|
50
|
+
function extractDeltas(text) {
|
|
51
|
+
const re = /(\d+(?:\.\d+)?)\s*(?:→|->|–>|~>)\s*(\d+(?:\.\d+)?)/g;
|
|
52
|
+
const out = [];
|
|
53
|
+
let m;
|
|
54
|
+
while ((m = re.exec(text)) !== null) {
|
|
55
|
+
const from = Number(m[1]);
|
|
56
|
+
const to = Number(m[2]);
|
|
57
|
+
if (Number.isFinite(from) && Number.isFinite(to))
|
|
58
|
+
out.push({ from, to });
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function findScoreSignals(text, max = 3) {
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const p of SELF_SCORE_PATTERNS) {
|
|
65
|
+
if (out.length >= max)
|
|
66
|
+
break;
|
|
67
|
+
// 각 호출마다 lastIndex 초기화를 위해 새 RegExp 생성
|
|
68
|
+
const re = new RegExp(p.source, p.flags);
|
|
69
|
+
let m;
|
|
70
|
+
while ((m = re.exec(text)) !== null && out.length < max) {
|
|
71
|
+
out.push(m[0]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
export function checkSelfScoreInflation(input) {
|
|
77
|
+
const minDelta = input.minDelta ?? 0;
|
|
78
|
+
const minMeasurements = input.minMeasurements ?? 1;
|
|
79
|
+
const scoreSignals = findScoreSignals(input.text);
|
|
80
|
+
const allDeltas = extractDeltas(input.text);
|
|
81
|
+
const positiveDeltas = allDeltas.filter((d) => d.to - d.from > minDelta);
|
|
82
|
+
const measurementCount = input.recentTools.filter((t) => MEASUREMENT_TOOLS.has(t)).length;
|
|
83
|
+
const measurementMissing = measurementCount < minMeasurements;
|
|
84
|
+
// 인플레이션 신호가 하나라도 있고 측정이 없으면 block
|
|
85
|
+
const hasInflationSignal = scoreSignals.length > 0 || positiveDeltas.length > 0;
|
|
86
|
+
const block = hasInflationSignal && measurementMissing;
|
|
87
|
+
let reason = '';
|
|
88
|
+
if (block) {
|
|
89
|
+
const parts = [];
|
|
90
|
+
if (positiveDeltas.length > 0) {
|
|
91
|
+
const sample = positiveDeltas.slice(0, 2).map((d) => `${d.from}→${d.to}`).join(', ');
|
|
92
|
+
parts.push(`자가 점수 상승 선언 ${positiveDeltas.length}건 (${sample})`);
|
|
93
|
+
}
|
|
94
|
+
if (scoreSignals.length > 0) {
|
|
95
|
+
parts.push(`점수 표현 ${scoreSignals.length}건 ("${scoreSignals[0]}")`);
|
|
96
|
+
}
|
|
97
|
+
parts.push(`측정 도구 호출 ${measurementCount}회 (< ${minMeasurements}) — 숫자 변동을 뒷받침할 실행/확인 증거 없음`);
|
|
98
|
+
parts.push('block: 테스트/빌드/curl 실행 결과를 턴에 포함하여 재응답');
|
|
99
|
+
reason = parts.join('. ');
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
block,
|
|
103
|
+
scoreSignals,
|
|
104
|
+
deltas: positiveDeltas,
|
|
105
|
+
measurementCount,
|
|
106
|
+
reason,
|
|
107
|
+
};
|
|
108
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -112,8 +112,31 @@ const commands = [
|
|
|
112
112
|
await displayHookStatus(process.cwd());
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
else if (sub === 'default-host') {
|
|
116
|
+
const value = args[1];
|
|
117
|
+
const valid = new Set(['claude', 'codex', 'ask']);
|
|
118
|
+
if (value === undefined) {
|
|
119
|
+
const { getDefaultHost } = await import('./store/profile-store.js');
|
|
120
|
+
const current = getDefaultHost();
|
|
121
|
+
console.log(` current default_host: ${current ?? '(unset → claude fallback)'}`);
|
|
122
|
+
console.log(' Usage: forgen config default-host {claude|codex|ask}');
|
|
123
|
+
}
|
|
124
|
+
else if (!valid.has(value)) {
|
|
125
|
+
console.log(` Invalid value: ${value}. Use one of: claude, codex, ask`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const { setDefaultHost } = await import('./store/profile-store.js');
|
|
130
|
+
const ok = setDefaultHost(value);
|
|
131
|
+
if (!ok) {
|
|
132
|
+
console.log(' ✗ Profile not found. Run `forgen onboarding` first.');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
console.log(` ✓ default_host set to: ${value}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
115
138
|
else {
|
|
116
|
-
console.log('Usage
|
|
139
|
+
console.log('Usage:\n forgen config hooks [--regenerate]\n forgen config default-host [claude|codex|ask]');
|
|
117
140
|
}
|
|
118
141
|
},
|
|
119
142
|
},
|
|
@@ -133,6 +156,53 @@ const commands = [
|
|
|
133
156
|
await handleInit(args);
|
|
134
157
|
},
|
|
135
158
|
},
|
|
159
|
+
{
|
|
160
|
+
name: 'install',
|
|
161
|
+
description: 'Install forgen into a host. Usage: forgen install [claude|codex|both] [--dry-run] [--no-mcp]',
|
|
162
|
+
handler: async (args) => {
|
|
163
|
+
const knownSubs = new Set(['claude', 'codex', 'both']);
|
|
164
|
+
const target = args[0] && knownSubs.has(args[0]) ? args[0] : args[0]?.startsWith('--') ? undefined : args[0];
|
|
165
|
+
if (target !== undefined && !knownSubs.has(target)) {
|
|
166
|
+
console.log('Usage:\n forgen install [claude|codex|both] [--dry-run] [--no-mcp]\n\n No arg → interactive 3-choice (Claude/Codex/Both).');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const dryRun = args.includes('--dry-run');
|
|
170
|
+
const registerMcp = !args.includes('--no-mcp');
|
|
171
|
+
const { runInstall, renderResult, resolvePkgRootFromBinary } = await import('./host/install-orchestrator.js');
|
|
172
|
+
const pkgRoot = resolvePkgRootFromBinary(import.meta.url);
|
|
173
|
+
const result = await runInstall({ target, pkgRoot, dryRun, registerMcp });
|
|
174
|
+
if (result === null) {
|
|
175
|
+
console.log('\n [forgen] Install skipped.');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
console.log(renderResult(result, dryRun));
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'parity',
|
|
183
|
+
description: 'Run host parity checks. Usage: forgen parity codex [--dry-run]',
|
|
184
|
+
handler: async (args) => {
|
|
185
|
+
const sub = args[0];
|
|
186
|
+
if (sub !== 'codex') {
|
|
187
|
+
console.log('Usage:\n forgen parity codex [--dry-run]\n\nNotes:\n - source 체크아웃에서만 작동합니다 (tests/ 디렉토리 필요).\n - npm install 로 설치된 패키지에서는 run-parity.sh 가 없습니다.');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const here = path.dirname(new URL(import.meta.url).pathname);
|
|
191
|
+
const scriptPath = path.resolve(here, '..', 'tests', 'e2e', 'codex', 'run-parity.sh');
|
|
192
|
+
if (!fs.existsSync(scriptPath)) {
|
|
193
|
+
console.error('[forgen] run-parity.sh 는 source 체크아웃에서만 작동. 직접 git clone 후 실행하세요.');
|
|
194
|
+
console.error(` expected: ${scriptPath}`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
const { spawnSync } = await import('node:child_process');
|
|
198
|
+
const dryRun = args.includes('--dry-run');
|
|
199
|
+
const spawnArgs = dryRun ? ['--dry-run'] : [];
|
|
200
|
+
const result = spawnSync('bash', [scriptPath, ...spawnArgs], { stdio: 'inherit' });
|
|
201
|
+
if (result.status !== 0) {
|
|
202
|
+
process.exit(result.status ?? 1);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
},
|
|
136
206
|
{
|
|
137
207
|
name: 'notepad',
|
|
138
208
|
description: 'Notepad (show|add|clear)',
|
|
@@ -151,7 +221,7 @@ const commands = [
|
|
|
151
221
|
},
|
|
152
222
|
{
|
|
153
223
|
name: 'onboarding',
|
|
154
|
-
description: 'v1
|
|
224
|
+
description: 'v1 4-question onboarding flow',
|
|
155
225
|
handler: async (_args) => {
|
|
156
226
|
const { runOnboarding } = await import('./forge/onboarding-cli.js');
|
|
157
227
|
await runOnboarding();
|
|
@@ -226,6 +296,22 @@ const commands = [
|
|
|
226
296
|
await handleInspect(['violations', '--last', '1']);
|
|
227
297
|
},
|
|
228
298
|
},
|
|
299
|
+
{
|
|
300
|
+
name: 'recall',
|
|
301
|
+
description: 'Show recent compound recalls (matched solutions) with optional body preview.',
|
|
302
|
+
handler: async (args) => {
|
|
303
|
+
const { handleRecall } = await import('./core/recall-cli.js');
|
|
304
|
+
await handleRecall(args);
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'migrate',
|
|
309
|
+
description: 'One-shot schema migrations (implicit-feedback category backfill).',
|
|
310
|
+
handler: async (args) => {
|
|
311
|
+
const { handleMigrate } = await import('./core/migrate-cli.js');
|
|
312
|
+
await handleMigrate(args);
|
|
313
|
+
},
|
|
314
|
+
},
|
|
229
315
|
{
|
|
230
316
|
name: 'suppress-rule',
|
|
231
317
|
description: '[alias: rule suppress] Disable a rule by id/prefix. Hard rules refused.',
|
|
@@ -391,7 +477,8 @@ async function main() {
|
|
|
391
477
|
${dim}Code, forged for you.${reset}
|
|
392
478
|
${dim}Scope: v1(${context.v1.session?.quality_pack ?? 'onboarding needed'})${reset}
|
|
393
479
|
`);
|
|
394
|
-
const
|
|
480
|
+
const { getHostRuntime } = await import('./host/host-runtime.js');
|
|
481
|
+
const runtimeLabel = getHostRuntime(runtime).displayName;
|
|
395
482
|
console.log(`[forgen] Starting ${runtimeLabel}...\n`);
|
|
396
483
|
await spawnClaudeWithResume(args, context, () => prepareHarness(process.cwd(), { runtime }), runtime);
|
|
397
484
|
}
|
|
@@ -425,17 +512,22 @@ function printHelp() {
|
|
|
425
512
|
|
|
426
513
|
Commands:
|
|
427
514
|
forgen forge Personalize your coding profile
|
|
428
|
-
forgen onboarding Run
|
|
515
|
+
forgen onboarding Run 4-question onboarding
|
|
429
516
|
forgen inspect [profile|rules|corrections|session]
|
|
430
517
|
Inspect v1 state (alias: evidence → corrections)
|
|
431
518
|
forgen rule <list|suppress|activate|scan|health-scan|classify>
|
|
432
519
|
Rule management (see: forgen rule help)
|
|
433
|
-
forgen stats One-screen trust-layer dashboard
|
|
520
|
+
forgen stats One-screen trust-layer dashboard (+ philosophy)
|
|
434
521
|
forgen last-block Show the most recent block event
|
|
522
|
+
forgen recall [--limit N] [--show]
|
|
523
|
+
최근 compound 주입 이력 (solution body preview)
|
|
524
|
+
forgen migrate [implicit-feedback|evidence-host|all]
|
|
525
|
+
One-shot schema migration (category backfill / host backfill)
|
|
526
|
+
forgen parity codex [--dry-run] Run codex parity checks (source checkout only)
|
|
435
527
|
forgen compound Manage accumulated knowledge
|
|
436
528
|
forgen dashboard Compound system dashboard
|
|
437
529
|
forgen me Personal dashboard
|
|
438
|
-
forgen init Initialize project
|
|
530
|
+
forgen init Initialize project (+ starter-pack solutions)
|
|
439
531
|
forgen config hooks Hook management
|
|
440
532
|
forgen mcp MCP server management
|
|
441
533
|
forgen skill promote|list Skill management
|
|
@@ -11,47 +11,74 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
|
-
import * as os from 'node:os';
|
|
15
14
|
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
16
|
import { containsPromptInjection, filterSolutionContent } from '../hooks/prompt-injection-filter.js';
|
|
17
17
|
import { redactSecrets } from '../hooks/secret-filter.js';
|
|
18
18
|
import { createEvidence, saveEvidence, promoteSessionCandidates } from '../store/evidence-store.js';
|
|
19
19
|
import { loadProfile } from '../store/profile-store.js';
|
|
20
|
+
import { FORGEN_HOME, ME_DIR } from './paths.js';
|
|
21
|
+
import { classifyBehaviorKind, mapKindToAxisRefs } from './behavior-classifier.js';
|
|
20
22
|
/** Auto-compound에 사용할 모델 — background 추출이므로 haiku로 충분 */
|
|
21
23
|
const COMPOUND_MODEL = 'haiku';
|
|
22
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Host-aware exec retry — feat/codex-support P2-3 (Phase 2 critic fix).
|
|
26
|
+
*
|
|
27
|
+
* 보안 회귀 방지: Claude 분기는 *args 그대로* execFileSync 호출 → P1-S1 의
|
|
28
|
+
* `--allowedTools Bash(forgen compound:*)` sandbox hardening 보존.
|
|
29
|
+
* Codex 분기에서만 -p prompt 추출 → execHost (codex 는 --allowedTools 모름).
|
|
30
|
+
*
|
|
31
|
+
* Codex retry 정책 fix: ETIMEDOUT 시 sleep 후 retry 는 *Claude only*. Codex 는
|
|
32
|
+
* 60-90s response 가 정상이라 timeout 누적 retry 가 무의미 (즉시 fail).
|
|
33
|
+
*/
|
|
23
34
|
function execClaudeRetry(args, opts) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
const mod = createRequire(import.meta.url)('../host/exec-host.js');
|
|
36
|
+
// profile.default_host 로 host 결정 (lazy load)
|
|
37
|
+
const profileMod = createRequire(import.meta.url)('../store/profile-store.js');
|
|
38
|
+
const resolved = profileMod.resolveDefaultHost();
|
|
39
|
+
const host = resolved === 'codex' ? 'codex' : 'claude';
|
|
40
|
+
if (host === 'claude') {
|
|
41
|
+
// Claude 측은 기존 보안 hardening 보존: --allowedTools 등 args 그대로 전달.
|
|
42
|
+
const TRANSIENT = /ETIMEDOUT|ECONNRESET|ECONNREFUSED|EPIPE/;
|
|
43
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
44
|
+
try {
|
|
45
|
+
return execFileSync('claude', args, opts);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
49
|
+
if (attempt === 0 && TRANSIENT.test(msg)) {
|
|
50
|
+
process.stderr.write(`[forgen-auto-compound] transient error, retrying in 3s...\n`);
|
|
51
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 3000);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
throw e;
|
|
42
55
|
}
|
|
43
|
-
throw e;
|
|
44
56
|
}
|
|
57
|
+
throw new Error('unreachable');
|
|
45
58
|
}
|
|
46
|
-
|
|
59
|
+
// host === 'codex' — prompt 만 추출 (codex 는 --allowedTools 등 미인식).
|
|
60
|
+
const pIdx = args.indexOf('-p');
|
|
61
|
+
if (pIdx === -1 || !args[pIdx + 1]) {
|
|
62
|
+
throw new Error('execClaudeRetry: codex host requires -p prompt argument');
|
|
63
|
+
}
|
|
64
|
+
const prompt = args[pIdx + 1];
|
|
65
|
+
const modelIdx = args.indexOf('--model');
|
|
66
|
+
const model = modelIdx !== -1 ? args[modelIdx + 1] : undefined;
|
|
67
|
+
const r = mod.execHost({
|
|
68
|
+
prompt,
|
|
69
|
+
model,
|
|
70
|
+
host: 'codex',
|
|
71
|
+
timeout: typeof opts.timeout === 'number' ? opts.timeout : 60000,
|
|
72
|
+
cwd: typeof opts.cwd === 'string' ? opts.cwd : undefined,
|
|
73
|
+
});
|
|
74
|
+
return r.message;
|
|
47
75
|
}
|
|
48
76
|
const [, , cwd, transcriptPath, sessionId] = process.argv;
|
|
49
77
|
if (!cwd || !transcriptPath || !sessionId) {
|
|
50
78
|
process.exit(1);
|
|
51
79
|
}
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const BEHAVIOR_DIR = path.join(FORGEN_HOME, 'me', 'behavior');
|
|
80
|
+
const SOLUTIONS_DIR = path.join(ME_DIR, 'solutions');
|
|
81
|
+
const BEHAVIOR_DIR = path.join(ME_DIR, 'behavior');
|
|
55
82
|
/** Lightweight quality gate for auto-extracted solution files */
|
|
56
83
|
/** Toxicity patterns — code-context only to avoid false positives on prose */
|
|
57
84
|
const SOLUTION_TOXICITY_PATTERNS = [/@ts-ignore/i, /:\s*any\b/, /\/\/\s*TODO\b/];
|
|
@@ -206,9 +233,7 @@ function mergeOrCreateBehavior(dir, newContent, kind, today) {
|
|
|
206
233
|
fs.writeFileSync(filePath, updated);
|
|
207
234
|
return true;
|
|
208
235
|
}
|
|
209
|
-
catch {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
236
|
+
catch { }
|
|
212
237
|
}
|
|
213
238
|
return false;
|
|
214
239
|
}
|
|
@@ -309,14 +334,15 @@ ${sanitizedSummary.slice(0, 6000)}
|
|
|
309
334
|
관찰된 패턴을 다음 형식으로 1~3개만 출력해주세요 (없으면 "관찰된 패턴 없음"):
|
|
310
335
|
- [카테고리] 패턴 설명 (관찰 근거)
|
|
311
336
|
|
|
312
|
-
카테고리:
|
|
337
|
+
카테고리: 커뮤니케이션/작업습관/기술선호/의사결정/워크플로우/품질안전/자율성
|
|
313
338
|
|
|
314
|
-
|
|
315
|
-
-
|
|
316
|
-
-
|
|
317
|
-
-
|
|
339
|
+
각 카테고리 가이드:
|
|
340
|
+
- "워크플로우": 반복하는 작업 순서, 판단 규칙, 조건부 접근법 (예: "테스트 먼저 → 구현 → 리팩토링 순서")
|
|
341
|
+
- "품질안전": 검증/테스트/안전성 관련 강한 선호 (예: "프로덕션 배포 전 Docker e2e 의무", "mock-only 검증 거부")
|
|
342
|
+
- "자율성": 확인/독립 결정 관련 선호 (예: "사소한 변경은 묻지 않고 진행", "큰 결정은 반드시 확인")
|
|
318
343
|
|
|
319
344
|
워크플로우 패턴이 감지되면 반드시 구체적인 순서를 포함하세요.
|
|
345
|
+
품질안전/자율성 패턴은 4축 개인화의 입력이므로 quality/autonomy 신호가 명확하면 반드시 해당 라벨을 사용하세요 (커뮤니케이션/작업습관 으로 흡수 금지).
|
|
320
346
|
|
|
321
347
|
기존 패턴과 중복이면 건너뛰세요.${existingBehaviorPatterns}
|
|
322
348
|
|
|
@@ -347,11 +373,11 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
347
373
|
fs.mkdirSync(BEHAVIOR_DIR, { recursive: true });
|
|
348
374
|
const today = new Date().toISOString().split('T')[0];
|
|
349
375
|
const trimmed = userResult.trim();
|
|
350
|
-
// 카테고리에 따라 kind 분류
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
376
|
+
// 카테고리에 따라 kind 분류 — D1'' (2026-04-27): quality/autonomy 라벨 추가.
|
|
377
|
+
// 이전 3분기(workflow/thinking/preference)는 quality_safety/autonomy 축으로
|
|
378
|
+
// 가는 자동 신호를 communication_style 로 흡수해 626건 중 자동 추출 0건이
|
|
379
|
+
// 이 두 축에 닿지 못했음. 5분기로 확장. (분류 로직은 behavior-classifier.ts)
|
|
380
|
+
const kind = classifyBehaviorKind(trimmed);
|
|
355
381
|
// 기존 유사 패턴이 있으면 observedCount 누적
|
|
356
382
|
const merged = mergeOrCreateBehavior(BEHAVIOR_DIR, trimmed, kind, today);
|
|
357
383
|
if (!merged) {
|
|
@@ -368,10 +394,7 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
368
394
|
session_id: sessionId,
|
|
369
395
|
source_component: 'auto-compound-runner',
|
|
370
396
|
summary: trimmed.slice(0, 200),
|
|
371
|
-
axis_refs: kind
|
|
372
|
-
: kind === 'preference' ? ['communication_style']
|
|
373
|
-
: kind === 'thinking' ? ['judgment_philosophy']
|
|
374
|
-
: [],
|
|
397
|
+
axis_refs: mapKindToAxisRefs(kind),
|
|
375
398
|
confidence: 0.6,
|
|
376
399
|
raw_payload: { kind, observedCount: 1 },
|
|
377
400
|
});
|
|
@@ -383,10 +406,8 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
383
406
|
}
|
|
384
407
|
// 3단계: 세션 학습 요약 (SessionLearningSummary) 생성
|
|
385
408
|
try {
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
const V1_PROFILE = path.join(V1_ME_DIR, 'forge-profile.json');
|
|
389
|
-
const V1_EVIDENCE_DIR = path.join(V1_ME_DIR, 'behavior');
|
|
409
|
+
const V1_PROFILE = path.join(ME_DIR, 'forge-profile.json');
|
|
410
|
+
const V1_EVIDENCE_DIR = path.join(ME_DIR, 'behavior');
|
|
390
411
|
if (fs.existsSync(V1_PROFILE)) {
|
|
391
412
|
const currentProfile = loadProfile();
|
|
392
413
|
let profileContext = '';
|
|
@@ -485,8 +506,9 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
485
506
|
process.stderr.write(`[forgen-auto-compound] session learning: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
486
507
|
}
|
|
487
508
|
// Step 4: prefer-from-now / avoid-this 교정 → scope:'me' 영구 규칙 승격
|
|
509
|
+
let promotedCount = 0;
|
|
488
510
|
try {
|
|
489
|
-
|
|
511
|
+
promotedCount = promoteSessionCandidates(sessionId);
|
|
490
512
|
if (promotedCount > 0) {
|
|
491
513
|
process.stderr.write(`[forgen-auto-compound] promoted ${promotedCount} correction(s) to permanent rules\n`);
|
|
492
514
|
}
|
|
@@ -494,6 +516,21 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
494
516
|
catch (e) {
|
|
495
517
|
process.stderr.write(`[forgen-auto-compound] rule promotion: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
496
518
|
}
|
|
519
|
+
// H2: count newly extracted solutions (post-quality-gate) for Stop hook 알림.
|
|
520
|
+
// solutionsBefore 스냅샷 vs 현재 디스크 상태 차분 → "N개 패턴 학습됨" 1줄.
|
|
521
|
+
let extractedSolutionsCount = 0;
|
|
522
|
+
try {
|
|
523
|
+
if (fs.existsSync(SOLUTIONS_DIR)) {
|
|
524
|
+
const current = fs.readdirSync(SOLUTIONS_DIR).filter((f) => f.endsWith('.md'));
|
|
525
|
+
for (const f of current) {
|
|
526
|
+
if (!solutionsBefore.has(f))
|
|
527
|
+
extractedSolutionsCount++;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
catch (e) {
|
|
532
|
+
process.stderr.write(`[forgen-auto-compound] solution count failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
533
|
+
}
|
|
497
534
|
// Step 5: meta-learning (HyperAgents-inspired self-tuning)
|
|
498
535
|
try {
|
|
499
536
|
const { runMetaLearning } = await import('../engine/meta-learning/runner.js');
|
|
@@ -508,10 +545,61 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
508
545
|
catch (e) {
|
|
509
546
|
process.stderr.write(`[forgen-meta] ${e instanceof Error ? e.message : String(e)}\n`);
|
|
510
547
|
}
|
|
511
|
-
//
|
|
548
|
+
// Step 5.5 (v0.4.1): state hygiene — 세션 스코프 ephemeral 파일 7일 retention
|
|
549
|
+
// 자동 정리. 이전에는 `forgen doctor --prune-state` 수동만 있어서 injection-cache
|
|
550
|
+
// 2343 / modified-files 431 처럼 수천 파일 누적. 몇 달 사용하면 10만+ 파일 → stat
|
|
551
|
+
// 호출 느려지고 디스크 낭비. auto-compound 마다 호출되면 자연스레 정돈.
|
|
552
|
+
try {
|
|
553
|
+
const { pruneState } = await import('./state-gc.js');
|
|
554
|
+
const report = pruneState({ dryRun: false });
|
|
555
|
+
if (report.pruned > 0) {
|
|
556
|
+
const mb = (report.bytesFreed / 1024 / 1024).toFixed(2);
|
|
557
|
+
process.stderr.write(`[forgen-gc] pruned ${report.pruned} stale state files (${mb} MB freed)\n`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
catch (e) {
|
|
561
|
+
process.stderr.write(`[forgen-gc] state prune failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
562
|
+
}
|
|
563
|
+
// Step 6 (v0.4.1): rule lifecycle 자동 실행 — rule 의 violations/bypass/drift
|
|
564
|
+
// 신호에 따른 자동 강등/승격. 이전에는 CLI (`forgen rule scan --apply`) 수동
|
|
565
|
+
// 호출만 있어서 구매자가 몇 주 써도 rule 정비 안 됨 → 쓸모없는 rule 이 계속
|
|
566
|
+
// active. 판매 관점 심각한 "자동 학습 단절". auto-compound-runner 끝에 자동
|
|
567
|
+
// 실행해 세션마다 rule 품질 유지.
|
|
568
|
+
try {
|
|
569
|
+
const { handleLifecycleScan } = await import('../engine/lifecycle/lifecycle-cli.js');
|
|
570
|
+
// silent mode 로 돌리기 위해 stdout 을 임시 리다이렉트 (내부가 console.log 씀)
|
|
571
|
+
const origLog = console.log;
|
|
572
|
+
let applied = 0;
|
|
573
|
+
console.log = (...args) => {
|
|
574
|
+
const msg = args.join(' ');
|
|
575
|
+
const match = msg.match(/apply(?:ied)?\s+(\d+)/i);
|
|
576
|
+
if (match)
|
|
577
|
+
applied = Number(match[1]);
|
|
578
|
+
};
|
|
579
|
+
try {
|
|
580
|
+
await handleLifecycleScan(['--apply']);
|
|
581
|
+
}
|
|
582
|
+
finally {
|
|
583
|
+
console.log = origLog;
|
|
584
|
+
}
|
|
585
|
+
if (applied > 0) {
|
|
586
|
+
process.stderr.write(`[forgen-meta] rule lifecycle: ${applied} event(s) applied\n`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
catch (e) {
|
|
590
|
+
process.stderr.write(`[forgen-meta] lifecycle scan failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
591
|
+
}
|
|
592
|
+
// 완료 기록 — H2: Stop hook 알림용으로 extractedSolutions / promotedRules 포함.
|
|
593
|
+
// noticeShown=false 로 시작해서 Stop hook 가 최초 1회만 surface.
|
|
512
594
|
const statePath = path.join(FORGEN_HOME, 'state', 'last-auto-compound.json');
|
|
513
595
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
514
|
-
fs.writeFileSync(statePath, JSON.stringify({
|
|
596
|
+
fs.writeFileSync(statePath, JSON.stringify({
|
|
597
|
+
sessionId,
|
|
598
|
+
completedAt: new Date().toISOString(),
|
|
599
|
+
extractedSolutions: extractedSolutionsCount,
|
|
600
|
+
promotedRules: promotedCount,
|
|
601
|
+
noticeShown: false,
|
|
602
|
+
}));
|
|
515
603
|
}
|
|
516
604
|
catch (e) {
|
|
517
605
|
process.stderr.write(`[forgen-auto-compound] ${e instanceof Error ? e.message : String(e)}\n`);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior Classifier — D1'' (2026-04-27)
|
|
3
|
+
*
|
|
4
|
+
* LLM 이 추출한 사용자 패턴을 5개 kind 로 분류하고 4축 axis_refs 로 매핑한다.
|
|
5
|
+
*
|
|
6
|
+
* 결함 history:
|
|
7
|
+
* v0.4.1 까지: kind 3분기(workflow/thinking/preference) → axis 2축
|
|
8
|
+
* (judgment_philosophy / communication_style) 만 자동 추출 가능.
|
|
9
|
+
* quality_safety / autonomy 축은 explicit_correction 16건 (Hooks 경로) 으로만
|
|
10
|
+
* 자라고, 자동 학습 600+ 건은 이 두 축에 0% 기여 — 측정 자기증거.
|
|
11
|
+
*
|
|
12
|
+
* v0.4.2: 5분기 [품질안전] / [자율성] 추가 → 4축 모두 cover.
|
|
13
|
+
* LLM prompt (auto-compound-runner) 에도 같은 라벨 가이드를 명시하여
|
|
14
|
+
* 형식 강제. 새 라벨이 안 나오면 기존 5분기로 fallback (호환).
|
|
15
|
+
*/
|
|
16
|
+
export type BehaviorKind = 'safety' | 'autonomy' | 'workflow' | 'thinking' | 'preference';
|
|
17
|
+
/**
|
|
18
|
+
* LLM 출력 텍스트(`[카테고리] 설명` 형식)를 5개 kind 로 분류.
|
|
19
|
+
*
|
|
20
|
+
* 라벨 우선순위 (위에서 아래):
|
|
21
|
+
* 1. [품질안전] → safety
|
|
22
|
+
* 2. [자율성] → autonomy
|
|
23
|
+
* 3. [워크플로우] OR "순서"/"→" 토큰 → workflow
|
|
24
|
+
* 4. [의사결정] → thinking
|
|
25
|
+
* 5. 그 외 → preference (default)
|
|
26
|
+
*/
|
|
27
|
+
export declare function classifyBehaviorKind(text: string): BehaviorKind;
|
|
28
|
+
export declare function mapKindToAxisRefs(kind: BehaviorKind): string[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior Classifier — D1'' (2026-04-27)
|
|
3
|
+
*
|
|
4
|
+
* LLM 이 추출한 사용자 패턴을 5개 kind 로 분류하고 4축 axis_refs 로 매핑한다.
|
|
5
|
+
*
|
|
6
|
+
* 결함 history:
|
|
7
|
+
* v0.4.1 까지: kind 3분기(workflow/thinking/preference) → axis 2축
|
|
8
|
+
* (judgment_philosophy / communication_style) 만 자동 추출 가능.
|
|
9
|
+
* quality_safety / autonomy 축은 explicit_correction 16건 (Hooks 경로) 으로만
|
|
10
|
+
* 자라고, 자동 학습 600+ 건은 이 두 축에 0% 기여 — 측정 자기증거.
|
|
11
|
+
*
|
|
12
|
+
* v0.4.2: 5분기 [품질안전] / [자율성] 추가 → 4축 모두 cover.
|
|
13
|
+
* LLM prompt (auto-compound-runner) 에도 같은 라벨 가이드를 명시하여
|
|
14
|
+
* 형식 강제. 새 라벨이 안 나오면 기존 5분기로 fallback (호환).
|
|
15
|
+
*/
|
|
16
|
+
const AXIS_REFS_BY_KIND = {
|
|
17
|
+
safety: ['quality_safety'],
|
|
18
|
+
autonomy: ['autonomy'],
|
|
19
|
+
workflow: ['judgment_philosophy'],
|
|
20
|
+
thinking: ['judgment_philosophy'],
|
|
21
|
+
preference: ['communication_style'],
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* LLM 출력 텍스트(`[카테고리] 설명` 형식)를 5개 kind 로 분류.
|
|
25
|
+
*
|
|
26
|
+
* 라벨 우선순위 (위에서 아래):
|
|
27
|
+
* 1. [품질안전] → safety
|
|
28
|
+
* 2. [자율성] → autonomy
|
|
29
|
+
* 3. [워크플로우] OR "순서"/"→" 토큰 → workflow
|
|
30
|
+
* 4. [의사결정] → thinking
|
|
31
|
+
* 5. 그 외 → preference (default)
|
|
32
|
+
*/
|
|
33
|
+
export function classifyBehaviorKind(text) {
|
|
34
|
+
if (text.includes('[품질안전]'))
|
|
35
|
+
return 'safety';
|
|
36
|
+
if (text.includes('[자율성]'))
|
|
37
|
+
return 'autonomy';
|
|
38
|
+
if (text.includes('[워크플로우]') || text.includes('순서') || text.includes('→'))
|
|
39
|
+
return 'workflow';
|
|
40
|
+
if (text.includes('[의사결정]'))
|
|
41
|
+
return 'thinking';
|
|
42
|
+
return 'preference';
|
|
43
|
+
}
|
|
44
|
+
export function mapKindToAxisRefs(kind) {
|
|
45
|
+
return [...AXIS_REFS_BY_KIND[kind]];
|
|
46
|
+
}
|
package/dist/core/dashboard.d.ts
CHANGED
|
@@ -86,6 +86,13 @@ export declare function collectLifecycleActivity(): LifecycleActivity;
|
|
|
86
86
|
export declare function collectSessionHistory(): SessionHistory;
|
|
87
87
|
/** Collect hook error data. */
|
|
88
88
|
export declare function collectHookHealth(): HookHealth;
|
|
89
|
+
export interface MultiHostData {
|
|
90
|
+
claude: number;
|
|
91
|
+
codex: number;
|
|
92
|
+
total: number;
|
|
93
|
+
}
|
|
94
|
+
/** Collect multi-host evidence distribution from host-mismatch store. */
|
|
95
|
+
export declare function collectMultiHostData(): MultiHostData;
|
|
89
96
|
export interface LearningCurve {
|
|
90
97
|
correctionsLast7d: number;
|
|
91
98
|
correctionsPrev7d: number;
|