@wooojin/forgen 0.2.0 → 0.3.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/CHANGELOG.md +72 -0
- package/README.ja.md +79 -14
- package/README.ko.md +100 -14
- package/README.md +124 -17
- package/README.zh.md +79 -14
- 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/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 +273 -0
- 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 +26 -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 +108 -0
- package/dist/core/dashboard.js +495 -0
- package/dist/core/doctor.js +151 -21
- package/dist/core/drift-score.d.ts +49 -0
- package/dist/core/drift-score.js +87 -0
- package/dist/core/harness.d.ts +6 -1
- package/dist/core/harness.js +75 -19
- package/dist/core/mcp-config.d.ts +2 -0
- package/dist/core/mcp-config.js +6 -1
- package/dist/core/paths.d.ts +6 -1
- package/dist/core/paths.js +18 -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-export.d.ts +41 -0
- package/dist/engine/compound-export.js +169 -0
- package/dist/engine/compound-lifecycle.d.ts +4 -3
- package/dist/engine/compound-lifecycle.js +91 -46
- package/dist/engine/compound-loop.js +18 -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-format.d.ts +2 -2
- package/dist/engine/solution-format.js +249 -34
- package/dist/engine/solution-index.d.ts +1 -1
- package/dist/engine/solution-matcher.d.ts +30 -1
- package/dist/engine/solution-matcher.js +235 -45
- package/dist/fgx.js +12 -8
- package/dist/hooks/context-guard.d.ts +15 -0
- package/dist/hooks/context-guard.js +218 -56
- package/dist/hooks/db-guard.js +2 -2
- package/dist/hooks/hook-config.d.ts +27 -1
- package/dist/hooks/hook-config.js +72 -12
- package/dist/hooks/hooks-generator.d.ts +3 -0
- package/dist/hooks/hooks-generator.js +23 -6
- package/dist/hooks/intent-classifier.d.ts +0 -2
- package/dist/hooks/intent-classifier.js +32 -18
- package/dist/hooks/keyword-detector.js +126 -204
- package/dist/hooks/notepad-injector.js +2 -2
- package/dist/hooks/permission-handler.js +2 -2
- package/dist/hooks/post-tool-failure.js +12 -6
- package/dist/hooks/post-tool-handlers.d.ts +1 -1
- package/dist/hooks/post-tool-handlers.js +14 -11
- package/dist/hooks/post-tool-use.d.ts +11 -0
- package/dist/hooks/post-tool-use.js +184 -71
- package/dist/hooks/pre-compact.d.ts +11 -1
- package/dist/hooks/pre-compact.js +112 -37
- package/dist/hooks/pre-tool-use.js +86 -56
- package/dist/hooks/rate-limiter.js +3 -3
- package/dist/hooks/secret-filter.js +2 -2
- package/dist/hooks/session-recovery.js +256 -236
- package/dist/hooks/shared/hook-response.d.ts +4 -4
- package/dist/hooks/shared/hook-response.js +13 -24
- package/dist/hooks/shared/hook-timing.d.ts +15 -0
- package/dist/hooks/shared/hook-timing.js +64 -0
- package/dist/hooks/skill-injector.d.ts +4 -3
- package/dist/hooks/skill-injector.js +47 -16
- package/dist/hooks/slop-detector.js +3 -3
- package/dist/hooks/solution-injector.js +224 -197
- package/dist/hooks/subagent-tracker.js +2 -2
- 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/renderer/rule-renderer.js +9 -11
- 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 +266 -0
- 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/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/tdd/SKILL.md +0 -178
- package/skills/testing-strategy/SKILL.md +0 -260
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* 모델에 컨텍스트를 주입하려면 반드시 additionalContext를 사용해야 함.
|
|
15
15
|
*/
|
|
16
16
|
import * as fs from 'node:fs';
|
|
17
|
+
import * as os from 'node:os';
|
|
17
18
|
import * as path from 'node:path';
|
|
18
|
-
import { STATE_DIR } from '../../core/paths.js';
|
|
19
19
|
/** 통과 응답 (컨텍스트 없음, 모든 이벤트 공통) */
|
|
20
20
|
export function approve() {
|
|
21
21
|
return JSON.stringify({ continue: true });
|
|
@@ -63,31 +63,20 @@ export function ask(reason) {
|
|
|
63
63
|
export function failOpen() {
|
|
64
64
|
return JSON.stringify({ continue: true });
|
|
65
65
|
}
|
|
66
|
-
/** 훅별 에러 카운트를 STATE_DIR/hook-errors.json에 누적 */
|
|
67
|
-
export function incrementHookErrorCount(hookName) {
|
|
68
|
-
try {
|
|
69
|
-
const errorPath = path.join(STATE_DIR, 'hook-errors.json');
|
|
70
|
-
let errors = {};
|
|
71
|
-
try {
|
|
72
|
-
if (fs.existsSync(errorPath)) {
|
|
73
|
-
errors = JSON.parse(fs.readFileSync(errorPath, 'utf-8'));
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch { /* start fresh */ }
|
|
77
|
-
if (!errors[hookName])
|
|
78
|
-
errors[hookName] = { count: 0, lastAt: '' };
|
|
79
|
-
errors[hookName].count++;
|
|
80
|
-
errors[hookName].lastAt = new Date().toISOString();
|
|
81
|
-
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
82
|
-
fs.writeFileSync(errorPath, JSON.stringify(errors, null, 2));
|
|
83
|
-
}
|
|
84
|
-
catch { /* meta-error in error tracking — ignore */ }
|
|
85
|
-
}
|
|
86
66
|
/**
|
|
87
|
-
* fail-open
|
|
88
|
-
*
|
|
67
|
+
* fail-open with error tracking: 에러 시 안전하게 통과하되, 실패 정보를 기록.
|
|
68
|
+
* forgen doctor의 Hook Health 섹션에서 실패 이력을 표시할 수 있도록 JSONL 로그에 기록.
|
|
69
|
+
*
|
|
70
|
+
* @fail-open: hook failure must never block the user's workflow
|
|
89
71
|
*/
|
|
90
72
|
export function failOpenWithTracking(hookName) {
|
|
91
|
-
|
|
73
|
+
try {
|
|
74
|
+
const stateDir = path.join(os.homedir(), '.forgen', 'state');
|
|
75
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
76
|
+
const logPath = path.join(stateDir, 'hook-errors.jsonl');
|
|
77
|
+
const entry = JSON.stringify({ hook: hookName, at: Date.now() });
|
|
78
|
+
fs.appendFileSync(logPath, entry + '\n');
|
|
79
|
+
}
|
|
80
|
+
catch { /* fail-open: tracking itself must not throw */ }
|
|
92
81
|
return JSON.stringify({ continue: true });
|
|
93
82
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — Hook Timing Profiler
|
|
3
|
+
*
|
|
4
|
+
* Records hook execution durations and provides timing statistics
|
|
5
|
+
* for visibility into which hooks are slow.
|
|
6
|
+
*/
|
|
7
|
+
export declare function recordHookTiming(hookName: string, durationMs: number, event: string): void;
|
|
8
|
+
export interface TimingStats {
|
|
9
|
+
hook: string;
|
|
10
|
+
count: number;
|
|
11
|
+
p50: number;
|
|
12
|
+
p95: number;
|
|
13
|
+
max: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function getTimingStats(): TimingStats[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — Hook Timing Profiler
|
|
3
|
+
*
|
|
4
|
+
* Records hook execution durations and provides timing statistics
|
|
5
|
+
* for visibility into which hooks are slow.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { STATE_DIR } from '../../core/paths.js';
|
|
10
|
+
const TIMING_LOG = path.join(STATE_DIR, 'hook-timing.jsonl');
|
|
11
|
+
const MAX_LINES = 500;
|
|
12
|
+
export function recordHookTiming(hookName, durationMs, event) {
|
|
13
|
+
try {
|
|
14
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
15
|
+
const entry = JSON.stringify({ hook: hookName, ms: durationMs, event, at: Date.now() });
|
|
16
|
+
fs.appendFileSync(TIMING_LOG, entry + '\n');
|
|
17
|
+
// Rotate if too large
|
|
18
|
+
try {
|
|
19
|
+
const content = fs.readFileSync(TIMING_LOG, 'utf-8');
|
|
20
|
+
const lines = content.trim().split('\n');
|
|
21
|
+
if (lines.length > MAX_LINES) {
|
|
22
|
+
fs.writeFileSync(TIMING_LOG, lines.slice(-MAX_LINES).join('\n') + '\n');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch { /* skip rotation on error */ }
|
|
26
|
+
}
|
|
27
|
+
catch { /* fail-open */ }
|
|
28
|
+
}
|
|
29
|
+
export function getTimingStats() {
|
|
30
|
+
try {
|
|
31
|
+
if (!fs.existsSync(TIMING_LOG))
|
|
32
|
+
return [];
|
|
33
|
+
const content = fs.readFileSync(TIMING_LOG, 'utf-8');
|
|
34
|
+
const entries = content.trim().split('\n')
|
|
35
|
+
.map(line => { try {
|
|
36
|
+
return JSON.parse(line);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
} })
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
const byHook = new Map();
|
|
43
|
+
for (const e of entries) {
|
|
44
|
+
if (!byHook.has(e.hook))
|
|
45
|
+
byHook.set(e.hook, []);
|
|
46
|
+
byHook.get(e.hook).push(e.ms);
|
|
47
|
+
}
|
|
48
|
+
const stats = [];
|
|
49
|
+
for (const [hook, times] of byHook) {
|
|
50
|
+
times.sort((a, b) => a - b);
|
|
51
|
+
stats.push({
|
|
52
|
+
hook,
|
|
53
|
+
count: times.length,
|
|
54
|
+
p50: times[Math.floor(times.length * 0.5)] ?? 0,
|
|
55
|
+
p95: times[Math.floor(times.length * 0.95)] ?? 0,
|
|
56
|
+
max: times[times.length - 1] ?? 0,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return stats.sort((a, b) => b.p95 - a.p95);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* Claude Code UserPromptSubmit 훅으로 등록.
|
|
6
6
|
* 프롬프트와 매칭되는 학습된 스킬을 자동으로 컨텍스트에 주입합니다.
|
|
7
7
|
*
|
|
8
|
-
* 스킬 파일
|
|
8
|
+
* 스킬 파일 위치 (우선순위):
|
|
9
|
+
* 0. {project}/.forgen/skills/*.md (프로젝트 포지 스킬 — 최우선)
|
|
9
10
|
* 1. {project}/.compound/skills/*.md (프로젝트 스킬)
|
|
10
|
-
* 2. ~/.
|
|
11
|
-
* 3. ~/.
|
|
11
|
+
* 2. ~/.forgen/me/skills/*.md (개인 학습 스킬)
|
|
12
|
+
* 3. ~/.forgen/skills/*.md (글로벌 스킬)
|
|
12
13
|
*
|
|
13
14
|
* 스킬 포맷:
|
|
14
15
|
* ---
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* Claude Code UserPromptSubmit 훅으로 등록.
|
|
6
6
|
* 프롬프트와 매칭되는 학습된 스킬을 자동으로 컨텍스트에 주입합니다.
|
|
7
7
|
*
|
|
8
|
-
* 스킬 파일
|
|
8
|
+
* 스킬 파일 위치 (우선순위):
|
|
9
|
+
* 0. {project}/.forgen/skills/*.md (프로젝트 포지 스킬 — 최우선)
|
|
9
10
|
* 1. {project}/.compound/skills/*.md (프로젝트 스킬)
|
|
10
|
-
* 2. ~/.
|
|
11
|
-
* 3. ~/.
|
|
11
|
+
* 2. ~/.forgen/me/skills/*.md (개인 학습 스킬)
|
|
12
|
+
* 3. ~/.forgen/skills/*.md (글로벌 스킬)
|
|
12
13
|
*
|
|
13
14
|
* 스킬 포맷:
|
|
14
15
|
* ---
|
|
@@ -34,10 +35,11 @@ function escapeXmlAttr(s) {
|
|
|
34
35
|
return s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
35
36
|
}
|
|
36
37
|
import { atomicWriteJSON } from './shared/atomic-write.js';
|
|
38
|
+
import { withFileLockSync } from './shared/file-lock.js';
|
|
37
39
|
import { FORGEN_HOME, ME_DIR, STATE_DIR } from '../core/paths.js';
|
|
38
40
|
import { KEYWORD_PATTERNS } from './keyword-detector.js';
|
|
39
41
|
import { isHookEnabled } from './hook-config.js';
|
|
40
|
-
import { approve, approveWithContext,
|
|
42
|
+
import { approve, approveWithContext, failOpenWithTracking } from './shared/hook-response.js';
|
|
41
43
|
/** keyword-detector가 처리하는 키워드 이름 집합 (skill + inject 모두 포함, 이중 주입 방지) */
|
|
42
44
|
const KEYWORD_DETECTOR_SKILL_NAMES = new Set(KEYWORD_PATTERNS
|
|
43
45
|
.filter(p => p.type === 'skill' || p.type === 'inject')
|
|
@@ -65,11 +67,39 @@ function loadSessionCache(sessionId) {
|
|
|
65
67
|
}
|
|
66
68
|
return new Set();
|
|
67
69
|
}
|
|
68
|
-
function saveSessionCache(sessionId,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
function saveSessionCache(sessionId, newSkillNames) {
|
|
71
|
+
const cachePath = getSessionCachePath(sessionId);
|
|
72
|
+
try {
|
|
73
|
+
withFileLockSync(cachePath, () => {
|
|
74
|
+
// Lock 안에서 fresh re-read → merge → write
|
|
75
|
+
const freshInjected = new Set();
|
|
76
|
+
try {
|
|
77
|
+
if (fs.existsSync(cachePath)) {
|
|
78
|
+
const fresh = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
|
|
79
|
+
const age = fresh.updatedAt ? Date.now() - new Date(fresh.updatedAt).getTime() : Infinity;
|
|
80
|
+
if (Number.isFinite(age) && age <= 24 * 60 * 60 * 1000) {
|
|
81
|
+
for (const name of fresh.injected ?? [])
|
|
82
|
+
freshInjected.add(name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch { /* fresh re-read 실패 시 빈 set으로 진행 */ }
|
|
87
|
+
for (const name of newSkillNames)
|
|
88
|
+
freshInjected.add(name);
|
|
89
|
+
atomicWriteJSON(cachePath, {
|
|
90
|
+
injected: [...freshInjected],
|
|
91
|
+
updatedAt: new Date().toISOString(),
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
// Lock 실패 시 fail-open: 직접 write (중복 주입 가능성 있지만 기능 차단 안 함)
|
|
97
|
+
log.debug('skill session cache lock 실패 — fallback write', e);
|
|
98
|
+
atomicWriteJSON(cachePath, {
|
|
99
|
+
injected: [...new Set([...loadSessionCache(sessionId), ...newSkillNames])],
|
|
100
|
+
updatedAt: new Date().toISOString(),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
73
103
|
}
|
|
74
104
|
/** YAML frontmatter 파싱 (간단한 구현) */
|
|
75
105
|
export function parseFrontmatter(content) {
|
|
@@ -152,8 +182,9 @@ function collectSkills() {
|
|
|
152
182
|
const seen = new Map(); // name → source dir
|
|
153
183
|
// 패키지 내장 스킬 경로 (dist/../skills/)
|
|
154
184
|
const pkgSkillsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'commands');
|
|
155
|
-
//
|
|
185
|
+
// 프로젝트 .forgen > 프로젝트 .compound > 개인 > 글로벌 > 패키지 내장
|
|
156
186
|
const dirs = [
|
|
187
|
+
path.join(process.cwd(), '.forgen', 'skills'),
|
|
157
188
|
path.join(process.cwd(), '.compound', 'skills'),
|
|
158
189
|
path.join(ME_DIR, 'skills'),
|
|
159
190
|
path.join(FORGEN_HOME, 'skills'),
|
|
@@ -258,11 +289,11 @@ async function main() {
|
|
|
258
289
|
}
|
|
259
290
|
// 최대 제한 적용
|
|
260
291
|
const toInject = matched.slice(0, MAX_SKILLS_PER_SESSION - injected.size);
|
|
261
|
-
// 파일 기반 캐시 업데이트
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
saveSessionCache(sessionId,
|
|
292
|
+
// 파일 기반 캐시 업데이트 (lock 보호)
|
|
293
|
+
const newSkillNames = toInject.map(s => s.name);
|
|
294
|
+
for (const name of newSkillNames)
|
|
295
|
+
injected.add(name);
|
|
296
|
+
saveSessionCache(sessionId, newSkillNames);
|
|
266
297
|
// Adaptive budget: 다른 플러그인 감지 시 스킬 주입량 축소
|
|
267
298
|
let skillCap = 3000; // INJECTION_CAPS.skillContentMax 기본값
|
|
268
299
|
try {
|
|
@@ -281,5 +312,5 @@ async function main() {
|
|
|
281
312
|
}
|
|
282
313
|
main().catch((e) => {
|
|
283
314
|
process.stderr.write(`[ch-hook] ${e instanceof Error ? e.message : String(e)}\n`);
|
|
284
|
-
console.log(
|
|
315
|
+
console.log(failOpenWithTracking('skill-injector'));
|
|
285
316
|
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { readStdinJSON } from './shared/read-stdin.js';
|
|
10
10
|
import { createLogger } from '../core/logger.js';
|
|
11
11
|
import { isHookEnabled, loadHookConfig } from './hook-config.js';
|
|
12
|
-
import { approve, approveWithWarning,
|
|
12
|
+
import { approve, approveWithWarning, failOpenWithTracking } from './shared/hook-response.js';
|
|
13
13
|
const log = createLogger('slop-detector');
|
|
14
14
|
export const SLOP_PATTERNS = [
|
|
15
15
|
{ pattern: /\/\/\s*TODO:?\s*(implement|add|fix|handle)/i, message: 'Leftover TODO comment', severity: 'warn' },
|
|
@@ -84,10 +84,10 @@ async function main() {
|
|
|
84
84
|
}
|
|
85
85
|
catch (e) {
|
|
86
86
|
log.debug('슬롭 감지 실패', e);
|
|
87
|
-
console.log(
|
|
87
|
+
console.log(failOpenWithTracking('slop-detector'));
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
main().catch((e) => {
|
|
91
91
|
process.stderr.write(`[ch-hook] ${e instanceof Error ? e.message : String(e)}\n`);
|
|
92
|
-
console.log(
|
|
92
|
+
console.log(failOpenWithTracking('slop-detector'));
|
|
93
93
|
});
|