@wooojin/forgen 0.5.5 → 0.5.6
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 +69 -0
- package/README.ja.md +4 -0
- package/README.ko.md +5 -1
- package/README.md +5 -1
- package/README.zh.md +4 -0
- package/assets/claude/skills/verify/SKILL.md +66 -0
- package/assets/shared/hook-registry.json +10 -3
- package/dist/cli.js +7 -3
- package/dist/core/doctor.js +9 -1
- package/dist/core/uninstall.d.ts +7 -0
- package/dist/core/uninstall.js +46 -1
- package/dist/hooks/context-guard.d.ts +3 -0
- package/dist/hooks/context-guard.js +9 -5
- package/dist/hooks/hook-registry.d.ts +11 -0
- package/dist/hooks/hooks-generator.d.ts +2 -0
- package/dist/hooks/hooks-generator.js +5 -1
- package/dist/hooks/session-end.d.ts +10 -3
- package/dist/hooks/session-end.js +17 -4
- package/dist/host/codex-adapter.js +5 -0
- package/dist/host/codex-hook-alive.d.ts +23 -0
- package/dist/host/codex-hook-alive.js +51 -0
- package/dist/host/codex-notify.d.ts +55 -0
- package/dist/host/codex-notify.js +153 -0
- package/dist/host/codex-rollout.d.ts +21 -0
- package/dist/host/codex-rollout.js +82 -0
- package/dist/host/exec-host.js +5 -1
- package/dist/host/install-claude.d.ts +14 -0
- package/dist/host/install-claude.js +53 -0
- package/dist/host/install-codex.d.ts +67 -5
- package/dist/host/install-codex.js +285 -45
- package/dist/host/install-orchestrator.d.ts +4 -0
- package/dist/host/install-orchestrator.js +23 -3
- package/package.json +1 -1
- package/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,75 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.6] — 2026-10-02 — Codex notify 폴백 · 훅 번들(재승인 2건) · Claude verify 스킬 (ADR-016)
|
|
11
|
+
|
|
12
|
+
> **Codex 사용자 — 재승인 필요**: 업그레이드 후 `forgen install codex` 를 다시 실행하면 훅 2개
|
|
13
|
+
> (`session_start` 변경, `session_end` 신규)가 Codex `/hooks` 승인 전까지 skip 된다. 그동안 Codex 세션에
|
|
14
|
+
> `<forgen-rules>` 블록이 주입되지 않는다. install 출력과 `forgen doctor` 가 대상 훅을 표시한다.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Codex `notify` 폴백** (`dist/host/codex-notify.js`). `forgen install codex` 가 config.toml 최상단에 마커 블록으로
|
|
18
|
+
`notify` 를 등록한다. Codex 의 notify 는 훅 신뢰와 무관하게 턴 완료마다 detached 로 실행되므로, forgen 훅이
|
|
19
|
+
미승인/변경 상태로 조용히 skip 되는 동안에도 (a) `state/codex-hooks-silent.json` 에 관측을 남겨 `forgen doctor`
|
|
20
|
+
[Codex Hooks] 가 보여주고 (b) 프롬프트 ≥10 인 세션은 Stop 훅과 같은 디바운스 경로로 auto-compound 를 띄운다.
|
|
21
|
+
훅이 정상 실행 중이면(codex-adapter 의 alive 마커) 아무것도 하지 않는다. 사용자가 이미 `notify` 를 정의했으면
|
|
22
|
+
**건드리지 않는다** (수동 체인: argv 뒤에 `"--", "<program>", …`). 끄기: `--no-notify` (기존 블록도 제거).
|
|
23
|
+
`forgen uninstall` 도 블록을 제거한다. Codex 가 블록 사이에 써 넣은 root 키(`model` 등)와 BOM/CRLF 는 보존한다.
|
|
24
|
+
- **Codex `SessionEnd` 훅** — `session-end` 를 Codex 에도 등록. Stop 없이 끝나는 세션의 학습 추출 트리거.
|
|
25
|
+
Codex rollout 의 실제 사용자 프롬프트(`event_msg`/`user_message`)를 raw 바이트 스캔으로 센다.
|
|
26
|
+
- **Claude `verify` 스킬** — `forgen install claude` 가 `~/.claude/skills/verify/SKILL.md` 를 설치한다. Claude Code
|
|
27
|
+
2.1.286+ 는 project/user 스킬에 `verify` 가 있으면 코드 커밋 직전에 실행하도록 모델에 안내한다 (플러그인 스킬
|
|
28
|
+
`forgen:verify` 는 대상이 아님). 본문은 "프로젝트 자체 레시피 우선 → 실제 build/type-check/lint/test 실행 →
|
|
29
|
+
confirmed / refuted / unverified 판정, mock 통과는 증거 아님". 사용자가 만든 `verify` 스킬은 덮어쓰지 않고,
|
|
30
|
+
`forgen uninstall` 은 forgen 이 설치한 것만 제거한다. 끄기: `--no-verify-skill`. (npm postinstall 은 설치하지 않음 —
|
|
31
|
+
명시적 `forgen install claude` 에서만.)
|
|
32
|
+
- `forgen install` 플래그 `--no-notify`, `--no-verify-skill`.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- **`forgen install codex` 재실행이 Codex 훅 신뢰를 전부 지우던 결함 (0.5.3~).** 신규 설치는 MCP 마커 블록을
|
|
36
|
+
config.toml 끝에 붙이는데, Codex 는 `/hooks` 승인 시 `[hooks.state]` 테이블을 파일 끝 주석(= forgen 의 END 마커)
|
|
37
|
+
*앞* 에 써 넣는다 — 즉 블록 안. 재설치가 블록을 통째로 교체하면서 22개 신뢰 기록, `[features]`, MCP 서버의
|
|
38
|
+
`enabled = false` 등이 사라졌다. 이제 forgen 이 쓴 줄만 다시 쓰고 사이에 끼어든 내용은 블록 뒤로 옮겨 보존한다.
|
|
39
|
+
(실 Codex 0.153.4 app-server 로 재현·수정 확인: 재설치 후 22/22 유지.)
|
|
40
|
+
- Codex 추출 run(`codex exec --ephemeral`)에 `FORGEN_NESTED_RUN=1` 이 전달되지 않던 것 — forgen 훅이 추출 세션에서
|
|
41
|
+
발화하지 않도록 Claude 분기와 동일하게 표식.
|
|
42
|
+
- `[mcp_servers.forgen-compound]` 가 마커 없이 이미 있으면 중복 테이블을 append 하지 않는다.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- **Codex 훅 신뢰 감사가 해시를 대조한다.** `auditCodexHookTrust` 가 Codex 0.153.4 의 핸들러 단위 trust 해시를
|
|
46
|
+
계산해 `trusted` / `modified` / `untrusted` 를 구분한다. 이전엔 `hooks.state` 키의 존재만 봐서, 핸들러가 바뀌어
|
|
47
|
+
Codex 가 skip 하는 훅을 "trusted" 로 표시했다. `/hooks` 에서 끈 훅(`enabled = false`)은 `disabled` 로 따로 센다.
|
|
48
|
+
읽기 전용 대조이며 신뢰 기록은 쓰지 않는다.
|
|
49
|
+
- **`session-recovery` 의 Codex 핸들러에 `additionalContextLimit: 0`.** Codex 는 약 10KB 를 넘는
|
|
50
|
+
`additionalContext` 를 임시 파일로 스필하고 모델에는 잘린 미리보기만 준다 — 한국어 룰 블록(상한 15,000자)은 이를
|
|
51
|
+
쉽게 넘는다.
|
|
52
|
+
- `post-tool-failure` 를 Codex hooks.json 에서 제외 (`PostToolUseFailure` 는 Codex 이벤트가 아니라 무시되던 죽은 엔트리).
|
|
53
|
+
- `forgen install codex` 는 config.toml 내용이 바뀔 때만 파일을 쓴다.
|
|
54
|
+
- CI: 태그 푸시 발행을 npm Trusted Publishing(OIDC) 으로 전환, `release.yml`/`compat.yml` 에 누락됐던
|
|
55
|
+
`hooks/hooks.json` 생성 단계 추가 (v0.5.0 이후 태그 발행이 계속 실패하던 원인).
|
|
56
|
+
|
|
57
|
+
### Not done (정직 표기 — ADR-016)
|
|
58
|
+
- `async: true` 훅: Codex 는 같은 이벤트의 핸들러를 이미 동시 실행한다. 어댑터 경유 훅 1회 91~137ms 실측 →
|
|
59
|
+
이득 상한 ≈130ms/이벤트. 반면 async 는 block/deny 가 적용되지 않고 핸들러마다 재승인이 든다.
|
|
60
|
+
- `PostCompact` / `Interrupt` 등록: 둘 다 컨텍스트 주입이 불가능하고 forgen 이 거기서 할 일이 없다.
|
|
61
|
+
- 사용자 `notify` 자동 체인: TOML 임의 배열 재작성 + 원복 보장 불가로 수동 체인 안내만 제공.
|
|
62
|
+
|
|
63
|
+
### Verified
|
|
64
|
+
- vitest 3161 통과 (신규: trust 해시·notify 블록 upsert·notify 폴백 분기·rollout 카운터·verify 스킬 install/uninstall).
|
|
65
|
+
- trust 해시: 실머신 `~/.codex/config.toml` 의 trusted_hash 20건과 일치 (fixture 8건 vendoring), 격리 `CODEX_HOME` 에서
|
|
66
|
+
forgen 계산 해시를 기록한 뒤 Codex `hooks/list` 가 22/22 `trusted` 로 판정 (`additionalContextLimit:0`·SessionEnd 포함).
|
|
67
|
+
- 격리 Codex 0.153.4 실세션 (`codex exec`):
|
|
68
|
+
- 훅 미승인: notify 발화 → silent 기록, `forgen doctor` 가 "forgen 훅 미발화" 표시.
|
|
69
|
+
- 훅 승인: alive 마커(Stop) 기록 → notify 가 silent 를 지움, hook-timing 에 `SessionEnd:session-end (rt: codex)`.
|
|
70
|
+
- 스필: 21KB SessionStart 컨텍스트 — 기본값은 "Full hook output saved to" + 중간 내용 미전달, `additionalContextLimit:0`
|
|
71
|
+
은 전문 전달.
|
|
72
|
+
- notify 폴백의 auto-compound 트리거: 실 바이너리 + 12-프롬프트 rollout 으로 러너 인자(cwd, rollout, session id, 12)와
|
|
73
|
+
`FORGEN_RUNTIME=codex` 전달, 재호출 시 in-flight 게이트로 skip 확인. (러너 자체는 스텁으로 대체 — LLM 추출은 이 검증 범위 밖.)
|
|
74
|
+
- fresh-context critic 리뷰 1라운드: CRITICAL 2 · MAJOR 1 · MINOR 10 → 위 Fixed 항목 포함 전부 반영 또는 한계로 명시
|
|
75
|
+
(ADR-016 Review 절). 리뷰어의 실 Codex 재현 스크립트를 수정 후 빌드에 다시 돌려 통과 확인.
|
|
76
|
+
- 이 머신의 실 `~/.codex` 에는 아직 설치하지 않았다 (재승인 전까지 룰 주입이 멈추므로 오너 결정 후).
|
|
77
|
+
|
|
78
|
+
|
|
10
79
|
## [0.5.5] — 2026-10-01 — Codex Stop 분기 복구 (Stop 트리거 auto-compound·finalizeSession)
|
|
11
80
|
|
|
12
81
|
### Fixed
|
package/README.ja.md
CHANGED
|
@@ -192,6 +192,10 @@ forgen install both # 3択インタラクティブ: claude / codex / bo
|
|
|
192
192
|
forgen install claude
|
|
193
193
|
forgen install codex
|
|
194
194
|
# Codex のみ: codex 内で `/hooks` から forgen フックを一度承認 — Codex は未承認フックをスキップ (forgen doctor の [Codex Hooks] で確認)
|
|
195
|
+
# v0.5.6 へのアップグレード: `forgen install codex` を再実行するとフック 2 件 (session_start, session_end) が変わる — `/hooks` でもう一度承認。
|
|
196
|
+
# config.toml に `notify` フォールバックも登録 (既に `notify` を定義していれば変更しない; 無効化: --no-notify)。
|
|
197
|
+
# Claude のみ: `verify` スキルを ~/.claude/skills/verify にインストール — Claude Code がコードのコミット直前に実行
|
|
198
|
+
# (自作の `verify` スキルは上書きしない; 無効化: --no-verify-skill)。
|
|
195
199
|
|
|
196
200
|
# 3. 初回実行 — 4問オンボーディング (英語/韓国語選択)
|
|
197
201
|
forgen # デフォルト: Claude
|
package/README.ko.md
CHANGED
|
@@ -152,6 +152,10 @@ forgen install both # 3지선다 인터랙티브: claude / codex / both
|
|
|
152
152
|
forgen install claude
|
|
153
153
|
forgen install codex
|
|
154
154
|
# Codex 만: codex 안에서 `/hooks` 로 forgen 훅을 한 번 승인 — Codex 는 미승인 훅을 skip (forgen doctor 의 [Codex Hooks] 로 확인)
|
|
155
|
+
# v0.5.6 업그레이드: `forgen install codex` 를 다시 돌리면 훅 2개(session_start, session_end)가 바뀜 — `/hooks` 에서 한 번 더 승인.
|
|
156
|
+
# config.toml 에 `notify` 폴백도 등록됨 (이미 `notify` 를 쓰고 있으면 건드리지 않음; 끄기: --no-notify).
|
|
157
|
+
# Claude 만: `verify` 스킬을 ~/.claude/skills/verify 에 설치 — Claude Code 가 코드 커밋 직전에 실행
|
|
158
|
+
# (직접 만든 `verify` 스킬은 덮어쓰지 않음; 끄기: --no-verify-skill).
|
|
155
159
|
|
|
156
160
|
# 3. 첫 실행 — 4문항 온보딩 (영어/한국어 선택)
|
|
157
161
|
forgen # 기본: Claude
|
|
@@ -670,7 +674,7 @@ forgen는 설치 시 다른 Claude Code 플러그인(oh-my-claudecode, superpowe
|
|
|
670
674
|
|
|
671
675
|
| 문서 | 설명 |
|
|
672
676
|
|------|------|
|
|
673
|
-
| [훅 레퍼런스](docs/reference/hooks-reference.md) | 3개 계층의
|
|
677
|
+
| [훅 레퍼런스](docs/reference/hooks-reference.md) | 3개 계층의 23개 훅 — 이벤트, 타임아웃, 동작 |
|
|
674
678
|
| [공존 가이드](docs/guides/with-omc.md) | oh-my-claudecode와 forgen 함께 사용하기 |
|
|
675
679
|
| [CHANGELOG](CHANGELOG.md) | 버전 히스토리 및 릴리즈 노트 |
|
|
676
680
|
|
package/README.md
CHANGED
|
@@ -245,6 +245,10 @@ forgen install both # 3-choice interactive: claude / codex / both
|
|
|
245
245
|
forgen install claude
|
|
246
246
|
forgen install codex
|
|
247
247
|
# Codex only: trust the forgen hooks once with `/hooks` inside codex — Codex skips untrusted hooks (forgen doctor shows [Codex Hooks])
|
|
248
|
+
# Upgrading to v0.5.6: re-running `forgen install codex` changes 2 hooks (session_start, session_end) — approve them in `/hooks` once more.
|
|
249
|
+
# It also registers a `notify` fallback in config.toml (left alone if you already define `notify`; opt out: --no-notify).
|
|
250
|
+
# Claude only: installs a `verify` skill to ~/.claude/skills/verify — Claude Code runs it right before code commits
|
|
251
|
+
# (your own `verify` skill is never overwritten; opt out: --no-verify-skill).
|
|
248
252
|
|
|
249
253
|
# 3. First run — 4-question onboarding (English or Korean)
|
|
250
254
|
forgen # default: Claude
|
|
@@ -924,7 +928,7 @@ See [Coexistence Guide](docs/guides/with-omc.md) for the full plugin-detection m
|
|
|
924
928
|
|
|
925
929
|
| Document | Description |
|
|
926
930
|
|----------|-------------|
|
|
927
|
-
| [Hooks Reference](docs/reference/hooks-reference.md) |
|
|
931
|
+
| [Hooks Reference](docs/reference/hooks-reference.md) | 23 hooks across 3 tiers — events, timeouts, behavior |
|
|
928
932
|
| [Coexistence Guide](docs/guides/with-omc.md) | Using forgen alongside oh-my-claudecode |
|
|
929
933
|
| [forgen-eval testbed](packages/forgen-eval/) | Alpha self-measurement package — multi-host parity, 7-axis metrics, drift detection (private workspace, v0.4.3+) |
|
|
930
934
|
| [Multi-host core design](docs/superpowers/specs/2026-04-27-forgen-multi-host-core-design.md) | Codex/Claude symmetric host adapter spec |
|
package/README.zh.md
CHANGED
|
@@ -192,6 +192,10 @@ forgen install both # 三选交互: claude / codex / both
|
|
|
192
192
|
forgen install claude
|
|
193
193
|
forgen install codex
|
|
194
194
|
# 仅 Codex: 在 codex 内用 `/hooks` 信任一次 forgen 钩子 — Codex 会跳过未信任的钩子 (用 forgen doctor 的 [Codex Hooks] 查看)
|
|
195
|
+
# 升级到 v0.5.6: 重新运行 `forgen install codex` 会改动 2 个钩子 (session_start, session_end) — 在 `/hooks` 中再信任一次。
|
|
196
|
+
# 同时在 config.toml 注册 `notify` 兜底 (如果你已定义 `notify` 则不改动; 关闭: --no-notify)。
|
|
197
|
+
# 仅 Claude: 将 `verify` 技能安装到 ~/.claude/skills/verify — Claude Code 在提交代码前运行它
|
|
198
|
+
# (不会覆盖你自己的 `verify` 技能; 关闭: --no-verify-skill)。
|
|
195
199
|
|
|
196
200
|
# 3. 首次运行 — 4题引导问卷 (英语/韩语选择)
|
|
197
201
|
forgen # 默认: Claude
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify
|
|
3
|
+
description: Confirm a code change actually works using real execution evidence — run the project's own build, type-check, lint and tests, exercise the change itself, and report a confirmed / refuted / unverified verdict. Use right before committing a code change, or when asked to verify that something works.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- forgen-managed -->
|
|
7
|
+
|
|
8
|
+
# verify — prove the change works before it is committed
|
|
9
|
+
|
|
10
|
+
"Saying it is done and proving it is done are different things."
|
|
11
|
+
|
|
12
|
+
This is the forgen default verification recipe. Claude Code runs a skill named `verify` right before
|
|
13
|
+
commits that touch code (docs-only and tests-only commits are skipped).
|
|
14
|
+
|
|
15
|
+
## 0. Prefer the repository's own recipe
|
|
16
|
+
|
|
17
|
+
If this repository documents how to verify changes — its own `.claude/skills/verify/SKILL.md`, or
|
|
18
|
+
verification/test commands in `CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md` or the README — follow that
|
|
19
|
+
recipe first. It knows the project better than this generic one. Use the steps below for whatever it
|
|
20
|
+
does not cover, and keep the evidence rules in step 3 either way.
|
|
21
|
+
|
|
22
|
+
## 1. Scope the change
|
|
23
|
+
|
|
24
|
+
- `git status --short` and `git diff --stat` (staged and unstaged) to see what is about to be committed.
|
|
25
|
+
- State in one sentence what the change is supposed to do. That sentence is the claim you are verifying.
|
|
26
|
+
|
|
27
|
+
## 2. Run the project's real checks
|
|
28
|
+
|
|
29
|
+
Find the commands the project actually uses (`package.json` scripts, `Makefile`, `justfile`,
|
|
30
|
+
`pyproject.toml`, `Cargo.toml`, `go.mod`, CI workflow files) and run the ones relevant to the change:
|
|
31
|
+
|
|
32
|
+
1. build / compile
|
|
33
|
+
2. type-check
|
|
34
|
+
3. lint
|
|
35
|
+
4. tests — the tests covering the changed code first, then the full suite when it is reasonably fast
|
|
36
|
+
|
|
37
|
+
Keep it proportional: a one-line fix needs its targeted test and a build, not a 20-minute end-to-end run.
|
|
38
|
+
Say which checks you skipped and why.
|
|
39
|
+
|
|
40
|
+
## 3. Evidence rules
|
|
41
|
+
|
|
42
|
+
- Only output from commands you ran **now** counts. "It should pass" and results quoted from earlier
|
|
43
|
+
in the session are not evidence.
|
|
44
|
+
- A passing test that mocks or stubs the unit you changed does not show the change works. Look for,
|
|
45
|
+
or add, a check that executes the real code path.
|
|
46
|
+
- Exercise the change itself where feasible: run the CLI command, call the endpoint, load the page,
|
|
47
|
+
import and call the function. A green test suite that never touches the new behaviour is not enough.
|
|
48
|
+
- Do not attach confidence percentages you did not measure.
|
|
49
|
+
|
|
50
|
+
## 4. Verdict
|
|
51
|
+
|
|
52
|
+
Report exactly one of:
|
|
53
|
+
|
|
54
|
+
- **confirmed** — the commands ran and their output supports the claim. Quote each command and the
|
|
55
|
+
1–3 lines of output that matter.
|
|
56
|
+
- **refuted** — something failed or the behaviour is wrong. Do **not** commit. Show the failing output,
|
|
57
|
+
fix the problem, then run this skill again.
|
|
58
|
+
- **unverified** — you could not run what was needed (missing dependency, no test covers it, needs
|
|
59
|
+
credentials). Say precisely what could not be checked and ask before committing; never round
|
|
60
|
+
unverified up to confirmed.
|
|
61
|
+
|
|
62
|
+
## 5. Riskier changes
|
|
63
|
+
|
|
64
|
+
For changes that touch persistence, authentication, money, concurrency, or public interfaces, hand the
|
|
65
|
+
claim to a fresh verifier sub-agent (`forgen-verify` or `ch-verifier` when available) and ask it to try
|
|
66
|
+
to break the claim rather than confirm it. Include its verdict in your report.
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
"matcher": "*",
|
|
25
25
|
"script": "hooks/session-recovery.js",
|
|
26
26
|
"timeout": 3,
|
|
27
|
-
"compoundCritical": true
|
|
27
|
+
"compoundCritical": true,
|
|
28
|
+
"codex": {
|
|
29
|
+
"additionalContextLimit": 0
|
|
30
|
+
}
|
|
28
31
|
},
|
|
29
32
|
{
|
|
30
33
|
"name": "post-tool-use",
|
|
@@ -168,7 +171,10 @@
|
|
|
168
171
|
"matcher": "*",
|
|
169
172
|
"script": "hooks/post-tool-failure.js",
|
|
170
173
|
"timeout": 3,
|
|
171
|
-
"compoundCritical": false
|
|
174
|
+
"compoundCritical": false,
|
|
175
|
+
"hosts": [
|
|
176
|
+
"claude"
|
|
177
|
+
]
|
|
172
178
|
},
|
|
173
179
|
{
|
|
174
180
|
"name": "solution-injector",
|
|
@@ -206,7 +212,8 @@
|
|
|
206
212
|
"timeout": 3,
|
|
207
213
|
"compoundCritical": true,
|
|
208
214
|
"hosts": [
|
|
209
|
-
"claude"
|
|
215
|
+
"claude",
|
|
216
|
+
"codex"
|
|
210
217
|
]
|
|
211
218
|
}
|
|
212
219
|
]
|
package/dist/cli.js
CHANGED
|
@@ -181,19 +181,23 @@ const commands = [
|
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
name: 'install',
|
|
184
|
-
description: 'Install forgen into a host. Usage: forgen install [claude|codex|opencode|both] [--dry-run] [--no-mcp]',
|
|
184
|
+
description: 'Install forgen into a host. Usage: forgen install [claude|codex|opencode|both] [--dry-run] [--no-mcp] [--no-notify] [--no-verify-skill]',
|
|
185
185
|
handler: async (args) => {
|
|
186
186
|
const knownSubs = new Set(['claude', 'codex', 'opencode', 'both']);
|
|
187
187
|
const target = args[0] && knownSubs.has(args[0]) ? args[0] : args[0]?.startsWith('--') ? undefined : args[0];
|
|
188
188
|
if (target !== undefined && !knownSubs.has(target)) {
|
|
189
|
-
console.log('Usage:\n forgen install [claude|codex|opencode|both] [--dry-run] [--no-mcp]\n\n No arg → interactive 3-choice (Claude/Codex/Both). opencode: 명시 타겟(P1 실험적).');
|
|
189
|
+
console.log('Usage:\n forgen install [claude|codex|opencode|both] [--dry-run] [--no-mcp] [--no-notify] [--no-verify-skill]\n\n No arg → interactive 3-choice (Claude/Codex/Both). opencode: 명시 타겟(P1 실험적).\n --no-notify: Codex config.toml 에 notify 폴백을 등록하지 않음.\n --no-verify-skill: Claude ~/.claude/skills/verify 를 설치하지 않음.');
|
|
190
190
|
return;
|
|
191
191
|
}
|
|
192
192
|
const dryRun = args.includes('--dry-run');
|
|
193
193
|
const registerMcp = !args.includes('--no-mcp');
|
|
194
194
|
const { runInstall, renderResult, resolvePkgRootFromBinary } = await import('./host/install-orchestrator.js');
|
|
195
195
|
const pkgRoot = resolvePkgRootFromBinary(import.meta.url);
|
|
196
|
-
const result = await runInstall({
|
|
196
|
+
const result = await runInstall({
|
|
197
|
+
target, pkgRoot, dryRun, registerMcp,
|
|
198
|
+
registerNotify: !args.includes('--no-notify'),
|
|
199
|
+
installVerifySkill: !args.includes('--no-verify-skill'),
|
|
200
|
+
});
|
|
197
201
|
if (result === null) {
|
|
198
202
|
console.log('\n [forgen] Install skipped.');
|
|
199
203
|
return;
|
package/dist/core/doctor.js
CHANGED
|
@@ -74,7 +74,15 @@ async function renderCodexHookTrust() {
|
|
|
74
74
|
console.log(` △ ${t.total} forgen hooks registered, Codex trust 기록 없음 — codex 안에서 /hooks 로 승인 (미승인 훅은 skip 됨)`);
|
|
75
75
|
}
|
|
76
76
|
else {
|
|
77
|
-
|
|
77
|
+
const pending = [...t.modified.map((k) => `${k} modified`), ...t.untrusted.map((k) => `${k} new`), ...t.disabled.map((k) => `${k} disabled`)];
|
|
78
|
+
console.log(` ✗ ${pending.length}/${t.total} forgen hooks skipped by Codex (${pending.slice(0, 4).join(', ')}${pending.length > 4 ? ', …' : ''}) — codex 안에서 /hooks 로 승인`);
|
|
79
|
+
}
|
|
80
|
+
// ADR-016 D1 — notify 폴백이 관측한 "턴은 끝났는데 forgen 훅이 돌지 않음"
|
|
81
|
+
const { readCodexHooksSilent } = await import('../host/codex-notify.js');
|
|
82
|
+
const silent = readCodexHooksSilent();
|
|
83
|
+
// 1회 관측은 Stop 훅이 돌지 않는 내부 서브세션(/review 등)의 notify 일 수 있다 — 연속 2회부터 표시.
|
|
84
|
+
if (silent && silent.count >= 2) {
|
|
85
|
+
console.log(` ✗ notify 폴백 관측: 최근 Codex 턴 ${silent.count}회에서 forgen 훅 미발화 (마지막 ${silent.detectedAt}, session ${silent.sessionId.slice(0, 8)}) — /hooks 승인 필요`);
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
88
|
catch { /* fail-open */ }
|
package/dist/core/uninstall.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** ADR-016 D3 — ~/.claude/skills/verify 제거 (forgen-managed 마커가 있는 것만; 사용자 스킬은 보존) */
|
|
2
|
+
export declare function cleanVerifySkill(homeDir?: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* ADR-016 D1 — Codex config.toml 의 forgen notify 블록 제거. 남겨 두면 Codex 가 매 턴 사라진 스크립트를
|
|
5
|
+
* spawn 한다. (Codex hooks.json / MCP 블록 정리는 아직 uninstall 범위 밖 — 알려진 갭.)
|
|
6
|
+
*/
|
|
7
|
+
export declare function cleanCodexNotify(codexHome?: string): Promise<boolean>;
|
|
1
8
|
/** forgen uninstall 메인 */
|
|
2
9
|
export declare function handleUninstall(cwd: string, options: {
|
|
3
10
|
force?: boolean;
|
package/dist/core/uninstall.js
CHANGED
|
@@ -96,6 +96,47 @@ function cleanSlashCommands() {
|
|
|
96
96
|
console.log(' - No forgen-managed slash commands found');
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
/** ADR-016 D3 — ~/.claude/skills/verify 제거 (forgen-managed 마커가 있는 것만; 사용자 스킬은 보존) */
|
|
100
|
+
export function cleanVerifySkill(homeDir = os.homedir()) {
|
|
101
|
+
const dir = path.join(homeDir, '.claude', 'skills', 'verify');
|
|
102
|
+
const file = path.join(dir, 'SKILL.md');
|
|
103
|
+
try {
|
|
104
|
+
if (fs.lstatSync(dir).isSymbolicLink() || fs.lstatSync(file).isSymbolicLink())
|
|
105
|
+
return false;
|
|
106
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
107
|
+
if (!/^---\n[\s\S]*?\n---\n\s*<!-- forgen-managed -->/.test(content))
|
|
108
|
+
return false;
|
|
109
|
+
fs.unlinkSync(file);
|
|
110
|
+
try {
|
|
111
|
+
if (fs.readdirSync(dir).length === 0)
|
|
112
|
+
fs.rmdirSync(dir);
|
|
113
|
+
}
|
|
114
|
+
catch { /* ignore */ }
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false; // 없음
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* ADR-016 D1 — Codex config.toml 의 forgen notify 블록 제거. 남겨 두면 Codex 가 매 턴 사라진 스크립트를
|
|
123
|
+
* spawn 한다. (Codex hooks.json / MCP 블록 정리는 아직 uninstall 범위 밖 — 알려진 갭.)
|
|
124
|
+
*/
|
|
125
|
+
export async function cleanCodexNotify(codexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex')) {
|
|
126
|
+
const configPath = path.join(codexHome, 'config.toml');
|
|
127
|
+
try {
|
|
128
|
+
const current = fs.readFileSync(configPath, 'utf-8');
|
|
129
|
+
const { removeNotifyBlock } = await import('../host/install-codex.js');
|
|
130
|
+
const r = removeNotifyBlock(current);
|
|
131
|
+
if (!r.removed)
|
|
132
|
+
return false;
|
|
133
|
+
fs.writeFileSync(configPath, r.content, 'utf-8');
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false; // Codex 미사용
|
|
138
|
+
}
|
|
139
|
+
}
|
|
99
140
|
/** 사용자에게 y/n 확인 */
|
|
100
141
|
function confirm(message) {
|
|
101
142
|
return new Promise((resolve) => {
|
|
@@ -301,7 +342,7 @@ export async function handleUninstall(cwd, options) {
|
|
|
301
342
|
console.log(' 2. Delete .claude/agents/ch-*.md agent files');
|
|
302
343
|
console.log(' 3. Delete .claude/rules/ rule files (project-context, routing, forge-*)');
|
|
303
344
|
console.log(' 4. Remove forgen block from CLAUDE.md');
|
|
304
|
-
console.log(' 5. Remove slash commands (~/.claude/commands/forgen/)');
|
|
345
|
+
console.log(' 5. Remove slash commands (~/.claude/commands/forgen/) and the forgen-managed verify skill (~/.claude/skills/verify/)');
|
|
305
346
|
console.log(' 6. Remove plugin artifacts (cache, installed_plugins.json, plugin directory)');
|
|
306
347
|
if (options.purge) {
|
|
307
348
|
console.log(' 7. --purge: Delete ~/.forgen/ entirely (rules, me/, state/, solutions/, behavior/)');
|
|
@@ -330,6 +371,10 @@ export async function handleUninstall(cwd, options) {
|
|
|
330
371
|
cleanCompoundRules(cwd);
|
|
331
372
|
cleanClaudeMd(cwd);
|
|
332
373
|
cleanSlashCommands();
|
|
374
|
+
if (cleanVerifySkill())
|
|
375
|
+
console.log(' ✓ Removed forgen verify skill (~/.claude/skills/verify/)');
|
|
376
|
+
if (await cleanCodexNotify())
|
|
377
|
+
console.log(' ✓ Removed forgen notify block from Codex config.toml');
|
|
333
378
|
cleanPluginArtifacts();
|
|
334
379
|
if (options.purge) {
|
|
335
380
|
try {
|
|
@@ -57,6 +57,9 @@ export declare function effectiveCooldownMs(parsed: {
|
|
|
57
57
|
userPatternFound?: boolean;
|
|
58
58
|
promptCount?: number;
|
|
59
59
|
}, currentPromptCount?: number): number;
|
|
60
|
+
export declare function maybeSpawnAutoCompound(sessionId: string, transcriptPath: string | undefined, promptCount: number,
|
|
61
|
+
/** ADR-016 D1: notify 폴백은 훅 env(FORGEN_CWD) 없이 돌므로 페이로드의 cwd 를 넘긴다. */
|
|
62
|
+
cwdOverride?: string): Promise<boolean>;
|
|
60
63
|
/**
|
|
61
64
|
* forge-loop 활성 시 미완료 스토리가 있으면 Stop을 차단하고 지속 메시지 주입.
|
|
62
65
|
* OMC의 persistent-mode.cjs 패턴 참고.
|
|
@@ -439,9 +439,11 @@ export function effectiveCooldownMs(parsed, currentPromptCount = 0) {
|
|
|
439
439
|
const grew = currentPromptCount - (parsed.promptCount ?? 0) >= AUTO_COMPOUND_GROWTH_THRESHOLD;
|
|
440
440
|
return grew ? AUTO_COMPOUND_COOLDOWN_MS : AUTO_COMPOUND_BARREN_COOLDOWN_MS;
|
|
441
441
|
}
|
|
442
|
-
async function maybeSpawnAutoCompound(sessionId, transcriptPath, promptCount
|
|
442
|
+
export async function maybeSpawnAutoCompound(sessionId, transcriptPath, promptCount,
|
|
443
|
+
/** ADR-016 D1: notify 폴백은 훅 env(FORGEN_CWD) 없이 돌므로 페이로드의 cwd 를 넘긴다. */
|
|
444
|
+
cwdOverride) {
|
|
443
445
|
if (!transcriptPath || promptCount < 10)
|
|
444
|
-
return;
|
|
446
|
+
return false;
|
|
445
447
|
const markerPath = path.join(STATE_DIR, 'last-auto-compound.json');
|
|
446
448
|
try {
|
|
447
449
|
const raw = fs.readFileSync(markerPath, 'utf-8');
|
|
@@ -450,12 +452,12 @@ async function maybeSpawnAutoCompound(sessionId, transcriptPath, promptCount) {
|
|
|
450
452
|
const last = parsed.completedAt ? Date.parse(parsed.completedAt) : 0;
|
|
451
453
|
const cooldown = effectiveCooldownMs(parsed, promptCount);
|
|
452
454
|
if (Number.isFinite(last) && Date.now() - last < cooldown)
|
|
453
|
-
return;
|
|
455
|
+
return false;
|
|
454
456
|
}
|
|
455
457
|
}
|
|
456
458
|
catch { /* first time or corrupt — proceed */ }
|
|
457
459
|
const { spawn: spawnProcess } = await import('node:child_process');
|
|
458
|
-
const cwd = process.env.FORGEN_CWD ?? process.env.COMPOUND_CWD ?? process.cwd();
|
|
460
|
+
const cwd = cwdOverride ?? process.env.FORGEN_CWD ?? process.env.COMPOUND_CWD ?? process.cwd();
|
|
459
461
|
// 기본: 번들된 auto-compound-runner. 프로덕션 빌드는 이 경로만 실행.
|
|
460
462
|
const defaultRunner = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'core', 'auto-compound-runner.js');
|
|
461
463
|
// 테스트 주입 경로 — FORGEN_TEST=1 게이트 + 경로 containment (~/.forgen 또는 /tmp 하위만 허용).
|
|
@@ -486,7 +488,7 @@ async function maybeSpawnAutoCompound(sessionId, transcriptPath, promptCount) {
|
|
|
486
488
|
const { claimAutoCompoundInflight, releaseAutoCompoundInflight } = await import('../core/spawn.js');
|
|
487
489
|
if (!claimAutoCompoundInflight(sessionId)) {
|
|
488
490
|
log.debug('Stop-triggered auto-compound skip: 세션 in-flight');
|
|
489
|
-
return;
|
|
491
|
+
return false;
|
|
490
492
|
}
|
|
491
493
|
try {
|
|
492
494
|
const child = spawnProcess('node', [runnerPath, cwd, transcriptPath, sessionId, String(promptCount)], {
|
|
@@ -495,10 +497,12 @@ async function maybeSpawnAutoCompound(sessionId, transcriptPath, promptCount) {
|
|
|
495
497
|
});
|
|
496
498
|
child.unref();
|
|
497
499
|
log.debug(`Stop-triggered auto-compound 시작: ${sessionId} (${promptCount} prompts)`);
|
|
500
|
+
return true;
|
|
498
501
|
}
|
|
499
502
|
catch (e) {
|
|
500
503
|
releaseAutoCompoundInflight(sessionId); // spawn 실패 → 재시도 가능
|
|
501
504
|
log.debug('Stop-triggered auto-compound spawn 실패', e);
|
|
505
|
+
return false;
|
|
502
506
|
}
|
|
503
507
|
}
|
|
504
508
|
// forge-loop 차단 안전 상한 (무한 루프 방지)
|
|
@@ -32,6 +32,17 @@ export interface HookEntry {
|
|
|
32
32
|
* 훅 신뢰(trust) 와 묶여 있으므로, Claude 에만 추가하는 이벤트는 `["claude"]` 로 한정한다.
|
|
33
33
|
*/
|
|
34
34
|
hosts?: Array<'claude' | 'codex' | 'opencode'>;
|
|
35
|
+
/**
|
|
36
|
+
* ADR-016 D2: Codex hooks.json 핸들러에만 붙는 필드. Codex 의 trust 해시는 핸들러 단위라
|
|
37
|
+
* 여기 값을 바꾸면 *그 핸들러만* `/hooks` 재승인이 필요하다 (다른 핸들러는 영향 없음).
|
|
38
|
+
*/
|
|
39
|
+
codex?: {
|
|
40
|
+
/**
|
|
41
|
+
* `additionalContext` 를 임시 파일로 스필하는 임계(근사 토큰, 기본 2500 ≈ 10KB). 0 = 스필 안 함.
|
|
42
|
+
* Codex 는 PreToolUse/PostToolUse/SessionStart/UserPromptSubmit/SubagentStart 에서만 인정한다.
|
|
43
|
+
*/
|
|
44
|
+
additionalContextLimit?: number;
|
|
45
|
+
};
|
|
35
46
|
}
|
|
36
47
|
export declare const HOOK_REGISTRY: HookEntry[];
|
|
37
48
|
/** 티어별 훅 목록 조회 */
|
|
@@ -92,7 +92,11 @@ export function generateHooksJson(options) {
|
|
|
92
92
|
matcher,
|
|
93
93
|
hooks: matcherEntries.map(h => {
|
|
94
94
|
const command = buildHookCommand(pluginRoot, h.script, runtime);
|
|
95
|
-
|
|
95
|
+
const handler = { type: 'command', command, timeout: h.timeout };
|
|
96
|
+
if (runtime === 'codex' && typeof h.codex?.additionalContextLimit === 'number') {
|
|
97
|
+
handler.additionalContextLimit = h.codex.additionalContextLimit;
|
|
98
|
+
}
|
|
99
|
+
return handler;
|
|
96
100
|
}),
|
|
97
101
|
}));
|
|
98
102
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Forgen — SessionEnd Hook (ADR-015 C-G6
|
|
3
|
+
* Forgen — SessionEnd Hook (ADR-015 C-G6 Claude, ADR-016 D2 Codex)
|
|
4
4
|
*
|
|
5
5
|
* Claude Code `SessionEnd` (reason: clear|resume|logout|prompt_input_exit|other, 기본 예산 1.5s)
|
|
6
6
|
* 에서 이전 세션 transcript 를 auto-compound 러너에 넘긴다. 기존 트리거(Stop / PreCompact /
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* - 예산(기본 1.5s, registry timeout 3s 로 상향) 안에 끝나야 하므로: stdin 파싱 → user 메시지 수
|
|
12
12
|
* (앞 200KB 만 읽음, 대용량 transcript 보호) → detached spawn.
|
|
13
|
-
* - Codex
|
|
14
|
-
*
|
|
13
|
+
* - Codex 0.153+ 에도 등록한다 (ADR-016 D2). Codex 의 SessionEnd 는 stdin 이
|
|
14
|
+
* `session_id`/`transcript_path`/`cwd`/`reason`(항상 "other") 이고 stdout 을 무시하며 타임아웃을 1~3s 로
|
|
15
|
+
* clamp 한다 — 같은 "bounded count → detached spawn" 경로가 그대로 맞는다. trust 해시가 핸들러 단위라
|
|
16
|
+
* 새 이벤트 추가는 기존 훅의 신뢰를 깨지 않는다 (이 훅 1개만 `/hooks` 승인 필요).
|
|
15
17
|
* - fail-open: 어떤 실패도 종료를 막지 않는다.
|
|
16
18
|
*/
|
|
17
19
|
/** session-recovery 와 동일 임계값 — 짧은 세션은 compound 가치가 없다. */
|
|
@@ -29,6 +31,11 @@ export declare const COUNT_SCAN_BYTES: number;
|
|
|
29
31
|
* 임계값(MIN_USER_MESSAGES) 판정에만 쓰이므로 하한 추정으로 충분하다. 큰 transcript 에서도 O(200KB).
|
|
30
32
|
*/
|
|
31
33
|
export declare function countUserMessagesBounded(transcriptPath: string, maxBytes?: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* host 별 user 메시지 수. Codex rollout 은 스키마가 달라 전용 카운터를 쓴다 (ADR-016 D2 — 실제 사용자
|
|
36
|
+
* 프롬프트만, raw 바이트 스캔). codex-adapter 가 delegate 훅에 FORGEN_RUNTIME=codex 를 주입한다.
|
|
37
|
+
*/
|
|
38
|
+
export declare function countSessionUserMessages(transcriptPath: string, runtime?: string | undefined): Promise<number>;
|
|
32
39
|
/** 순수 판정: 이 입력으로 auto-compound 를 띄울지. (테스트 대상) */
|
|
33
40
|
export declare function shouldRunSessionEndCompound(input: SessionEndInput | null, userMessageCount: number): boolean;
|
|
34
41
|
export declare function main(): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Forgen — SessionEnd Hook (ADR-015 C-G6
|
|
3
|
+
* Forgen — SessionEnd Hook (ADR-015 C-G6 Claude, ADR-016 D2 Codex)
|
|
4
4
|
*
|
|
5
5
|
* Claude Code `SessionEnd` (reason: clear|resume|logout|prompt_input_exit|other, 기본 예산 1.5s)
|
|
6
6
|
* 에서 이전 세션 transcript 를 auto-compound 러너에 넘긴다. 기존 트리거(Stop / PreCompact /
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* - 예산(기본 1.5s, registry timeout 3s 로 상향) 안에 끝나야 하므로: stdin 파싱 → user 메시지 수
|
|
12
12
|
* (앞 200KB 만 읽음, 대용량 transcript 보호) → detached spawn.
|
|
13
|
-
* - Codex
|
|
14
|
-
*
|
|
13
|
+
* - Codex 0.153+ 에도 등록한다 (ADR-016 D2). Codex 의 SessionEnd 는 stdin 이
|
|
14
|
+
* `session_id`/`transcript_path`/`cwd`/`reason`(항상 "other") 이고 stdout 을 무시하며 타임아웃을 1~3s 로
|
|
15
|
+
* clamp 한다 — 같은 "bounded count → detached spawn" 경로가 그대로 맞는다. trust 해시가 핸들러 단위라
|
|
16
|
+
* 새 이벤트 추가는 기존 훅의 신뢰를 깨지 않는다 (이 훅 1개만 `/hooks` 승인 필요).
|
|
15
17
|
* - fail-open: 어떤 실패도 종료를 막지 않는다.
|
|
16
18
|
*/
|
|
17
19
|
import * as fs from 'node:fs';
|
|
@@ -52,6 +54,17 @@ export function countUserMessagesBounded(transcriptPath, maxBytes = COUNT_SCAN_B
|
|
|
52
54
|
fs.closeSync(fd);
|
|
53
55
|
}
|
|
54
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* host 별 user 메시지 수. Codex rollout 은 스키마가 달라 전용 카운터를 쓴다 (ADR-016 D2 — 실제 사용자
|
|
59
|
+
* 프롬프트만, raw 바이트 스캔). codex-adapter 가 delegate 훅에 FORGEN_RUNTIME=codex 를 주입한다.
|
|
60
|
+
*/
|
|
61
|
+
export async function countSessionUserMessages(transcriptPath, runtime = process.env.FORGEN_RUNTIME) {
|
|
62
|
+
if (runtime === 'codex') {
|
|
63
|
+
const { countCodexUserPrompts } = await import('../host/codex-rollout.js');
|
|
64
|
+
return countCodexUserPrompts(transcriptPath);
|
|
65
|
+
}
|
|
66
|
+
return countUserMessagesBounded(transcriptPath);
|
|
67
|
+
}
|
|
55
68
|
/** 순수 판정: 이 입력으로 auto-compound 를 띄울지. (테스트 대상) */
|
|
56
69
|
export function shouldRunSessionEndCompound(input, userMessageCount) {
|
|
57
70
|
if (!input || typeof input.transcript_path !== 'string' || input.transcript_path.length === 0)
|
|
@@ -71,7 +84,7 @@ export async function main() {
|
|
|
71
84
|
let count = 0;
|
|
72
85
|
if (transcript && fs.existsSync(transcript)) {
|
|
73
86
|
try {
|
|
74
|
-
count =
|
|
87
|
+
count = await countSessionUserMessages(transcript);
|
|
75
88
|
}
|
|
76
89
|
catch (e) {
|
|
77
90
|
log.debug('user message count 실패', e);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { spawnSync } from 'node:child_process';
|
|
14
14
|
import { projectCodexToClaude } from './projection.js';
|
|
15
|
+
import { markCodexHookAlive } from './codex-hook-alive.js';
|
|
15
16
|
function lastJSONObjectFromText(raw) {
|
|
16
17
|
const lines = raw.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
17
18
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
@@ -54,6 +55,10 @@ async function main() {
|
|
|
54
55
|
return {};
|
|
55
56
|
}
|
|
56
57
|
})();
|
|
58
|
+
// ADR-016 D1 — "Codex 가 forgen 훅을 실제로 실행 중" 마커 (notify 폴백이 신선도를 본다).
|
|
59
|
+
// forgen 자신의 추출용 중첩 실행은 사용자 세션이 아니므로 제외.
|
|
60
|
+
if (process.env.FORGEN_NESTED_RUN !== '1')
|
|
61
|
+
markCodexHookAlive(input);
|
|
57
62
|
try {
|
|
58
63
|
// 0.4.6 fix — delegate hook 이 자기가 codex runtime context 인지 알 수 있게
|
|
59
64
|
// FORGEN_RUNTIME=codex 명시 주입. 이전엔 buildEnv (forgen wrapper) 경로로만
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex 훅 생존 마커 — ADR-016 D1
|
|
3
|
+
*
|
|
4
|
+
* Codex 는 미승인(untrusted)/변경된(modified) 훅을 조용히 skip 한다. "forgen 훅이 실제로 실행되고 있는가"
|
|
5
|
+
* 를 관측하는 유일한 choke point 는 codex-adapter 다 (Codex 가 forgen 훅을 실행하면 반드시 거친다).
|
|
6
|
+
* 어댑터가 턴 경계 이벤트에서 전역 마커를 갱신하고, notify 폴백(codex-notify)이 그 신선도를 본다.
|
|
7
|
+
*
|
|
8
|
+
* 훅 신뢰 상태는 hooks.json 단위로 모든 세션이 공유하므로 마커는 세션별이 아니라 전역 1개다.
|
|
9
|
+
* 어댑터가 매 훅마다 import 하므로 의존성은 node 내장 + paths 만 둔다.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CODEX_HOOK_ALIVE_PATH: string;
|
|
12
|
+
/** notify 는 Stop 훅 직후 발화한다. Stop 훅 타임아웃(10s) 대비 넉넉한 창. */
|
|
13
|
+
export declare const CODEX_HOOK_ALIVE_WINDOW_MS = 120000;
|
|
14
|
+
export interface CodexHookAliveMarker {
|
|
15
|
+
at: number;
|
|
16
|
+
event: string;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
}
|
|
19
|
+
/** 어댑터 입력(stdin JSON)으로 마커 갱신. 대상 이벤트가 아니면 no-op. 실패는 삼킨다 (fail-open). */
|
|
20
|
+
export declare function markCodexHookAlive(input: unknown, now?: number): boolean;
|
|
21
|
+
export declare function readCodexHookAlive(): CodexHookAliveMarker | null;
|
|
22
|
+
/** 최근 window 안에 forgen 훅이 Codex 에서 실행됐는가. */
|
|
23
|
+
export declare function isCodexHookAlive(now?: number, windowMs?: number): boolean;
|