@wooojin/forgen 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.ko.md +25 -14
  3. package/README.md +61 -17
  4. package/agents/analyst.md +48 -4
  5. package/agents/architect.md +39 -4
  6. package/agents/code-reviewer.md +107 -77
  7. package/agents/critic.md +47 -4
  8. package/agents/debugger.md +46 -4
  9. package/agents/designer.md +40 -4
  10. package/agents/executor.md +112 -30
  11. package/agents/explore.md +45 -5
  12. package/agents/git-master.md +48 -4
  13. package/agents/planner.md +121 -18
  14. package/agents/solution-evolver.md +115 -0
  15. package/agents/test-engineer.md +58 -4
  16. package/agents/verifier.md +92 -77
  17. package/commands/architecture-decision.md +127 -258
  18. package/commands/calibrate.md +225 -0
  19. package/commands/code-review.md +163 -178
  20. package/commands/compound.md +127 -68
  21. package/commands/deep-interview.md +212 -110
  22. package/commands/docker.md +68 -178
  23. package/commands/forge-loop.md +215 -0
  24. package/commands/learn.md +231 -0
  25. package/commands/retro.md +215 -0
  26. package/commands/ship.md +277 -0
  27. package/dist/cli.js +25 -9
  28. package/dist/core/auto-compound-runner.js +14 -0
  29. package/dist/core/config-injector.d.ts +2 -1
  30. package/dist/core/config-injector.js +2 -1
  31. package/dist/core/dashboard.d.ts +17 -0
  32. package/dist/core/dashboard.js +158 -2
  33. package/dist/core/harness.d.ts +6 -1
  34. package/dist/core/harness.js +75 -19
  35. package/dist/core/paths.d.ts +31 -1
  36. package/dist/core/paths.js +43 -2
  37. package/dist/core/spawn.d.ts +3 -2
  38. package/dist/core/spawn.js +27 -8
  39. package/dist/core/types.d.ts +34 -0
  40. package/dist/engine/compound-lifecycle.d.ts +4 -3
  41. package/dist/engine/compound-lifecycle.js +91 -46
  42. package/dist/engine/learn-cli.d.ts +1 -0
  43. package/dist/engine/learn-cli.js +182 -0
  44. package/dist/engine/meta-learning/adaptive-thresholds.d.ts +20 -0
  45. package/dist/engine/meta-learning/adaptive-thresholds.js +126 -0
  46. package/dist/engine/meta-learning/extraction-tuner.d.ts +15 -0
  47. package/dist/engine/meta-learning/extraction-tuner.js +99 -0
  48. package/dist/engine/meta-learning/matcher-weight-tuner.d.ts +21 -0
  49. package/dist/engine/meta-learning/matcher-weight-tuner.js +151 -0
  50. package/dist/engine/meta-learning/runner.d.ts +14 -0
  51. package/dist/engine/meta-learning/runner.js +90 -0
  52. package/dist/engine/meta-learning/scope-promoter.d.ts +21 -0
  53. package/dist/engine/meta-learning/scope-promoter.js +84 -0
  54. package/dist/engine/meta-learning/session-quality-scorer.d.ts +61 -0
  55. package/dist/engine/meta-learning/session-quality-scorer.js +166 -0
  56. package/dist/engine/meta-learning/types.d.ts +114 -0
  57. package/dist/engine/meta-learning/types.js +43 -0
  58. package/dist/engine/solution-candidate.d.ts +30 -0
  59. package/dist/engine/solution-candidate.js +124 -0
  60. package/dist/engine/solution-fitness.d.ts +52 -0
  61. package/dist/engine/solution-fitness.js +95 -0
  62. package/dist/engine/solution-fixup.d.ts +30 -0
  63. package/dist/engine/solution-fixup.js +116 -0
  64. package/dist/engine/solution-format.d.ts +10 -2
  65. package/dist/engine/solution-format.js +287 -57
  66. package/dist/engine/solution-index.d.ts +1 -1
  67. package/dist/engine/solution-index.js +10 -0
  68. package/dist/engine/solution-matcher.d.ts +7 -1
  69. package/dist/engine/solution-matcher.js +137 -37
  70. package/dist/engine/solution-outcomes.d.ts +70 -0
  71. package/dist/engine/solution-outcomes.js +242 -0
  72. package/dist/engine/solution-quarantine.d.ts +36 -0
  73. package/dist/engine/solution-quarantine.js +172 -0
  74. package/dist/engine/solution-weakness.d.ts +45 -0
  75. package/dist/engine/solution-weakness.js +225 -0
  76. package/dist/engine/solution-writer.d.ts +5 -0
  77. package/dist/engine/solution-writer.js +18 -0
  78. package/dist/fgx.js +12 -8
  79. package/dist/hooks/context-guard.d.ts +5 -0
  80. package/dist/hooks/context-guard.js +118 -2
  81. package/dist/hooks/hooks-generator.d.ts +3 -0
  82. package/dist/hooks/hooks-generator.js +23 -6
  83. package/dist/hooks/keyword-detector.js +16 -100
  84. package/dist/hooks/post-tool-failure.js +7 -0
  85. package/dist/hooks/skill-injector.d.ts +4 -3
  86. package/dist/hooks/skill-injector.js +6 -4
  87. package/dist/hooks/solution-injector.js +20 -0
  88. package/dist/host/codex-adapter.d.ts +10 -0
  89. package/dist/host/codex-adapter.js +154 -0
  90. package/dist/mcp/solution-reader.d.ts +5 -5
  91. package/dist/mcp/solution-reader.js +34 -24
  92. package/dist/mcp/tools.js +8 -0
  93. package/dist/services/session.d.ts +19 -0
  94. package/dist/services/session.js +62 -0
  95. package/hooks/hooks.json +2 -2
  96. package/package.json +2 -1
  97. package/skills/architecture-decision/SKILL.md +113 -257
  98. package/skills/calibrate/SKILL.md +207 -0
  99. package/skills/code-review/SKILL.md +151 -178
  100. package/skills/compound/SKILL.md +126 -68
  101. package/skills/deep-interview/SKILL.md +210 -110
  102. package/skills/docker/SKILL.md +57 -179
  103. package/skills/forge-loop/SKILL.md +198 -0
  104. package/skills/learn/SKILL.md +216 -0
  105. package/skills/retro/SKILL.md +199 -0
  106. package/skills/ship/SKILL.md +259 -0
  107. package/agents/code-simplifier.md +0 -197
  108. package/agents/performance-reviewer.md +0 -172
  109. package/agents/qa-tester.md +0 -158
  110. package/agents/refactoring-expert.md +0 -168
  111. package/agents/scientist.md +0 -144
  112. package/agents/security-reviewer.md +0 -137
  113. package/agents/writer.md +0 -184
  114. package/commands/api-design.md +0 -268
  115. package/commands/ci-cd.md +0 -270
  116. package/commands/database.md +0 -263
  117. package/commands/debug-detective.md +0 -99
  118. package/commands/documentation.md +0 -276
  119. package/commands/ecomode.md +0 -51
  120. package/commands/frontend.md +0 -271
  121. package/commands/git-master.md +0 -90
  122. package/commands/incident-response.md +0 -292
  123. package/commands/migrate.md +0 -101
  124. package/commands/performance.md +0 -288
  125. package/commands/refactor.md +0 -105
  126. package/commands/security-review.md +0 -288
  127. package/commands/specify.md +0 -128
  128. package/commands/tdd.md +0 -183
  129. package/commands/testing-strategy.md +0 -265
  130. package/skills/api-design/SKILL.md +0 -262
  131. package/skills/ci-cd/SKILL.md +0 -264
  132. package/skills/database/SKILL.md +0 -257
  133. package/skills/debug-detective/SKILL.md +0 -95
  134. package/skills/documentation/SKILL.md +0 -270
  135. package/skills/ecomode/SKILL.md +0 -46
  136. package/skills/frontend/SKILL.md +0 -265
  137. package/skills/git-master/SKILL.md +0 -86
  138. package/skills/incident-response/SKILL.md +0 -286
  139. package/skills/migrate/SKILL.md +0 -96
  140. package/skills/performance/SKILL.md +0 -282
  141. package/skills/refactor/SKILL.md +0 -100
  142. package/skills/security-review/SKILL.md +0 -282
  143. package/skills/specify/SKILL.md +0 -122
  144. package/skills/tdd/SKILL.md +0 -178
  145. package/skills/testing-strategy/SKILL.md +0 -260
