@wooojin/forgen 0.4.12 → 0.5.0
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 +1 -1
- package/CHANGELOG.md +109 -0
- package/README.ja.md +4 -4
- package/README.md +89 -27
- package/README.zh.md +4 -4
- package/assets/claude/commands/forge-loop.md +7 -1
- package/assets/claude/commands/ship.md +18 -1
- package/assets/opencode/forgen.ts +52 -0
- package/dist/checks/_shared/meta-guard-dispatch.d.ts +7 -0
- package/dist/checks/_shared/meta-guard-dispatch.js +12 -2
- package/dist/checks/_shared/model-profile.d.ts +25 -0
- package/dist/checks/_shared/model-profile.js +63 -0
- package/dist/cli.js +99 -153
- package/dist/core/auto-compound-runner.d.ts +0 -11
- package/dist/core/auto-compound-runner.js +127 -79
- package/dist/core/compound-consent.d.ts +15 -0
- package/dist/core/compound-consent.js +48 -0
- package/dist/core/compound-sweep-cli.d.ts +57 -0
- package/dist/core/compound-sweep-cli.js +354 -0
- package/dist/core/config-injector.d.ts +16 -1
- package/dist/core/config-injector.js +47 -28
- package/dist/core/dashboard-cli.js +40 -16
- package/dist/core/dashboard.d.ts +3 -4
- package/dist/core/dashboard.js +15 -34
- package/dist/core/dev-cli.d.ts +13 -0
- package/dist/core/dev-cli.js +70 -0
- package/dist/core/doctor.d.ts +16 -5
- package/dist/core/doctor.js +161 -176
- package/dist/core/drift-score.d.ts +2 -0
- package/dist/core/drift-score.js +9 -1
- package/dist/core/harness.js +115 -43
- package/dist/core/health-cli.d.ts +2 -0
- package/dist/core/health-cli.js +6 -1
- package/dist/core/host-detect.d.ts +3 -1
- package/dist/core/host-detect.js +26 -1
- package/dist/core/migrate-cli.js +15 -0
- package/dist/core/migrate-evidence-host.d.ts +2 -1
- package/dist/core/migrate-tenetx.d.ts +50 -0
- package/dist/core/migrate-tenetx.js +262 -0
- package/dist/core/probe-workflow-cli.d.ts +3 -3
- package/dist/core/probe-workflow-cli.js +13 -13
- package/dist/core/recall-cli.js +1 -1
- package/dist/core/regress-map-cli.js +1 -1
- package/dist/core/rendered-rules-manifest.d.ts +29 -0
- package/dist/core/rendered-rules-manifest.js +60 -0
- package/dist/core/session-store.js +14 -3
- package/dist/core/settings-injector.d.ts +3 -0
- package/dist/core/settings-injector.js +12 -16
- package/dist/core/spawn.d.ts +11 -1
- package/dist/core/spawn.js +79 -7
- package/dist/core/state-gc.js +1 -0
- package/dist/core/status-cli.d.ts +20 -0
- package/dist/core/status-cli.js +100 -0
- package/dist/core/statusline-cli.d.ts +7 -0
- package/dist/core/statusline-cli.js +63 -19
- package/dist/core/transcript-summary.d.ts +18 -0
- package/dist/core/transcript-summary.js +81 -0
- package/dist/core/trust-layer-intent.d.ts +21 -1
- package/dist/core/trust-layer-intent.js +7 -0
- package/dist/core/types.d.ts +3 -2
- package/dist/core/uninstall.js +12 -0
- package/dist/core/usage-telemetry.d.ts +7 -1
- package/dist/core/usage-telemetry.js +7 -9
- package/dist/core/v1-bootstrap.js +1 -1
- package/dist/core/watch-cli.js +1 -1
- package/dist/engine/compound-extractor.js +10 -0
- package/dist/engine/compound-loop.js +35 -6
- package/dist/engine/compound-share.d.ts +85 -0
- package/dist/engine/compound-share.js +606 -0
- package/dist/engine/correction-cluster-runner.d.ts +38 -0
- package/dist/engine/correction-cluster-runner.js +188 -0
- package/dist/engine/correction-clustering.d.ts +80 -0
- package/dist/engine/correction-clustering.js +167 -0
- package/dist/engine/enforce-classifier.d.ts +10 -1
- package/dist/engine/enforce-classifier.js +111 -22
- package/dist/engine/extraction-session.js +10 -3
- package/dist/engine/private-filter.d.ts +36 -0
- package/dist/engine/private-filter.js +100 -0
- package/dist/engine/ranking-pipeline.js +4 -2
- package/dist/engine/relevance-gate.d.ts +12 -0
- package/dist/engine/relevance-gate.js +12 -0
- package/dist/engine/roi-demotion.d.ts +79 -0
- package/dist/engine/roi-demotion.js +159 -0
- package/dist/engine/solution-format.d.ts +1 -0
- package/dist/engine/solution-format.js +26 -0
- package/dist/engine/solution-matcher.js +10 -1
- package/dist/fgx.js +19 -6
- package/dist/forge/cli.js +8 -2
- package/dist/hooks/compound-reflection.js +6 -1
- package/dist/hooks/context-guard.d.ts +16 -1
- package/dist/hooks/context-guard.js +92 -46
- package/dist/hooks/post-tool-use.js +2 -3
- package/dist/hooks/pre-compact.js +14 -0
- package/dist/hooks/pre-tool-use.js +5 -1
- package/dist/hooks/shared/stop-triggers.d.ts +29 -2
- package/dist/hooks/shared/stop-triggers.js +35 -2
- package/dist/hooks/solution-injector.d.ts +28 -0
- package/dist/hooks/solution-injector.js +126 -28
- package/dist/hooks/stop-guard.js +5 -2
- package/dist/hooks/subagent-stop-guard.js +4 -1
- package/dist/host/capabilities-claude.js +1 -0
- package/dist/host/capabilities-codex.js +1 -0
- package/dist/host/capabilities-opencode.d.ts +26 -0
- package/dist/host/capabilities-opencode.js +78 -0
- package/dist/host/capabilities-registry.d.ts +7 -0
- package/dist/host/capabilities-registry.js +14 -0
- package/dist/host/exec-host.d.ts +4 -3
- package/dist/host/exec-host.js +2 -0
- package/dist/host/host-binding.d.ts +27 -0
- package/dist/host/host-binding.js +11 -0
- package/dist/host/host-runtime.js +18 -0
- package/dist/host/install-codex.d.ts +8 -0
- package/dist/host/install-codex.js +2 -2
- package/dist/host/install-opencode.d.ts +38 -0
- package/dist/host/install-opencode.js +148 -0
- package/dist/host/install-orchestrator.d.ts +4 -1
- package/dist/host/install-orchestrator.js +20 -1
- package/dist/host/invoke-agent.d.ts +3 -2
- package/dist/host/opencode/context-cli.d.ts +15 -0
- package/dist/host/opencode/context-cli.js +25 -0
- package/dist/host/opencode/guard-cli.d.ts +13 -0
- package/dist/host/opencode/guard-cli.js +39 -0
- package/dist/host/opencode/plugin/forgen.d.ts +31 -0
- package/dist/host/opencode/plugin/forgen.js +60 -0
- package/dist/host/opencode/translate.d.ts +49 -0
- package/dist/host/opencode/translate.js +96 -0
- package/dist/host/parity-harness.d.ts +10 -2
- package/dist/host/projection.js +11 -0
- package/dist/mcp/tools.js +17 -4
- package/dist/store/evidence-store.d.ts +2 -6
- package/dist/store/evidence-store.js +68 -14
- package/dist/store/host-mismatch.d.ts +2 -1
- package/dist/store/host-mismatch.js +8 -8
- package/dist/store/profile-store.d.ts +3 -2
- package/dist/store/types.d.ts +9 -2
- package/package.json +3 -2
- package/plugin.json +2 -2
- package/skills/forge-loop/SKILL.md +7 -1
- package/skills/ship/SKILL.md +18 -1
package/dist/core/dashboard.js
CHANGED
|
@@ -386,8 +386,7 @@ function renderMultiHost(data) {
|
|
|
386
386
|
return lines.join('\n');
|
|
387
387
|
}
|
|
388
388
|
/**
|
|
389
|
-
*
|
|
390
|
-
* evidence 파일(교정 기록)과 compound 활용률을 교차 분석하여 "쓸수록 나아진다"를 정량화.
|
|
389
|
+
* 교정·활동 요약 수집 (관찰). evidence 파일(교정 기록) 기반. 인과 효과/절약은 미주장.
|
|
391
390
|
*/
|
|
392
391
|
export function collectLearningCurve() {
|
|
393
392
|
const now = Date.now();
|
|
@@ -429,14 +428,15 @@ export function collectLearningCurve() {
|
|
|
429
428
|
}
|
|
430
429
|
}
|
|
431
430
|
catch { /* fail-open */ }
|
|
432
|
-
//
|
|
433
|
-
|
|
431
|
+
// 추세: 전주 대비 카운트 델타의 *관찰* (30%↓=down, 30%↑=up). forgen 인과 효과가
|
|
432
|
+
// 아니라 단순 교정 횟수 변화 — 라벨도 개선/악화(가치 판단)가 아닌 감소/증가로 둔다.
|
|
433
|
+
let correctionTrend = 'flat';
|
|
434
434
|
if (correctionsPrev7d > 0) {
|
|
435
435
|
const delta = (correctionsLast7d - correctionsPrev7d) / correctionsPrev7d;
|
|
436
436
|
if (delta < -0.3)
|
|
437
|
-
correctionTrend = '
|
|
437
|
+
correctionTrend = 'down';
|
|
438
438
|
else if (delta > 0.3)
|
|
439
|
-
correctionTrend = '
|
|
439
|
+
correctionTrend = 'up';
|
|
440
440
|
}
|
|
441
441
|
// 상위 교정 축
|
|
442
442
|
const topCorrectionAxes = Array.from(axisCounts.entries())
|
|
@@ -445,59 +445,40 @@ export function collectLearningCurve() {
|
|
|
445
445
|
.map(([axis, count]) => ({ axis, count }));
|
|
446
446
|
// 세션 수: evidence 날짜 기준 (고유 날짜 × 평균 2세션/일 가정)
|
|
447
447
|
const sessionsAnalyzed = uniqueDays.size * 2;
|
|
448
|
-
//
|
|
449
|
-
// (경쟁자 분석에서 도출한 경험적 수치 — 카운터팩추얼의 하한 추정)
|
|
450
|
-
const injection = collectInjectionActivity();
|
|
451
|
-
let successfulInjections = 0;
|
|
452
|
-
try {
|
|
453
|
-
for (const rec of injection.recentInjections ?? []) {
|
|
454
|
-
const ts = new Date(rec.ts).getTime();
|
|
455
|
-
if (Number.isFinite(ts) && now - ts < SEVEN_DAYS_MS)
|
|
456
|
-
successfulInjections++;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
catch { /* fail-open */ }
|
|
460
|
-
const estimatedMinutesSaved = Math.round(successfulInjections * 8);
|
|
448
|
+
// (은퇴) estimatedMinutesSaved: "주입당 8분 절약" 카운터팩추얼은 honest-null 로 제거.
|
|
461
449
|
return {
|
|
462
450
|
correctionsLast7d,
|
|
463
451
|
correctionsPrev7d,
|
|
464
452
|
correctionTrend,
|
|
465
453
|
evidenceTotalDays: uniqueDays.size,
|
|
466
454
|
sessionsAnalyzed,
|
|
467
|
-
estimatedMinutesSaved,
|
|
468
455
|
topCorrectionAxes,
|
|
469
456
|
};
|
|
470
457
|
}
|
|
471
458
|
function renderLearningCurve(data) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
459
|
+
// 관찰: 교정 카운트 델타. 개선/악화 색(가치 판단) 대신 중립 감소/증가 표기.
|
|
460
|
+
const trendIcon = data.correctionTrend === 'down'
|
|
461
|
+
? dim('↓ 감소')
|
|
462
|
+
: data.correctionTrend === 'up'
|
|
463
|
+
? dim('↑ 증가')
|
|
476
464
|
: dim('→ 유지');
|
|
477
465
|
const axisLines = data.topCorrectionAxes.length > 0
|
|
478
466
|
? data.topCorrectionAxes.map(a => ` ${a.axis}: ${a.count}회`).join('\n')
|
|
479
467
|
: ` ${dim('(아직 교정 데이터 없음)')}`;
|
|
480
|
-
const savedHours = Math.floor(data.estimatedMinutesSaved / 60);
|
|
481
|
-
const savedMins = data.estimatedMinutesSaved % 60;
|
|
482
|
-
const savedStr = savedHours > 0 ? `${savedHours}시간 ${savedMins}분` : `${savedMins}분`;
|
|
483
468
|
return [
|
|
484
|
-
` ${bold('
|
|
469
|
+
` ${bold('📊 교정·활동 요약')} ${dim('(관찰 — 인과 효과/절약 미주장)')}`,
|
|
485
470
|
``,
|
|
486
471
|
` 교정 추이 (지난 7일):`,
|
|
487
472
|
` 이번 주: ${data.correctionsLast7d}건`,
|
|
488
473
|
` 지난 주: ${data.correctionsPrev7d}건`,
|
|
489
|
-
`
|
|
474
|
+
` 카운트 변화: ${trendIcon} ${dim('(관찰치, forgen 효과 아님)')}`,
|
|
490
475
|
``,
|
|
491
476
|
` 주요 교정 축 (누적):`,
|
|
492
477
|
axisLines,
|
|
493
478
|
``,
|
|
494
|
-
` 누적
|
|
479
|
+
` 누적 활동:`,
|
|
495
480
|
` 활동한 일수: ${data.evidenceTotalDays}일`,
|
|
496
481
|
` 분석된 세션: 약 ${data.sessionsAnalyzed}회`,
|
|
497
|
-
``,
|
|
498
|
-
` ${cyan('추정 절약 시간')} (compound 주입 성공 기반):`,
|
|
499
|
-
` ${bold(savedStr)} ${dim('(지난 7일)')}`,
|
|
500
|
-
` ${dim('※ compound가 힌트를 제공한 매 1회당 평균 8분 절약 가정')}`,
|
|
501
482
|
].join('\n');
|
|
502
483
|
}
|
|
503
484
|
function renderFitnessSummary() {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgen dev — 개발/유지보수 명령 네임스페이스 (Wave 1 W1-3, feature-audit 2026-07-21).
|
|
3
|
+
*
|
|
4
|
+
* probe-workflow / parity / migrate / regress-map 은 개발·CI·스키마 유지보수용으로,
|
|
5
|
+
* 일반 사용자 표면(top-level)을 어지럽혔다. `forgen dev <sub>` 하나로 모아 사용자
|
|
6
|
+
* 명령 표면을 정리한다. 각 핸들러는 기존 것을 재사용.
|
|
7
|
+
*
|
|
8
|
+
* forgen dev probe-workflow arm|report|status ADR-009 §1 훅 발화 측정
|
|
9
|
+
* forgen dev parity codex [--dry-run] host parity (source 체크아웃 전용)
|
|
10
|
+
* forgen dev migrate [implicit-feedback|...] one-shot 스키마 마이그레이션
|
|
11
|
+
* forgen dev regress-map [--days N --top N] 최근 fix 집중 파일
|
|
12
|
+
*/
|
|
13
|
+
export declare function handleDev(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgen dev — 개발/유지보수 명령 네임스페이스 (Wave 1 W1-3, feature-audit 2026-07-21).
|
|
3
|
+
*
|
|
4
|
+
* probe-workflow / parity / migrate / regress-map 은 개발·CI·스키마 유지보수용으로,
|
|
5
|
+
* 일반 사용자 표면(top-level)을 어지럽혔다. `forgen dev <sub>` 하나로 모아 사용자
|
|
6
|
+
* 명령 표면을 정리한다. 각 핸들러는 기존 것을 재사용.
|
|
7
|
+
*
|
|
8
|
+
* forgen dev probe-workflow arm|report|status ADR-009 §1 훅 발화 측정
|
|
9
|
+
* forgen dev parity codex [--dry-run] host parity (source 체크아웃 전용)
|
|
10
|
+
* forgen dev migrate [implicit-feedback|...] one-shot 스키마 마이그레이션
|
|
11
|
+
* forgen dev regress-map [--days N --top N] 최근 fix 집중 파일
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
function printDevHelp() {
|
|
17
|
+
console.log(`
|
|
18
|
+
forgen dev — developer / maintenance utilities
|
|
19
|
+
|
|
20
|
+
forgen dev probe-workflow arm|report|status Measure hook firing in dynamic-workflow subagents (ADR-009 §1)
|
|
21
|
+
forgen dev parity codex [--dry-run] Host parity checks (source checkout only)
|
|
22
|
+
forgen dev migrate [target] One-shot schema migration
|
|
23
|
+
forgen dev regress-map [--days N --top N] Top fix-touched files
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
async function runParity(args) {
|
|
27
|
+
const sub = args[0];
|
|
28
|
+
if (sub !== 'codex') {
|
|
29
|
+
console.log('Usage:\n forgen dev parity codex [--dry-run]\n\nNotes:\n - source 체크아웃에서만 작동합니다 (tests/ 디렉토리 필요).\n - npm install 로 설치된 패키지에서는 run-parity.sh 가 없습니다.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const here = path.dirname(fileURLToPath(import.meta.url)); // dist/core
|
|
33
|
+
const scriptPath = path.resolve(here, '..', '..', 'tests', 'e2e', 'codex', 'run-parity.sh');
|
|
34
|
+
if (!fs.existsSync(scriptPath)) {
|
|
35
|
+
console.error('[forgen] run-parity.sh 는 source 체크아웃에서만 작동. 직접 git clone 후 실행하세요.');
|
|
36
|
+
console.error(` expected: ${scriptPath}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const { spawnSync } = await import('node:child_process');
|
|
40
|
+
const dryRun = args.includes('--dry-run');
|
|
41
|
+
const result = spawnSync('bash', [scriptPath, ...(dryRun ? ['--dry-run'] : [])], { stdio: 'inherit' });
|
|
42
|
+
if (result.status !== 0)
|
|
43
|
+
process.exit(result.status ?? 1);
|
|
44
|
+
}
|
|
45
|
+
export async function handleDev(args) {
|
|
46
|
+
const sub = args[0];
|
|
47
|
+
const rest = args.slice(1);
|
|
48
|
+
switch (sub) {
|
|
49
|
+
case 'probe-workflow': {
|
|
50
|
+
const { handleProbeWorkflow } = await import('./probe-workflow-cli.js');
|
|
51
|
+
await handleProbeWorkflow(rest);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
case 'parity':
|
|
55
|
+
await runParity(rest);
|
|
56
|
+
return;
|
|
57
|
+
case 'migrate': {
|
|
58
|
+
const { handleMigrate } = await import('./migrate-cli.js');
|
|
59
|
+
await handleMigrate(rest);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
case 'regress-map': {
|
|
63
|
+
const { handleRegressMap } = await import('./regress-map-cli.js');
|
|
64
|
+
await handleRegressMap(rest);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
printDevHelp();
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/core/doctor.d.ts
CHANGED
|
@@ -4,16 +4,27 @@ export interface DoctorOptions {
|
|
|
4
4
|
pruneState?: boolean;
|
|
5
5
|
/**
|
|
6
6
|
* When true, auto-fix recoverable failures (e.g. missing plugin cache /
|
|
7
|
-
* stale installPath) by running `
|
|
8
|
-
*
|
|
7
|
+
* stale installPath) by running `node scripts/postinstall.js` inside the
|
|
8
|
+
* forgen install directory (`npm run build` 는 dist 부재 시에만 — W1-4).
|
|
9
|
+
* Triggered by `forgen doctor --repair`.
|
|
9
10
|
*
|
|
10
|
-
* v0.4.8 (E3) — 이전엔
|
|
11
|
-
*
|
|
12
|
-
* doctor 흐름은 정상 종료.
|
|
11
|
+
* v0.4.8 (E3) — 이전엔 안내문만 출력. fail-open: repair 실패해도 doctor
|
|
12
|
+
* 흐름은 정상 종료. W1-4 (ADR-010) — 실행 사실이 아니라 재검증 결과를 보고.
|
|
13
13
|
*/
|
|
14
14
|
repair?: boolean;
|
|
15
15
|
/** When true, run only essential checks (Tools + Plugins + Directories +
|
|
16
16
|
* Initialization Status) for fast onboarding verification. ~10 lines output. */
|
|
17
17
|
quick?: boolean;
|
|
18
|
+
/** W1-1 (ADR-010): tenetx/legacy 규칙 스프롤 스캔 (dry-run — 삭제는
|
|
19
|
+
* `forgen migrate tenetx` 가 수행). native /doctor 가 비용을 flag 하면
|
|
20
|
+
* forgen 이 provenance 로 안전하게 회수하는 보완 관계. */
|
|
21
|
+
reclaim?: boolean;
|
|
22
|
+
/** W2-1 (ADR-010): hook timing 표 등 상세 진단 표시. slow-hook 감지는
|
|
23
|
+
* native /doctor 영역이라 기본 출력에서 강등됨 (수집은 유지). */
|
|
24
|
+
verbose?: boolean;
|
|
18
25
|
}
|
|
26
|
+
/** plugin cache 디렉토리에 버전 엔트리가 하나 이상 있는가 (check + repair 재검증 공용) */
|
|
27
|
+
export declare function pluginCacheOk(): boolean;
|
|
28
|
+
/** installed_plugins.json 의 forgen entry 가 존재하며 installPath 가 살아있는가 */
|
|
29
|
+
export declare function pluginRegisteredOk(): boolean;
|
|
19
30
|
export declare function runDoctor(opts?: DoctorOptions): Promise<void>;
|
package/dist/core/doctor.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as os from 'node:os';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { FORGEN_HOME, LAB_DIR, ME_BEHAVIOR, ME_DIR, ME_SOLUTIONS, ME_RULES,
|
|
6
|
+
import { FORGEN_HOME, LAB_DIR, ME_BEHAVIOR, ME_DIR, ME_SOLUTIONS, ME_RULES, PACKS_DIR, SESSIONS_DIR, STATE_DIR, V1_SESSIONS_DIR } from './paths.js';
|
|
7
7
|
import { getTimingStats } from '../hooks/shared/hook-timing.js';
|
|
8
8
|
import { countSessionScopedFiles, pruneState } from './state-gc.js';
|
|
9
9
|
import { summarizeAllByHost } from '../store/host-mismatch.js';
|
|
@@ -57,7 +57,7 @@ function renderCodexParity() {
|
|
|
57
57
|
console.log(' [Codex Parity]');
|
|
58
58
|
const parityPath = path.join(STATE_DIR, 'parity-result.json');
|
|
59
59
|
if (!fs.existsSync(parityPath)) {
|
|
60
|
-
console.log(' △ Codex parity 미실행 — tests/e2e/codex/run-parity.sh 또는 forgen parity codex');
|
|
60
|
+
console.log(' △ Codex parity 미실행 — tests/e2e/codex/run-parity.sh 또는 forgen dev parity codex');
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
let data;
|
|
@@ -90,10 +90,51 @@ function renderCodexParity() {
|
|
|
90
90
|
console.log(` ✓ Codex parity green (last run: ${timeStr},${version})`);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
/** plugin cache 디렉토리에 버전 엔트리가 하나 이상 있는가 (check + repair 재검증 공용) */
|
|
94
|
+
export function pluginCacheOk() {
|
|
95
|
+
const pluginCacheBase = path.join(os.homedir(), '.claude', 'plugins', 'cache', 'forgen-local', 'forgen');
|
|
96
|
+
if (!exists(pluginCacheBase))
|
|
97
|
+
return false;
|
|
98
|
+
try {
|
|
99
|
+
return fs.readdirSync(pluginCacheBase).some(f => {
|
|
100
|
+
try {
|
|
101
|
+
const lstat = fs.lstatSync(path.join(pluginCacheBase, f));
|
|
102
|
+
return lstat.isDirectory() || lstat.isSymbolicLink();
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** installed_plugins.json 의 forgen entry 가 존재하며 installPath 가 살아있는가 */
|
|
114
|
+
export function pluginRegisteredOk() {
|
|
115
|
+
const installedPluginsPath = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
116
|
+
if (!exists(installedPluginsPath))
|
|
117
|
+
return false;
|
|
118
|
+
try {
|
|
119
|
+
const installed = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
|
|
120
|
+
const entry = installed?.plugins?.['forgen@forgen-local'];
|
|
121
|
+
if (Array.isArray(entry) && entry.length > 0) {
|
|
122
|
+
const installPath = entry[0]?.installPath;
|
|
123
|
+
return !!installPath && exists(installPath);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch { /* ignore */ }
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
93
129
|
/**
|
|
94
130
|
* v0.4.8 (E3): plugin cache / installPath 진단이 실패했을 때 자동 복구.
|
|
95
|
-
* forgen 패키지 디렉토리에서 `npm run build` + postinstall 을 차례로 실행.
|
|
96
131
|
* 실패해도 doctor 자체는 계속 진행 (fail-open).
|
|
132
|
+
*
|
|
133
|
+
* W1-4 수정 (ADR-010, 실측 2026-07-16): 이전 구현은 무조건 `npm run build` 를
|
|
134
|
+
* 먼저 실행했는데, 글로벌 설치엔 devDeps(tsc)가 없어 MODULE_NOT_FOUND 로 실패
|
|
135
|
+
* → postinstall 에 도달하지 못해 캐시가 영원히 복구되지 않았다. published
|
|
136
|
+
* 패키지는 dist 가 이미 있으므로 build 는 dist 부재(dev checkout)시에만 시도.
|
|
137
|
+
* 또한 "실행했다"가 아니라 "복구됐다"를 재검증 후 보고한다.
|
|
97
138
|
*/
|
|
98
139
|
function attemptPluginRepair() {
|
|
99
140
|
try {
|
|
@@ -102,20 +143,53 @@ function attemptPluginRepair() {
|
|
|
102
143
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
103
144
|
const pkgRoot = path.resolve(here, '..', '..');
|
|
104
145
|
console.log(`\n [Repair] forgen 패키지 자가복구 시도 — ${pkgRoot}`);
|
|
105
|
-
|
|
146
|
+
if (!exists(path.join(pkgRoot, 'dist', 'cli.js'))) {
|
|
147
|
+
// dev checkout 등 dist 부재 시에만 build (devDeps 가 있는 환경)
|
|
148
|
+
execFileSync('npm', ['run', 'build'], { cwd: pkgRoot, stdio: 'inherit' });
|
|
149
|
+
}
|
|
106
150
|
execFileSync('node', ['scripts/postinstall.js'], { cwd: pkgRoot, stdio: 'inherit' });
|
|
107
|
-
|
|
108
|
-
|
|
151
|
+
// 결과 재검증 — 성공 주장은 검증된 상태에만 한다.
|
|
152
|
+
const ok = pluginCacheOk() && pluginRegisteredOk();
|
|
153
|
+
if (ok) {
|
|
154
|
+
console.log(' [Repair] ✓ 복구 확인 — plugin cache/registry 재검증 통과');
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.warn(' [Repair] ✗ postinstall 은 실행됐지만 재검증 실패 — 수동 확인 필요');
|
|
158
|
+
console.warn(' [Repair] 수동 복구: cd <forgen pkgRoot> && node scripts/postinstall.js');
|
|
159
|
+
}
|
|
160
|
+
return ok;
|
|
109
161
|
}
|
|
110
162
|
catch (e) {
|
|
111
163
|
console.warn(` [Repair] 실패: ${e instanceof Error ? e.message : String(e)}`);
|
|
112
|
-
console.warn(' [Repair] 수동 복구: cd <forgen pkgRoot> &&
|
|
164
|
+
console.warn(' [Repair] 수동 복구: cd <forgen pkgRoot> && node scripts/postinstall.js (dist 부재 시 npm run build 선행)');
|
|
113
165
|
return false;
|
|
114
166
|
}
|
|
115
167
|
}
|
|
168
|
+
/** W1-1: --reclaim 스캔 (읽기 전용) — full/quick 양쪽에서 재사용 */
|
|
169
|
+
async function runReclaimScan() {
|
|
170
|
+
try {
|
|
171
|
+
const { runReclaim, printReclaimResult } = await import('./migrate-tenetx.js');
|
|
172
|
+
printReclaimResult(runReclaim({ cwd: process.cwd(), dryRun: true }));
|
|
173
|
+
console.log(' 실제 회수: forgen migrate tenetx [--yes] [--apply-settings]');
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
console.warn(` [reclaim] 스캔 실패: ${e instanceof Error ? e.message : String(e)}`);
|
|
177
|
+
}
|
|
178
|
+
console.log();
|
|
179
|
+
}
|
|
180
|
+
/** W1-4: repair 성공 시 Summary 에서 걷어내는 대상 — check() 라벨과 단일 소스 */
|
|
181
|
+
const REPAIRABLE_PLUGIN_LABELS = Object.freeze({
|
|
182
|
+
cache: 'forgen plugin cache',
|
|
183
|
+
registered: 'forgen plugin registered & installPath exists',
|
|
184
|
+
});
|
|
116
185
|
export async function runDoctor(opts = {}) {
|
|
117
186
|
failedChecks = [];
|
|
118
|
-
console.log('\n Forgen — Diagnostics
|
|
187
|
+
console.log('\n Forgen — Diagnostics');
|
|
188
|
+
// ADR-010 W2-1 경계 선언: 일반 환경 건강(컨텍스트 비용, unused skill/MCP,
|
|
189
|
+
// CLAUDE.md 정리, slow hook)은 native /doctor 의 영역. 이 명령은 forgen
|
|
190
|
+
// 자체 기계(플러그인 캐시·훅 오류·상태 위생)와 효과-측정 게이트만 본다.
|
|
191
|
+
console.log(' 환경 전반 진단은 Claude Code native /doctor 를 사용하세요.');
|
|
192
|
+
console.log(' 이 명령은 forgen 자체 기계와 효과-측정 게이트를 검사합니다.\n');
|
|
119
193
|
section('Tools');
|
|
120
194
|
check('claude CLI', commandExists('claude'));
|
|
121
195
|
check('tmux', commandExists('tmux'));
|
|
@@ -125,46 +199,24 @@ export async function runDoctor(opts = {}) {
|
|
|
125
199
|
section('Plugins');
|
|
126
200
|
const ralphLoopInstalled = exists(path.join(os.homedir(), '.claude', 'plugins', 'cache', 'claude-plugins-official', 'ralph-loop'));
|
|
127
201
|
check('ralph-loop plugin', ralphLoopInstalled, 'Required for ralph mode auto-iteration. Install: claude plugins install ralph-loop');
|
|
128
|
-
// forgen 플러그인 캐시
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
if (exists(pluginCacheBase)) {
|
|
132
|
-
const versions = fs.readdirSync(pluginCacheBase).filter(f => {
|
|
133
|
-
try {
|
|
134
|
-
const lstat = fs.lstatSync(path.join(pluginCacheBase, f));
|
|
135
|
-
return lstat.isDirectory() || lstat.isSymbolicLink();
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
forgenPluginCacheOk = versions.length > 0;
|
|
142
|
-
}
|
|
143
|
-
check('forgen plugin cache', forgenPluginCacheOk, opts.repair
|
|
202
|
+
// forgen 플러그인 캐시 / registry 정합성 — 훅 실행의 필수 전제
|
|
203
|
+
const forgenPluginCacheOk = pluginCacheOk();
|
|
204
|
+
check(REPAIRABLE_PLUGIN_LABELS.cache, forgenPluginCacheOk, opts.repair
|
|
144
205
|
? 'Hook execution requires plugin cache. Attempting auto-repair (--repair)…'
|
|
145
|
-
: 'Hook execution requires plugin cache. Fix:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
let pluginRegistered = false;
|
|
149
|
-
if (exists(installedPluginsPath)) {
|
|
150
|
-
try {
|
|
151
|
-
const installed = JSON.parse(fs.readFileSync(installedPluginsPath, 'utf-8'));
|
|
152
|
-
const entry = installed?.plugins?.['forgen@forgen-local'];
|
|
153
|
-
if (Array.isArray(entry) && entry.length > 0) {
|
|
154
|
-
const installPath = entry[0]?.installPath;
|
|
155
|
-
pluginRegistered = !!installPath && exists(installPath);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
catch { /* ignore */ }
|
|
159
|
-
}
|
|
160
|
-
check('forgen plugin registered & installPath exists', pluginRegistered, opts.repair
|
|
206
|
+
: 'Hook execution requires plugin cache. Fix: node scripts/postinstall.js in the forgen package (or rerun with --repair)');
|
|
207
|
+
const pluginRegistered = pluginRegisteredOk();
|
|
208
|
+
check(REPAIRABLE_PLUGIN_LABELS.registered, pluginRegistered, opts.repair
|
|
161
209
|
? 'Plugin registered but installPath missing on disk. Attempting auto-repair (--repair)…'
|
|
162
|
-
: 'Plugin registered but installPath missing on disk. Fix:
|
|
163
|
-
// v0.4.8 (E3): plugin cache 또는 installPath 가 깨졌고 --repair 가
|
|
164
|
-
// 있으면
|
|
165
|
-
//
|
|
210
|
+
: 'Plugin registered but installPath missing on disk. Fix: node scripts/postinstall.js in the forgen package (or rerun with --repair)');
|
|
211
|
+
// v0.4.8 (E3) + W1-4: plugin cache 또는 installPath 가 깨졌고 --repair 가
|
|
212
|
+
// 켜져 있으면 자가복구. 재검증까지 통과하면 failedChecks 에서 해당 항목을
|
|
213
|
+
// 걷어내 Summary 가 복구된 상태를 정직하게 반영하게 한다.
|
|
166
214
|
if (opts.repair && (!forgenPluginCacheOk || !pluginRegistered)) {
|
|
167
|
-
attemptPluginRepair();
|
|
215
|
+
const repaired = attemptPluginRepair();
|
|
216
|
+
if (repaired) {
|
|
217
|
+
const pluginLabels = new Set(Object.values(REPAIRABLE_PLUGIN_LABELS));
|
|
218
|
+
failedChecks = failedChecks.filter(f => !pluginLabels.has(f.label));
|
|
219
|
+
}
|
|
168
220
|
}
|
|
169
221
|
console.log();
|
|
170
222
|
section('Directories');
|
|
@@ -204,6 +256,9 @@ export async function runDoctor(opts = {}) {
|
|
|
204
256
|
}
|
|
205
257
|
console.log();
|
|
206
258
|
if (opts.quick) {
|
|
259
|
+
// --reclaim 은 읽기 전용 스캔이라 --quick 과 조합 가능 (silent-ignore 방지)
|
|
260
|
+
if (opts.reclaim)
|
|
261
|
+
await runReclaimScan();
|
|
207
262
|
console.log();
|
|
208
263
|
if (failedChecks.length === 0) {
|
|
209
264
|
console.log(' All essential checks passed.\n');
|
|
@@ -213,9 +268,32 @@ export async function runDoctor(opts = {}) {
|
|
|
213
268
|
}
|
|
214
269
|
return;
|
|
215
270
|
}
|
|
271
|
+
// 리뷰 SEV-3 (c): critic-review 룰의 트리거가 stale(skip-review 시그널 미포함)이면 발견 nudge.
|
|
272
|
+
// "고쳤는데 기존 유저에겐 안 닿는" 상태 방지 — 재-bake 명령을 안내한다.
|
|
273
|
+
section('Rules');
|
|
274
|
+
try {
|
|
275
|
+
const [{ loadActiveRules }, { needsCriticTriggerMigration }] = await Promise.all([
|
|
276
|
+
import('../store/rule-store.js'),
|
|
277
|
+
import('../engine/enforce-classifier.js'),
|
|
278
|
+
]);
|
|
279
|
+
const outdated = loadActiveRules().filter(needsCriticTriggerMigration);
|
|
280
|
+
check('critic-review 룰 트리거 최신', outdated.length === 0, `${outdated.length}개 critic 룰이 구 트리거(skip-review 미포함) — \`forgen rule classify --apply --force\` 로 갱신 권장`);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
/* rule-store 로드 실패는 무해 — 스킵 */
|
|
284
|
+
}
|
|
285
|
+
console.log();
|
|
216
286
|
section('Environment');
|
|
217
287
|
check('Inside tmux session', !!process.env.TMUX, 'FORGEN auto-compound relies on tmux. Launch: tmux new -s forgen');
|
|
218
288
|
check('FORGEN_HARNESS env var', (process.env.FORGEN_HARNESS ?? process.env.COMPOUND_HARNESS) === '1', 'Set by `forgen` / `fgx` launcher. Hooks assume harness mode is active.');
|
|
289
|
+
// ADR-012: auto-compound Haiku 추출 동의 상태 고지 (transcript 요약 API 전송 여부).
|
|
290
|
+
try {
|
|
291
|
+
const { getHaikuConsentState } = await import('./compound-consent.js');
|
|
292
|
+
const s = getHaikuConsentState();
|
|
293
|
+
console.log(` auto-compound Haiku 추출: ${s.enabled ? 'ON' : 'OFF'} (${s.source}) — ` +
|
|
294
|
+
`${s.enabled ? '세션 요약이 Haiku 로 전송됨' : '결정론 룰 승급만(egress 0)'}. 변경: forgen compound consent on|off`);
|
|
295
|
+
}
|
|
296
|
+
catch { /* noop */ }
|
|
219
297
|
console.log();
|
|
220
298
|
// v0.4.1 파일 확장자 버그 수정: rules 는 .json, behavior 도 대부분 .json 포맷.
|
|
221
299
|
// 이전에 .md 만 count 해서 실 rules 4개인데 0 으로 표시되는 incident 관찰.
|
|
@@ -301,26 +379,28 @@ export async function runDoctor(opts = {}) {
|
|
|
301
379
|
console.log(' Unable to read hook error log.');
|
|
302
380
|
}
|
|
303
381
|
console.log();
|
|
304
|
-
// Hook Timing:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
382
|
+
// Hook Timing: W2-1 (ADR-010) — slow hook 감지는 native /doctor 가 하므로
|
|
383
|
+
// 표시는 --verbose 로 강등. 수집(hook-timing.jsonl)은 자체 회귀 테스트용으로 유지.
|
|
384
|
+
if (opts.verbose) {
|
|
385
|
+
console.log(' [Hook Timing]');
|
|
386
|
+
const timingStats = getTimingStats();
|
|
387
|
+
if (timingStats.length === 0) {
|
|
388
|
+
console.log(' No timing data collected yet.');
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
console.log(' Hook Count p50ms p95ms max ms');
|
|
392
|
+
console.log(` ${'-'.repeat(56)}`);
|
|
393
|
+
for (const s of timingStats) {
|
|
394
|
+
const hook = s.hook.padEnd(22);
|
|
395
|
+
const count = String(s.count).padStart(5);
|
|
396
|
+
const p50 = String(s.p50).padStart(7);
|
|
397
|
+
const p95 = String(s.p95).padStart(7);
|
|
398
|
+
const max = String(s.max).padStart(8);
|
|
399
|
+
console.log(` ${hook}${count}${p50}${p95}${max}`);
|
|
400
|
+
}
|
|
320
401
|
}
|
|
402
|
+
console.log();
|
|
321
403
|
}
|
|
322
|
-
console.log();
|
|
323
|
-
console.log();
|
|
324
404
|
// v1: 팀 팩 시스템 제거. 개인 모드만 지원.
|
|
325
405
|
console.log(' [Pack Connections]');
|
|
326
406
|
console.log(' v1: Personal mode only (team packs removed)');
|
|
@@ -365,107 +445,9 @@ export async function runDoctor(opts = {}) {
|
|
|
365
445
|
console.log();
|
|
366
446
|
}
|
|
367
447
|
}
|
|
368
|
-
// Harness Maturity
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
// 1. Preparation
|
|
372
|
-
const hasClaude = fs.existsSync(path.join(cwd, 'CLAUDE.md'));
|
|
373
|
-
let rulesCount = 0;
|
|
374
|
-
try {
|
|
375
|
-
const rulesDir = path.join(cwd, '.claude', 'rules');
|
|
376
|
-
if (fs.existsSync(rulesDir)) {
|
|
377
|
-
rulesCount = fs.readdirSync(rulesDir).filter(f => f.endsWith('.md')).length;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
catch { /* fail-open */ }
|
|
381
|
-
let hooksActive = 0;
|
|
382
|
-
try {
|
|
383
|
-
const hooksJsonPath = path.join(cwd, 'hooks', 'hooks.json');
|
|
384
|
-
if (fs.existsSync(hooksJsonPath)) {
|
|
385
|
-
const hooksData = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf-8'));
|
|
386
|
-
if (hooksData.hooks && typeof hooksData.hooks === 'object') {
|
|
387
|
-
for (const eventHooks of Object.values(hooksData.hooks)) {
|
|
388
|
-
if (Array.isArray(eventHooks)) {
|
|
389
|
-
for (const group of eventHooks) {
|
|
390
|
-
if (Array.isArray(group.hooks)) {
|
|
391
|
-
hooksActive += (group.hooks).length;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
catch { /* fail-open */ }
|
|
400
|
-
const prepL = hasClaude && rulesCount >= 3 && hooksActive > 0 ? 'L3' : hasClaude && hooksActive > 0 ? 'L2' : hasClaude ? 'L1' : 'L0';
|
|
401
|
-
// 2. Context
|
|
402
|
-
let solutionsCount = 0;
|
|
403
|
-
try {
|
|
404
|
-
if (exists(ME_SOLUTIONS))
|
|
405
|
-
solutionsCount = fs.readdirSync(ME_SOLUTIONS).filter(f => f.endsWith('.md')).length;
|
|
406
|
-
}
|
|
407
|
-
catch { /* fail-open */ }
|
|
408
|
-
let behaviorCount = 0;
|
|
409
|
-
try {
|
|
410
|
-
if (exists(ME_BEHAVIOR))
|
|
411
|
-
behaviorCount = fs.readdirSync(ME_BEHAVIOR).filter(f => f.endsWith('.md')).length;
|
|
412
|
-
}
|
|
413
|
-
catch { /* fail-open */ }
|
|
414
|
-
const ctxL = solutionsCount >= 5 && behaviorCount >= 3 ? 'L3' : solutionsCount >= 3 || behaviorCount >= 1 ? 'L2' : solutionsCount > 0 || behaviorCount > 0 ? 'L1' : 'L0';
|
|
415
|
-
// 3. Execution
|
|
416
|
-
const hasSkills = exists(ME_SKILLS);
|
|
417
|
-
const execL = hasSkills ? 'L2' : 'L1';
|
|
418
|
-
// 4. Validation
|
|
419
|
-
const hasTests = fs.existsSync(path.join(cwd, 'tests'));
|
|
420
|
-
const hasCI = fs.existsSync(path.join(cwd, '.github', 'workflows'));
|
|
421
|
-
const validL = hasTests && hasCI ? 'L3' : hasTests ? 'L2' : 'L1';
|
|
422
|
-
// 5. Improvement: reflection rate from solutions
|
|
423
|
-
let reflectionRate = 0;
|
|
424
|
-
try {
|
|
425
|
-
if (exists(ME_SOLUTIONS)) {
|
|
426
|
-
const solFiles = fs.readdirSync(ME_SOLUTIONS).filter(f => f.endsWith('.md'));
|
|
427
|
-
if (solFiles.length > 0) {
|
|
428
|
-
let reflected = 0;
|
|
429
|
-
for (const f of solFiles) {
|
|
430
|
-
try {
|
|
431
|
-
const content = fs.readFileSync(path.join(ME_SOLUTIONS, f), 'utf-8');
|
|
432
|
-
const match = content.match(/reflected:\s*(\d+)/);
|
|
433
|
-
if (match && parseInt(match[1], 10) > 0)
|
|
434
|
-
reflected++;
|
|
435
|
-
}
|
|
436
|
-
catch { /* skip */ }
|
|
437
|
-
}
|
|
438
|
-
reflectionRate = Math.round((reflected / solFiles.length) * 100);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
catch { /* fail-open */ }
|
|
443
|
-
const improvL = reflectionRate > 0 ? 'L3' : solutionsCount > 0 ? 'L2' : 'L1';
|
|
444
|
-
const levelIcon = (l) => l === 'L3' ? '✓' : l === 'L2' ? '✓' : l === 'L1' ? '✗' : '✗';
|
|
445
|
-
console.log(` Axis Level Detail`);
|
|
446
|
-
console.log(` ${'─'.repeat(55)}`);
|
|
447
|
-
console.log(` ${levelIcon(prepL)} Preparation ${prepL} CLAUDE.md:${hasClaude ? 'yes' : 'no'}, rules:${rulesCount}, hooks:${hooksActive}`);
|
|
448
|
-
console.log(` ${levelIcon(ctxL)} Context ${ctxL} solutions:${solutionsCount}, behavior:${behaviorCount}`);
|
|
449
|
-
console.log(` ${levelIcon(execL)} Execution ${execL} skills:${hasSkills ? 'yes' : 'no'}`);
|
|
450
|
-
console.log(` ${levelIcon(validL)} Validation ${validL} tests:${hasTests ? 'yes' : 'no'}, CI:${hasCI ? 'yes' : 'no'}`);
|
|
451
|
-
console.log(` ${levelIcon(improvL)} Improvement ${improvL} reflection:${reflectionRate}%`);
|
|
452
|
-
console.log();
|
|
453
|
-
// Quick wins: suggest for lowest scoring axes
|
|
454
|
-
const axes = [
|
|
455
|
-
{ name: 'Preparation', level: prepL, hint: 'Add CLAUDE.md + .claude/rules/ files' },
|
|
456
|
-
{ name: 'Context', level: ctxL, hint: 'Run /compound to accumulate solutions' },
|
|
457
|
-
{ name: 'Execution', level: execL, hint: 'Promote solutions to skills' },
|
|
458
|
-
{ name: 'Validation', level: validL, hint: 'Add tests/ dir and .github/workflows' },
|
|
459
|
-
{ name: 'Improvement', level: improvL, hint: 'Reflect on existing solutions' },
|
|
460
|
-
];
|
|
461
|
-
const quickWins = axes.filter(a => a.level === 'L0' || a.level === 'L1').slice(0, 3);
|
|
462
|
-
if (quickWins.length > 0) {
|
|
463
|
-
console.log(' Quick Wins (Top 3):');
|
|
464
|
-
for (const win of quickWins) {
|
|
465
|
-
console.log(` → ${win.name}: ${win.hint}`);
|
|
466
|
-
}
|
|
467
|
-
console.log();
|
|
468
|
-
}
|
|
448
|
+
// W2-1 (ADR-010): Harness Maturity / Quick Wins 섹션 제거 —
|
|
449
|
+
// "CLAUDE.md 추가하세요" 류 일반 셋업 조언은 native /doctor 영역.
|
|
450
|
+
// forgen 고유 진단(아래 State Hygiene, ψ-long, parity 등)만 유지한다.
|
|
469
451
|
// State bloat check — session-scoped files accumulate until pruned.
|
|
470
452
|
console.log(' [State Hygiene]');
|
|
471
453
|
const sessionFiles = countSessionScopedFiles();
|
|
@@ -525,7 +507,7 @@ export async function runDoctor(opts = {}) {
|
|
|
525
507
|
if (map.available && map.hotspots.length > 0) {
|
|
526
508
|
const top = map.hotspots.map((h) => `${h.path} (${h.fixHits})`).join(', ');
|
|
527
509
|
console.log(` → 진앙 후보 top 3: ${top}`);
|
|
528
|
-
console.log(' → 전체 보기: forgen regress-map');
|
|
510
|
+
console.log(' → 전체 보기: forgen dev regress-map');
|
|
529
511
|
}
|
|
530
512
|
}
|
|
531
513
|
}
|
|
@@ -589,20 +571,23 @@ export async function runDoctor(opts = {}) {
|
|
|
589
571
|
console.log(` Unable to read psi-long state: ${e instanceof Error ? e.message : 'unknown'}`);
|
|
590
572
|
}
|
|
591
573
|
console.log();
|
|
592
|
-
// [Effort
|
|
593
|
-
//
|
|
594
|
-
|
|
574
|
+
// [Effort] — ADR-009 §5 nudge-only. W2-1 (ADR-010): Sonnet 5 는 adaptive
|
|
575
|
+
// thinking 으로 effort 를 자체 관리하므로, 권고가 실제로 의미 있는 상황
|
|
576
|
+
// (forge-loop 활성 = long-running → xhigh 넛지)에서만 표시한다.
|
|
595
577
|
try {
|
|
596
578
|
const loopActive = !!readForgeLoopState()?.active;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
579
|
+
if (loopActive) {
|
|
580
|
+
const adv = effortAdvisory({ longRunningActive: true });
|
|
581
|
+
console.log(' [Effort]');
|
|
582
|
+
console.log(` → recommend: ${adv.recommend}`);
|
|
583
|
+
console.log(` ${adv.reason}`);
|
|
584
|
+
console.log();
|
|
585
|
+
}
|
|
604
586
|
}
|
|
605
|
-
|
|
587
|
+
catch { /* effort 권고 실패는 침묵 — 부가 정보일 뿐 */ }
|
|
588
|
+
// W1-1 (ADR-010): --reclaim → legacy 규칙 스프롤 스캔 (읽기 전용).
|
|
589
|
+
if (opts.reclaim)
|
|
590
|
+
await runReclaimScan();
|
|
606
591
|
// [Summary] — 최종 상태 요약과 복구 액션을 한눈에 보이게
|
|
607
592
|
console.log(' [Summary]');
|
|
608
593
|
if (failedChecks.length === 0) {
|