@wooojin/forgen 0.2.1 → 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 +44 -0
- package/README.ko.md +25 -14
- package/README.md +61 -17
- package/agents/analyst.md +48 -4
- package/agents/architect.md +39 -4
- package/agents/code-reviewer.md +107 -77
- package/agents/critic.md +47 -4
- package/agents/debugger.md +46 -4
- package/agents/designer.md +40 -4
- package/agents/executor.md +112 -30
- package/agents/explore.md +45 -5
- package/agents/git-master.md +48 -4
- package/agents/planner.md +121 -18
- package/agents/test-engineer.md +58 -4
- package/agents/verifier.md +92 -77
- package/commands/architecture-decision.md +127 -258
- package/commands/calibrate.md +225 -0
- package/commands/code-review.md +163 -178
- package/commands/compound.md +127 -68
- package/commands/deep-interview.md +212 -110
- package/commands/docker.md +68 -178
- package/commands/forge-loop.md +215 -0
- package/commands/learn.md +231 -0
- package/commands/retro.md +215 -0
- package/commands/ship.md +277 -0
- package/dist/cli.js +17 -9
- package/dist/core/auto-compound-runner.js +14 -0
- package/dist/core/config-injector.d.ts +2 -1
- package/dist/core/config-injector.js +2 -1
- package/dist/core/dashboard.d.ts +17 -0
- package/dist/core/dashboard.js +112 -2
- package/dist/core/harness.d.ts +6 -1
- package/dist/core/harness.js +75 -19
- 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-lifecycle.d.ts +4 -3
- package/dist/engine/compound-lifecycle.js +91 -46
- 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 +7 -1
- package/dist/engine/solution-matcher.js +114 -37
- package/dist/fgx.js +12 -8
- package/dist/hooks/context-guard.d.ts +5 -0
- package/dist/hooks/context-guard.js +118 -2
- package/dist/hooks/hooks-generator.d.ts +3 -0
- package/dist/hooks/hooks-generator.js +23 -6
- package/dist/hooks/keyword-detector.js +16 -100
- package/dist/hooks/skill-injector.d.ts +4 -3
- package/dist/hooks/skill-injector.js +6 -4
- 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/services/session.d.ts +19 -0
- package/dist/services/session.js +62 -0
- package/hooks/hooks.json +2 -2
- package/package.json +2 -1
- package/skills/architecture-decision/SKILL.md +113 -257
- package/skills/calibrate/SKILL.md +207 -0
- package/skills/code-review/SKILL.md +151 -178
- package/skills/compound/SKILL.md +126 -68
- package/skills/deep-interview/SKILL.md +210 -110
- package/skills/docker/SKILL.md +57 -179
- package/skills/forge-loop/SKILL.md +198 -0
- package/skills/learn/SKILL.md +216 -0
- package/skills/retro/SKILL.md +199 -0
- package/skills/ship/SKILL.md +259 -0
- package/agents/code-simplifier.md +0 -197
- package/agents/performance-reviewer.md +0 -172
- package/agents/qa-tester.md +0 -158
- package/agents/refactoring-expert.md +0 -168
- package/agents/scientist.md +0 -144
- package/agents/security-reviewer.md +0 -137
- package/agents/writer.md +0 -184
- package/commands/api-design.md +0 -268
- package/commands/ci-cd.md +0 -270
- package/commands/database.md +0 -263
- package/commands/debug-detective.md +0 -99
- package/commands/documentation.md +0 -276
- package/commands/ecomode.md +0 -51
- package/commands/frontend.md +0 -271
- package/commands/git-master.md +0 -90
- package/commands/incident-response.md +0 -292
- package/commands/migrate.md +0 -101
- package/commands/performance.md +0 -288
- package/commands/refactor.md +0 -105
- package/commands/security-review.md +0 -288
- package/commands/specify.md +0 -128
- package/commands/tdd.md +0 -183
- package/commands/testing-strategy.md +0 -265
- package/skills/api-design/SKILL.md +0 -262
- package/skills/ci-cd/SKILL.md +0 -264
- package/skills/database/SKILL.md +0 -257
- package/skills/debug-detective/SKILL.md +0 -95
- package/skills/documentation/SKILL.md +0 -270
- package/skills/ecomode/SKILL.md +0 -46
- package/skills/frontend/SKILL.md +0 -265
- package/skills/git-master/SKILL.md +0 -86
- package/skills/incident-response/SKILL.md +0 -286
- package/skills/migrate/SKILL.md +0 -96
- package/skills/performance/SKILL.md +0 -282
- package/skills/refactor/SKILL.md +0 -100
- package/skills/security-review/SKILL.md +0 -282
- package/skills/specify/SKILL.md +0 -122
- package/skills/tdd/SKILL.md +0 -178
- package/skills/testing-strategy/SKILL.md +0 -260
package/commands/ship.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ship
|
|
3
|
+
description: This skill should be used when the user asks to "ship, 배포, 릴리스, release". 비대화형 자동 릴리스 파이프라인 — 테스트, 리뷰, 버전, CHANGELOG, PR을 원커맨드로.
|
|
4
|
+
argument-hint: "[patch|minor|major]"
|
|
5
|
+
model: inherit
|
|
6
|
+
disable-model-invocation: true
|
|
7
|
+
allowed-tools:
|
|
8
|
+
- Read
|
|
9
|
+
- Grep
|
|
10
|
+
- Glob
|
|
11
|
+
- Bash
|
|
12
|
+
- Agent
|
|
13
|
+
- Edit
|
|
14
|
+
- Write
|
|
15
|
+
triggers:
|
|
16
|
+
- "ship"
|
|
17
|
+
- "배포"
|
|
18
|
+
- "릴리스"
|
|
19
|
+
- "release"
|
|
20
|
+
- "릴리즈"
|
|
21
|
+
- "배포해줘"
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<Purpose>
|
|
25
|
+
"사용자가 /ship이라고 했으면 실행하라."
|
|
26
|
+
|
|
27
|
+
테스트 -> 리뷰 -> 버전 범프 -> CHANGELOG -> 커밋 -> PR 생성까지의 릴리스 파이프라인을 완전 자동화합니다.
|
|
28
|
+
사용자에게 묻지 않습니다. 중단 사유가 아닌 한 끝까지 진행합니다.
|
|
29
|
+
|
|
30
|
+
### 중단 사유 (이것만 멈춘다)
|
|
31
|
+
- main/master 브랜치에서 직접 실행 (abort)
|
|
32
|
+
- 해결 불가능한 머지 충돌
|
|
33
|
+
- 테스트 실패
|
|
34
|
+
- CRITICAL 리뷰 발견
|
|
35
|
+
- MAJOR 버전 범프 결정 (사용자 확인 필요)
|
|
36
|
+
|
|
37
|
+
이 외의 모든 상황은 자동 판단하여 진행합니다.
|
|
38
|
+
</Purpose>
|
|
39
|
+
|
|
40
|
+
<Compound_Integration>
|
|
41
|
+
## Compound-In: 이전 릴리스 이슈 로드
|
|
42
|
+
|
|
43
|
+
Step 1.5에서 실행합니다.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
compound-search("ship release 이슈 배포 실패")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
이전 릴리스에서 발생한 문제 패턴을 확인합니다.
|
|
50
|
+
결과가 있으면 해당 체크포인트를 강화합니다.
|
|
51
|
+
|
|
52
|
+
## Compound-Out: 릴리스 이슈 기록
|
|
53
|
+
|
|
54
|
+
Step 10에서 실행합니다.
|
|
55
|
+
릴리스 중 발생한 이슈가 있었으면 compound troubleshoot로 기록을 제안합니다.
|
|
56
|
+
</Compound_Integration>
|
|
57
|
+
|
|
58
|
+
<Steps>
|
|
59
|
+
## Step 0: 플랫폼 감지 + 베이스 브랜치 감지
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 플랫폼 감지
|
|
63
|
+
if command -v gh &>/dev/null && gh repo view &>/dev/null; then
|
|
64
|
+
PLATFORM="github"
|
|
65
|
+
elif command -v glab &>/dev/null; then
|
|
66
|
+
PLATFORM="gitlab"
|
|
67
|
+
else
|
|
68
|
+
PLATFORM="unknown"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# 베이스 브랜치 감지
|
|
72
|
+
BASE=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
|
|
73
|
+
if [ -z "$BASE" ]; then
|
|
74
|
+
BASE=$(git branch -r | grep -E 'origin/(main|master)' | head -1 | sed 's@.*origin/@@' | tr -d ' ')
|
|
75
|
+
fi
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Step 1: Pre-flight 체크
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
BRANCH=$(git branch --show-current)
|
|
82
|
+
|
|
83
|
+
# main/master -> ABORT
|
|
84
|
+
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
|
|
85
|
+
echo "ABORT: main/master 브랜치에서 직접 릴리스할 수 없습니다."
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
git status --porcelain
|
|
90
|
+
git diff --stat
|
|
91
|
+
git diff --staged --stat
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- main/master -> ABORT (새 브랜치 제안하지 않음. 그냥 중단.)
|
|
95
|
+
- 미커밋 변경 -> 자동 커밋 ("chore: stage uncommitted changes before release")
|
|
96
|
+
|
|
97
|
+
## Step 1.5: Compound-In
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
compound-search("ship release 배포 이슈")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
이전 문제 패턴이 있으면 체크포인트 강화. 없으면 조용히 진행.
|
|
104
|
+
|
|
105
|
+
## Step 2: 베이스 브랜치 머지
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git fetch origin $BASE
|
|
109
|
+
git merge origin/$BASE --no-edit
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- 충돌 -> ABORT ("머지 충돌 발생. 수동 해결 후 다시 /ship하세요.")
|
|
113
|
+
|
|
114
|
+
## Step 3: 테스트 실행
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# 자동 감지: package.json -> vitest/jest -> pytest -> cargo test -> go test
|
|
118
|
+
if [ -f package.json ]; then
|
|
119
|
+
if grep -q '"test"' package.json; then npm test
|
|
120
|
+
elif command -v vitest &>/dev/null; then npx vitest run
|
|
121
|
+
fi
|
|
122
|
+
elif [ -f pytest.ini ] || [ -f pyproject.toml ]; then pytest
|
|
123
|
+
elif [ -f Cargo.toml ]; then cargo test
|
|
124
|
+
elif [ -f go.mod ]; then go test ./...
|
|
125
|
+
fi
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- 전체 통과 -> Step 3.5
|
|
129
|
+
- 1개라도 실패 -> ABORT
|
|
130
|
+
|
|
131
|
+
## Step 3.5: 테스트 커버리지 감사 (선택)
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx vitest run --coverage 2>/dev/null || true
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
커버리지는 정보 제공용. 미달로 중단하지 않음.
|
|
138
|
+
|
|
139
|
+
## Step 4: Pre-landing 리뷰
|
|
140
|
+
|
|
141
|
+
ch-code-reviewer 에이전트(READ-ONLY) 위임.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
git diff $BASE...HEAD
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- CRITICAL -> ABORT
|
|
148
|
+
- AUTO-FIX (데드 코드, 미사용 import, stale 주석) -> 직접 수정 + 커밋
|
|
149
|
+
- 판단 필요 -> 사용자에게 질문
|
|
150
|
+
- APPROVED -> Step 5
|
|
151
|
+
|
|
152
|
+
## Step 5: 버전 범프
|
|
153
|
+
|
|
154
|
+
### 인수가 있으면: 인수 그대로 사용
|
|
155
|
+
### 없으면 자동 판단:
|
|
156
|
+
- < 50줄 -> PATCH (자동)
|
|
157
|
+
- 50줄+ 기능 없음 -> PATCH (자동)
|
|
158
|
+
- 기능 신호 (feat/, 새 라우트) -> MINOR (확인)
|
|
159
|
+
- Breaking change -> MAJOR (확인)
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm version {patch|minor|major} --no-git-tag-version
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Step 6: CHANGELOG 생성
|
|
166
|
+
|
|
167
|
+
커밋을 주제별 그룹핑 -> CHANGELOG.md 상단에 추가.
|
|
168
|
+
|
|
169
|
+
## Step 7: 릴리스 커밋
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
git add package.json CHANGELOG.md
|
|
173
|
+
git commit -m "release: v{version}"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Step 8: Push
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
git push -u origin $(git branch --show-current)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
force push 절대 안 함.
|
|
183
|
+
|
|
184
|
+
## Step 9: PR 생성
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
gh pr create --title "release: v{version}" --body "..."
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Step 10: Compound-Out
|
|
191
|
+
|
|
192
|
+
이슈가 있었으면 compound troubleshoot로 기록 제안.
|
|
193
|
+
</Steps>
|
|
194
|
+
|
|
195
|
+
<Review_Readiness_Dashboard>
|
|
196
|
+
```
|
|
197
|
+
+============================================+
|
|
198
|
+
| REVIEW READINESS |
|
|
199
|
+
+============================================+
|
|
200
|
+
| Check | Status | Required |
|
|
201
|
+
|--------------------|-----------|-----------|
|
|
202
|
+
| Tests | {N} PASS | YES |
|
|
203
|
+
| Code Review | {result} | YES |
|
|
204
|
+
| Build | {result} | YES |
|
|
205
|
+
| Coverage | {N}% | no |
|
|
206
|
+
| Base Merge | CLEAN | YES |
|
|
207
|
+
+============================================+
|
|
208
|
+
| VERDICT: {READY TO SHIP / BLOCKED} |
|
|
209
|
+
+============================================+
|
|
210
|
+
```
|
|
211
|
+
</Review_Readiness_Dashboard>
|
|
212
|
+
|
|
213
|
+
<Verification_Gate>
|
|
214
|
+
## 검증의 철칙 (IRON LAW)
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
"아마 될 거야" -> 실행해라.
|
|
218
|
+
"확신이 있다" -> 확신은 증거가 아니다.
|
|
219
|
+
"아까 테스트했는데" -> 코드가 바뀌었다. 다시 테스트해라.
|
|
220
|
+
"사소한 변경이라" -> 사소한 변경이 프로덕션을 깨뜨린다.
|
|
221
|
+
"이건 리뷰 안 해도" -> 모든 diff는 리뷰한다.
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
실행 결과가 곧 증거입니다. 실행하지 않은 검증은 존재하지 않습니다.
|
|
225
|
+
</Verification_Gate>
|
|
226
|
+
|
|
227
|
+
<Failure_Modes>
|
|
228
|
+
**테스트 실패 상태에서 진행**: 0 failures만 통과.
|
|
229
|
+
**main 브랜치에서 직접 실행**: ABORT. 새 브랜치 제안도 안 함.
|
|
230
|
+
**머지 충돌 자동 해결**: 충돌은 사용자가 수동 해결.
|
|
231
|
+
**CRITICAL 리뷰 이슈 무시**: CRITICAL 1개라도 있으면 ABORT.
|
|
232
|
+
**force push**: 어떤 상황에서도 사용하지 않음.
|
|
233
|
+
**불필요한 질문**: /ship은 비대화형. 중단 사유만 보고.
|
|
234
|
+
</Failure_Modes>
|
|
235
|
+
|
|
236
|
+
<Output>
|
|
237
|
+
## 성공 시
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
SHIP COMPLETE / 배포 완료
|
|
241
|
+
=========================
|
|
242
|
+
Version: {old} -> {new} ({type})
|
|
243
|
+
Tests: {N} passed, 0 failed
|
|
244
|
+
Review: APPROVED ({N} auto-fixed)
|
|
245
|
+
PR: #{number}
|
|
246
|
+
URL: {url}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 실패 시
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
SHIP ABORTED / 배포 중단
|
|
253
|
+
=========================
|
|
254
|
+
Step: {실패한 Step}
|
|
255
|
+
Reason: {중단 사유}
|
|
256
|
+
Action: {다음 행동}
|
|
257
|
+
```
|
|
258
|
+
</Output>
|
|
259
|
+
|
|
260
|
+
<Policy>
|
|
261
|
+
- 테스트 통과는 필수. 예외 없음.
|
|
262
|
+
- CRITICAL 리뷰 이슈 -> 사용자 동의 없이 진행하지 않음.
|
|
263
|
+
- main 브랜치 직접 릴리스 = ABORT.
|
|
264
|
+
- 중단 사유가 아닌 한 묻지 않고 끝까지 진행.
|
|
265
|
+
- 실행 결과 = 증거. 추측 != 검증.
|
|
266
|
+
- force push 금지.
|
|
267
|
+
- AUTO-FIX는 데드 코드/미사용 import/stale 주석에만 적용.
|
|
268
|
+
</Policy>
|
|
269
|
+
|
|
270
|
+
<Arguments>
|
|
271
|
+
- `patch`: 버그 수정 릴리스 (0.0.X)
|
|
272
|
+
- `minor`: 하위 호환 새 기능 릴리스 (0.X.0)
|
|
273
|
+
- `major`: 하위 비호환 변경 릴리스 (X.0.0)
|
|
274
|
+
- 생략 시: 변경 내용 자동 분석하여 결정 (major만 사용자 확인)
|
|
275
|
+
</Arguments>
|
|
276
|
+
|
|
277
|
+
$ARGUMENTS
|
package/dist/cli.js
CHANGED
|
@@ -9,11 +9,14 @@ import * as path from 'node:path';
|
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { prepareHarness, isFirstRun } from './core/harness.js';
|
|
11
11
|
import { spawnClaudeWithResume } from './core/spawn.js';
|
|
12
|
+
import { resolveLaunchContext } from './services/session.js';
|
|
12
13
|
// global-config is used by harness internally
|
|
13
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
const pkgJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
15
16
|
const PKG_VERSION = pkgJson.version ?? '0.0.0';
|
|
16
|
-
const
|
|
17
|
+
const launchContext = resolveLaunchContext(process.argv.slice(2));
|
|
18
|
+
const args = launchContext.args;
|
|
19
|
+
const runtime = launchContext.runtime;
|
|
17
20
|
const commands = [
|
|
18
21
|
{
|
|
19
22
|
name: 'forge',
|
|
@@ -93,7 +96,7 @@ const commands = [
|
|
|
93
96
|
if (args.includes('--regenerate')) {
|
|
94
97
|
const { writeHooksJson } = await import('./hooks/hooks-generator.js');
|
|
95
98
|
const hooksDir = path.join(process.cwd(), 'hooks');
|
|
96
|
-
const result = writeHooksJson(hooksDir, { cwd: process.cwd() });
|
|
99
|
+
const result = writeHooksJson(hooksDir, { cwd: process.cwd(), runtime });
|
|
97
100
|
console.log(`[forgen] hooks.json regenerated: ${result.active} active, ${result.disabled} disabled`);
|
|
98
101
|
}
|
|
99
102
|
else {
|
|
@@ -227,14 +230,14 @@ async function main() {
|
|
|
227
230
|
|
|
228
231
|
Setting up...`);
|
|
229
232
|
}
|
|
230
|
-
let context = await prepareHarness(process.cwd());
|
|
233
|
+
let context = await prepareHarness(process.cwd(), { runtime });
|
|
231
234
|
// 첫 실행 또는 프로필 없음 → 자동 온보딩 (interactive 환경)
|
|
232
235
|
if (context.v1.needsOnboarding && process.stdin.isTTY) {
|
|
233
236
|
console.log('\n 프로필이 없습니다. 온보딩을 시작합니다.\n');
|
|
234
237
|
const { runOnboarding } = await import('./forge/onboarding-cli.js');
|
|
235
238
|
await runOnboarding();
|
|
236
239
|
// 온보딩 후 harness 재실행 (프로필 반영)
|
|
237
|
-
context = await prepareHarness(process.cwd());
|
|
240
|
+
context = await prepareHarness(process.cwd(), { runtime });
|
|
238
241
|
}
|
|
239
242
|
if (firstRun && !context.v1.needsOnboarding) {
|
|
240
243
|
console.log(`
|
|
@@ -252,12 +255,16 @@ async function main() {
|
|
|
252
255
|
${dim}Code, forged for you.${reset}
|
|
253
256
|
${dim}Scope: v1(${context.v1.session?.quality_pack ?? 'onboarding needed'})${reset}
|
|
254
257
|
`);
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
const runtimeLabel = runtime === 'codex' ? 'Codex' : 'Claude';
|
|
259
|
+
console.log(`[forgen] Starting ${runtimeLabel}...\n`);
|
|
260
|
+
await spawnClaudeWithResume(args, context, () => prepareHarness(process.cwd(), { runtime }), runtime);
|
|
257
261
|
}
|
|
258
262
|
catch (err) {
|
|
259
263
|
const msg = err instanceof Error ? err.message : String(err);
|
|
260
|
-
if (msg.includes('
|
|
264
|
+
if (msg.includes('Codex is not installed')) {
|
|
265
|
+
console.error('[forgen] Codex is not installed.');
|
|
266
|
+
}
|
|
267
|
+
else if (msg.includes('Claude Code is not installed')) {
|
|
261
268
|
console.error('[forgen] Claude Code not found. Install: npm install -g @anthropic-ai/claude-code');
|
|
262
269
|
}
|
|
263
270
|
else {
|
|
@@ -275,9 +282,10 @@ function printHelp() {
|
|
|
275
282
|
The more you use Claude, the better it knows you.
|
|
276
283
|
|
|
277
284
|
Usage:
|
|
278
|
-
forgen Start
|
|
285
|
+
forgen Start runtime launcher (harness mode)
|
|
279
286
|
forgen "prompt" Start with a prompt
|
|
280
287
|
forgen --resume Resume previous session
|
|
288
|
+
forgen --runtime claude|codex Select launch runtime
|
|
281
289
|
|
|
282
290
|
Commands:
|
|
283
291
|
forgen forge Personalize your coding profile
|
|
@@ -296,7 +304,7 @@ function printHelp() {
|
|
|
296
304
|
forgen uninstall Remove forgen
|
|
297
305
|
|
|
298
306
|
Harness mode (default):
|
|
299
|
-
Wraps Claude
|
|
307
|
+
Wraps Claude (or Codex) with personalization, auto-compound, and safety hooks.
|
|
300
308
|
`);
|
|
301
309
|
}
|
|
302
310
|
main().catch(() => {
|
|
@@ -483,6 +483,20 @@ ${sanitizedSummary.slice(0, 4000)}
|
|
|
483
483
|
catch (e) {
|
|
484
484
|
process.stderr.write(`[forgen-auto-compound] rule promotion: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
485
485
|
}
|
|
486
|
+
// Step 5: meta-learning (HyperAgents-inspired self-tuning)
|
|
487
|
+
try {
|
|
488
|
+
const { runMetaLearning } = await import('../engine/meta-learning/runner.js');
|
|
489
|
+
const metaResult = runMetaLearning(sessionId, cwd);
|
|
490
|
+
if (metaResult.qualityScore) {
|
|
491
|
+
process.stderr.write(`[forgen-meta] session quality: ${metaResult.qualityScore.overallScore}/100\n`);
|
|
492
|
+
}
|
|
493
|
+
if (metaResult.scopePromotions && metaResult.scopePromotions.length > 0) {
|
|
494
|
+
process.stderr.write(`[forgen-meta] promoted ${metaResult.scopePromotions.length} solution(s) to universal scope\n`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch (e) {
|
|
498
|
+
process.stderr.write(`[forgen-meta] ${e instanceof Error ? e.message : String(e)}\n`);
|
|
499
|
+
}
|
|
486
500
|
// 완료 기록
|
|
487
501
|
const statePath = path.join(FORGEN_HOME, 'state', 'last-auto-compound.json');
|
|
488
502
|
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Authoritative: docs/plans/2026-04-03-forgen-rule-renderer-spec.md
|
|
8
8
|
*/
|
|
9
|
+
import type { RuntimeHost } from './types.js';
|
|
9
10
|
/** 보안 규칙 (정적 — v1 GLOBAL_SAFETY_RULES와 동일 맥락) */
|
|
10
11
|
export declare function generateSecurityRules(): string;
|
|
11
12
|
/** 안티패턴 감지 규칙 (정적) */
|
|
@@ -36,7 +37,7 @@ export declare function registerTmuxBindings(): Promise<void>;
|
|
|
36
37
|
* or user scripts may still read them). When all consumers have been
|
|
37
38
|
* migrated and a major version ships, remove the `COMPOUND_*` lines.
|
|
38
39
|
*/
|
|
39
|
-
export declare function buildEnv(cwd: string, v1SessionId?: string): Record<string, string>;
|
|
40
|
+
export declare function buildEnv(cwd: string, v1SessionId?: string, runtime?: RuntimeHost): Record<string, string>;
|
|
40
41
|
/**
|
|
41
42
|
* Test-only exports for the C5 rendering pipeline. The ergonomic choice
|
|
42
43
|
* over `export function normalizeDescription` is intentional: anything
|
|
@@ -428,12 +428,13 @@ export async function registerTmuxBindings() {
|
|
|
428
428
|
* or user scripts may still read them). When all consumers have been
|
|
429
429
|
* migrated and a major version ships, remove the `COMPOUND_*` lines.
|
|
430
430
|
*/
|
|
431
|
-
export function buildEnv(cwd, v1SessionId) {
|
|
431
|
+
export function buildEnv(cwd, v1SessionId, runtime = 'claude') {
|
|
432
432
|
const env = {
|
|
433
433
|
// New canonical names
|
|
434
434
|
FORGEN_HARNESS: '1',
|
|
435
435
|
FORGEN_CWD: cwd,
|
|
436
436
|
FORGEN_V1: '1',
|
|
437
|
+
FORGEN_RUNTIME: runtime,
|
|
437
438
|
// Legacy compat (remove in next major)
|
|
438
439
|
COMPOUND_HARNESS: '1',
|
|
439
440
|
COMPOUND_CWD: cwd,
|
package/dist/core/dashboard.d.ts
CHANGED
|
@@ -86,6 +86,23 @@ export declare function collectLifecycleActivity(): LifecycleActivity;
|
|
|
86
86
|
export declare function collectSessionHistory(): SessionHistory;
|
|
87
87
|
/** Collect hook error data. */
|
|
88
88
|
export declare function collectHookHealth(): HookHealth;
|
|
89
|
+
export interface LearningCurve {
|
|
90
|
+
correctionsLast7d: number;
|
|
91
|
+
correctionsPrev7d: number;
|
|
92
|
+
correctionTrend: 'improving' | 'stable' | 'worsening';
|
|
93
|
+
evidenceTotalDays: number;
|
|
94
|
+
sessionsAnalyzed: number;
|
|
95
|
+
estimatedMinutesSaved: number;
|
|
96
|
+
topCorrectionAxes: Array<{
|
|
97
|
+
axis: string;
|
|
98
|
+
count: number;
|
|
99
|
+
}>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Learning Curve 수집.
|
|
103
|
+
* evidence 파일(교정 기록)과 compound 활용률을 교차 분석하여 "쓸수록 나아진다"를 정량화.
|
|
104
|
+
*/
|
|
105
|
+
export declare function collectLearningCurve(): LearningCurve;
|
|
89
106
|
export declare function renderDashboard(): string;
|
|
90
107
|
/** CLI handler: forgen dashboard */
|
|
91
108
|
export declare function handleDashboard(): Promise<void>;
|
package/dist/core/dashboard.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import * as fs from 'node:fs';
|
|
15
15
|
import * as path from 'node:path';
|
|
16
|
-
import { ME_SOLUTIONS, ME_RULES, ME_BEHAVIOR, STATE_DIR, } from './paths.js';
|
|
16
|
+
import { ME_SOLUTIONS, ME_RULES, ME_BEHAVIOR, STATE_DIR, V1_EVIDENCE_DIR, } from './paths.js';
|
|
17
17
|
import { parseFrontmatterOnly } from '../engine/solution-format.js';
|
|
18
18
|
import { readMatchEvalLog } from '../engine/match-eval-log.js';
|
|
19
19
|
// ── ANSI color helpers ──
|
|
@@ -349,7 +349,114 @@ function renderHookHealth(data) {
|
|
|
349
349
|
lines.push(tableSep(widths, false, true));
|
|
350
350
|
return lines.join('\n');
|
|
351
351
|
}
|
|
352
|
-
|
|
352
|
+
/**
|
|
353
|
+
* Learning Curve 수집.
|
|
354
|
+
* evidence 파일(교정 기록)과 compound 활용률을 교차 분석하여 "쓸수록 나아진다"를 정량화.
|
|
355
|
+
*/
|
|
356
|
+
export function collectLearningCurve() {
|
|
357
|
+
const now = Date.now();
|
|
358
|
+
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
359
|
+
let correctionsLast7d = 0;
|
|
360
|
+
let correctionsPrev7d = 0;
|
|
361
|
+
const axisCounts = new Map();
|
|
362
|
+
const uniqueDays = new Set();
|
|
363
|
+
try {
|
|
364
|
+
if (fs.existsSync(V1_EVIDENCE_DIR)) {
|
|
365
|
+
const files = fs.readdirSync(V1_EVIDENCE_DIR).filter(f => f.endsWith('.json'));
|
|
366
|
+
for (const f of files) {
|
|
367
|
+
try {
|
|
368
|
+
const data = JSON.parse(fs.readFileSync(path.join(V1_EVIDENCE_DIR, f), 'utf-8'));
|
|
369
|
+
if (!data.timestamp)
|
|
370
|
+
continue;
|
|
371
|
+
const ts = new Date(data.timestamp).getTime();
|
|
372
|
+
if (!Number.isFinite(ts))
|
|
373
|
+
continue;
|
|
374
|
+
const age = now - ts;
|
|
375
|
+
if (age < SEVEN_DAYS_MS)
|
|
376
|
+
correctionsLast7d++;
|
|
377
|
+
else if (age < 2 * SEVEN_DAYS_MS)
|
|
378
|
+
correctionsPrev7d++;
|
|
379
|
+
if (data.axis_hint) {
|
|
380
|
+
axisCounts.set(data.axis_hint, (axisCounts.get(data.axis_hint) ?? 0) + 1);
|
|
381
|
+
}
|
|
382
|
+
uniqueDays.add(new Date(ts).toISOString().slice(0, 10));
|
|
383
|
+
}
|
|
384
|
+
catch { /* 개별 파일 파싱 실패 무시 */ }
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch { /* fail-open */ }
|
|
389
|
+
// 추세 판정: 전주 대비 30% 이상 감소 = improving, 30% 이상 증가 = worsening
|
|
390
|
+
let correctionTrend = 'stable';
|
|
391
|
+
if (correctionsPrev7d > 0) {
|
|
392
|
+
const delta = (correctionsLast7d - correctionsPrev7d) / correctionsPrev7d;
|
|
393
|
+
if (delta < -0.3)
|
|
394
|
+
correctionTrend = 'improving';
|
|
395
|
+
else if (delta > 0.3)
|
|
396
|
+
correctionTrend = 'worsening';
|
|
397
|
+
}
|
|
398
|
+
// 상위 교정 축
|
|
399
|
+
const topCorrectionAxes = Array.from(axisCounts.entries())
|
|
400
|
+
.sort((a, b) => b[1] - a[1])
|
|
401
|
+
.slice(0, 3)
|
|
402
|
+
.map(([axis, count]) => ({ axis, count }));
|
|
403
|
+
// 세션 수: evidence 날짜 기준 (고유 날짜 × 평균 2세션/일 가정)
|
|
404
|
+
const sessionsAnalyzed = uniqueDays.size * 2;
|
|
405
|
+
// 추정 절약 시간: 지난 7일 compound 주입 이벤트당 평균 8분 절약 가정
|
|
406
|
+
// (경쟁자 분석에서 도출한 경험적 수치 — 카운터팩추얼의 하한 추정)
|
|
407
|
+
const injection = collectInjectionActivity();
|
|
408
|
+
let successfulInjections = 0;
|
|
409
|
+
try {
|
|
410
|
+
for (const rec of injection.recentInjections ?? []) {
|
|
411
|
+
const ts = new Date(rec.ts).getTime();
|
|
412
|
+
if (Number.isFinite(ts) && now - ts < SEVEN_DAYS_MS)
|
|
413
|
+
successfulInjections++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch { /* fail-open */ }
|
|
417
|
+
const estimatedMinutesSaved = Math.round(successfulInjections * 8);
|
|
418
|
+
return {
|
|
419
|
+
correctionsLast7d,
|
|
420
|
+
correctionsPrev7d,
|
|
421
|
+
correctionTrend,
|
|
422
|
+
evidenceTotalDays: uniqueDays.size,
|
|
423
|
+
sessionsAnalyzed,
|
|
424
|
+
estimatedMinutesSaved,
|
|
425
|
+
topCorrectionAxes,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function renderLearningCurve(data) {
|
|
429
|
+
const trendIcon = data.correctionTrend === 'improving'
|
|
430
|
+
? green('↓ 감소')
|
|
431
|
+
: data.correctionTrend === 'worsening'
|
|
432
|
+
? red('↑ 증가')
|
|
433
|
+
: dim('→ 유지');
|
|
434
|
+
const axisLines = data.topCorrectionAxes.length > 0
|
|
435
|
+
? data.topCorrectionAxes.map(a => ` ${a.axis}: ${a.count}회`).join('\n')
|
|
436
|
+
: ` ${dim('(아직 교정 데이터 없음)')}`;
|
|
437
|
+
const savedHours = Math.floor(data.estimatedMinutesSaved / 60);
|
|
438
|
+
const savedMins = data.estimatedMinutesSaved % 60;
|
|
439
|
+
const savedStr = savedHours > 0 ? `${savedHours}시간 ${savedMins}분` : `${savedMins}분`;
|
|
440
|
+
return [
|
|
441
|
+
` ${bold('📈 Learning Curve / 학습 곡선')}`,
|
|
442
|
+
``,
|
|
443
|
+
` 교정 추이 (지난 7일):`,
|
|
444
|
+
` 이번 주: ${data.correctionsLast7d}건`,
|
|
445
|
+
` 지난 주: ${data.correctionsPrev7d}건`,
|
|
446
|
+
` 추세: ${trendIcon}`,
|
|
447
|
+
``,
|
|
448
|
+
` 주요 교정 축 (누적):`,
|
|
449
|
+
axisLines,
|
|
450
|
+
``,
|
|
451
|
+
` 누적 사용:`,
|
|
452
|
+
` 활동한 일수: ${data.evidenceTotalDays}일`,
|
|
453
|
+
` 분석된 세션: 약 ${data.sessionsAnalyzed}회`,
|
|
454
|
+
``,
|
|
455
|
+
` ${cyan('추정 절약 시간')} (compound 주입 성공 기반):`,
|
|
456
|
+
` ${bold(savedStr)} ${dim('(지난 7일)')}`,
|
|
457
|
+
` ${dim('※ compound가 힌트를 제공한 매 1회당 평균 8분 절약 가정')}`,
|
|
458
|
+
].join('\n');
|
|
459
|
+
}
|
|
353
460
|
export function renderDashboard() {
|
|
354
461
|
const knowledge = collectKnowledgeOverview();
|
|
355
462
|
const injection = collectInjectionActivity();
|
|
@@ -357,6 +464,7 @@ export function renderDashboard() {
|
|
|
357
464
|
const lifecycle = collectLifecycleActivity();
|
|
358
465
|
const session = collectSessionHistory();
|
|
359
466
|
const hookHealth = collectHookHealth();
|
|
467
|
+
const learning = collectLearningCurve();
|
|
360
468
|
const divider = ` ${dim('─'.repeat(50))}`;
|
|
361
469
|
const sections = [
|
|
362
470
|
'',
|
|
@@ -364,6 +472,8 @@ export function renderDashboard() {
|
|
|
364
472
|
` ${BOLD}${CYAN}║ Forgen Compound Dashboard ║${RESET}`,
|
|
365
473
|
` ${BOLD}${CYAN}╚══════════════════════════════════════════════╝${RESET}`,
|
|
366
474
|
'',
|
|
475
|
+
renderLearningCurve(learning),
|
|
476
|
+
divider,
|
|
367
477
|
renderKnowledgeOverview(knowledge),
|
|
368
478
|
divider,
|
|
369
479
|
renderInjectionActivity(injection),
|
package/dist/core/harness.d.ts
CHANGED
|
@@ -11,14 +11,19 @@
|
|
|
11
11
|
* - Lines 400-550: Rule file injection, gitignore, compound memory
|
|
12
12
|
* - Lines 550+: prepareHarness — main orchestration
|
|
13
13
|
*/
|
|
14
|
+
import { type RuntimeHost } from './types.js';
|
|
14
15
|
import { rollbackSettings } from './settings-lock.js';
|
|
15
16
|
import { type V1BootstrapResult } from './v1-bootstrap.js';
|
|
16
17
|
export interface V1HarnessContext {
|
|
17
18
|
cwd: string;
|
|
18
19
|
inTmux: boolean;
|
|
19
20
|
v1: V1BootstrapResult;
|
|
21
|
+
runtime: RuntimeHost;
|
|
20
22
|
}
|
|
21
23
|
/** 최초 실행 여부: ~/.forgen/ 디렉토리가 없으면 true */
|
|
22
24
|
export declare function isFirstRun(): boolean;
|
|
23
25
|
export { rollbackSettings };
|
|
24
|
-
|
|
26
|
+
interface PrepareHarnessOptions {
|
|
27
|
+
runtime?: RuntimeHost;
|
|
28
|
+
}
|
|
29
|
+
export declare function prepareHarness(cwd: string, options?: PrepareHarnessOptions): Promise<V1HarnessContext>;
|