@@ -20,6 +20,7 @@ import { buildEnv, generateClaudeRuleFiles, registerTmuxBindings } from './confi
20
20
  import { createLogger } from './logger.js';
21
21
  import { HANDOFFS_DIR, ME_BEHAVIOR, ME_DIR, ME_RULES, ME_SKILLS, ME_SOLUTIONS, SESSIONS_DIR, STATE_DIR, FORGEN_HOME } from './paths.js';
22
22
  import { RULE_FILE_CAPS } from '../hooks/shared/injection-caps.js';
23
+ import { generateHooksJson } from '../hooks/hooks-generator.js';
23
24
  import { acquireLock, atomicWriteFileSync, CLAUDE_DIR, releaseLock, rollbackSettings, SETTINGS_BACKUP_PATH, SETTINGS_PATH, } from './settings-lock.js';
24
25
  import { ConfigError } from './errors.js';
25
26
  import { bootstrapV1Session, ensureV1Directories } from './v1-bootstrap.js';
@@ -104,7 +105,7 @@ function isForgenHookEntry(entry, pkgRoot) {
104
105
  return Array.isArray(hooks) && hooks.some(h => typeof h.command === 'string' && matchesPath(h.command));
105
106
  }
106
107
  /** Strip existing forgen hooks from settings, merge fresh hooks.json. */
