@wooojin/forgen 0.4.13 → 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 +94 -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 +7 -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/spawn.js
CHANGED
|
@@ -225,6 +225,34 @@ async function scanCommitDiffForActedOn(sessionId, cwd, sessionStartTime) {
|
|
|
225
225
|
*/
|
|
226
226
|
// Stop 훅(maybeSpawnAutoCompound)/session-recovery 와 공유하는 dedup cooldown.
|
|
227
227
|
const AUTO_COMPOUND_DEDUP_COOLDOWN_MS = 5 * 60 * 1000; // 5min
|
|
228
|
+
// per-session in-flight start-gate TTL — 러너 최대 실행(~210s)+마진. ADR-011 리뷰 SEV-1:
|
|
229
|
+
// 완료-게이트 단일슬롯 마커는 sweep↔Stop 훅 / 다중세션 double-spawn 을 못 막는다.
|
|
230
|
+
// 세션별 start-gate 로 spawn 전에 표식해 이중실행(중복 Haiku 과금)을 차단.
|
|
231
|
+
const AUTO_COMPOUND_INFLIGHT_TTL_MS = 10 * 60 * 1000; // 10min
|
|
232
|
+
/** 세션별 in-flight 마커 경로 (파일명 안전화). */
|
|
233
|
+
function inflightPathFor(sessionId) {
|
|
234
|
+
return path.join(STATE_DIR, 'compound-inflight', `${sessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
|
|
235
|
+
}
|
|
236
|
+
/** TTL 지난 in-flight 마커 정리 (무한 증가 방지). */
|
|
237
|
+
function cleanupStaleInflight(dir, now) {
|
|
238
|
+
try {
|
|
239
|
+
for (const f of fs.readdirSync(dir)) {
|
|
240
|
+
const fp = path.join(dir, f);
|
|
241
|
+
try {
|
|
242
|
+
const { startedAt } = JSON.parse(fs.readFileSync(fp, 'utf-8'));
|
|
243
|
+
if (!Number.isFinite(startedAt) || now - startedAt >= AUTO_COMPOUND_INFLIGHT_TTL_MS) {
|
|
244
|
+
fs.rmSync(fp, { force: true });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
fs.rmSync(fp, { force: true }); // 손상 마커 제거
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
/* dir 없음 — noop */
|
|
254
|
+
}
|
|
255
|
+
}
|
|
228
256
|
/**
|
|
229
257
|
* 세션 종료 후 자동 compound 추출을 백그라운드(detached)로 시작.
|
|
230
258
|
*
|
|
@@ -237,33 +265,77 @@ const AUTO_COMPOUND_DEDUP_COOLDOWN_MS = 5 * 60 * 1000; // 5min
|
|
|
237
265
|
* dedup: last-auto-compound.json 을 Stop 훅과 공유 — 같은 세션의 최근 run 이 있으면
|
|
238
266
|
* skip 하여 detached spawn 의 double-run 을 방지한다.
|
|
239
267
|
*/
|
|
240
|
-
|
|
241
|
-
|
|
268
|
+
/**
|
|
269
|
+
* per-session in-flight 슬롯 확보 (spawn *전* 표식). 이미 in-flight(TTL 내)면 false 반환 → skip.
|
|
270
|
+
* runAutoCompound 와 context-guard.maybeSpawnAutoCompound 가 **공유**해 이중 spawn(중복 Haiku
|
|
271
|
+
* 과금)을 막는다 (리뷰 SEV-2 #3: 완료-게이트 단일슬롯만으로는 부족).
|
|
272
|
+
*/
|
|
273
|
+
export function claimAutoCompoundInflight(sessionId, now = Date.now()) {
|
|
274
|
+
const inflightPath = inflightPathFor(sessionId);
|
|
275
|
+
try {
|
|
276
|
+
const { startedAt } = JSON.parse(fs.readFileSync(inflightPath, 'utf-8'));
|
|
277
|
+
if (Number.isFinite(startedAt) && now - startedAt < AUTO_COMPOUND_INFLIGHT_TTL_MS)
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
/* 마커 없음/손상 — 진행 */
|
|
282
|
+
}
|
|
283
|
+
const dir = path.dirname(inflightPath);
|
|
284
|
+
cleanupStaleInflight(dir, now);
|
|
285
|
+
try {
|
|
286
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
287
|
+
fs.writeFileSync(inflightPath, JSON.stringify({ startedAt: now, sessionId }));
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
log.debug('in-flight 마커 기록 실패 (계속)', e);
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
/** in-flight 슬롯 해제 (spawn 실패 시 재시도 가능하도록). */
|
|
295
|
+
export function releaseAutoCompoundInflight(sessionId) {
|
|
242
296
|
try {
|
|
243
|
-
|
|
297
|
+
fs.rmSync(inflightPathFor(sessionId), { force: true });
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
/* noop */
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export function runAutoCompound(cwd, transcriptPath, sessionId, promptCount = 0) {
|
|
304
|
+
const now = Date.now();
|
|
305
|
+
// 1. 완료 마커 cooldown (secondary, 단일슬롯 — 값싼 read 먼저).
|
|
306
|
+
try {
|
|
307
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(STATE_DIR, 'last-auto-compound.json'), 'utf-8'));
|
|
244
308
|
if (parsed.sessionId === sessionId) {
|
|
245
309
|
const last = parsed.completedAt ? Date.parse(parsed.completedAt) : 0;
|
|
246
|
-
if (Number.isFinite(last) &&
|
|
310
|
+
if (Number.isFinite(last) && now - last < AUTO_COMPOUND_DEDUP_COOLDOWN_MS) {
|
|
247
311
|
log.debug('auto-compound skip: 최근 동일 세션 run 존재 (dedup)');
|
|
248
|
-
return;
|
|
312
|
+
return 'skipped-dedup';
|
|
249
313
|
}
|
|
250
314
|
}
|
|
251
315
|
}
|
|
252
316
|
catch {
|
|
253
|
-
|
|
317
|
+
/* 마커 없음(최초)/손상 — 진행 (fail-open). */
|
|
318
|
+
}
|
|
319
|
+
// 2. per-session in-flight start-gate (공유 헬퍼).
|
|
320
|
+
if (!claimAutoCompoundInflight(sessionId, now)) {
|
|
321
|
+
log.debug('auto-compound skip: 세션 in-flight (start-gate)');
|
|
322
|
+
return 'skipped-inflight';
|
|
254
323
|
}
|
|
255
324
|
const runnerPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'auto-compound-runner.js');
|
|
256
325
|
try {
|
|
257
|
-
const child = spawn('node', [runnerPath, cwd, transcriptPath, sessionId], {
|
|
326
|
+
const child = spawn('node', [runnerPath, cwd, transcriptPath, sessionId, String(promptCount)], {
|
|
258
327
|
cwd,
|
|
259
328
|
detached: true,
|
|
260
329
|
stdio: 'ignore',
|
|
261
330
|
});
|
|
262
331
|
child.unref();
|
|
263
332
|
console.log('\n[forgen] 세션 분석을 백그라운드로 시작했습니다 (자동 compound) — 결과는 다음 세션 시작 시 반영됩니다.\n');
|
|
333
|
+
return 'spawned';
|
|
264
334
|
}
|
|
265
335
|
catch (e) {
|
|
266
336
|
log.debug('auto-compound 시작 실패', e);
|
|
337
|
+
releaseAutoCompoundInflight(sessionId); // spawn 실패 시 표식 제거 → 재시도 가능
|
|
338
|
+
return 'failed';
|
|
267
339
|
}
|
|
268
340
|
}
|
|
269
341
|
/**
|
package/dist/core/state-gc.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgen status — 통합 상태 명령 (Wave 1, feature-audit 2026-07-21).
|
|
3
|
+
*
|
|
4
|
+
* 이전에는 "내 상태를 보여줘"가 stats/health/dashboard/me/recall/explain/
|
|
5
|
+
* last-block/watch/inspect 9개 명령으로 파편화돼 있었다(~1800줄). 이 하나로
|
|
6
|
+
* 통합해 표면을 줄이고 진입점을 일원화한다. 기존 render/compute 함수는 재사용만
|
|
7
|
+
* 하고 재작성하지 않는다.
|
|
8
|
+
*
|
|
9
|
+
* forgen status 요약 (health 헤더 + one-screen stats)
|
|
10
|
+
* forgen status --compound compound 상태 + 최근 회상 이력
|
|
11
|
+
* forgen status --profile 4축 프로필 + 최근 교정
|
|
12
|
+
* forgen status --rules 활성 룰
|
|
13
|
+
* forgen status --blocks [N] 최근 차단 N건 (rule·사유·해결)
|
|
14
|
+
* forgen status --live 실시간 훅 이벤트 스트림
|
|
15
|
+
*/
|
|
16
|
+
declare const VIEWS: readonly ["--compound", "--profile", "--rules", "--blocks", "--live", "--overview"];
|
|
17
|
+
type View = (typeof VIEWS)[number];
|
|
18
|
+
export declare function resolveView(args: string[]): View | null;
|
|
19
|
+
export declare function handleStatus(args: string[]): Promise<void>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgen status — 통합 상태 명령 (Wave 1, feature-audit 2026-07-21).
|
|
3
|
+
*
|
|
4
|
+
* 이전에는 "내 상태를 보여줘"가 stats/health/dashboard/me/recall/explain/
|
|
5
|
+
* last-block/watch/inspect 9개 명령으로 파편화돼 있었다(~1800줄). 이 하나로
|
|
6
|
+
* 통합해 표면을 줄이고 진입점을 일원화한다. 기존 render/compute 함수는 재사용만
|
|
7
|
+
* 하고 재작성하지 않는다.
|
|
8
|
+
*
|
|
9
|
+
* forgen status 요약 (health 헤더 + one-screen stats)
|
|
10
|
+
* forgen status --compound compound 상태 + 최근 회상 이력
|
|
11
|
+
* forgen status --profile 4축 프로필 + 최근 교정
|
|
12
|
+
* forgen status --rules 활성 룰
|
|
13
|
+
* forgen status --blocks [N] 최근 차단 N건 (rule·사유·해결)
|
|
14
|
+
* forgen status --live 실시간 훅 이벤트 스트림
|
|
15
|
+
*/
|
|
16
|
+
const VIEWS = ['--compound', '--profile', '--rules', '--blocks', '--live', '--overview'];
|
|
17
|
+
const ALIASES = {
|
|
18
|
+
'-c': '--compound',
|
|
19
|
+
'-p': '--profile',
|
|
20
|
+
'-r': '--rules',
|
|
21
|
+
'-b': '--blocks',
|
|
22
|
+
'-l': '--live',
|
|
23
|
+
'-o': '--overview',
|
|
24
|
+
};
|
|
25
|
+
export function resolveView(args) {
|
|
26
|
+
for (const a of args) {
|
|
27
|
+
const flag = a.split('=')[0]; // --blocks=1 → --blocks
|
|
28
|
+
if (VIEWS.includes(flag))
|
|
29
|
+
return flag;
|
|
30
|
+
if (ALIASES[flag])
|
|
31
|
+
return ALIASES[flag];
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
export async function handleStatus(args) {
|
|
36
|
+
// 하위호환: 구 `forgen status --watch|--json|--interval N`(observability dashboard)
|
|
37
|
+
// 은 통합 status 의 observability 모드로 계속 지원. 통합 전 별도 status 였음.
|
|
38
|
+
if (args.includes('--watch') || args.includes('--json') || args.includes('--interval')) {
|
|
39
|
+
const { runDashboard } = await import('./dashboard-cli.js');
|
|
40
|
+
const intervalIdx = args.indexOf('--interval');
|
|
41
|
+
await runDashboard({
|
|
42
|
+
watch: args.includes('--watch'),
|
|
43
|
+
json: args.includes('--json'),
|
|
44
|
+
intervalSec: intervalIdx !== -1 ? Number(args[intervalIdx + 1]) || 5 : 5,
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const view = resolveView(args);
|
|
49
|
+
switch (view) {
|
|
50
|
+
case '--compound': {
|
|
51
|
+
const { runDashboard } = await import('./dashboard-cli.js');
|
|
52
|
+
await runDashboard({});
|
|
53
|
+
const { handleRecall } = await import('./recall-cli.js');
|
|
54
|
+
await handleRecall(args.filter((a) => a !== '--compound' && a !== '-c'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
case '--profile': {
|
|
58
|
+
const { handleInspect } = await import('./inspect-cli.js');
|
|
59
|
+
await handleInspect(['profile']);
|
|
60
|
+
await handleInspect(['corrections']);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
case '--rules': {
|
|
64
|
+
const { handleInspect } = await import('./inspect-cli.js');
|
|
65
|
+
await handleInspect(['rules']);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
case '--blocks': {
|
|
69
|
+
// 남은 positional(숫자)이 있으면 explain N, 없으면 최근 1건.
|
|
70
|
+
const { handleExplain } = await import('./explain-cli.js');
|
|
71
|
+
await handleExplain(args.filter((a) => a !== '--blocks' && a !== '-b'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
case '--live': {
|
|
75
|
+
const { handleWatch } = await import('./watch-cli.js');
|
|
76
|
+
await handleWatch();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
case '--overview': {
|
|
80
|
+
// 리치 운영 대시보드(hook health·session history·learning curve·multi-host).
|
|
81
|
+
// 구 `forgen dashboard`의 고유 콘텐츠 — --compound(ROI/compound)와 다른 축이라
|
|
82
|
+
// 별도 뷰로 보존 (Wave 1 리뷰: 콘텐츠 손실 방지).
|
|
83
|
+
const { handleDashboard } = await import('./dashboard.js');
|
|
84
|
+
await handleDashboard();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
default: {
|
|
88
|
+
// 요약: health 헤더 + one-screen stats (둘 다 computeStats() 기반).
|
|
89
|
+
const { computeHealth, renderHealthLine } = await import('./health-cli.js');
|
|
90
|
+
const { computeStats, renderStats } = await import('./stats-cli.js');
|
|
91
|
+
console.log(renderHealthLine(computeHealth()));
|
|
92
|
+
console.log(renderStats(computeStats()));
|
|
93
|
+
console.log(` ${dim('views:')} forgen status --compound | --profile | --rules | --blocks [N] | --live`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function dim(s) {
|
|
99
|
+
return `\x1b[2m${s}\x1b[0m`;
|
|
100
|
+
}
|
|
@@ -10,4 +10,11 @@
|
|
|
10
10
|
* Line 4: (TODO: tool counts — 추적 인프라 없음)
|
|
11
11
|
* Line 5: (TODO: active task — 추적 인프라 없음)
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* W1-2 (feature-audit 2026-07-21): forgen 가치 카운터 라인 — invisible 가치 가시화.
|
|
15
|
+
* native 플랜/토큰 usage(→ /usage, W2에서 물러남)가 아니라 forgen *자체 활동량*을
|
|
16
|
+
* 노출한다: recall 주입·surfaced·교정 캡처·ROI 강등·차단. 전부 computeStats()가 이미
|
|
17
|
+
* 실측 중인 카운터라 조작 위험 0. computeStats 실패 시 라인 생략(fail-open).
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildValueLine(): string | null;
|
|
13
20
|
export declare function handleStatusline(): Promise<void>;
|
|
@@ -15,9 +15,10 @@ import * as path from 'node:path';
|
|
|
15
15
|
import * as os from 'node:os';
|
|
16
16
|
import { execSync } from 'node:child_process';
|
|
17
17
|
import { loadActiveRules } from '../store/rule-store.js';
|
|
18
|
-
import { getUsageStats } from './usage-telemetry.js';
|
|
19
18
|
import { STATE_DIR } from './paths.js';
|
|
20
19
|
import { classifySolutions } from './lifecycle-classifier.js';
|
|
20
|
+
import { computeStats } from './stats-cli.js';
|
|
21
|
+
import { loadRoiDemotions } from '../engine/roi-demotion.js';
|
|
21
22
|
// 0.4.6 perf #13 — statusline 출력을 5초 캐싱.
|
|
22
23
|
// claude statusLine 은 짧은 간격으로 재호출되는데 매번 git/find/rule-store 를
|
|
23
24
|
// 실행하면 ~100ms 누적. CACHE_TTL_MS 동안 동일 출력 재사용.
|
|
@@ -148,18 +149,48 @@ function buildLifecycleLine() {
|
|
|
148
149
|
return null;
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* ADR-010 W2-2: 사용량 세그먼트("📊 N/5h · N/wk") 제거 — native /usage 가
|
|
154
|
+
* plan limit 를 정확히 분해한다. 이관 사실을 딱 1회만 공지 (state flag).
|
|
155
|
+
*/
|
|
152
156
|
function buildUsageLine() {
|
|
153
157
|
try {
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
const noticeFlag = path.join(STATE_DIR, 'usage-notice-shown');
|
|
159
|
+
if (!fs.existsSync(noticeFlag)) {
|
|
160
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
161
|
+
fs.writeFileSync(noticeFlag, new Date().toISOString());
|
|
162
|
+
return `${DIM}ℹ 사용량 표시는 native /usage 로 이동했습니다 (이 안내는 1회만 표시)${RESET}`;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* W1-2 (feature-audit 2026-07-21): forgen 가치 카운터 라인 — invisible 가치 가시화.
|
|
172
|
+
* native 플랜/토큰 usage(→ /usage, W2에서 물러남)가 아니라 forgen *자체 활동량*을
|
|
173
|
+
* 노출한다: recall 주입·surfaced·교정 캡처·ROI 강등·차단. 전부 computeStats()가 이미
|
|
174
|
+
* 실측 중인 카운터라 조작 위험 0. computeStats 실패 시 라인 생략(fail-open).
|
|
175
|
+
*/
|
|
176
|
+
export function buildValueLine() {
|
|
177
|
+
try {
|
|
178
|
+
let roiDemoted = 0;
|
|
179
|
+
try {
|
|
180
|
+
roiDemoted = Object.keys(loadRoiDemotions()).length;
|
|
181
|
+
}
|
|
182
|
+
catch { /* roi store 없음 → 0 */ }
|
|
183
|
+
const s = computeStats();
|
|
184
|
+
const a = s.assistToday;
|
|
185
|
+
const parts = [
|
|
186
|
+
`${YELLOW}✦${RESET}`,
|
|
187
|
+
`${DIM}recall${RESET} ${a.recallHits}`,
|
|
188
|
+
`${DIM}surfaced${RESET} ${a.surfaced}`,
|
|
189
|
+
`${DIM}교정${RESET} +${s.corrections7d}${DIM}(7d)${RESET}`,
|
|
190
|
+
`${DIM}ROI↓${RESET} ${roiDemoted}`,
|
|
191
|
+
`${DIM}차단${RESET} ${s.blocks7d}${DIM}(7d)${RESET}`,
|
|
192
|
+
];
|
|
193
|
+
return parts.join(` ${DIM}·${RESET} `);
|
|
163
194
|
}
|
|
164
195
|
catch {
|
|
165
196
|
return null;
|
|
@@ -204,7 +235,19 @@ function writeCache(content) {
|
|
|
204
235
|
catch { /* fail-open */ }
|
|
205
236
|
}
|
|
206
237
|
export async function handleStatusline() {
|
|
207
|
-
//
|
|
238
|
+
// W4-3 (ADR-010): hook stdin 엔 모델 필드가 없으므로 statusline 이 세션별
|
|
239
|
+
// 모델을 캐시 → Stop/SubagentStop 가드가 per-model 프로필 조회에 사용.
|
|
240
|
+
// 리뷰 SEV-2: 이 기록은 반드시 5초 표시-캐시 early-return **앞**에 있어야
|
|
241
|
+
// 한다 — /model 로 세션 중 모델을 바꾸면 cache-hit 렌더가 payload 를 아예
|
|
242
|
+
// 안 읽어 stale 모델(잘못된 가드 모드)이 유지되는 창이 생긴다.
|
|
243
|
+
// 필드 부재(구버전 CC 등) 시 기록 안 함 = 가드는 'unknown' → block 유지.
|
|
244
|
+
const payload = readStdinJson();
|
|
245
|
+
if (payload.session_id && payload.model?.id) {
|
|
246
|
+
const { cacheSessionModel } = await import('../checks/_shared/model-profile.js');
|
|
247
|
+
const { sanitizeId } = await import('../hooks/shared/sanitize-id.js');
|
|
248
|
+
cacheSessionModel(sanitizeId(payload.session_id), payload.model.id);
|
|
249
|
+
}
|
|
250
|
+
// 캐시 hit 시 표시만 캐시에서 (5초 윈도우 내 동일 출력 가정).
|
|
208
251
|
// 라인 단위 cache → console.log 라인별 (테스트 호환).
|
|
209
252
|
const cached = readCacheIfFresh();
|
|
210
253
|
if (cached !== null) {
|
|
@@ -212,27 +255,28 @@ export async function handleStatusline() {
|
|
|
212
255
|
console.log(line);
|
|
213
256
|
return;
|
|
214
257
|
}
|
|
215
|
-
const payload = readStdinJson();
|
|
216
258
|
const cwd = payload.workspace?.current_dir ?? process.cwd();
|
|
217
259
|
const claudeDir = path.join(os.homedir(), '.claude');
|
|
218
260
|
const line1 = buildLine1(payload, cwd);
|
|
219
261
|
const line3 = buildLine3(claudeDir, cwd);
|
|
220
262
|
const usageLine = buildUsageLine();
|
|
263
|
+
const valueLine = buildValueLine(); // W1-2: forgen 가치 카운터
|
|
221
264
|
const lifecycleLine = buildLifecycleLine();
|
|
222
|
-
// Line 2 (context/usage): stdin JSON spec 미확인으로 생략 — TODO
|
|
223
|
-
// Line 4 (tool counts): 추적 인프라 없음 — TODO
|
|
224
|
-
// Line 5 (active task): 추적 인프라 없음 — TODO
|
|
225
265
|
console.log(line1);
|
|
226
266
|
console.log(line3);
|
|
227
267
|
if (usageLine)
|
|
228
268
|
console.log(usageLine);
|
|
269
|
+
if (valueLine)
|
|
270
|
+
console.log(valueLine);
|
|
229
271
|
if (lifecycleLine)
|
|
230
272
|
console.log(lifecycleLine);
|
|
273
|
+
// W2-2: 1회 공지(usageLine)는 캐시에 넣지 않는다 — 캐시 재생 시
|
|
274
|
+
// "1회만" 약속이 5초 창 동안 반복 위반되는 실측 버그 방지.
|
|
231
275
|
const cacheLines = [line1, line3];
|
|
232
|
-
if (
|
|
233
|
-
cacheLines.push(
|
|
276
|
+
if (valueLine)
|
|
277
|
+
cacheLines.push(valueLine);
|
|
234
278
|
if (lifecycleLine)
|
|
235
279
|
cacheLines.push(lifecycleLine);
|
|
236
|
-
const cacheBody = cacheLines.join('\n')
|
|
280
|
+
const cacheBody = `${cacheLines.join('\n')}\n`;
|
|
237
281
|
writeCache(cacheBody);
|
|
238
282
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript → 요약 텍스트 (순수, IO 없음 — 테스트 가능).
|
|
3
|
+
*
|
|
4
|
+
* auto-compound-runner 의 extractSummary 에서 분리(2026-08-04). 분리 이유: 실 Claude
|
|
5
|
+
* transcript 스키마 버그(text 가 `entry.message.content` 에 있는데 top-level `entry.content`
|
|
6
|
+
* 만 읽어 전 user/assistant 턴 누락 → auto-compound 가 실데이터에서 dead)를 회귀 테스트로
|
|
7
|
+
* 고정하기 위함. 러너 본문은 process.argv 실행부와 묶여 있어 직접 unit-test 불가였다.
|
|
8
|
+
*
|
|
9
|
+
* 스키마:
|
|
10
|
+
* Claude: {type:'user'|'assistant', message:{role, content: string | [{type:'text',text}]}}
|
|
11
|
+
* (일부 queue-operation 등은 top-level `content` 에 저장 → fallback)
|
|
12
|
+
* Codex: {type:'response_item', payload:{role, content:[{type:'input_text', text}]}}
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractText(c: unknown): string;
|
|
15
|
+
/** Codex content array → flat string. content: [{type:'input_text', text}] */
|
|
16
|
+
export declare function extractCodexText(content: unknown): string;
|
|
17
|
+
/** JSONL 원문 → `[User]/[Assistant]` 요약. maxChars 초과 시 조기 종료. */
|
|
18
|
+
export declare function summarizeTranscript(rawText: string, maxChars?: number): string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript → 요약 텍스트 (순수, IO 없음 — 테스트 가능).
|
|
3
|
+
*
|
|
4
|
+
* auto-compound-runner 의 extractSummary 에서 분리(2026-08-04). 분리 이유: 실 Claude
|
|
5
|
+
* transcript 스키마 버그(text 가 `entry.message.content` 에 있는데 top-level `entry.content`
|
|
6
|
+
* 만 읽어 전 user/assistant 턴 누락 → auto-compound 가 실데이터에서 dead)를 회귀 테스트로
|
|
7
|
+
* 고정하기 위함. 러너 본문은 process.argv 실행부와 묶여 있어 직접 unit-test 불가였다.
|
|
8
|
+
*
|
|
9
|
+
* 스키마:
|
|
10
|
+
* Claude: {type:'user'|'assistant', message:{role, content: string | [{type:'text',text}]}}
|
|
11
|
+
* (일부 queue-operation 등은 top-level `content` 에 저장 → fallback)
|
|
12
|
+
* Codex: {type:'response_item', payload:{role, content:[{type:'input_text', text}]}}
|
|
13
|
+
*/
|
|
14
|
+
export function extractText(c) {
|
|
15
|
+
if (typeof c === 'string')
|
|
16
|
+
return c;
|
|
17
|
+
if (Array.isArray(c)) {
|
|
18
|
+
return c
|
|
19
|
+
.filter((x) => typeof x === 'object' && x !== null && x.type === 'text')
|
|
20
|
+
.map((x) => (typeof x.text === 'string' ? x.text : ''))
|
|
21
|
+
.join('\n');
|
|
22
|
+
}
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
/** Codex content array → flat string. content: [{type:'input_text', text}] */
|
|
26
|
+
export function extractCodexText(content) {
|
|
27
|
+
if (!Array.isArray(content))
|
|
28
|
+
return '';
|
|
29
|
+
const parts = [];
|
|
30
|
+
for (const item of content) {
|
|
31
|
+
if (item && typeof item === 'object' && 'text' in item && typeof item.text === 'string') {
|
|
32
|
+
parts.push(item.text);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return parts.join('\n');
|
|
36
|
+
}
|
|
37
|
+
/** JSONL 원문 → `[User]/[Assistant]` 요약. maxChars 초과 시 조기 종료. */
|
|
38
|
+
export function summarizeTranscript(rawText, maxChars = 8000) {
|
|
39
|
+
const lines = rawText.split('\n').filter(Boolean);
|
|
40
|
+
const messages = [];
|
|
41
|
+
let totalChars = 0;
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
try {
|
|
44
|
+
const entry = JSON.parse(line);
|
|
45
|
+
// Claude schema — text 는 message.content 우선(실버그 수정), top-level 은 fallback.
|
|
46
|
+
if (entry.type === 'user' || entry.type === 'queue-operation') {
|
|
47
|
+
const text = extractText(entry.message?.content ?? entry.content);
|
|
48
|
+
if (text) {
|
|
49
|
+
messages.push(`[User] ${text.slice(0, 500)}`);
|
|
50
|
+
totalChars += text.length;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (entry.type === 'assistant') {
|
|
54
|
+
const text = extractText(entry.message?.content ?? entry.content);
|
|
55
|
+
if (text) {
|
|
56
|
+
messages.push(`[Assistant] ${text.slice(0, 500)}`);
|
|
57
|
+
totalChars += text.length;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Codex schema
|
|
61
|
+
else if (entry.type === 'response_item' && entry.payload?.role === 'user') {
|
|
62
|
+
const text = extractCodexText(entry.payload.content);
|
|
63
|
+
if (text) {
|
|
64
|
+
messages.push(`[User] ${text.slice(0, 500)}`);
|
|
65
|
+
totalChars += text.length;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (entry.type === 'response_item' && entry.payload?.role === 'assistant') {
|
|
69
|
+
const text = extractCodexText(entry.payload.content);
|
|
70
|
+
if (text) {
|
|
71
|
+
messages.push(`[Assistant] ${text.slice(0, 500)}`);
|
|
72
|
+
totalChars += text.length;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* skip malformed line */ }
|
|
77
|
+
if (totalChars > maxChars)
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
return messages.join('\n\n');
|
|
81
|
+
}
|
|
@@ -19,9 +19,29 @@ export interface CapabilityDeclaration {
|
|
|
19
19
|
/** source-of-truth (spec 또는 외부 docs/source 인용). */
|
|
20
20
|
readonly source?: string;
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* 지원 host 의 정준 런타임 목록(단일 소스). `HostId` 는 여기서 파생되므로 새 host 추가 시
|
|
24
|
+
* 이 배열만 넓히면 타입·런타임이 함께 확장된다. 자유형 `host === 'claude'` 이진 비교 대신
|
|
25
|
+
* `(HOST_IDS as readonly string[]).includes(host)` 로 "유효 host 인지" 를 판정하라
|
|
26
|
+
* (W3-3 리뷰 SEV-3 #5: Record<HostId> 는 Record 리터럴만 강제, 자유비교는 미포착).
|
|
27
|
+
*/
|
|
28
|
+
export declare const HOST_IDS: readonly ["claude", "codex", "opencode"];
|
|
29
|
+
export type HostId = (typeof HOST_IDS)[number];
|
|
30
|
+
/**
|
|
31
|
+
* 능력 선언의 검증 수준 (W3-3 리뷰 SEV-3 #1).
|
|
32
|
+
* - 'runtime': forgen 이 이 host 에서 실제로 강제/실행함(레퍼런스 host — claude).
|
|
33
|
+
* - 'source' : host 의 hook schema 소스로 검증했고 forgen 배선 완료(codex).
|
|
34
|
+
* - 'docs' : host 문서 기반 선언이나 **forgen 배선 미완**(opencode P1 — plugin 슬림 전).
|
|
35
|
+
*
|
|
36
|
+
* status='supported' 의 의미가 host 마다 다른 문제를 구조화한다: 'runtime'/'source' 의
|
|
37
|
+
* supported 는 "forgen 이 강제함", 'docs' 의 supported 는 "플랫폼이 가능하나 forgen 미배선".
|
|
38
|
+
* 프로그램 소비자는 verificationLevel 로 둘을 게이트해야 한다(intentEnforced 참조).
|
|
39
|
+
*/
|
|
40
|
+
export type CapabilityVerificationLevel = 'runtime' | 'source' | 'docs';
|
|
23
41
|
export interface HostCapabilities {
|
|
24
42
|
readonly hostId: HostId;
|
|
43
|
+
/** 이 host 선언 전체의 검증 수준. 'docs' 면 아래 status 는 "플랫폼-가능"이지 "forgen-배선"이 아니다. */
|
|
44
|
+
readonly verificationLevel: CapabilityVerificationLevel;
|
|
25
45
|
/**
|
|
26
46
|
* 모든 TrustLayerIntent 에 대한 선언. `Record<TrustLayerIntent, _>` 타입이
|
|
27
47
|
* 컴파일 타임에 누락을 차단한다.
|
|
@@ -16,6 +16,13 @@ export const TRUST_LAYER_INTENTS = [
|
|
|
16
16
|
'forge-loop-state-inject',
|
|
17
17
|
'self-evidence-record',
|
|
18
18
|
];
|
|
19
|
+
/**
|
|
20
|
+
* 지원 host 의 정준 런타임 목록(단일 소스). `HostId` 는 여기서 파생되므로 새 host 추가 시
|
|
21
|
+
* 이 배열만 넓히면 타입·런타임이 함께 확장된다. 자유형 `host === 'claude'` 이진 비교 대신
|
|
22
|
+
* `(HOST_IDS as readonly string[]).includes(host)` 로 "유효 host 인지" 를 판정하라
|
|
23
|
+
* (W3-3 리뷰 SEV-3 #5: Record<HostId> 는 Record 리터럴만 강제, 자유비교는 미포착).
|
|
24
|
+
*/
|
|
25
|
+
export const HOST_IDS = ['claude', 'codex', 'opencode'];
|
|
19
26
|
/**
|
|
20
27
|
* 런타임 assertion — host adapter 가 새 의도 추가를 누락한 경우 fail.
|
|
21
28
|
* 컴파일 타임 가드를 우회하는 동적 생성 코드를 위한 안전망.
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HostId } from './trust-layer-intent.js';
|
|
1
2
|
export interface Principle {
|
|
2
3
|
belief: string;
|
|
3
4
|
/**
|
|
@@ -106,8 +107,8 @@ export interface HarnessContext {
|
|
|
106
107
|
/** 모델 라우팅 프리셋 (default, cost-saving, max-quality) */
|
|
107
108
|
routingPreset?: string;
|
|
108
109
|
}
|
|
109
|
-
/** 런타임 Host */
|
|
110
|
-
export type RuntimeHost =
|
|
110
|
+
/** 런타임 Host — 정식 정의는 `trust-layer-intent.ts` 의 HostId (Multi-Harness Adapter Plan P0). */
|
|
111
|
+
export type RuntimeHost = HostId;
|
|
111
112
|
/** 런칭 컨텍스트 — CLI에서 runtime/args 결정을 모델화 */
|
|
112
113
|
export interface LaunchContext {
|
|
113
114
|
runtime: RuntimeHost;
|
package/dist/core/uninstall.js
CHANGED
|
@@ -236,6 +236,10 @@ function cleanCompoundRules(cwd) {
|
|
|
236
236
|
// v4.1+ consolidated
|
|
237
237
|
'project-context.md',
|
|
238
238
|
'routing.md',
|
|
239
|
+
// v0.5.0+ project-scoped (ADR-010 W1-3)
|
|
240
|
+
'v1-rules.md',
|
|
241
|
+
'forge-behavioral.md',
|
|
242
|
+
'user-profile.md',
|
|
239
243
|
// legacy (v4.0 and earlier)
|
|
240
244
|
'security.md',
|
|
241
245
|
'golden-principles.md',
|
|
@@ -257,6 +261,14 @@ function cleanCompoundRules(cwd) {
|
|
|
257
261
|
fs.unlinkSync(legacyPath);
|
|
258
262
|
removedCount++;
|
|
259
263
|
}
|
|
264
|
+
// 레거시 글로벌 사이드채널 (ADR-010 W1-3 이전 버전이 ~/.claude/rules/ 에 씀).
|
|
265
|
+
// 새 세션이 열리기 전에 uninstall 하는 경우 harness 마이그레이션이 못 지우므로
|
|
266
|
+
// 여기서도 회수한다 — 방치 시 언인스톨 후에도 전 세션에 주입된다.
|
|
267
|
+
const legacyGlobalBehavioral = path.join(os.homedir(), '.claude', 'rules', 'forge-behavioral.md');
|
|
268
|
+
if (fs.existsSync(legacyGlobalBehavioral)) {
|
|
269
|
+
fs.unlinkSync(legacyGlobalBehavioral);
|
|
270
|
+
removedCount++;
|
|
271
|
+
}
|
|
260
272
|
if (removedCount > 0) {
|
|
261
273
|
console.log(` ✓ Removed ${removedCount} rule file(s)`);
|
|
262
274
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* 0.4.6 신설. limit prediction 은 의도적으로 제외 — Anthropic 의 실제 limit 가
|
|
14
14
|
* 계정/플랜별 가변이라 hard-code 부정확. raw count 만 노출하고 사용자가 판단.
|
|
15
15
|
*/
|
|
16
|
+
import type { HostId } from './trust-layer-intent.js';
|
|
16
17
|
export interface UsageStats {
|
|
17
18
|
hour5: {
|
|
18
19
|
claude: number;
|
|
@@ -25,7 +26,12 @@ export interface UsageStats {
|
|
|
25
26
|
total: number;
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated ADR-010 W2-2 (2026-07-16): native `/usage` 가 plan limit 를
|
|
31
|
+
* skill/subagent/plugin/MCP 별로 정확히 분해하므로 forgen 의 raw count 는
|
|
32
|
+
* 중복 표면. 기록 중단(no-op shim — API 만 유지), v0.6.0 에서 모듈 삭제 예정.
|
|
33
|
+
*/
|
|
34
|
+
export declare function recordToolCall(_runtime?: HostId): void;
|
|
29
35
|
/**
|
|
30
36
|
* sliding window count. fail-open: 파일 미존재/parse 실패는 0 반환.
|
|
31
37
|
*
|
|
@@ -22,15 +22,13 @@ const TELEMETRY_PATH = path.join(STATE_DIR, 'usage-telemetry.jsonl');
|
|
|
22
22
|
const HOUR5_MS = 5 * 60 * 60 * 1000;
|
|
23
23
|
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
24
|
const PRUNE_THRESHOLD = 10_000; // 10K lines 누적 시 prune
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
log.debug('telemetry append 실패', e);
|
|
33
|
-
}
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated ADR-010 W2-2 (2026-07-16): native `/usage` 가 plan limit 를
|
|
27
|
+
* skill/subagent/plugin/MCP 별로 정확히 분해하므로 forgen 의 raw count 는
|
|
28
|
+
* 중복 표면. 기록 중단(no-op shim — API 만 유지), v0.6.0 에서 모듈 삭제 예정.
|
|
29
|
+
*/
|
|
30
|
+
export function recordToolCall(_runtime = 'claude') {
|
|
31
|
+
// no-op — 사용량 추적은 native /usage 로 이관됨.
|
|
34
32
|
}
|
|
35
33
|
/**
|
|
36
34
|
* sliding window count. fail-open: 파일 미존재/parse 실패는 0 반환.
|
|
@@ -129,7 +129,7 @@ export function bootstrapV1Session() {
|
|
|
129
129
|
mismatchResult = detectMismatch(allSignals);
|
|
130
130
|
// mismatch 감지 시 재추천 생성
|
|
131
131
|
if (mismatchResult.quality_mismatch || mismatchResult.autonomy_mismatch) {
|
|
132
|
-
session.warnings.push(`Pack mismatch 감지: quality=${mismatchResult.quality_score}, autonomy=${mismatchResult.autonomy_score}. forgen forge --reset soft 로 재설정하거나 forgen onboarding 으로 재추천을 받으세요.`);
|
|
132
|
+
session.warnings.push(`Pack mismatch 감지: quality=${mismatchResult.quality_score}, autonomy=${mismatchResult.autonomy_score}. forgen forge --reset soft 로 재설정하거나 forgen forge --onboarding 으로 재추천을 받으세요.`);
|
|
133
133
|
const rec = createRecommendation({
|
|
134
134
|
source: 'mismatch_recommendation',
|
|
135
135
|
quality_pack: mismatchResult.quality_mismatch
|
package/dist/core/watch-cli.js
CHANGED
|
@@ -136,7 +136,7 @@ export async function handleWatch() {
|
|
|
136
136
|
format: formatMatchEval,
|
|
137
137
|
},
|
|
138
138
|
];
|
|
139
|
-
console.log(`\n ${C.cyan}forgen
|
|
139
|
+
console.log(`\n ${C.cyan}forgen status --live${C.reset} — real-time event stream`);
|
|
140
140
|
console.log(` ${C.dim}Watching: hook-timing, violations, match-eval-log${C.reset}`);
|
|
141
141
|
console.log(` ${C.dim}Press Ctrl+C to stop${C.reset}\n`);
|
|
142
142
|
// Show recent events (last 10 per source)
|