@wooojin/forgen 0.5.7 → 0.5.9
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 +68 -0
- package/dist/core/uninstall.js +10 -1
- package/dist/host/install-claude.js +7 -33
- package/dist/host/install-codex.d.ts +32 -8
- package/dist/host/install-codex.js +429 -160
- package/dist/host/install-opencode.d.ts +5 -0
- package/dist/host/install-opencode.js +9 -6
- package/dist/host/managed-marker.d.ts +22 -0
- package/dist/host/managed-marker.js +79 -0
- package/dist/host/uninstall-codex.d.ts +2 -0
- package/dist/host/uninstall-codex.js +12 -9
- package/dist/host/uninstall-opencode.d.ts +36 -0
- package/dist/host/uninstall-opencode.js +141 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/postinstall.js +76 -42
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,74 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.9] — 2026-10-02 — 남겨 둔 한계 정리: 사용자 스킬 보존 · AGENTS.md 전체 정리 · OpenCode uninstall · claude-mem 13.28
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`forgen install` / npm postinstall 이 사용자가 만든 `forgen-*` 스킬을 지우던 것.** dev-guide 스킬의 stale 정리가 이름만 보고
|
|
14
|
+
디렉토리를 재귀 삭제했다 (`install claude` 는 `forgen-*` 전부, `install codex`/postinstall 은 `forgen-(react|vue|node|go)-*`).
|
|
15
|
+
이제 forgen 소유만 정리한다: 패키지가 현재 제공하는 이름, 또는 `SKILL.md` 가 `…/assets/dev-guide/…` 를 가리키는 심링크(이전
|
|
16
|
+
버전이 설치한 것). 삭제도 `SKILL.md` 한 파일과 빈 디렉토리만. 설치는 이미 있는 것을 덮어쓰지 않는다 — 심링크된 스킬
|
|
17
|
+
디렉토리(dotfiles 등)를 따라 들어가 쓰지 않고, 한 스킬 자리가 막혀 있어도 나머지 설치는 계속한다.
|
|
18
|
+
- AGENTS.md 블록 처리: 마커가 한 줄을 통째로 차지할 때만 블록 경계로 본다. 본문에 마커 문자열을 인용한 문서에서 인용 줄부터
|
|
19
|
+
실제 블록 끝까지를 덮어쓰거나 지울 수 있었다.
|
|
20
|
+
- **forgen-eval**: `workerStatus()` 가 "Worker is not running" 을 실행 중으로 판정하던 것. claude-mem recall 이 `sqlite3` CLI 에만
|
|
21
|
+
의존해 CLI 가 없는 머신에서는 조용히 비어 있던 것(forgen+mem arm 이 forgen-only 와 같아짐) — `node:sqlite` 우선 + CLI 폴백,
|
|
22
|
+
둘 다 없으면 run 경고. `CLAUDE_MEM_TESTED_VERSION` 이 12.4.8 에 머물러 매 실행 version-mismatch 경고가 나던 것.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **`forgen uninstall` 이 다른 프로젝트의 AGENTS.md 블록도 정리한다.** `forgen install codex|opencode` 가 블록을 쓴 경로를 host
|
|
26
|
+
설정 디렉토리의 `forgen-agents-md.json` 에 기록하고, uninstall 이 기록된 경로 전부 + 실행한 cwd 를 정리한 뒤 기록을 지운다.
|
|
27
|
+
(0.5.9 이전 설치분은 기록이 없어 여전히 cwd 만 정리된다.)
|
|
28
|
+
- **OpenCode uninstall.** forgen-managed plugin 제거(설치 때 백업한 사용자 plugin 이 있으면 되돌림), config(JSONC) 의
|
|
29
|
+
`mcp.forgen-compound` 를 forgen 시그니처일 때만 제거(다른 키·서버의 값은 보존, 인접 주석/포맷은 바뀔 수 있음; 파싱 불가면
|
|
30
|
+
건드리지 않음), AGENTS.md 블록. plugin 소유는 첫 줄의 `// forgen-managed` 로만 판정한다.
|
|
31
|
+
- uninstall 이 쓰지 못한 AGENTS.md(읽기 전용 등)는 오류로 알리고 기록에 남겨 다음 실행에서 다시 시도한다.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- **claude-mem 13.12.4 → 13.28.0** (forgen-eval 테스트베드 핀, Dependabot #136). 0.5.7 에서 "재측정 필요" 로 보류했으나 근거가
|
|
35
|
+
약했다 — 저장소의 측정 리포트는 claude-mem 버전에 묶여 있지 않고 forgen-eval 이 의존하는 것은 CLI 계약이다. 격리 HOME 에 두
|
|
36
|
+
버전을 설치해 계약이 동일함을 실측: `version`/`start`/`status`/`stop`, `search` 출력(JSON + `| #N |` 표), DB 컬럼, 비매칭
|
|
37
|
+
쿼리 동작.
|
|
38
|
+
|
|
39
|
+
### Verified
|
|
40
|
+
- fresh-context critic 리뷰: MAJOR 2 · MINOR 10 → 반영 또는 한계로 명시(ADR-016). 리뷰어의 재현 환경(심링크된 스킬 디렉토리)을
|
|
41
|
+
CLI `install both` 와 postinstall 에 다시 돌려 사용자 파일이 그대로임을 확인.
|
|
42
|
+
- vitest 3242 통과 + forgen-eval 72 통과 (신규: 실 SQLite DB 로 recall 계약, worker 판정, 핀 일치 강제; 사용자 스킬 보존;
|
|
43
|
+
AGENTS.md 기록/정리; OpenCode uninstall).
|
|
44
|
+
- 격리 HOME 실행: 실 claude-mem 13.28.0 워커에 대해 빌드된 recall 경로가 시드한 observation 본문을 돌려주고, 워커 on/off 판정이
|
|
45
|
+
맞음. CLI `install both` + `install opencode` 를 두 프로젝트에서 실행한 뒤 다른 디렉토리에서 `uninstall --force` —
|
|
46
|
+
두 AGENTS.md 블록·OpenCode plugin/MCP·Codex 등록분 제거, 사용자 스킬과 사용자 AGENTS.md 내용 보존.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## [0.5.8] — 2026-10-02 — Codex 가 재배치한 마커에서도 설치·제거가 정확하게 (Codex 0.160 확인)
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- **Codex 가 config.toml 을 다시 쓰면서 forgen 마커를 뒤집어 놓은 형태에서 `forgen uninstall` 이 MCP 서버 등록을 지우지
|
|
53
|
+
못하던 것 (0.5.7).** 실머신에서 `/hooks` 승인 후 관측: `# >>> forgen-managed-mcp` 는 forgen 테이블과 함께 파일 끝으로 가고
|
|
54
|
+
`# <<< forgen-managed-mcp` 는 앞쪽에 고아로 남는다. 이제 마커를 *범위* 가 아니라 *표식* 으로만 쓰고, 범위는 TOML 구조
|
|
55
|
+
(테이블 헤더 ~ 다음 헤더, notify 한 줄)로 정한다. 재설치는 고아 마커를 걷어 테이블 위아래로 정규화하고, 제거는 테이블·하위
|
|
56
|
+
테이블·모든 마커 줄을 지운다. notify 블록도 END 마커가 옮겨지거나 사라져도 동작한다.
|
|
57
|
+
- 사용자가 forgen MCP 테이블만 지우고 마커가 남은 경우, 재설치가 고아 마커를 정리하고 블록을 한 번만 쓴다.
|
|
58
|
+
- **`codex mcp add <다른 서버>` 뒤에는 forgen 테이블을 알아보지 못하던 것.** Codex 가 mcp_servers 를 다시 쓰며 시작 마커를
|
|
59
|
+
없앤다. 소유 판정을 "헤더 바로 위의 마커 또는 forgen 내용 시그니처(args 의 `dist/mcp/server.js` + `--host=codex`,
|
|
60
|
+
notify 의 `dist/host/codex-notify.js`)" 로 바꿨다 — 마커가 사라져도 경로 갱신과 제거가 된다.
|
|
61
|
+
- CRLF config.toml 에서 블록 제거 후 파일이 `\r` 로만 끝나 Codex 가 로드하지 못할 수 있던 것 (0.5.8 작업 중 회귀로 발견, 미출시).
|
|
62
|
+
- forgen 테이블 뒤·다음 테이블 헤더 위의 사용자 주석을 forgen 본문으로 취급하지 않는다.
|
|
63
|
+
- Codex 가 forgen notify 블록 안의 값을 사용자의 notifier 로 바꿔 놓은 경우 그 값을 보존한다 (이전엔 재설치가 덮어썼다).
|
|
64
|
+
- 같은 MCP 서버가 다른 TOML 표기(따옴표·공백 헤더, inline table, dotted key)로 정의돼 있으면 중복 정의를 추가하지 않는다
|
|
65
|
+
(추가하면 Codex 가 `duplicate key` 로 로드 실패).
|
|
66
|
+
- 블록 제거가 "지웠다" 고 보고하는 것은 실제로 테이블/notify 줄을 지웠을 때만.
|
|
67
|
+
|
|
68
|
+
### Verified
|
|
69
|
+
- vitest 3222 통과 (신규: 실머신에서 채취한 재배치 형태, CRLF/BOM, 마커 소실, 사용자 주석, 다른 TOML 표기).
|
|
70
|
+
- fresh-context critic 리뷰: CRITICAL 1 · MAJOR 2 · MINOR 6 → 전부 반영. 리뷰어의 케이스 68건 + 퍼징 14,000건에서 Codex 로드
|
|
71
|
+
불가 출력·사용자 데이터 변경·비멱등 0건, 실 Codex 0.160 재현 스크립트(`codex mcp add` 후 설치/제거, CRLF 왕복) 통과.
|
|
72
|
+
- 이 머신의 실 `~/.codex/config.toml` 사본에 적용: 재설치 diff 는 고아 END 마커 한 줄 이동뿐(hooks.state 31개 보존),
|
|
73
|
+
제거 후 forgen 항목 0건, 두 결과 모두 Codex 0.160.0 이 정상 파싱.
|
|
74
|
+
- **Codex 0.160.0 호환 확인**: 훅 출력 스키마 11종이 vendoring 한 0.153.4 사본과 동일, trust 해시 일치(실머신 22/22 trusted,
|
|
75
|
+
Codex `hooks/list` 30/30), 실세션에서 SessionStart(룰 블록 주입, 스필 없음)·UserPromptSubmit·Stop·SessionEnd 훅 발화, 오류 0건.
|
|
76
|
+
|
|
77
|
+
|
|
10
78
|
## [0.5.7] — 2026-10-02 — `forgen uninstall` 의 Codex 정리 · 의존성 메이저 업그레이드
|
|
11
79
|
|
|
12
80
|
### Added
|
package/dist/core/uninstall.js
CHANGED
|
@@ -364,7 +364,8 @@ export async function handleUninstall(cwd, options) {
|
|
|
364
364
|
console.log(' 4. Remove forgen block from CLAUDE.md');
|
|
365
365
|
console.log(' 5. Remove slash commands (~/.claude/commands/forgen/) and the forgen-managed verify skill (~/.claude/skills/verify/)');
|
|
366
366
|
console.log(' 6. Remove plugin artifacts (cache, installed_plugins.json, plugin directory), dev-guide skills, and the forgen-compound MCP entry in ~/.claude.json');
|
|
367
|
-
console.log(' 7. Codex (if $CODEX_HOME or ~/.codex exists): forgen hooks in hooks.json, the MCP/notify blocks in config.toml, forgen skills, ch-*.toml agents, and the AGENTS.md
|
|
367
|
+
console.log(' 7. Codex (if $CODEX_HOME or ~/.codex exists): forgen hooks in hooks.json, the MCP/notify blocks in config.toml, forgen skills, ch-*.toml agents, and the AGENTS.md blocks (every project recorded at install time) — hooks and files from other tools are kept');
|
|
368
|
+
console.log(' OpenCode (if its config dir exists): the forgen plugin, the forgen-compound MCP entry, AGENTS.md blocks');
|
|
368
369
|
if (options.purge) {
|
|
369
370
|
console.log(' 8. --purge: Delete ~/.forgen/ entirely (rules, me/, state/, solutions/, behavior/)');
|
|
370
371
|
console.log(' WARNING: this erases all accumulated corrections, rules, drift, and lifecycle history.');
|
|
@@ -409,6 +410,14 @@ export async function handleUninstall(cwd, options) {
|
|
|
409
410
|
catch (e) {
|
|
410
411
|
console.error(' ✗ Codex cleanup failed:', e instanceof Error ? e.message : String(e));
|
|
411
412
|
}
|
|
413
|
+
try {
|
|
414
|
+
const { planOpencodeUninstall, renderOpencodeUninstall } = await import('../host/uninstall-opencode.js');
|
|
415
|
+
for (const line of renderOpencodeUninstall(planOpencodeUninstall({ pkgRoot })))
|
|
416
|
+
console.log(line);
|
|
417
|
+
}
|
|
418
|
+
catch (e) {
|
|
419
|
+
console.error(' ✗ OpenCode cleanup failed:', e instanceof Error ? e.message : String(e));
|
|
420
|
+
}
|
|
412
421
|
cleanPluginArtifacts();
|
|
413
422
|
if (options.purge) {
|
|
414
423
|
try {
|
|
@@ -18,7 +18,7 @@ import * as fs from 'node:fs';
|
|
|
18
18
|
import * as os from 'node:os';
|
|
19
19
|
import * as path from 'node:path';
|
|
20
20
|
import { generateHooksJson } from '../hooks/hooks-generator.js';
|
|
21
|
-
import { hasManagedSkillMarker } from './managed-marker.js';
|
|
21
|
+
import { hasManagedSkillMarker, installSkillFile, removeOwnedDevGuideSkills } from './managed-marker.js';
|
|
22
22
|
const PLUGIN_KEY = 'forgen@forgen-local';
|
|
23
23
|
const FORGEN_MANAGED_MARKER = '<!-- forgen-managed -->';
|
|
24
24
|
function readPkgVersion(pkgRoot) {
|
|
@@ -266,40 +266,14 @@ function installDevGuideSkills(opts) {
|
|
|
266
266
|
return { skillsPath: skillsDir, skillsInstalled: entries.length, skillsRemoved: 0 };
|
|
267
267
|
}
|
|
268
268
|
fs.mkdirSync(skillsDir, { recursive: true });
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
continue;
|
|
274
|
-
const fullPath = path.join(skillsDir, entry);
|
|
275
|
-
if (fs.statSync(fullPath).isDirectory()) {
|
|
276
|
-
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
277
|
-
removed += 1;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
// Install each skill via symlink → cpSync fallback (mirrors plugin cache pattern)
|
|
269
|
+
// stale 정리 — forgen 이 설치한 dev-guide 스킬만 (패키지가 제공하는 이름 / dev-guide 를 가리키는 심링크).
|
|
270
|
+
// 이전엔 `forgen-*` 디렉토리를 이름만 보고 재귀 삭제해 사용자가 만든 스킬까지 지웠다 (ADR-016 0.5.9).
|
|
271
|
+
const removed = removeOwnedDevGuideSkills(skillsDir, pkgRoot);
|
|
272
|
+
// 설치 — 이미 있는 것(사용자 심링크 디렉토리, 남아 있는 SKILL.md)은 덮어쓰지 않는다.
|
|
281
273
|
let installed = 0;
|
|
282
274
|
for (const { name, src } of entries) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const targetFile = path.join(targetDir, 'SKILL.md');
|
|
286
|
-
let linked = false;
|
|
287
|
-
let symlinkErr = null;
|
|
288
|
-
try {
|
|
289
|
-
fs.symlinkSync(src, targetFile, 'file');
|
|
290
|
-
linked = true;
|
|
291
|
-
}
|
|
292
|
-
catch (e) {
|
|
293
|
-
symlinkErr = e;
|
|
294
|
-
}
|
|
295
|
-
if (!linked && symlinkErr) {
|
|
296
|
-
const code = symlinkErr.code ?? 'UNKNOWN';
|
|
297
|
-
process.stderr.write(`[forgen] symlink ${src} → ${targetFile} failed (${code}); falling back to copyFile.\n`);
|
|
298
|
-
}
|
|
299
|
-
if (!linked) {
|
|
300
|
-
fs.copyFileSync(src, targetFile);
|
|
301
|
-
}
|
|
302
|
-
installed += 1;
|
|
275
|
+
if (installSkillFile(src, skillsDir, name))
|
|
276
|
+
installed += 1;
|
|
303
277
|
}
|
|
304
278
|
return { skillsPath: skillsDir, skillsInstalled: installed, skillsRemoved: removed };
|
|
305
279
|
}
|
|
@@ -95,9 +95,9 @@ export declare const FORGEN_SKILL_MARKER = "<!-- forgen-managed -->";
|
|
|
95
95
|
export declare function isForgenHookCommand(command: unknown, pkgRoot: string): boolean;
|
|
96
96
|
export declare function readJsonFile<T>(p: string): T | null;
|
|
97
97
|
/**
|
|
98
|
-
* forgen MCP 블록 제거 (uninstall, ADR-016 D4). forgen 테이블(본문 +
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* forgen MCP 블록 제거 (uninstall, ADR-016 D4). forgen 테이블(본문 + 하위 테이블)과 마커 줄만 걷어낸다.
|
|
99
|
+
* 마커도 시그니처도 없는 같은 이름의 테이블(사용자 관리)은 건드리지 않는다.
|
|
100
|
+
* `removed` 는 테이블을 실제로 지웠을 때만 true — 고아 마커만 치운 경우는 false.
|
|
101
101
|
*/
|
|
102
102
|
export declare function removeMcpBlock(currentToml: string): {
|
|
103
103
|
content: string;
|
|
@@ -107,22 +107,24 @@ export declare function removeMcpBlock(currentToml: string): {
|
|
|
107
107
|
* config.toml 에 forgen notify 블록을 upsert.
|
|
108
108
|
*
|
|
109
109
|
* - Codex 의 `notify` 는 top-level 단일 argv 다. 사용자가 이미 정의했으면 **건드리지 않는다** — 그리고
|
|
110
|
-
* forgen
|
|
110
|
+
* forgen 줄이 남아 있으면 제거한다 (중복 키 = config.toml 파싱 실패 → Codex 기동 불가).
|
|
111
111
|
* - top-level 키는 첫 테이블 헤더 앞에 와야 하므로 블록은 항상 파일 최상단(BOM 뒤)에 둔다.
|
|
112
|
-
* - 사용자가 forgen
|
|
112
|
+
* - 사용자가 forgen argv 뒤에 `"--", "<prog>", …` 로 자기 notifier 를 체인해 뒀으면 그 꼬리를 보존.
|
|
113
113
|
* 블록의 notify 줄을 한 줄 JSON 으로 읽을 수 없으면(여러 줄 배열 등 손편집) 아무것도 바꾸지 않는다.
|
|
114
|
-
* -
|
|
114
|
+
* - forgen 이 쓴 줄 외에는 원래 순서 그대로 둔다 (Codex 가 사이에 끼워 넣은 root 키 포함).
|
|
115
115
|
*/
|
|
116
116
|
export declare function upsertNotifyBlock(currentToml: string, pkgRoot: string): {
|
|
117
117
|
content: string;
|
|
118
118
|
status: CodexNotifyStatus;
|
|
119
119
|
};
|
|
120
120
|
/**
|
|
121
|
-
* forgen notify 블록 제거 (`--no-notify`, uninstall).
|
|
121
|
+
* forgen notify 블록 제거 (`--no-notify`, uninstall).
|
|
122
122
|
*
|
|
123
123
|
* - 사용자가 블록의 notify 줄을 여러 줄 배열 등으로 손편집했으면(`custom`) **건드리지 않는다** — 첫 줄만
|
|
124
124
|
* 지우면 남은 줄이 깨진 TOML 이 되어 Codex 가 기동하지 못한다 (critic 2026-10-02).
|
|
125
125
|
* - `"--"` 뒤에 사용자가 체인해 둔 자기 notifier 가 있으면 그 argv 만으로 `notify` 를 되돌려 놓는다.
|
|
126
|
+
* - `removed` 는 forgen notify 줄을 실제로 지웠을 때만 true. 고아 마커/주석만 치운 경우는 false
|
|
127
|
+
* (내용은 정리된 것을 돌려준다).
|
|
126
128
|
*/
|
|
127
129
|
export declare function removeNotifyBlock(currentToml: string): {
|
|
128
130
|
content: string;
|
|
@@ -197,7 +199,29 @@ export declare function upsertForgenRulesInAgentsMd(opts: {
|
|
|
197
199
|
}): {
|
|
198
200
|
injected: boolean;
|
|
199
201
|
};
|
|
200
|
-
|
|
202
|
+
export declare function readAgentsMdInstalls(hostDir: string): string[];
|
|
203
|
+
export declare function recordAgentsMdInstall(hostDir: string, agentsMdPath: string): void;
|
|
204
|
+
export declare function clearAgentsMdInstalls(hostDir: string): void;
|
|
205
|
+
/**
|
|
206
|
+
* 기록된 경로 + 지금의 cwd 경로에서 forgen 블록을 걷어낸다.
|
|
207
|
+
* 쓰지 못한 경로(읽기 전용 등)는 `failed` 로 돌려주고 **기록에 남긴다** — 다음 uninstall 이 다시 시도할 수 있게.
|
|
208
|
+
*/
|
|
209
|
+
export declare function removeForgenRulesEverywhere(opts: {
|
|
210
|
+
hostDir: string;
|
|
211
|
+
cwdAgentsMdPath: string;
|
|
212
|
+
dryRun: boolean;
|
|
213
|
+
}): {
|
|
214
|
+
cleaned: string[];
|
|
215
|
+
failed: Array<{
|
|
216
|
+
path: string;
|
|
217
|
+
error: string;
|
|
218
|
+
}>;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* AGENTS.md 의 forgen 블록 제거 (uninstall). 블록뿐이던 파일은 삭제한다.
|
|
222
|
+
* BEGIN/END 마커가 각각 한 줄로, 순서대로 있을 때만 동작한다 (블록이 여러 개면 전부 제거).
|
|
223
|
+
* 읽을 수 없으면 no-op, 쓸 수 없으면 throw — 호출부가 보고한다.
|
|
224
|
+
*/
|
|
201
225
|
export declare function removeForgenRulesFromAgentsMd(opts: {
|
|
202
226
|
agentsMdPath: string;
|
|
203
227
|
dryRun: boolean;
|