107
- function mergeHooksIntoSettings(settings) {
108
+ function mergeHooksIntoSettings(settings, runtime, cwd) {
108
109
  const pkgRoot = getPackageRoot();
109
110
  const hooksConfig = settings.hooks ?? {};
110
111
  // Remove existing forgen hooks (clean slate before re-inject)
@@ -117,18 +118,28 @@ function mergeHooksIntoSettings(settings) {
117
118
  else
118
119
  hooksConfig[event] = filtered;
119
120
  }
120
- // Read hooks.json and inject, replacing ${CLAUDE_PLUGIN_ROOT}
121
- const hooksJsonPath = path.join(pkgRoot, 'hooks', 'hooks.json');
122
121
  try {
123
- if (fs.existsSync(hooksJsonPath)) {
124
- const hooksJson = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf-8'));
125
- const hooksData = hooksJson.hooks;
126
- if (hooksData) {
127
- const resolved = JSON.parse(JSON.stringify(hooksData).replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pkgRoot));
128
- for (const [event, handlers] of Object.entries(resolved)) {
129
- if (!hooksConfig[event])
130
- hooksConfig[event] = [];
131
- hooksConfig[event].push(...handlers);
122
+ if (runtime === 'codex') {
123
+ const generated = generateHooksJson({ cwd, runtime, pluginRoot: path.join(pkgRoot, 'dist') });
124
+ for (const [event, handlers] of Object.entries(generated.hooks)) {
125
+ if (!hooksConfig[event])
126
+ hooksConfig[event] = [];
127
+ hooksConfig[event].push(...handlers);
128
+ }
129
+ }
130
+ else {
131
+ // Read hooks.json and inject, replacing ${CLAUDE_PLUGIN_ROOT}
132
+ const hooksJsonPath = path.join(pkgRoot, 'hooks', 'hooks.json');
133
+ if (fs.existsSync(hooksJsonPath)) {
134
+ const hooksJson = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf-8'));
135
+ const hooksData = hooksJson.hooks;
136
+ if (hooksData) {
137
+ const resolved = JSON.parse(JSON.stringify(hooksData).replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pkgRoot));
138
+ for (const [event, handlers] of Object.entries(resolved)) {
139
+ if (!hooksConfig[event])
140
+ hooksConfig[event] = [];
141
+ hooksConfig[event].push(...handlers);
142
+ }
132
143
  }
133
144
  }
134
145
  }
@@ -177,14 +188,14 @@ function applyTrustPolicyPermissions(settings, v1Result) {
177
188
  * atomic write). Each phase is now a named function with a single
178
189
  * responsibility, testable in isolation if needed.
179
190
  */
180
- function injectSettings(env, v1Result) {
191
+ function injectSettings(env, v1Result, runtime, cwd) {
181
192
  fs.mkdirSync(CLAUDE_DIR, { recursive: true });
182
193
  acquireLock();
183
194
  const settings = readSettingsWithBackup();
184
195
  // Merge env vars
185
196
  settings.env = { ...(settings.env ?? {}), ...env };
186
197
  applyStatusLine(settings);
187
- mergeHooksIntoSettings(settings);
198
+ mergeHooksIntoSettings(settings, runtime, cwd);
188
199
  applyTrustPolicyPermissions(settings, v1Result);
189
200
  try {
190
201
  atomicWriteFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
@@ -252,14 +263,58 @@ function installAgentsFromDir(sourceDir, targetDir, prefix, hashes) {
252
263
  hashes[dstName] = newHash;
253
264
  }
254
265
  }
266
+ /**
267
+ * 현재 source에 없는 stale ch-*.md 에이전트 파일을 정리.
268
+ * forgen-managed 마커가 있는 파일만 삭제 (사용자 수정 파일 보호).
269
+ */
270
+ function cleanupStaleAgents(sourceDir, targetDir, prefix, hashes) {
271
+ if (!fs.existsSync(targetDir))
272
+ return;
273
+ if (!fs.existsSync(sourceDir))
274
+ return;
275
+ // 현재 source의 유효한 파일 목록
276
+ const validFiles = new Set(fs.readdirSync(sourceDir)
277
+ .filter((f) => f.endsWith('.md'))
278
+ .map((f) => `${prefix}${f}`));
279
+ // targetDir에서 prefix로 시작하지만 유효 목록에 없는 파일 삭제
280
+ for (const existing of fs.readdirSync(targetDir)) {
281
+ if (!existing.startsWith(prefix) || !existing.endsWith('.md'))
282
+ continue;
283
+ if (validFiles.has(existing))
284
+ continue;
285
+ const filePath = path.join(targetDir, existing);
286
+ try {
287
+ const content = fs.readFileSync(filePath, 'utf-8');
288
+ // 사용자 수정 보호: forgen-managed 마커가 있고 hash가 기록된 경우만 삭제
289
+ const recordedHash = hashes[existing];
290
+ const hasMarker = content.includes('<!-- forgen-managed -->');
291
+ if (!hasMarker) {
292
+ log.debug(`에이전트 삭제 스킵: ${existing} (forgen-managed 마커 없음)`);
293
+ continue;
294
+ }
295
+ if (recordedHash && contentHash(content) !== recordedHash) {
296
+ log.debug(`에이전트 삭제 스킵: ${existing} (사용자 수정 감지)`);
297
+ continue;
298
+ }
299
+ fs.unlinkSync(filePath);
300
+ delete hashes[existing];
301
+ log.debug(`stale 에이전트 삭제: ${existing}`);
302
+ }
303
+ catch (e) {
304
+ log.debug(`에이전트 삭제 실패: ${existing}`, e);
305
+ }
306
+ }
307
+ }
255
308
  /** 에이전트 정의 파일 설치 (패키지 내장만) */
256
309
  function installAgents(cwd) {
257
310
  const pkgRoot = getPackageRoot();
258
311
  const targetDir = path.join(cwd, '.claude', 'agents');
259
312
  fs.mkdirSync(targetDir, { recursive: true });
260
313
  const hashes = loadAgentHashes();
314
+ const sourceDir = path.join(pkgRoot, 'agents');
261
315
  try {
262
- installAgentsFromDir(path.join(pkgRoot, 'agents'), targetDir, 'ch-', hashes);
316
+ installAgentsFromDir(sourceDir, targetDir, 'ch-', hashes);
317
+ cleanupStaleAgents(sourceDir, targetDir, 'ch-', hashes);
263
318
  saveAgentHashes(hashes);
264
319
  }
265
320
  catch (e) {
@@ -560,7 +615,8 @@ function checkCompoundStaleness() {
560
615
  log.debug('Staleness check failed (non-fatal)', e);
561
616
  }
562
617
  }
563
- export async function prepareHarness(cwd) {
618
+ export async function prepareHarness(cwd, options = {}) {
619
+ const runtime = options.runtime ?? 'claude';
564
620
  try {
565
621
  // 0. 스토리지 마이그레이션 (v5.1: ~/.compound/ → ~/.forgen/)
566
622
  migrateToForgen();
@@ -591,8 +647,8 @@ export async function prepareHarness(cwd) {
591
647
  // 3. 환경 확인
592
648
  const inTmux = !!process.env.TMUX;
593
649
  // 4. Claude Code 설정 주입 (환경변수 + trust 기반 permissions)
594
- const env = buildEnv(cwd, v1Result.session?.session_id);
595
- injectSettings(env, v1Result);
650
+ const env = buildEnv(cwd, v1Result.session?.session_id, runtime);
651
+ injectSettings(env, v1Result, runtime, cwd);
596
652
  // 5. 에이전트 설치
597
653
  installAgents(cwd);
598
654
  // 6. 규칙 파일 생성 및 주입 (v1 부트스트랩 결과의 renderedRules를 직접 전달)
@@ -612,7 +668,7 @@ export async function prepareHarness(cwd) {
612
668
  await startLegacySessionLog(cwd, inTmux, v1Result);
613
669
  // 12. Compound staleness guard
614
670
  checkCompoundStaleness();
615
- return { cwd, inTmux, v1: v1Result };
671
+ return { cwd, inTmux, v1: v1Result, runtime };
616
672
  }
617
673
  catch (err) {
618
674
  rollbackSettings();
@@ -1,5 +1,6 @@
1
1
  /** ~/.claude/ — Claude Code 설정 디렉토리 */
2
2
  export declare const CLAUDE_DIR: string;
3
+ export declare const CODEX_DIR: string;
3
4
  /** ~/.claude/settings.json — Claude Code 설정 파일 */
4
5
  export declare const SETTINGS_PATH: string;
5
6
  /**
@@ -43,10 +44,39 @@ export declare const STATE_DIR: string;
43
44
  * `src/engine/match-eval-log.ts`; never on the hook critical path.
44
45
  */
45
46
  export declare const MATCH_EVAL_LOG_PATH: string;
47
+ /**
48
+ * ~/.forgen/state/solution-quarantine.jsonl — JSONL log of solution files
49
+ * dropped during index build due to malformed frontmatter. Append-only,
50
+ * dedupe-by-path. Used by `forgen doctor` to surface dead solutions that
51
+ * would otherwise vanish silently (see `diagnoseFrontmatter`).
52
+ */
53
+ export declare const SOLUTION_QUARANTINE_PATH: string;
54
+ /**
55
+ * ~/.forgen/state/outcomes/ — per-session JSONL logs of solution inject →
56
+ * outcome events (accept / correct / error / unknown). Written by the
57
+ * solution-outcome-tracker hook. One file per session for write-safety
58
+ * under concurrent sessions. Consumers aggregate across files to compute
59
+ * fitness (see `solution-fitness.ts`).
60
+ */
61
+ export declare const OUTCOMES_DIR: string;
62
+ /**
63
+ * ~/.forgen/lab/candidates/ — Phase 4 quarantine zone for evolver-agent
64
+ * proposals before they enter the live solution index. The evolver writes
65
+ * here; promotion and rollback commands move files out (to ME_SOLUTIONS
66
+ * or to `lab/archived-{ts}/`). Keeping candidates isolated means a
67
+ * runaway agent cannot silently poison the match pool.
68
+ */
69
+ export declare const CANDIDATES_DIR: string;
70
+ /** ~/.forgen/lab/archived/ — rollback destination for evolved solutions. */
71
+ export declare const ARCHIVED_DIR: string;
46
72
  /** ~/.forgen/sessions/ — 세션 로그 */
47
73
  export declare const SESSIONS_DIR: string;
48
74
  /** ~/.forgen/config.json — 글로벌 설정 */
49
75
  export declare const GLOBAL_CONFIG: string;
76
+ /** ~/.forgen/state/session-quality/ — 세션 품질 점수 */
77
+ export declare const SESSION_QUALITY_DIR: string;
78
+ /** ~/.forgen/state/meta-learning/ — 메타학습 상태 파일 */
79
+ export declare const META_LEARNING_DIR: string;
50
80
  /** ~/.forgen/lab/ — Lab 적응형 최적화 엔진 데이터 */
51
81
  export declare const LAB_DIR: string;
52
82
  /** ~/.forgen/lab/events.jsonl — Lab 이벤트 로그 (JSONL) */
@@ -74,7 +104,7 @@ export declare const V1_RAW_LOGS_DIR: string;
74
104
  /** @deprecated use GLOBAL_CONFIG */
75
105
  export declare const V1_GLOBAL_CONFIG: string;
76
106
  /** 모든 실행 모드 이름 (cancel/recovery 시 사용) */
77
- export declare const ALL_MODES: readonly ["ralph", "autopilot", "ultrawork", "team", "pipeline", "ccg", "ralplan", "deep-interview", "ecomode", "specify"];
107
+ export declare const ALL_MODES: readonly ["ralph", "autopilot", "ultrawork", "team", "pipeline", "ccg", "ralplan", "deep-interview", "forge-loop", "ship", "retro", "learn", "calibrate"];
78
108
  /** {repo}/.compound/ — 프로젝트 로컬 디렉토리 */
79
109
  export declare function projectDir(cwd: string): string;
80
110
  /** {repo}/.compound/pack.link — 팀 팩 연결 파일 */
@@ -3,6 +3,7 @@ import * as path from 'node:path';
3
3
  const HOME = os.homedir();
4
4
  /** ~/.claude/ — Claude Code 설정 디렉토리 */
5
5
  export const CLAUDE_DIR = path.join(HOME, '.claude');
6
+ export const CODEX_DIR = path.join(HOME, '.codex');
6
7
  /** ~/.claude/settings.json — Claude Code 설정 파일 */
7
8
  export const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
8
9
  /**
@@ -46,10 +47,39 @@ export const STATE_DIR = path.join(FORGEN_HOME, 'state');
46
47
  * `src/engine/match-eval-log.ts`; never on the hook critical path.
47
48
  */
48
49
  export const MATCH_EVAL_LOG_PATH = path.join(STATE_DIR, 'match-eval-log.jsonl');
50
+ /**
51
+ * ~/.forgen/state/solution-quarantine.jsonl — JSONL log of solution files
52
+ * dropped during index build due to malformed frontmatter. Append-only,
53
+ * dedupe-by-path. Used by `forgen doctor` to surface dead solutions that
54
+ * would otherwise vanish silently (see `diagnoseFrontmatter`).
55
+ */
56
+ export const SOLUTION_QUARANTINE_PATH = path.join(STATE_DIR, 'solution-quarantine.jsonl');
57
+ /**
58
+ * ~/.forgen/state/outcomes/ — per-session JSONL logs of solution inject →
59
+ * outcome events (accept / correct / error / unknown). Written by the
60
+ * solution-outcome-tracker hook. One file per session for write-safety
61
+ * under concurrent sessions. Consumers aggregate across files to compute
62
+ * fitness (see `solution-fitness.ts`).
63
+ */
64
+ export const OUTCOMES_DIR = path.join(STATE_DIR, 'outcomes');
65
+ /**
66
+ * ~/.forgen/lab/candidates/ — Phase 4 quarantine zone for evolver-agent
67
+ * proposals before they enter the live solution index. The evolver writes
68
+ * here; promotion and rollback commands move files out (to ME_SOLUTIONS
69
+ * or to `lab/archived-{ts}/`). Keeping candidates isolated means a
70
+ * runaway agent cannot silently poison the match pool.
71
+ */
72
+ export const CANDIDATES_DIR = path.join(FORGEN_HOME, 'lab', 'candidates');
73
+ /** ~/.forgen/lab/archived/ — rollback destination for evolved solutions. */
74
+ export const ARCHIVED_DIR = path.join(FORGEN_HOME, 'lab', 'archived');
49
75
  /** ~/.forgen/sessions/ — 세션 로그 */
50
76
  export const SESSIONS_DIR = path.join(FORGEN_HOME, 'sessions');
51
77
  /** ~/.forgen/config.json — 글로벌 설정 */
52
78
  export const GLOBAL_CONFIG = path.join(FORGEN_HOME, 'config.json');
79
+ /** ~/.forgen/state/session-quality/ — 세션 품질 점수 */
80
+ export const SESSION_QUALITY_DIR = path.join(STATE_DIR, 'session-quality');
81
+ /** ~/.forgen/state/meta-learning/ — 메타학습 상태 파일 */
82
+ export const META_LEARNING_DIR = path.join(STATE_DIR, 'meta-learning');
53
83
  /** ~/.forgen/lab/ — Lab 적응형 최적화 엔진 데이터 */
54
84
  export const LAB_DIR = path.join(FORGEN_HOME, 'lab');
55
85
  /** ~/.forgen/lab/events.jsonl — Lab 이벤트 로그 (JSONL) */
@@ -80,8 +110,19 @@ export const V1_GLOBAL_CONFIG = GLOBAL_CONFIG;
80
110
  // ── 레거시 ──
81
111
  /** 모든 실행 모드 이름 (cancel/recovery 시 사용) */
82
112
  export const ALL_MODES = [
83
- 'ralph', 'autopilot', 'ultrawork', 'team', 'pipeline',
84
- 'ccg', 'ralplan', 'deep-interview', 'ecomode', 'specify',
113
+ 'ralph',
114
+ 'autopilot',
115
+ 'ultrawork',
116
+ 'team',
117
+ 'pipeline',
118
+ 'ccg',
119
+ 'ralplan',
120
+ 'deep-interview',
121
+ 'forge-loop',
122
+ 'ship',
123
+ 'retro',
124
+ 'learn',
125
+ 'calibrate',
85
126
  ];
86
127
  /** {repo}/.compound/ — 프로젝트 로컬 디렉토리 */
87
128
  export function projectDir(cwd) {
@@ -1,8 +1,9 @@
1
1
  import type { V1HarnessContext } from './harness.js';
2
+ import { type RuntimeHost } from './types.js';
2
3
  /** Claude Code를 하네스 환경으로 실행. exit code를 반환. */
3
- export declare function spawnClaude(args: string[], context: V1HarnessContext): Promise<number>;
4
+ export declare function spawnClaude(args: string[], context: V1HarnessContext, runtime?: RuntimeHost): Promise<number>;
4
5
  /**
5
6
  * 토큰 한도 도달 시 자동 재시작을 지원하는 claude 실행 래퍼.
6
7
  * context-guard가 pending-resume.json 마커를 생성하면 쿨다운 후 재시작.
7
8
  */
8
- export declare function spawnClaudeWithResume(args: string[], context: V1HarnessContext, contextFactory: () => Promise<V1HarnessContext>): Promise<void>;
9
+ export declare function spawnClaudeWithResume(args: string[], context: V1HarnessContext, contextFactory: () => Promise<V1HarnessContext>, runtime?: RuntimeHost): Promise<void>;
@@ -12,6 +12,9 @@ const log = createLogger('spawn');
12
12
  function findClaude() {
13
13
  return 'claude';
14
14
  }
15
+ function findRuntimeLauncher(runtime) {
16
+ return runtime === 'codex' ? 'codex' : findClaude();
17
+ }
15
18
  /**
16
19
  * 가장 최근 transcript 파일을 찾는다.
17
20
  * Claude Code는 세션 대화를 ~/.claude/projects/{sanitized-cwd}/{uuid}.jsonl에 저장.
@@ -60,32 +63,43 @@ async function indexTranscriptToFTS(cwd, transcriptPath, sessionId) {
60
63
  }
61
64
  }
62
65
  /** Claude Code를 하네스 환경으로 실행. exit code를 반환. */
63
- export async function spawnClaude(args, context) {
64
- const claudePath = findClaude();
65
- const env = buildEnv(context.cwd);
66
+ export async function spawnClaude(args, context, runtime = 'claude') {
67
+ const launcher = findRuntimeLauncher(runtime);
68
+ const env = buildEnv(context.cwd, context.v1.session?.session_id, runtime);
66
69
  const cleanArgs = [...args];
67
70
  // config.json에서 dangerouslySkipPermissions 기본값 적용
68
71
  const globalConfig = loadGlobalConfig();
69
- if (globalConfig.dangerouslySkipPermissions && !cleanArgs.includes('--dangerously-skip-permissions')) {
72
+ if (runtime === 'claude' &&
73
+ globalConfig.dangerouslySkipPermissions &&
74
+ !cleanArgs.includes('--dangerously-skip-permissions')) {
70
75
  cleanArgs.unshift('--dangerously-skip-permissions');
71
76
  }
72
77
  // 세션 시작 전 timestamp 기록 (종료 후 transcript 찾기 위해)
73
78
  const sessionStartTime = Date.now();
74
79
  return new Promise((resolve, reject) => {
75
- const child = spawn(claudePath, cleanArgs, {
80
+ const child = spawn(launcher, cleanArgs, {
76
81
  stdio: 'inherit',
77
82
  env: { ...process.env, ...env },
78
83
  cwd: context.cwd,
79
84
  });
80
85
  child.on('error', (err) => {
81
86
  if (err.code === 'ENOENT') {
82
- reject(new Error('Claude Code is not installed. npm install -g @anthropic-ai/claude-code'));
87
+ if (runtime === 'codex') {
88
+ reject(new Error('Codex is not installed.'));
89
+ }
90
+ else {
91
+ reject(new Error('Claude Code is not installed. npm install -g @anthropic-ai/claude-code'));
92
+ }
83
93
  }
84
94
  else {
85
95
  reject(err);
86
96
  }
87
97
  });
88
98
  child.on('exit', async (code) => {
99
+ if (runtime !== 'claude') {
100
+ resolve(code ?? 0);
101
+ return;
102
+ }
89
103
  // 세션 종료 후 하네스 작업
90
104
  try {
91
105
  const transcript = findLatestTranscript(context.cwd);
@@ -135,11 +149,16 @@ const MAX_RESUMES = 3;
135
149
  * 토큰 한도 도달 시 자동 재시작을 지원하는 claude 실행 래퍼.
136
150
  * context-guard가 pending-resume.json 마커를 생성하면 쿨다운 후 재시작.
137
151
  */
138
- export async function spawnClaudeWithResume(args, context, contextFactory) {
152
+ export async function spawnClaudeWithResume(args, context, contextFactory, runtime = 'claude') {
139
153
  let resumeCount = 0;
140
154
  let currentContext = context;
141
155
  while (true) {
142
- const exitCode = await spawnClaude(args, currentContext);
156
+ const exitCode = await spawnClaude(args, currentContext, runtime);
157
+ if (runtime !== 'claude') {
158
+ if (exitCode !== 0)
159
+ process.exit(exitCode);
160
+ break;
161
+ }
143
162
  const resumePath = path.join(STATE_DIR, 'pending-resume.json');
144
163
  if (!fs.existsSync(resumePath)) {
145
164
  if (exitCode !== 0)
@@ -106,3 +106,37 @@ export interface HarnessContext {
106
106
  /** 모델 라우팅 프리셋 (default, cost-saving, max-quality) */
107
107
  routingPreset?: string;
108
108
  }
109
+ /** 런타임 Host */
110
+ export type RuntimeHost = 'claude' | 'codex';
111
+ /** 런칭 컨텍스트 — CLI에서 runtime/args 결정을 모델화 */
112
+ export interface LaunchContext {
113
+ runtime: RuntimeHost;
114
+ args: string[];
115
+ runtimeSource: 'flag' | 'env' | 'default';
116
+ }
117
+ /** 훅 입력 이벤트 스키마 (버전 간 상위 호환용 최소 스펙) */
118
+ export interface HookEventInput {
119
+ hookEventName?: string;
120
+ event?: string;
121
+ session_id?: string;
122
+ sessionId?: string;
123
+ tool_name?: string;
124
+ toolName?: string;
125
+ tool_input?: Record<string, unknown>;
126
+ toolInput?: Record<string, unknown>;
127
+ [key: string]: unknown;
128
+ }
129
+ /** 훅 출력 스키마 (Claude/Codex 정규화용 공통 뷰) */
130
+ export interface HookEventOutput {
131
+ continue?: boolean;
132
+ suppressOutput?: boolean;
133
+ systemMessage?: string;
134
+ hookSpecificOutput?: {
135
+ hookEventName?: string;
136
+ permissionDecision?: string;
137
+ permissionDecisionReason?: string;
138
+ additionalContext?: string;
139
+ [key: string]: unknown;
140
+ };
141
+ [key: string]: unknown;
142
+ }
@@ -1,4 +1,5 @@
1
1
  import type { SolutionFrontmatter, SolutionStatus } from './solution-format.js';
2
+ import type { AdaptiveLifecycleThresholds } from './meta-learning/types.js';
2
3
  export interface LifecycleResult {
3
4
  promoted: string[];
4
5
  demoted: string[];
@@ -11,8 +12,8 @@ export declare function nextStatus(current: SolutionStatus): SolutionStatus | nu
11
12
  * Spacing: 0.25 between levels for meaningful differentiation in matching scores.
12
13
  * Previous: 0.3/0.6/0.8/0.85 had only 0.05 gap between verified and mature. */
13
14
  export declare function statusConfidence(status: SolutionStatus): number;
14
- /** Check promotion eligibility */
15
- export declare function checkPromotion(fm: SolutionFrontmatter): boolean;
15
+ /** Check promotion eligibility (with optional adaptive thresholds) */
16
+ export declare function checkPromotion(fm: SolutionFrontmatter, thresholds?: AdaptiveLifecycleThresholds | null): boolean;
16
17
  /** Check if solution should be demoted due to confidence-status mismatch */
17
18
  export declare function checkConfidenceDemotion(fm: SolutionFrontmatter): SolutionStatus | null;
18
19
  /** Check if solution identifiers still exist in codebase (staleness detection) */
@@ -25,7 +26,7 @@ export declare function isStale(fm: SolutionFrontmatter): boolean;
25
26
  */
26
27
  export declare function updateSolutionFile(filePath: string, updates: Partial<SolutionFrontmatter>): boolean;
27
28
  /** Run lifecycle check on all solutions */
28
- export declare function runLifecycleCheck(sessionId?: string): LifecycleResult;
29
+ export declare function runLifecycleCheck(_sessionId?: string): LifecycleResult;
29
30
  /** Detect contradictions between solutions */
30
31
  export declare function detectContradictions(dirs: string[]): string[];
31
32
  /** Manual verify command: immediately promote to verified */