@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
|
@@ -12,38 +12,71 @@
|
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
15
16
|
import { containsPromptInjection, filterSolutionContent } from '../hooks/prompt-injection-filter.js';
|
|
16
17
|
import { redactSecrets } from '../hooks/secret-filter.js';
|
|
17
18
|
import { createEvidence, saveEvidence, promoteSessionCandidates } from '../store/evidence-store.js';
|
|
18
19
|
import { loadProfile } from '../store/profile-store.js';
|
|
19
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
|
+
const MAX_ATTEMPTS = 2;
|
|
44
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
return execFileSync('claude', args, opts);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
50
|
+
const match = msg.match(TRANSIENT);
|
|
51
|
+
if (attempt < MAX_ATTEMPTS && match) {
|
|
52
|
+
process.stderr.write(`[forgen-auto-compound] ${match[0]} on attempt ${attempt}/${MAX_ATTEMPTS}, retrying in 3s (auto-recovery)...\n`);
|
|
53
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 3000);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (match) {
|
|
57
|
+
process.stderr.write(`[forgen-auto-compound] ${match[0]} after ${attempt}/${MAX_ATTEMPTS} attempts — giving up (fail-open)\n`);
|
|
58
|
+
}
|
|
59
|
+
throw e;
|
|
42
60
|
}
|
|
43
|
-
throw e;
|
|
44
61
|
}
|
|
62
|
+
throw new Error('unreachable');
|
|
63
|
+
}
|
|
64
|
+
// host === 'codex' — prompt 만 추출 (codex 는 --allowedTools 등 미인식).
|
|
65
|
+
const pIdx = args.indexOf('-p');
|
|
66
|
+
if (pIdx === -1 || !args[pIdx + 1]) {
|
|
67
|
+
throw new Error('execClaudeRetry: codex host requires -p prompt argument');
|
|
45
68
|
}
|
|
46
|
-
|
|
69
|
+
const prompt = args[pIdx + 1];
|
|
70
|
+
const modelIdx = args.indexOf('--model');
|
|
71
|
+
const model = modelIdx !== -1 ? args[modelIdx + 1] : undefined;
|
|
72
|
+
const r = mod.execHost({
|
|
73
|
+
prompt,
|
|
74
|
+
model,
|
|
75
|
+
host: 'codex',
|
|
76
|
+
timeout: typeof opts.timeout === 'number' ? opts.timeout : 60000,
|
|
77
|
+
cwd: typeof opts.cwd === 'string' ? opts.cwd : undefined,
|
|
78
|
+
});
|
|
79
|
+
return r.message;
|
|
47
80
|
}
|
|
48
81
|
const [, , cwd, transcriptPath, sessionId] = process.argv;
|
|
49
82
|
if (!cwd || !transcriptPath || !sessionId) {
|
|
@@ -205,9 +238,7 @@ function mergeOrCreateBehavior(dir, newContent, kind, today) {
|
|
|
205
238
|
fs.writeFileSync(filePath, updated);
|
|
206
239
|
return true;
|
|
207
240
|
}
|
|
208
|
-
catch {
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
241
|
+
catch { }
|
|
211
242
|
}
|
|
212
243
|
return false;
|
|
213
244
|
}
|
|
@@ -308,14 +339,15 @@ ${sanitizedSummary.slice(0, 6000)}
|
|
|
308
339
|
관찰된 패턴을 다음 형식으로 1~3개만 출력해주세요 (없으면 "관찰된 패턴 없음"):
|
|
309
340
|
- [카테고리] 패턴 설명 (관찰 근거)
|
|
310
341
|
|
|
311
|
-
카테고리:
|
|
342
|
+
카테고리: 커뮤니케이션/작업습관/기술선호/의사결정/워크플로우/품질안전/자율성
|
|
312
343
|
|
|
313
|
-
|
|
314
|
-
-
|
|
315
|
-
-
|
|
316
|
-
-
|
|
344
|
+
각 카테고리 가이드:
|
|
345
|
+
- "워크플로우": 반복하는 작업 순서, 판단 규칙, 조건부 접근법 (예: "테스트 먼저 → 구현 → 리팩토링 순서")
|
|
346
|
+
- "품질안전": 검증/테스트/안전성 관련 강한 선호 (예: "프로덕션 배포 전 Docker e2e 의무", "mock-only 검증 거부")
|
|
347
|
+
- "자율성": 확인/독립 결정 관련 선호 (예: "사소한 변경은 묻지 않고 진행", "큰 결정은 반드시 확인")
|
|
317
348
|
|
|
318
349
|
워크플로우 패턴이 감지되면 반드시 구체적인 순서를 포함하세요.
|
|
350
|
+
품질안전/자율성 패턴은 4축 개인화의 입력이므로 quality/autonomy 신호가 명확하면 반드시 해당 라벨을 사용하세요 (커뮤니케이션/작업습관 으로 흡수 금지).
|
|
319
351
|
|
|
320
352
|
기존 패턴과 중복이면 건너뛰세요.${existingBehaviorPatterns}
|
|
321
353
|
|
|
@@ -346,11 +378,11 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
346
378
|
fs.mkdirSync(BEHAVIOR_DIR, { recursive: true });
|
|
347
379
|
const today = new Date().toISOString().split('T')[0];
|
|
348
380
|
const trimmed = userResult.trim();
|
|
349
|
-
// 카테고리에 따라 kind 분류
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
381
|
+
// 카테고리에 따라 kind 분류 — D1'' (2026-04-27): quality/autonomy 라벨 추가.
|
|
382
|
+
// 이전 3분기(workflow/thinking/preference)는 quality_safety/autonomy 축으로
|
|
383
|
+
// 가는 자동 신호를 communication_style 로 흡수해 626건 중 자동 추출 0건이
|
|
384
|
+
// 이 두 축에 닿지 못했음. 5분기로 확장. (분류 로직은 behavior-classifier.ts)
|
|
385
|
+
const kind = classifyBehaviorKind(trimmed);
|
|
354
386
|
// 기존 유사 패턴이 있으면 observedCount 누적
|
|
355
387
|
const merged = mergeOrCreateBehavior(BEHAVIOR_DIR, trimmed, kind, today);
|
|
356
388
|
if (!merged) {
|
|
@@ -367,10 +399,7 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
367
399
|
session_id: sessionId,
|
|
368
400
|
source_component: 'auto-compound-runner',
|
|
369
401
|
summary: trimmed.slice(0, 200),
|
|
370
|
-
axis_refs: kind
|
|
371
|
-
: kind === 'preference' ? ['communication_style']
|
|
372
|
-
: kind === 'thinking' ? ['judgment_philosophy']
|
|
373
|
-
: [],
|
|
402
|
+
axis_refs: mapKindToAxisRefs(kind),
|
|
374
403
|
confidence: 0.6,
|
|
375
404
|
raw_payload: { kind, observedCount: 1 },
|
|
376
405
|
});
|
|
@@ -401,7 +430,9 @@ ${profileContext}
|
|
|
401
430
|
"pack_direction": null 또는 "opposite_quality" 또는 "opposite_autonomy",
|
|
402
431
|
"profile_delta": {
|
|
403
432
|
"quality_safety": { "verification_depth": 0.0, "stop_threshold": 0.0, "change_conservatism": 0.0 },
|
|
404
|
-
"autonomy": { "confirmation_independence": 0.0, "assumption_tolerance": 0.0, "scope_expansion_tolerance": 0.0, "approval_threshold": 0.0 }
|
|
433
|
+
"autonomy": { "confirmation_independence": 0.0, "assumption_tolerance": 0.0, "scope_expansion_tolerance": 0.0, "approval_threshold": 0.0 },
|
|
434
|
+
"judgment_philosophy": { "minimal_change_bias": 0.0, "abstraction_bias": 0.0, "evidence_first_bias": 0.0 },
|
|
435
|
+
"communication_style": { "verbosity": 0.0, "structure": 0.0, "teaching_bias": 0.0 }
|
|
405
436
|
}
|
|
406
437
|
}
|
|
407
438
|
|
|
@@ -469,6 +500,26 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
469
500
|
}
|
|
470
501
|
}
|
|
471
502
|
}
|
|
503
|
+
if (parsed.profile_delta.judgment_philosophy) {
|
|
504
|
+
const d = parsed.profile_delta.judgment_philosophy;
|
|
505
|
+
const f = profile.axes.judgment_philosophy.facets;
|
|
506
|
+
for (const [k, v] of Object.entries(d)) {
|
|
507
|
+
if (typeof v === 'number' && Math.abs(v) > 0.001 && k in f) {
|
|
508
|
+
f[k] = clamp(f[k] + v);
|
|
509
|
+
changed = true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (parsed.profile_delta.communication_style) {
|
|
514
|
+
const d = parsed.profile_delta.communication_style;
|
|
515
|
+
const f = profile.axes.communication_style.facets;
|
|
516
|
+
for (const [k, v] of Object.entries(d)) {
|
|
517
|
+
if (typeof v === 'number' && Math.abs(v) > 0.001 && k in f) {
|
|
518
|
+
f[k] = clamp(f[k] + v);
|
|
519
|
+
changed = true;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
472
523
|
if (changed) {
|
|
473
524
|
profile.metadata.updated_at = new Date().toISOString();
|
|
474
525
|
fs.writeFileSync(V1_PROFILE, JSON.stringify(profile, null, 2));
|
|
@@ -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;
|
package/dist/core/dashboard.js
CHANGED
|
@@ -23,6 +23,7 @@ const require = createRequire(import.meta.url);
|
|
|
23
23
|
import { ME_SOLUTIONS, ME_RULES, ME_BEHAVIOR, STATE_DIR, } from './paths.js';
|
|
24
24
|
import { parseFrontmatterOnly } from '../engine/solution-format.js';
|
|
25
25
|
import { readMatchEvalLog } from '../engine/match-eval-log.js';
|
|
26
|
+
import { summarizeAllByHost } from '../store/host-mismatch.js';
|
|
26
27
|
// ── ANSI color helpers ──
|
|
27
28
|
const BOLD = '\x1b[1m';
|
|
28
29
|
const DIM = '\x1b[2m';
|
|
@@ -356,6 +357,34 @@ function renderHookHealth(data) {
|
|
|
356
357
|
lines.push(tableSep(widths, false, true));
|
|
357
358
|
return lines.join('\n');
|
|
358
359
|
}
|
|
360
|
+
/** Collect multi-host evidence distribution from host-mismatch store. */
|
|
361
|
+
export function collectMultiHostData() {
|
|
362
|
+
try {
|
|
363
|
+
return summarizeAllByHost();
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return { claude: 0, codex: 0, total: 0 };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function renderMultiHost(data) {
|
|
370
|
+
const lines = [];
|
|
371
|
+
lines.push(` ${bold(cyan('Multi-Host Evidence'))}`);
|
|
372
|
+
lines.push('');
|
|
373
|
+
if (data.total === 0) {
|
|
374
|
+
lines.push(` ${dim('No evidence recorded yet.')}`);
|
|
375
|
+
return lines.join('\n');
|
|
376
|
+
}
|
|
377
|
+
const claudePct = Math.round((data.claude / data.total) * 100);
|
|
378
|
+
const codexPct = Math.round((data.codex / data.total) * 100);
|
|
379
|
+
lines.push(` Hosts claude:${data.claude} (${claudePct}%) codex:${data.codex} (${codexPct}%) total:${data.total}`);
|
|
380
|
+
// skew 경고: 80%+ 집중
|
|
381
|
+
const maxShare = Math.max(claudePct, codexPct);
|
|
382
|
+
if (data.total >= 5 && maxShare >= 80) {
|
|
383
|
+
const dominant = data.claude >= data.codex ? 'claude' : 'codex';
|
|
384
|
+
lines.push(` ${yellow(`⚠ ${dominant} 에 ${maxShare}% 집중 — 다른 host 데이터 부족`)}`);
|
|
385
|
+
}
|
|
386
|
+
return lines.join('\n');
|
|
387
|
+
}
|
|
359
388
|
/**
|
|
360
389
|
* Learning Curve 수집.
|
|
361
390
|
* evidence 파일(교정 기록)과 compound 활용률을 교차 분석하여 "쓸수록 나아진다"를 정량화.
|
|
@@ -523,6 +552,7 @@ export function renderDashboard() {
|
|
|
523
552
|
const session = collectSessionHistory();
|
|
524
553
|
const hookHealth = collectHookHealth();
|
|
525
554
|
const learning = collectLearningCurve();
|
|
555
|
+
const multiHost = collectMultiHostData();
|
|
526
556
|
const divider = ` ${dim('─'.repeat(50))}`;
|
|
527
557
|
const sections = [
|
|
528
558
|
'',
|
|
@@ -538,6 +568,8 @@ export function renderDashboard() {
|
|
|
538
568
|
divider,
|
|
539
569
|
renderInjectionActivity(injection),
|
|
540
570
|
divider,
|
|
571
|
+
renderMultiHost(multiHost),
|
|
572
|
+
divider,
|
|
541
573
|
renderReflectionData(reflection),
|
|
542
574
|
divider,
|
|
543
575
|
renderLifecycleActivity(lifecycle),
|
package/dist/core/doctor.js
CHANGED
|
@@ -5,6 +5,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
5
5
|
import { FORGEN_HOME, LAB_DIR, ME_BEHAVIOR, ME_DIR, ME_SOLUTIONS, ME_RULES, ME_SKILLS, PACKS_DIR, SESSIONS_DIR, STATE_DIR } from './paths.js';
|
|
6
6
|
import { getTimingStats } from '../hooks/shared/hook-timing.js';
|
|
7
7
|
import { countSessionScopedFiles, pruneState } from './state-gc.js';
|
|
8
|
+
import { summarizeAllByHost } from '../store/host-mismatch.js';
|
|
8
9
|
/** ~/.claude/projects/ — Claude Code 세션 저장 경로 */
|
|
9
10
|
const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
|
|
10
11
|
let currentSection = '';
|
|
@@ -34,6 +35,58 @@ function commandExists(cmd) {
|
|
|
34
35
|
return false;
|
|
35
36
|
}
|
|
36
37
|
}
|
|
38
|
+
/** parity-result.json 내용에서 경과 시간을 사람이 읽기 좋은 문자열로 변환 */
|
|
39
|
+
function relativeTime(isoString) {
|
|
40
|
+
const diffMs = Date.now() - new Date(isoString).getTime();
|
|
41
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
42
|
+
if (diffDays === 0) {
|
|
43
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
44
|
+
if (diffHours === 0) {
|
|
45
|
+
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
46
|
+
return `${diffMins}m ago`;
|
|
47
|
+
}
|
|
48
|
+
return `${diffHours}h ago`;
|
|
49
|
+
}
|
|
50
|
+
return `${diffDays}d ago`;
|
|
51
|
+
}
|
|
52
|
+
/** [Codex Parity] 섹션 렌더링 — ~/.forgen/state/parity-result.json 신선도 검사 */
|
|
53
|
+
function renderCodexParity() {
|
|
54
|
+
console.log(' [Codex Parity]');
|
|
55
|
+
const parityPath = path.join(STATE_DIR, 'parity-result.json');
|
|
56
|
+
if (!fs.existsSync(parityPath)) {
|
|
57
|
+
console.log(' △ Codex parity 미실행 — tests/e2e/codex/run-parity.sh 또는 forgen parity codex');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let data;
|
|
61
|
+
try {
|
|
62
|
+
data = JSON.parse(fs.readFileSync(parityPath, 'utf-8'));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
console.log(' ✗ Codex parity — parity-result.json 파싱 실패');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (data.passed === null || data.passed === undefined) {
|
|
69
|
+
console.log(' △ Codex parity dry-run only — 실 실행 필요');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!data.passed) {
|
|
73
|
+
const timeStr = data.at ? relativeTime(data.at) : 'unknown';
|
|
74
|
+
const detail = data.result ?? data.note ?? 'no detail';
|
|
75
|
+
console.log(` ✗ Codex parity FAILED (at: ${timeStr}, detail: ${detail})`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// passed === true
|
|
79
|
+
const timeStr = data.at ? relativeTime(data.at) : 'unknown';
|
|
80
|
+
const version = data.version ? ` version ${data.version}` : '';
|
|
81
|
+
const diffMs = data.at ? Date.now() - new Date(data.at).getTime() : Infinity;
|
|
82
|
+
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
|
|
83
|
+
if (diffMs > sevenDaysMs) {
|
|
84
|
+
console.log(` △ Codex parity green but stale (last run: ${timeStr}) — 재실행 권장`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(` ✓ Codex parity green (last run: ${timeStr},${version})`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
37
90
|
export async function runDoctor(opts = {}) {
|
|
38
91
|
failedChecks = [];
|
|
39
92
|
console.log('\n Forgen — Diagnostics\n');
|
|
@@ -386,6 +439,45 @@ export async function runDoctor(opts = {}) {
|
|
|
386
439
|
// git 저장소가 아니거나 origin이 없으면 표시하지 않음
|
|
387
440
|
console.log(' git remote: (none)');
|
|
388
441
|
}
|
|
442
|
+
// P4 셀프 가드: fix:feat 비율 30% 초과 시 회귀 패턴 의심 경고.
|
|
443
|
+
try {
|
|
444
|
+
const { computeFixFeatRatio, formatFixRatio } = await import('./git-stats.js');
|
|
445
|
+
const ratio = computeFixFeatRatio();
|
|
446
|
+
if (ratio.available) {
|
|
447
|
+
console.log(` ${formatFixRatio(ratio)}`);
|
|
448
|
+
if (ratio.exceedsThreshold) {
|
|
449
|
+
console.log(' ⚠ fix:feat 비율이 임계값을 초과했습니다. "이거 고치면 저거 버그난다" 패턴 의심 — 검증 레이어 invariant 점검 권장.');
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch { /* fail-open */ }
|
|
454
|
+
console.log();
|
|
455
|
+
// [Multi-Host] — host 별 evidence 분포
|
|
456
|
+
console.log(' [Multi-Host]');
|
|
457
|
+
try {
|
|
458
|
+
const hostStats = summarizeAllByHost();
|
|
459
|
+
if (hostStats.total === 0) {
|
|
460
|
+
console.log(' No evidence recorded yet.');
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
const claudePct = hostStats.total > 0 ? Math.round((hostStats.claude / hostStats.total) * 100) : 0;
|
|
464
|
+
const codexPct = hostStats.total > 0 ? Math.round((hostStats.codex / hostStats.total) * 100) : 0;
|
|
465
|
+
console.log(` Registered hosts: claude, codex`);
|
|
466
|
+
console.log(` Evidence by host: claude:${hostStats.claude} (${claudePct}%) codex:${hostStats.codex} (${codexPct}%) total:${hostStats.total}`);
|
|
467
|
+
// 한 host 가 80% 이상이면 skew 경고
|
|
468
|
+
const maxShare = Math.max(claudePct, codexPct);
|
|
469
|
+
if (hostStats.total >= 5 && maxShare >= 80) {
|
|
470
|
+
const dominant = claudePct >= codexPct ? 'claude' : 'codex';
|
|
471
|
+
console.log(` ⚠ evidence 가 ${dominant} 에 ${maxShare}% 집중됨 — 다른 host 에서 학습 데이터 부족 가능`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
console.log(' Unable to read host evidence data.');
|
|
477
|
+
}
|
|
478
|
+
console.log();
|
|
479
|
+
// [Codex Parity] — parity-result.json 신선도 검사 (v0.4.2 패턴 확장)
|
|
480
|
+
renderCodexParity();
|
|
389
481
|
console.log();
|
|
390
482
|
// [Summary] — 최종 상태 요약과 복구 액션을 한눈에 보이게
|
|
391
483
|
console.log(' [Summary]');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Stats — P4 셀프 가드 (2026-04-27)
|
|
3
|
+
*
|
|
4
|
+
* 최근 N커밋의 conventional commit 분포를 측정해 fix:feat 비율을 계산.
|
|
5
|
+
* 정상 OSS 권장은 fix < 20%. 36% 초과 시 회귀 패턴 의심 — forgen 의 자기 메타 가드.
|
|
6
|
+
*
|
|
7
|
+
* 이번 세션 측정값: v0.4.1 시점 fix 비율 36% (정상의 약 2배). 이 코드가 다음 릴리즈
|
|
8
|
+
* 시 같은 비율을 자동 노출하여 사용자가 회귀 패턴을 빠르게 인지하게 한다.
|
|
9
|
+
*/
|
|
10
|
+
export interface FixRatioStats {
|
|
11
|
+
windowSize: number;
|
|
12
|
+
fixCount: number;
|
|
13
|
+
featCount: number;
|
|
14
|
+
/** fix / (fix + feat), 0~1. fix+feat=0 이면 0. */
|
|
15
|
+
ratio: number;
|
|
16
|
+
threshold: number;
|
|
17
|
+
exceedsThreshold: boolean;
|
|
18
|
+
/** git 명령이 성공했는지 (저장소 외부 또는 git 미설치 시 false). */
|
|
19
|
+
available: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* git log --no-merges -N 결과에서 conventional commit 형식의 fix/feat 만 카운트.
|
|
23
|
+
*
|
|
24
|
+
* 분류:
|
|
25
|
+
* - `feat: ...` / `feat(scope): ...` → feat
|
|
26
|
+
* - `fix: ...` / `fix(scope): ...` → fix (단, scope ∈ {test, tests, docs, doc} 제외)
|
|
27
|
+
* - 그 외 (chore, refactor, docs, style, test, hash 없는 라인) → 무시
|
|
28
|
+
*
|
|
29
|
+
* fix(test):, fix(docs): 가 제외되는 이유: 사소한 노이즈 fix 가 회귀 신호를
|
|
30
|
+
* 흐리지 않도록. 진짜 위험은 fix(core), fix(hook), fix(api) 같은 logic fix.
|
|
31
|
+
*/
|
|
32
|
+
export declare function computeFixFeatRatio(cwd?: string, windowSize?: number, threshold?: number): FixRatioStats;
|
|
33
|
+
/** 테스트용 — git log 출력 텍스트를 직접 파싱. */
|
|
34
|
+
export declare function parseGitLog(rawLog: string, windowSize?: number, threshold?: number): FixRatioStats;
|
|
35
|
+
/** 사람용 한 줄 라벨. */
|
|
36
|
+
export declare function formatFixRatio(s: FixRatioStats): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Stats — P4 셀프 가드 (2026-04-27)
|
|
3
|
+
*
|
|
4
|
+
* 최근 N커밋의 conventional commit 분포를 측정해 fix:feat 비율을 계산.
|
|
5
|
+
* 정상 OSS 권장은 fix < 20%. 36% 초과 시 회귀 패턴 의심 — forgen 의 자기 메타 가드.
|
|
6
|
+
*
|
|
7
|
+
* 이번 세션 측정값: v0.4.1 시점 fix 비율 36% (정상의 약 2배). 이 코드가 다음 릴리즈
|
|
8
|
+
* 시 같은 비율을 자동 노출하여 사용자가 회귀 패턴을 빠르게 인지하게 한다.
|
|
9
|
+
*/
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
const DEFAULT_THRESHOLD = 0.30;
|
|
12
|
+
const DEFAULT_WINDOW = 30;
|
|
13
|
+
const SCOPE_EXCLUSIONS = new Set(['test', 'tests', 'docs', 'doc']);
|
|
14
|
+
/**
|
|
15
|
+
* git log --no-merges -N 결과에서 conventional commit 형식의 fix/feat 만 카운트.
|
|
16
|
+
*
|
|
17
|
+
* 분류:
|
|
18
|
+
* - `feat: ...` / `feat(scope): ...` → feat
|
|
19
|
+
* - `fix: ...` / `fix(scope): ...` → fix (단, scope ∈ {test, tests, docs, doc} 제외)
|
|
20
|
+
* - 그 외 (chore, refactor, docs, style, test, hash 없는 라인) → 무시
|
|
21
|
+
*
|
|
22
|
+
* fix(test):, fix(docs): 가 제외되는 이유: 사소한 노이즈 fix 가 회귀 신호를
|
|
23
|
+
* 흐리지 않도록. 진짜 위험은 fix(core), fix(hook), fix(api) 같은 logic fix.
|
|
24
|
+
*/
|
|
25
|
+
export function computeFixFeatRatio(cwd = process.cwd(), windowSize = DEFAULT_WINDOW, threshold = DEFAULT_THRESHOLD) {
|
|
26
|
+
try {
|
|
27
|
+
const out = execFileSync('git', ['log', '--no-merges', '--oneline', `-${windowSize}`], { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
28
|
+
return parseGitLog(out, windowSize, threshold);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return makeUnavailable(windowSize, threshold);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** 테스트용 — git log 출력 텍스트를 직접 파싱. */
|
|
35
|
+
export function parseGitLog(rawLog, windowSize = DEFAULT_WINDOW, threshold = DEFAULT_THRESHOLD) {
|
|
36
|
+
const lines = rawLog.trim().split('\n').filter(Boolean);
|
|
37
|
+
let fix = 0;
|
|
38
|
+
let feat = 0;
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
const msg = line.replace(/^[a-f0-9]{4,40}\s+/, '');
|
|
41
|
+
const m = msg.match(/^(fix|feat)(?:\(([^)]+)\))?:/);
|
|
42
|
+
if (!m)
|
|
43
|
+
continue;
|
|
44
|
+
const type = m[1];
|
|
45
|
+
const scope = (m[2] ?? '').toLowerCase().trim();
|
|
46
|
+
if (type === 'fix' && SCOPE_EXCLUSIONS.has(scope))
|
|
47
|
+
continue;
|
|
48
|
+
if (type === 'fix')
|
|
49
|
+
fix++;
|
|
50
|
+
else
|
|
51
|
+
feat++;
|
|
52
|
+
}
|
|
53
|
+
const total = fix + feat;
|
|
54
|
+
const ratio = total === 0 ? 0 : fix / total;
|
|
55
|
+
return {
|
|
56
|
+
windowSize,
|
|
57
|
+
fixCount: fix,
|
|
58
|
+
featCount: feat,
|
|
59
|
+
ratio,
|
|
60
|
+
threshold,
|
|
61
|
+
exceedsThreshold: ratio > threshold,
|
|
62
|
+
available: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function makeUnavailable(windowSize, threshold) {
|
|
66
|
+
return {
|
|
67
|
+
windowSize, fixCount: 0, featCount: 0, ratio: 0,
|
|
68
|
+
threshold, exceedsThreshold: false, available: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** 사람용 한 줄 라벨. */
|
|
72
|
+
export function formatFixRatio(s) {
|
|
73
|
+
if (!s.available)
|
|
74
|
+
return 'fix:feat ratio n/a (git unavailable)';
|
|
75
|
+
const pct = (s.ratio * 100).toFixed(0);
|
|
76
|
+
const thresholdPct = (s.threshold * 100).toFixed(0);
|
|
77
|
+
const flag = s.exceedsThreshold ? ` ⚠ over ${thresholdPct}%` : '';
|
|
78
|
+
return `fix:feat ratio ${pct}% (${s.fixCount}/${s.fixCount + s.featCount} in last ${s.windowSize})${flag}`;
|
|
79
|
+
}
|
package/dist/core/harness.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Lines 50-120: Rule file injection, gitignore, compound memory
|
|
10
10
|
* - Lines 120+: prepareHarness — main orchestration
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import type { RuntimeHost } from './types.js';
|
|
13
13
|
import { rollbackSettings } from './settings-lock.js';
|
|
14
14
|
import { type V1BootstrapResult } from './v1-bootstrap.js';
|
|
15
15
|
export interface V1HarnessContext {
|
package/dist/core/harness.js
CHANGED
|
@@ -326,32 +326,39 @@ export async function prepareHarness(cwd, options = {}) {
|
|
|
326
326
|
}
|
|
327
327
|
// 3. 환경 확인
|
|
328
328
|
const inTmux = !!process.env.TMUX;
|
|
329
|
-
// 4. Claude
|
|
329
|
+
// 4-7. Claude artifact 작업 (settings.json + agents + rules + slash commands).
|
|
330
330
|
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
331
|
+
// feat/codex-support P1-7 (2026-04-27): runtime === 'codex' 시 *.claude/* 계열
|
|
332
|
+
// 작업은 *no-op*. Codex 측 동치 prep 은 Phase 3 (install-codex.ts 의 prompts +
|
|
333
|
+
// AGENTS.md inject) 에서 처리. 본 분기는 *Claude artifact 가 Codex 환경을
|
|
334
|
+
// 오염시키지 않도록* 보호하는 비대칭 게이트.
|
|
334
335
|
const pkgRoot = getPackageRoot();
|
|
335
336
|
const env = buildEnv(cwd, v1Result.session?.session_id, runtime);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
341
|
-
if (msg.includes('settings.json lock') || msg.includes('SettingsLockError')) {
|
|
342
|
-
console.error(`[forgen] ${msg} — settings 갱신 스킵, 이전 값 유지`);
|
|
337
|
+
if (runtime === 'claude') {
|
|
338
|
+
// 4. settings.json 인젝션
|
|
339
|
+
try {
|
|
340
|
+
injectSettings(env, v1Result, runtime, cwd, pkgRoot);
|
|
343
341
|
}
|
|
344
|
-
|
|
345
|
-
|
|
342
|
+
catch (e) {
|
|
343
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
344
|
+
if (msg.includes('settings.json lock') || msg.includes('SettingsLockError')) {
|
|
345
|
+
console.error(`[forgen] ${msg} — settings 갱신 스킵, 이전 값 유지`);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
throw e;
|
|
349
|
+
}
|
|
346
350
|
}
|
|
351
|
+
// 5. 에이전트 설치
|
|
352
|
+
installAgents(cwd, pkgRoot);
|
|
353
|
+
// 6. 규칙 파일 생성 + 주입
|
|
354
|
+
const ruleFiles = generateClaudeRuleFiles(cwd, v1Result.renderedRules);
|
|
355
|
+
injectClaudeRuleFiles(cwd, ruleFiles);
|
|
356
|
+
// 7. 슬래시 명령 설치
|
|
357
|
+
installSlashCommands(cwd, pkgRoot);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
log.debug(`prepareHarness: runtime=${runtime} — Claude artifact prep skipped (Phase 3 handles Codex prep)`);
|
|
347
361
|
}
|
|
348
|
-
// 5. 에이전트 설치
|
|
349
|
-
installAgents(cwd, pkgRoot);
|
|
350
|
-
// 6. 규칙 파일 생성 및 주입 (v1 부트스트랩 결과의 renderedRules를 직접 전달)
|
|
351
|
-
const ruleFiles = generateClaudeRuleFiles(cwd, v1Result.renderedRules);
|
|
352
|
-
injectClaudeRuleFiles(cwd, ruleFiles);
|
|
353
|
-
// 7. 슬래시 명령 설치
|
|
354
|
-
installSlashCommands(cwd, pkgRoot);
|
|
355
362
|
// 8. tmux 바인딩 등록
|
|
356
363
|
if (inTmux) {
|
|
357
364
|
await registerTmuxBindings();
|