@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.
- package/CHANGELOG.md +76 -0
- package/README.ko.md +25 -14
- package/README.md +61 -17
- package/agents/analyst.md +48 -4
- package/agents/architect.md +39 -4
- package/agents/code-reviewer.md +107 -77
- package/agents/critic.md +47 -4
- package/agents/debugger.md +46 -4
- package/agents/designer.md +40 -4
- package/agents/executor.md +112 -30
- package/agents/explore.md +45 -5
- package/agents/git-master.md +48 -4
- package/agents/planner.md +121 -18
- package/agents/solution-evolver.md +115 -0
- package/agents/test-engineer.md +58 -4
- package/agents/verifier.md +92 -77
- package/commands/architecture-decision.md +127 -258
- package/commands/calibrate.md +225 -0
- package/commands/code-review.md +163 -178
- package/commands/compound.md +127 -68
- package/commands/deep-interview.md +212 -110
- package/commands/docker.md +68 -178
- package/commands/forge-loop.md +215 -0
- package/commands/learn.md +231 -0
- package/commands/retro.md +215 -0
- package/commands/ship.md +277 -0
- package/dist/cli.js +25 -9
- package/dist/core/auto-compound-runner.js +14 -0
- package/dist/core/config-injector.d.ts +2 -1
- package/dist/core/config-injector.js +2 -1
- package/dist/core/dashboard.d.ts +17 -0
- package/dist/core/dashboard.js +158 -2
- package/dist/core/harness.d.ts +6 -1
- package/dist/core/harness.js +75 -19
- package/dist/core/paths.d.ts +31 -1
- package/dist/core/paths.js +43 -2
- package/dist/core/spawn.d.ts +3 -2
- package/dist/core/spawn.js +27 -8
- package/dist/core/types.d.ts +34 -0
- package/dist/engine/compound-lifecycle.d.ts +4 -3
- package/dist/engine/compound-lifecycle.js +91 -46
- package/dist/engine/learn-cli.d.ts +1 -0
- package/dist/engine/learn-cli.js +182 -0
- package/dist/engine/meta-learning/adaptive-thresholds.d.ts +20 -0
- package/dist/engine/meta-learning/adaptive-thresholds.js +126 -0
- package/dist/engine/meta-learning/extraction-tuner.d.ts +15 -0
- package/dist/engine/meta-learning/extraction-tuner.js +99 -0
- package/dist/engine/meta-learning/matcher-weight-tuner.d.ts +21 -0
- package/dist/engine/meta-learning/matcher-weight-tuner.js +151 -0
- package/dist/engine/meta-learning/runner.d.ts +14 -0
- package/dist/engine/meta-learning/runner.js +90 -0
- package/dist/engine/meta-learning/scope-promoter.d.ts +21 -0
- package/dist/engine/meta-learning/scope-promoter.js +84 -0
- package/dist/engine/meta-learning/session-quality-scorer.d.ts +61 -0
- package/dist/engine/meta-learning/session-quality-scorer.js +166 -0
- package/dist/engine/meta-learning/types.d.ts +114 -0
- package/dist/engine/meta-learning/types.js +43 -0
- package/dist/engine/solution-candidate.d.ts +30 -0
- package/dist/engine/solution-candidate.js +124 -0
- package/dist/engine/solution-fitness.d.ts +52 -0
- package/dist/engine/solution-fitness.js +95 -0
- package/dist/engine/solution-fixup.d.ts +30 -0
- package/dist/engine/solution-fixup.js +116 -0
- package/dist/engine/solution-format.d.ts +10 -2
- package/dist/engine/solution-format.js +287 -57
- package/dist/engine/solution-index.d.ts +1 -1
- package/dist/engine/solution-index.js +10 -0
- package/dist/engine/solution-matcher.d.ts +7 -1
- package/dist/engine/solution-matcher.js +137 -37
- package/dist/engine/solution-outcomes.d.ts +70 -0
- package/dist/engine/solution-outcomes.js +242 -0
- package/dist/engine/solution-quarantine.d.ts +36 -0
- package/dist/engine/solution-quarantine.js +172 -0
- package/dist/engine/solution-weakness.d.ts +45 -0
- package/dist/engine/solution-weakness.js +225 -0
- package/dist/engine/solution-writer.d.ts +5 -0
- package/dist/engine/solution-writer.js +18 -0
- package/dist/fgx.js +12 -8
- package/dist/hooks/context-guard.d.ts +5 -0
- package/dist/hooks/context-guard.js +118 -2
- package/dist/hooks/hooks-generator.d.ts +3 -0
- package/dist/hooks/hooks-generator.js +23 -6
- package/dist/hooks/keyword-detector.js +16 -100
- package/dist/hooks/post-tool-failure.js +7 -0
- package/dist/hooks/skill-injector.d.ts +4 -3
- package/dist/hooks/skill-injector.js +6 -4
- package/dist/hooks/solution-injector.js +20 -0
- package/dist/host/codex-adapter.d.ts +10 -0
- package/dist/host/codex-adapter.js +154 -0
- package/dist/mcp/solution-reader.d.ts +5 -5
- package/dist/mcp/solution-reader.js +34 -24
- package/dist/mcp/tools.js +8 -0
- package/dist/services/session.d.ts +19 -0
- package/dist/services/session.js +62 -0
- package/hooks/hooks.json +2 -2
- package/package.json +2 -1
- package/skills/architecture-decision/SKILL.md +113 -257
- package/skills/calibrate/SKILL.md +207 -0
- package/skills/code-review/SKILL.md +151 -178
- package/skills/compound/SKILL.md +126 -68
- package/skills/deep-interview/SKILL.md +210 -110
- package/skills/docker/SKILL.md +57 -179
- package/skills/forge-loop/SKILL.md +198 -0
- package/skills/learn/SKILL.md +216 -0
- package/skills/retro/SKILL.md +199 -0
- package/skills/ship/SKILL.md +259 -0
- package/agents/code-simplifier.md +0 -197
- package/agents/performance-reviewer.md +0 -172
- package/agents/qa-tester.md +0 -158
- package/agents/refactoring-expert.md +0 -168
- package/agents/scientist.md +0 -144
- package/agents/security-reviewer.md +0 -137
- package/agents/writer.md +0 -184
- package/commands/api-design.md +0 -268
- package/commands/ci-cd.md +0 -270
- package/commands/database.md +0 -263
- package/commands/debug-detective.md +0 -99
- package/commands/documentation.md +0 -276
- package/commands/ecomode.md +0 -51
- package/commands/frontend.md +0 -271
- package/commands/git-master.md +0 -90
- package/commands/incident-response.md +0 -292
- package/commands/migrate.md +0 -101
- package/commands/performance.md +0 -288
- package/commands/refactor.md +0 -105
- package/commands/security-review.md +0 -288
- package/commands/specify.md +0 -128
- package/commands/tdd.md +0 -183
- package/commands/testing-strategy.md +0 -265
- package/skills/api-design/SKILL.md +0 -262
- package/skills/ci-cd/SKILL.md +0 -264
- package/skills/database/SKILL.md +0 -257
- package/skills/debug-detective/SKILL.md +0 -95
- package/skills/documentation/SKILL.md +0 -270
- package/skills/ecomode/SKILL.md +0 -46
- package/skills/frontend/SKILL.md +0 -265
- package/skills/git-master/SKILL.md +0 -86
- package/skills/incident-response/SKILL.md +0 -286
- package/skills/migrate/SKILL.md +0 -96
- package/skills/performance/SKILL.md +0 -282
- package/skills/refactor/SKILL.md +0 -100
- package/skills/security-review/SKILL.md +0 -282
- package/skills/specify/SKILL.md +0 -122
- package/skills/tdd/SKILL.md +0 -178
- package/skills/testing-strategy/SKILL.md +0 -260
package/dist/core/harness.js
CHANGED
|
@@ -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 (
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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(
|
|
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();
|
package/dist/core/paths.d.ts
CHANGED
|
@@ -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", "
|
|
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 — 팀 팩 연결 파일 */
|
package/dist/core/paths.js
CHANGED
|
@@ -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',
|
|
84
|
-
'
|
|
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) {
|
package/dist/core/spawn.d.ts
CHANGED
|
@@ -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
|
|
9
|
+
export declare function spawnClaudeWithResume(args: string[], context: V1HarnessContext, contextFactory: () => Promise<V1HarnessContext>, runtime?: RuntimeHost): Promise<void>;
|
package/dist/core/spawn.js
CHANGED
|
@@ -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
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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)
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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(
|
|
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 */
|