@moreih29/nexus-core 0.14.1 → 0.15.1

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.
@@ -0,0 +1,283 @@
1
+ # harness-io 계약
2
+
3
+ > **규범 문서 (Normative).** `@moreih29/nexus-core`와 3 하네스 consumer plugin repo 간 I/O 계약의 SSOT.
4
+ > 튜토리얼·사용법은 [`docs/plugin-guide.md`](../plugin-guide.md) 참조.
5
+
6
+ ---
7
+
8
+ ## 1. 스코프와 버전 정책
9
+
10
+ ### 1-1. 문서 역할
11
+
12
+ 본 문서는 nexus-core가 외부 consumer(3 하네스 plugin repo)에게 보장하는 **계약**을 정의한다. 하네스별 설치 절차·코드 예시·온보딩 흐름은 이 문서의 범위 밖이다.
13
+
14
+ ### 1-2. semver 3축 분리
15
+
16
+ | 축 | 대상 | 변경 등급 |
17
+ |---|---|---|
18
+ | (i) Runtime exports | §2 전체 — 서브패스 API, 타입 시그니처 | 추가: minor / 제거·타입 축소: major |
19
+ | (ii) sync 출력 set | §4 전체 — 경로·파일명·Managed/Template 분류 | 추가: minor / 제거·이동·분류 전환: major |
20
+ | (iii) bin 인터페이스 | §3 전체 — 서브커맨드명·주요 플래그 시맨틱 | 추가: minor / 제거·시맨틱 변경: major |
21
+
22
+ 세 축의 변경 등급은 독립적으로 산출한다. 파일 콘텐츠 내부 구조(프론트매터 스키마 등)는 향후 `assets/schema/`에 별도 버전 문서로 기술될 예정이며, 본 계약은 해당 디렉터리를 스키마 호환 주소로 지정한다. 현재는 agent·skill frontmatter 스키마 파일이 미작성 상태로, 작성 시점에 §8-4 참조.
23
+
24
+ ---
25
+
26
+ ## 2. Runtime exports 계약
27
+
28
+ 소비자가 `@moreih29/nexus-core`의 서브패스로 import할 수 있는 런타임 API 전수. `package.json exports` 필드가 기계 판독 SSOT이며 본 섹션은 그 해석을 제공한다.
29
+
30
+ ### 2-1. 루트 (`"."`)
31
+
32
+ ```
33
+ null
34
+ ```
35
+
36
+ barrel 파일이 존재하지 않는다. `import "@moreih29/nexus-core"`는 항상 오류다. 서브패스를 통해서만 접근 가능하다.
37
+
38
+ ### 2-2. 공개 API 서브패스
39
+
40
+ | 서브패스 | 형태 | 제공 내용 |
41
+ |---|---|---|
42
+ | `@moreih29/nexus-core/mcp` | types + import | MCP stdio 서버 모듈 |
43
+ | `@moreih29/nexus-core/types` | types + import | `AgentConfig` 등 generator 산출물이 참조하는 타입 (#36) |
44
+ | `@moreih29/nexus-core/hooks/opencode-mount` | types + import | `mountHooks` 함수 및 관련 타입 |
45
+ | `@moreih29/nexus-core/hooks/runtime` | types + import | dispatcher helpers 및 관련 타입 |
46
+ | `@moreih29/nexus-core/hooks/opencode-manifest` | JSON 단일 경로 | hook manifest JSON (Node 22+ `with { type: "json" }` import) |
47
+
48
+ `hooks/opencode-manifest`는 조건부 export 객체가 아닌 단일 문자열 경로로 선언된다. 소비자는 반드시 `with { type: "json" }` import attribute와 함께 사용해야 한다. 이는 Node.js 22 이상을 요구한다(§3 참조).
49
+
50
+ ### 2-3. 자산 와일드카드 서브패스 (read-only)
51
+
52
+ | 서브패스 패턴 | 실제 경로 | 용도 |
53
+ |---|---|---|
54
+ | `@moreih29/nexus-core/agents/*` | `assets/agents/*` | 에이전트 원본 |
55
+ | `@moreih29/nexus-core/skills/*` | `assets/skills/*` | 스킬 원본 |
56
+ | `@moreih29/nexus-core/assets/*` | `assets/*` | 기타 자산 |
57
+ | `@moreih29/nexus-core/docs/*` | `docs/*` | 문서 |
58
+
59
+ 와일드카드 서브패스는 읽기 전용 참조 용도다. 소비자가 이 경로의 파일을 수정하면 다음 패키지 업그레이드 시 변경이 소실된다.
60
+
61
+ ---
62
+
63
+ ## 3. bin 계약
64
+
65
+ | 실행 파일 | 진입점 | 역할 |
66
+ |---|---|---|
67
+ | `nexus-core` | `./dist/scripts/cli.js` | 빌드 타임 CLI |
68
+ | `nexus-mcp` | `./dist/src/mcp/server.js` | 런타임 stdio MCP 서버 |
69
+
70
+ ### 3-1. `nexus-core` 서브커맨드
71
+
72
+ | 서브커맨드 | 역할 |
73
+ |---|---|
74
+ | `sync` | 하네스별 자산 동기화 (§4 참조) |
75
+ | `init` | 신규 plugin repo 스캐폴드 초기화 |
76
+ | `list` | 에이전트·스킬·훅 목록 출력 |
77
+ | `validate` | `assets/` frontmatter 및 YAML 유효성 검사 |
78
+ | `mcp` | MCP stdio 서버 직접 실행 (`nexus-mcp`와 동일) |
79
+
80
+ 서브커맨드 시맨틱 변경 및 제거는 major breaking이다.
81
+
82
+ ### 3-2. engines 요구사항
83
+
84
+ `engines.node >= 22` — `import ... with { type: "json" }` (import attributes) 지원 최소 버전. 이 요구사항은 runtime exports §2와 연동되며 하향 조정은 major breaking이다.
85
+
86
+ ---
87
+
88
+ ## 4. sync 출력 set — 3 하네스별 규범
89
+
90
+ **공통 규칙**: `nexus-core sync --harness=<x> --target=<dir>` 실행 시 `<dir>` 직속에 자산을 기록한다. harness 이름 prefix를 붙이지 않는다. flat 출력이 기본값이며 별도 플래그 없이 적용된다.
91
+
92
+ multi-harness 빌드가 필요한 경우 소비자가 `--harness`와 `--target`을 조합해 3회 호출한다.
93
+
94
+ ---
95
+
96
+ ### 4-1. Claude
97
+
98
+ **하네스 루트 = plugin 루트.** Claude Code marketplace의 `source: "./"` 직접 정합.
99
+
100
+ | 경로 (target 루트 기준) | 분류 | 용도 |
101
+ |---|---|---|
102
+ | `.claude-plugin/plugin.json` | Template | 플러그인 메타 — 저자 편집 허용 |
103
+ | `.claude-plugin/marketplace.json` | Template | 마켓플레이스 메타 |
104
+ | `agents/<name>.md` | Managed | 하네스 네이티브 에이전트 마크다운 |
105
+ | `skills/<name>/SKILL.md` | Managed | 스킬 마크다운 |
106
+ | `settings.json`[^claude-primary] | Managed | `{ "agent": "<primary>" }` — main thread 시스템 프롬프트 주입 fragment |
107
+ | `hooks/hooks.json`[^claude-hooks] | Managed | Claude Code hooks manifest (prebuilt `dist/manifests/claude-hooks.json` 복사) |
108
+ | `dist/hooks/<name>.js`[^claude-hooks] | Managed | 사전 번들된 hook handler 바이너리 (prebuilt `dist/hooks/<name>.js` 복사, 하네스에 등록된 hook만) |
109
+
110
+ [^claude-primary]: primary agent가 1개 이상 있을 때만 생성. primary 에이전트 부재 시 파일을 생성하지 않으며 기존 파일도 삭제하지 않는다. §6-1 Managed 정의의 조건부 생성 예외 참조.
111
+ [^claude-hooks]: consumer `sync` 경로는 절대 `assets/hooks/*/handler.ts`를 재컴파일하지 않는다. 모든 handler는 publish 시점에 `bun build`로 self-contained ESM으로 번들링되어 tarball의 `dist/hooks/`·`dist/manifests/`에 탑재되며, `sync`는 단순 복사만 수행한다 (#34·#35·#36 Bug 2·#37).
112
+
113
+ **fragment 경로**: `settings.json`은 core가 생성하는 fragment다. `{ "agent": "<primary>" }` 키 하나를 포함한다.
114
+
115
+ **hooks 경로**: `hooks/hooks.json`의 `command` 필드는 `node ${CLAUDE_PLUGIN_ROOT}/dist/hooks/<name>.js` 형태다. `CLAUDE_PLUGIN_ROOT`는 Claude Code 런타임이 플러그인 설치 디렉터리로 바인딩하는 환경 변수로, 본 계약이 기록하는 상대 경로 `dist/hooks/<name>.js`와 정확히 정합한다. consumer sync가 두 파일 집합을 같은 target 루트에 배치하므로 런타임 해석이 성립한다.
116
+
117
+ **Consumer(claude-nexus wrapper) 책임**: marketplace 등록 · version bump · git push. core는 `settings.json`·`hooks/hooks.json`·`dist/hooks/*.js` 생성까지만 담당한다. fragment의 consumer 환경 실제 적용은 Claude Code 런타임이 처리하며 wrapper가 그 경로를 보장한다.
118
+
119
+ ---
120
+
121
+ ### 4-2. OpenCode
122
+
123
+ **npm 패키지 루트 = repo 루트.**
124
+
125
+ | 경로 (target 루트 기준) | 분류 | 용도 |
126
+ |---|---|---|
127
+ | `package.json` | Template | npm 패키지 메타 — 저자 name·version 편집 |
128
+ | `src/plugin.ts` | **Template (신규)** | mountHooks 진입점 보일러플레이트 |
129
+ | `opencode.json.fragment` | Managed | consumer에 merge될 agents 배열 fragment |
130
+ | `src/index.ts` | Managed | 에이전트 export 인덱스 |
131
+ | `src/agents/<name>.ts` | Managed | 에이전트 TS 모듈 |
132
+ | `.opencode/skills/<name>/SKILL.md` | Managed | 스킬 마크다운 |
133
+
134
+ **fragment 경로**: `opencode.json.fragment`는 core가 생성하는 agents 배열 fragment다. 이 파일의 consumer `opencode.json` merge는 wrapper(opencode-nexus)의 postinstall 스크립트가 수행한다.
135
+
136
+ **hooks 경로**: OpenCode는 Claude/Codex와 달리 `hooks/hooks.json` 파일을 target에 기록하지 않는다. 대신 `src/plugin.ts`의 `mountHooks(ctx, manifest)`가 런타임에 `@moreih29/nexus-core/hooks/opencode-manifest` JSON과 `@moreih29/nexus-core/hooks/opencode-mount` 함수를 import해 consume하며, handler 경로는 node_modules 내부 패키지 기준으로 해석된다. consumer target에는 물리 복사가 발생하지 않는다.
137
+
138
+ **Skill discovery 경계 (결정 #7)**: core는 consumer 프로젝트의 `.opencode/skills/<name>/SKILL.md` 파일 존재를 보장한다. `node_modules/<plugin>/.opencode/skills/` 자동 감지 여부는 OpenCode 런타임 영역으로 본 계약 외부다. wrapper(opencode-nexus)는 postinstall 스크립트로 패키지 내부 `.opencode/skills/`를 consumer 경로로 복사할 책임을 진다.
139
+
140
+ **Consumer(opencode-nexus wrapper) 책임**: `src/plugin.ts` 실 진입점 유지 · `opencode.json.fragment` consumer merge · postinstall skill copy · npm publish.
141
+
142
+ ---
143
+
144
+ ### 4-3. Codex
145
+
146
+ **wrapper repo 루트 = installer + plugin body 번들.**
147
+
148
+ | 경로 (target 루트 기준) | 분류 | 용도 |
149
+ |---|---|---|
150
+ | `package.json` | **Template (신규)** | wrapper 메타 |
151
+ | `install/install.sh` | **Template (신규)** | block-marker 머지 설치 스크립트 |
152
+ | `plugin/.codex-plugin/plugin.json` | Managed | plugin body 메타 (`~/.codex/plugins/<name>/`로 설치) |
153
+ | `plugin/skills/<name>/SKILL.md` | Managed | 스킬 마크다운 (plugin body 내부) |
154
+ | `agents/<name>.toml` | Managed | native agent TOML (`~/.codex/agents/`로 설치) |
155
+ | `prompts/<name>.md` | Managed | 에이전트 프롬프트 마크다운 |
156
+ | `install/config.fragment.toml` | Managed | `~/.codex/config.toml`에 merge될 `[mcp_servers]` fragment |
157
+ | `install/AGENTS.fragment.md`[^codex-primary] | Managed | `~/.codex/AGENTS.md`에 merge될 primary agent body (block-marker) |
158
+ | `hooks/hooks.json`[^codex-hooks] | Managed | Codex hooks manifest (prebuilt `dist/manifests/codex-hooks.json` 복사) |
159
+ | `dist/hooks/<name>.js`[^codex-hooks] | Managed | 사전 번들된 hook handler 바이너리 (Codex에 등록된 hook만) |
160
+
161
+ [^codex-hooks]: Claude와 동일한 self-contained pre-bundle 정책을 따른다. consumer sync는 재컴파일하지 않고 tarball의 prebuilt 산출물을 단순 복사한다.
162
+
163
+ [^codex-primary]: primary agent가 1개 이상 있을 때만 생성. primary 에이전트 부재 시 파일을 생성하지 않으며 기존 파일도 삭제하지 않는다. §6-1 Managed 정의의 조건부 생성 예외 참조.
164
+
165
+ **fragment 경로**:
166
+ - `install/config.fragment.toml` — MCP 서버 등록 TOML fragment
167
+ - `install/AGENTS.fragment.md` — primary agent body를 `<!-- nexus-core:<agent-id>:start -->` / `<!-- nexus-core:<agent-id>:end -->` 마커로 감싼 fragment. `<agent-id>`는 primary agent의 frontmatter `id`이며 기본값은 `lead`이다
168
+
169
+ **도메인 서브디렉터리 구조**: Codex 내부 `plugin/`·`agents/`·`install/` 서브디렉터리는 Codex 생태계가 `~/.codex/plugins/`·`~/.codex/agents/`·`~/.codex/config.toml` 3곳에 분리 설치되는 구조를 반영한다. 이 도메인 prefix는 flat 출력 규칙(§4 공통 규칙)과 직교하며 유지된다.
170
+
171
+ **Consumer(codex-nexus wrapper) 책임**: `install.sh` 편집·실행 · block-marker merge(`config.toml`·`AGENTS.md`) · npm 혹은 GitHub source 배포 선택.
172
+
173
+ ---
174
+
175
+ ## 5. Ownership line (Model 2 통일)
176
+
177
+ 3 하네스 모두 동일한 ownership 모델을 따른다. 철학 §3 비목표("플러그인 자체의 빌드·배포·실행 — 하네스 책임")의 규범화다.
178
+
179
+ ### 5-1. core 책임
180
+
181
+ 1. agents·skills 원본 생성 (`assets/agents/*`, `assets/skills/*`)
182
+ 2. 하네스 네이티브 자산 빌드 — agents (`.md`/`.ts`/`.toml`), skills (`SKILL.md`), plugin manifest
183
+ 3. Integration fragment 제공:
184
+ - Claude: `settings.json`
185
+ - OpenCode: `opencode.json.fragment`
186
+ - Codex: `install/config.fragment.toml`·`install/AGENTS.fragment.md`
187
+ 4. Runtime exports (`mountHooks`, manifest JSON, mcp 서버)
188
+
189
+ ### 5-2. wrapper 책임
190
+
191
+ 1. Fragment의 consumer 환경 실제 머지 로직 — block-marker·conditional merge·OS 경로 해석
192
+ 2. 배포 — marketplace 등록·npm publish·`install.sh` 실행
193
+ 3. Plugin 진입점 코드 — OpenCode `src/plugin.ts`·Codex `install/install.sh`
194
+ 4. Version·changelog 관리
195
+
196
+ core는 merge 로직·배포·OS별 경로 해석을 떠안지 않는다.
197
+
198
+ ---
199
+
200
+ ## 6. Template vs Managed 정책
201
+
202
+ ### 6-1. 분류 정의
203
+
204
+ | 분류 | 쓰기 시점 | 소비자 편집 보존 여부 |
205
+ |---|---|---|
206
+ | **Managed** | 매 `sync` 실행마다 덮어씀 | 보존되지 않음 |
207
+ | **Template** | 파일 부재 시에만 생성. 존재 시 skip | 보존됨 (`--force` 지정 시 제외) |
208
+
209
+ **조건부 생성 예외**: §4에서 각주로 명시한 Managed 파일(`settings.json`, `install/AGENTS.fragment.md`)은 primary agent가 1개 이상 존재할 때만 생성된다. primary 부재 시 생성 자체가 일어나지 않으며, 기존 파일이 있어도 삭제하지 않는다. 이 파일들은 "생성되면 Managed" 규칙을 따른다.
210
+
211
+ ### 6-2. 플래그 시맨틱
212
+
213
+ | 플래그 | 동작 |
214
+ |---|---|
215
+ | `--dry-run` | 실제 쓰기 없이 변경될 파일 목록과 summary만 출력 |
216
+ | `--force` | Template 파일도 강제 덮어씀 |
217
+ | `--strict` | Managed 파일에 로컬 drift가 있으면 exit 1. Template skip은 정상 통과(exit 0) |
218
+
219
+ ### 6-3. dry-run 출력 포맷
220
+
221
+ 파일별 prefix:
222
+
223
+ | prefix | 의미 |
224
+ |---|---|
225
+ | `[M]` | Managed — 이번 sync에서 쓰여짐 |
226
+ | `[T]` | Template — 신규 생성 |
227
+ | `[T]{skip}` | Template — 파일 존재로 건너뜀 |
228
+ | `[T]{force}` | Template — `--force`로 덮어씀 |
229
+
230
+ Summary 라인 포맷:
231
+
232
+ ```
233
+ [build-agents] N managed, M template-create, K template-skipped, L template-force-overwrite
234
+ ```
235
+
236
+ ### 6-4. Managed·Template 분류 변경 정책
237
+
238
+ Managed에서 Template으로, 또는 Template에서 Managed로의 분류 전환은 major breaking이다(§8 참조).
239
+
240
+ ---
241
+
242
+ ## 7. 예약된 확장점
243
+
244
+ ### 7-1. consumer-manifest.json (현재 미도입, 결정 #2 유보)
245
+
246
+ 현재 3 wrapper 어느 쪽도 `managed_outputs`·`requires`·`primary_agents` 등 기계 판독 필드를 소비하는 코드가 없다. 소비처가 없는 상태에서 스키마를 동결하면 형식적 1.0 고정과 드리프트 위험이 발생한다.
247
+
248
+ **현재 SSOT**: `package.json exports`(runtime 계약) + 본 문서 `docs/contract/harness-io.md`(빌드 산출물 계약) 두 축으로 충분하다.
249
+
250
+ **재검토 조건**: 자동화가 `managed_outputs`·`requires` 등 필드를 실제로 기계 소비하기 시작하는 시점에 별도 안건으로 재검토한다. 그 전에는 본 문서가 SSOT 역할을 유지한다.
251
+
252
+ ---
253
+
254
+ ## 8. 변경 관리
255
+
256
+ ### 8-1. sync 출력 set (§4) 변경 등급
257
+
258
+ | 변경 유형 | 등급 |
259
+ |---|---|
260
+ | 경로 추가 | minor |
261
+ | 경로 제거·이동 | major breaking |
262
+ | Managed ↔ Template 분류 전환 | major breaking |
263
+
264
+ ### 8-2. Runtime exports (§2) 변경 등급
265
+
266
+ | 변경 유형 | 등급 |
267
+ |---|---|
268
+ | 새 서브패스 추가 | minor |
269
+ | 서브패스 제거 | major breaking |
270
+ | 타입 시그니처 축소·호환 불가 변경 | major breaking |
271
+ | 타입 확장(호환 유지) | minor 또는 patch |
272
+
273
+ ### 8-3. bin 인터페이스 (§3) 변경 등급
274
+
275
+ | 변경 유형 | 등급 |
276
+ |---|---|
277
+ | 새 서브커맨드·플래그 추가 | minor |
278
+ | 서브커맨드·플래그 제거 | major breaking |
279
+ | 주요 플래그 시맨틱 변경 | major breaking |
280
+
281
+ ### 8-4. 프론트매터 스키마
282
+
283
+ `assets/agents/*/body.md`·`assets/skills/*/body.md` frontmatter 스키마 변경은 `assets/schema/` 버전 문서에서 관리될 예정이다. 현재는 agent·skill frontmatter 전용 스키마 파일이 미작성 상태이며, 작성 시 `assets/schema/` 디렉터리에 위치한다. 본 문서에는 "스키마 호환 주소: `assets/schema/`" 만 명시한다.
@@ -20,6 +20,8 @@ end user (컨슈머)
20
20
 
21
21
  nexus-core는 자산을 **정의**합니다. plugin repo는 자산을 **패키징**합니다. end user는 플러그인을 **소비**합니다.
22
22
 
23
+ > 계약 명세는 [docs/contract/harness-io.md](./contract/harness-io.md)를 참조. 본 문서는 튜토리얼입니다.
24
+
23
25
  ---
24
26
 
25
27
  ## 2. `nexus-core sync` 사용법
@@ -37,7 +39,7 @@ bunx @moreih29/nexus-core sync --harness=<claude|opencode|codex> --target=<dir>
37
39
  | 플래그 | 기본값 | 설명 |
38
40
  |---|---|---|
39
41
  | `--harness=<name>` | 전체 (all) | 대상 하네스를 `claude`, `opencode`, `codex` 중 하나로 제한 |
40
- | `--target=<dir>` | `dist/` | 출력 디렉터리. plugin repo 루트를 지정하려면 `--target=./` |
42
+ | `--target=<dir>` | `dist/` | 출력 디렉터리. `<dir>` 직속에 자산을 기록합니다 (harness prefix 없음). plugin repo 루트에 직접 출력하려면 `--target=./` |
41
43
  | `--dry-run` | — | 변경될 파일 목록만 출력. 실제 쓰기 없음 |
42
44
  | `--force` | — | Template 파일(처음 한 번만 생성되는 파일)도 강제 덮어쓰기 |
43
45
  | `--strict` | — | Managed 파일에 미커밋 로컬 변경이 있으면 오류로 중단 |
@@ -47,8 +49,8 @@ bunx @moreih29/nexus-core sync --harness=<claude|opencode|codex> --target=<dir>
47
49
 
48
50
  | 분류 | 예시 경로 | 정책 |
49
51
  |---|---|---|
50
- | **Managed** | `agents/*.md`, `src/agents/*.ts`, `plugin/skills/*/SKILL.md`, `dist/claude/settings.json`, `dist/codex/install/AGENTS.fragment.md` | 항상 덮어씀 (`--dry-run` 시 제외) |
51
- | **Template** | `.claude-plugin/plugin.json`, `package.json` | 파일이 없을 때만 생성. `--force`로 강제 가능 |
52
+ | **Managed** | `agents/*.md`, `src/agents/*.ts`, `plugin/skills/*/SKILL.md`, `settings.json`, `install/AGENTS.fragment.md` | 항상 덮어씀 (`--dry-run` 시 제외) |
53
+ | **Template** | `.claude-plugin/plugin.json`, `package.json`, `src/plugin.ts`, `install/install.sh` | 파일이 없을 때만 생성. `--force`로 강제 가능 |
52
54
 
53
55
  **직접 편집하지 말아야 할 경로**: Managed 경로는 다음 sync 시 덮어집니다. 커스터마이즈가 필요하면 Template 경로나 별도 파일을 사용하세요.
54
56
 
@@ -92,9 +94,9 @@ nexus-core는 10개 에이전트 중 `lead`를 primary orchestrator로 지정합
92
94
 
93
95
  | 하네스 | 주입 경로 | Managed 산출물 | Consumer 작업 |
94
96
  |---|---|---|---|
95
- | Claude | `settings.json`의 `agent` 키 | `dist/claude/settings.json` | 없음 (자동) |
96
- | OpenCode | `AgentConfig.mode = "primary"` | `dist/opencode/src/agents/<name>.ts` | 없음 (자동) |
97
- | Codex | `AGENTS.md` 수동 머지 | `dist/codex/install/AGENTS.fragment.md` | [docs/consuming/codex-lead-merge.md](./consuming/codex-lead-merge.md) 참조 |
97
+ | Claude | `settings.json`의 `agent` 키 | `settings.json` | 없음 (자동) |
98
+ | OpenCode | `AgentConfig.mode = "primary"` | `src/agents/<name>.ts` | 없음 (자동) |
99
+ | Codex | `AGENTS.md` 수동 머지 | `install/AGENTS.fragment.md` | [docs/consuming/codex-lead-merge.md](./consuming/codex-lead-merge.md) 참조 |
98
100
 
99
101
  ---
100
102
 
@@ -170,7 +172,9 @@ Claude 플러그인은 플러그인 repo 자체가 설치 단위입니다. 컨
170
172
 
171
173
  `plugin.json`의 `version` 필드가 업데이트 감지 기준입니다. 릴리즈마다 버전을 올리세요.
172
174
 
173
- **Lead primary agent 주입**: sync는 `.claude-plugin/` 디렉터리가 아닌 플러그인 루트에 `dist/claude/settings.json`을 생성합니다. 이 파일에는 `{ "agent": "lead" }` 키가 포함되어, Claude Code가 플러그인 활성 시 main thread를 `lead` 에이전트로 실행합니다. 사용자가 다른 에이전트로 override하려면 프로젝트 또는 사용자 `.claude/settings.json`에서 scope 우선순위에 따라 재정의할 수 있습니다.
175
+ **Lead primary agent 주입**: sync는 `.claude-plugin/` 디렉터리가 아닌 플러그인 루트에 `settings.json`을 생성합니다. 이 파일에는 `{ "agent": "lead" }` 키가 포함되어, Claude Code가 플러그인 활성 시 main thread를 `lead` 에이전트로 실행합니다. 사용자가 다른 에이전트로 override하려면 프로젝트 또는 사용자 `.claude/settings.json`에서 scope 우선순위에 따라 재정의할 수 있습니다.
176
+
177
+ `--target=./` flat 출력은 Claude Code marketplace의 `source: "./"` 계약과 직접 정합합니다. sync 결과물이 repo 루트에 위치하기 때문에 별도의 경로 재지정 없이 마켓플레이스에서 바로 인식됩니다.
174
178
 
175
179
  ---
176
180
 
@@ -195,18 +199,12 @@ OpenCode 플러그인은 npm 패키지로 배포됩니다. 컨슈머가 `opencod
195
199
  }
196
200
  ```
197
201
 
198
- #### hook manifest 스키마 (v0.14.0)
202
+ **`src/plugin.ts`** (Template 처음 sync 시 생성됨): mountHooks 진입점 보일러플레이트. 내용은 [`docs/plugin-template/opencode/src/plugin.ts`](../docs/plugin-template/opencode/src/plugin.ts) 참조. 저자는 최초 생성 후 자유롭게 수정 가능 (다음 sync에서 덮어쓰지 않음).
199
203
 
200
- v0.14.0부터 OpenCode hook manifest 스키마가 변경되었습니다. 플러그인 진입점에서
201
- `@moreih29/nexus-core`의 서브패스를 통해 manifest를 임포트하세요.
202
-
203
- ```typescript
204
- import type { Plugin } from "@opencode-ai/plugin";
205
- import { mountHooks } from "@moreih29/nexus-core/hooks/opencode-mount";
206
- import manifest from "@moreih29/nexus-core/hooks/opencode-manifest" with { type: "json" };
204
+ #### hook manifest 스키마 (v0.14.0)
207
205
 
208
- export const OpencodeNexus: Plugin = async (ctx) => mountHooks(ctx, manifest);
209
- ```
206
+ v0.14.0부터 OpenCode hook manifest 스키마가 변경되었습니다. 진입점(`src/plugin.ts`)에서
207
+ `@moreih29/nexus-core`의 서브패스를 통해 manifest를 임포트합니다.
210
208
 
211
209
  manifest의 구조는 다음과 같습니다.
212
210
 
@@ -261,48 +259,7 @@ Codex는 플러그인 시스템(`.codex-plugin/plugin.json`)과 native agent 시
261
259
  1. `nexus-core sync --harness=codex --target=./` 실행 후 커밋
262
260
  2. install 헬퍼 스크립트(`install/install.sh`) 작성 또는 `package.json`의 `install-plugin` 스크립트 구현
263
261
 
264
- install 스크립트가 수행해야 하는 작업:
265
-
266
- ```bash
267
- #!/usr/bin/env bash
268
- # install/install.sh — 컨슈머 machine에서 실행
269
-
270
- PLUGIN_NAME="my-codex-plugin"
271
-
272
- # 1. config.toml에 MCP 서버 + agent 테이블 병합 (block-marker 패턴)
273
- MARKER_BEGIN="# BEGIN ${PLUGIN_NAME}"
274
- MARKER_END="# END ${PLUGIN_NAME}"
275
- CONFIG="$HOME/.codex/config.toml"
276
-
277
- # 기존 블록 제거 후 새 내용 삽입
278
- if grep -q "${MARKER_BEGIN}" "$CONFIG" 2>/dev/null; then
279
- sed -i.bak "/${MARKER_BEGIN}/,/${MARKER_END}/d" "$CONFIG"
280
- fi
281
-
282
- {
283
- echo "${MARKER_BEGIN}"
284
- cat install/config.fragment.toml
285
- echo "${MARKER_END}"
286
- } >> "$CONFIG"
287
-
288
- # 2. agent TOML 파일을 user scope로 복사
289
- mkdir -p "$HOME/.codex/agents"
290
- cp agents/*.toml "$HOME/.codex/agents/"
291
-
292
- # 3. lead agent body를 AGENTS.md에 머지 (block-marker 패턴)
293
- FRAGMENT="dist/codex/install/AGENTS.fragment.md"
294
- AGENTS_TARGET="$HOME/.codex/AGENTS.md"
295
- FRAG_BEGIN="<!-- nexus-core:lead:start -->"
296
- FRAG_END="<!-- nexus-core:lead:end -->"
297
-
298
- if grep -q "${FRAG_BEGIN}" "$AGENTS_TARGET" 2>/dev/null; then
299
- sed -i.bak "/${FRAG_BEGIN}/,/${FRAG_END}/d" "$AGENTS_TARGET"
300
- fi
301
-
302
- cat "$FRAGMENT" >> "$AGENTS_TARGET"
303
-
304
- echo "Installed ${PLUGIN_NAME}"
305
- ```
262
+ **`install/install.sh`** (Template 처음 sync 시 생성됨): block-marker 머지 설치 스크립트. 내용은 [`docs/plugin-template/codex/install/install.sh`](../docs/plugin-template/codex/install/install.sh) 참조. 저자는 `PLUGIN_NAME`·`CODEX_HOME` 등을 편집하여 최적화 가능.
306
263
 
307
264
  **block-marker 패턴**: `# BEGIN <plugin-name>` / `# END <plugin-name>` 마커 사이의 내용만 교체합니다.
308
265
  기존 사용자 설정을 덮어쓰지 않습니다. oh-my-codex의 `omx setup` 패턴에서 가져온 방식입니다.
@@ -316,7 +273,7 @@ command = "nexus-mcp"
316
273
 
317
274
  `nexus-mcp`는 `@moreih29/nexus-core`를 전역 설치하면 PATH에 추가됩니다.
318
275
 
319
- **Lead primary agent 주입**: sync는 `dist/codex/install/AGENTS.fragment.md`를 생성합니다. 이 파일은 `<!-- nexus-core:lead:start -->` / `<!-- nexus-core:lead:end -->` 마커로 감싸진 `lead` agent body를 포함합니다. Codex는 `AGENTS.md`를 공식 로드 경로로 사용하므로 컨슈머가 직접 머지해야 합니다. install 스크립트의 3단계가 이를 자동화합니다. 자세한 수동 머지 절차는 [docs/consuming/codex-lead-merge.md](./consuming/codex-lead-merge.md) 참조.
276
+ **Lead primary agent 주입**: sync는 `install/AGENTS.fragment.md`를 생성합니다. 이 파일은 `<!-- nexus-core:lead:start -->` / `<!-- nexus-core:lead:end -->` 마커로 감싸진 `lead` agent body를 포함합니다. Codex는 `AGENTS.md`를 공식 로드 경로로 사용하므로 컨슈머가 직접 머지해야 합니다. `install/install.sh`의 마지막 단계가 이를 자동화합니다. 자세한 수동 머지 절차는 [docs/consuming/codex-lead-merge.md](./consuming/codex-lead-merge.md) 참조.
320
277
 
321
278
  ---
322
279
 
@@ -353,14 +310,16 @@ Codex 플러그인의 npm 기반 설치는 공식 지원 여부가 확인되지
353
310
 
354
311
  ### Q: 여러 하네스를 한 번에 동기화할 수 있나요?
355
312
 
356
- `--harness` 플래그를 생략하면 모든 하네스를 순차로 처리합니다. `--target`은 공통 출력 루트가 됩니다.
313
+ `--harness` 플래그를 생략하면 3 하네스를 순차로 처리합니다. 단, flat 출력 특성상 모든 하네스 자산이 동일한 `--target` 디렉터리에 쌓입니다. 하네스별로 별도 디렉터리(또는 별도 repo)에 출력해야 할 경우 개별 실행을 권장합니다.
357
314
 
358
315
  ```bash
359
- # dist/claude/, dist/opencode/, dist/codex/ 에 동시 생성
360
- bunx @moreih29/nexus-core sync --target=./dist
316
+ # 하네스별로 target을 분리해 실행
317
+ bunx @moreih29/nexus-core sync --harness=claude --target=./dist/claude
318
+ bunx @moreih29/nexus-core sync --harness=opencode --target=./dist/opencode
319
+ bunx @moreih29/nexus-core sync --harness=codex --target=./dist/codex
361
320
  ```
362
321
 
363
- 각 하네스 디렉터리를 별도 git repo로 분리하는 구성에서는 `--harness` 플래그로 개별 실행을 권장합니다.
322
+ 각 하네스 repo 루트에 직접 출력하는 구성에서는 `--harness`와 `--target`을 함께 지정하세요.
364
323
 
365
324
  ### Q: plugin.json에서 오버라이드한 자산 경로가 sync 후 사라지나요?
366
325
 
@@ -374,8 +333,8 @@ Template 파일인 `plugin.json`은 `--force` 없이는 덮어쓰지 않습니
374
333
 
375
334
  빌드는 `mode: primary`인 에이전트를 전부 처리합니다. 단, 하네스별 동작이 다릅니다.
376
335
 
377
- - **Claude**: `dist/claude/settings.json`은 첫 번째 primary만 기록하고, 복수 발견 시 경고 log를 출력합니다.
378
- - **Codex**: `AGENTS.fragment.md`에 primary 에이전트를 순차 append합니다.
336
+ - **Claude**: `settings.json`은 첫 번째 primary만 기록하고, 복수 발견 시 경고 log를 출력합니다.
337
+ - **Codex**: `install/AGENTS.fragment.md`에 primary 에이전트를 순차 append합니다.
379
338
  - **OpenCode**: 해당 에이전트 TS 모듈 각각에 `mode: "primary"`를 설정합니다.
380
339
 
381
340
  ---
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bash
2
+ # Codex plugin installer — block-marker merge pattern.
3
+ # nexus-core Model 2: wrapper owns integration seams.
4
+ # Merges config.fragment.toml into ~/.codex/config.toml,
5
+ # copies native agent TOMLs to ~/.codex/agents/,
6
+ # and merges AGENTS.fragment.md into ~/.codex/AGENTS.md.
7
+ set -euo pipefail
8
+
9
+ PLUGIN_NAME="${PLUGIN_NAME:-codex-nexus}"
10
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
13
+
14
+ mkdir -p "$CODEX_HOME" "$CODEX_HOME/agents" "$CODEX_HOME/plugins"
15
+
16
+ # 1. config.toml — block-marker merge
17
+ MARKER_BEGIN="# BEGIN ${PLUGIN_NAME}"
18
+ MARKER_END="# END ${PLUGIN_NAME}"
19
+ CONFIG="$CODEX_HOME/config.toml"
20
+ CONFIG_FRAGMENT="$SCRIPT_DIR/config.fragment.toml"
21
+
22
+ touch "$CONFIG"
23
+ if grep -q "${MARKER_BEGIN}" "$CONFIG" 2>/dev/null; then
24
+ sed -i.bak "/${MARKER_BEGIN}/,/${MARKER_END}/d" "$CONFIG"
25
+ fi
26
+ {
27
+ echo ""
28
+ echo "${MARKER_BEGIN}"
29
+ cat "$CONFIG_FRAGMENT"
30
+ echo "${MARKER_END}"
31
+ } >> "$CONFIG"
32
+
33
+ # 2. native agent TOMLs
34
+ cp "$REPO_ROOT"/agents/*.toml "$CODEX_HOME/agents/" 2>/dev/null || true
35
+
36
+ # 3. plugin body → ~/.codex/plugins/<name>/
37
+ PLUGIN_DEST="$CODEX_HOME/plugins/${PLUGIN_NAME}"
38
+ rm -rf "$PLUGIN_DEST"
39
+ mkdir -p "$PLUGIN_DEST"
40
+ cp -R "$REPO_ROOT"/plugin/. "$PLUGIN_DEST/"
41
+
42
+ # 4. AGENTS.md — block-marker merge
43
+ AGENTS_TARGET="$CODEX_HOME/AGENTS.md"
44
+ AGENTS_FRAGMENT="$SCRIPT_DIR/AGENTS.fragment.md"
45
+ FRAG_BEGIN="<!-- nexus-core:lead:start -->"
46
+ FRAG_END="<!-- nexus-core:lead:end -->"
47
+
48
+ if [ -f "$AGENTS_FRAGMENT" ]; then
49
+ touch "$AGENTS_TARGET"
50
+ if grep -q "${FRAG_BEGIN}" "$AGENTS_TARGET" 2>/dev/null; then
51
+ awk -v begin="${FRAG_BEGIN}" -v end="${FRAG_END}" '
52
+ $0 ~ begin { skip=1; next }
53
+ $0 ~ end { skip=0; next }
54
+ !skip { print }
55
+ ' "$AGENTS_TARGET" > "$AGENTS_TARGET.tmp" && mv "$AGENTS_TARGET.tmp" "$AGENTS_TARGET"
56
+ fi
57
+ cat "$AGENTS_FRAGMENT" >> "$AGENTS_TARGET"
58
+ fi
59
+
60
+ echo "Installed ${PLUGIN_NAME} → $CODEX_HOME"
@@ -0,0 +1,6 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ import { mountHooks } from "@moreih29/nexus-core/hooks/opencode-mount";
3
+ import manifest from "@moreih29/nexus-core/hooks/opencode-manifest" with { type: "json" };
4
+
5
+ export const OpencodeNexus: Plugin = async (ctx) => mountHooks(ctx, manifest);
6
+ export default OpencodeNexus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moreih29/nexus-core",
3
- "version": "0.14.1",
3
+ "version": "0.15.1",
4
4
  "description": "Nexus ecosystem authoring layer — canonical agents/skills/hooks and 3-harness build pipeline (Claude Code, OpenCode, Codex)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,6 +18,10 @@
18
18
  "types": "./dist/src/mcp/server.d.ts",
19
19
  "import": "./dist/src/mcp/server.js"
20
20
  },
21
+ "./types": {
22
+ "types": "./dist/src/types/index.d.ts",
23
+ "import": "./dist/src/types/index.js"
24
+ },
21
25
  "./hooks/opencode-mount": {
22
26
  "types": "./dist/src/hooks/opencode-mount.d.ts",
23
27
  "import": "./dist/src/hooks/opencode-mount.js"
@@ -1,55 +0,0 @@
1
- // src/shared/json-store.js
2
- import { constants as fsConstants, appendFileSync, mkdirSync } from "node:fs";
3
- import path from "node:path";
4
- var inProcessQueues = new Map;
5
- var APPEND_SIZE_WARN_THRESHOLD = 4 * 1024;
6
- function appendJsonLine(filePath, record) {
7
- const line = JSON.stringify(record) + `
8
- `;
9
- if (line.length > APPEND_SIZE_WARN_THRESHOLD) {
10
- console.error(`[json-store] appendJsonLine line exceeds ${APPEND_SIZE_WARN_THRESHOLD} bytes ` + `(${line.length}) — write may not be atomic on some filesystems. path=${filePath}`);
11
- }
12
- mkdirSync(path.dirname(filePath), { recursive: true });
13
- appendFileSync(filePath, line);
14
- }
15
-
16
- // assets/hooks/post-tool-telemetry/handler.ts
17
- import { join, resolve, relative } from "node:path";
18
- var EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "ApplyPatch", "NotebookEdit"]);
19
- function isWithinMemory(filePath, projectRoot) {
20
- const memRoot = resolve(projectRoot, ".nexus/memory");
21
- const abs = resolve(filePath);
22
- return abs.startsWith(memRoot + "/") || abs === memRoot;
23
- }
24
- var handler = async (input) => {
25
- if (input.hook_event_name !== "PostToolUse")
26
- return;
27
- const { cwd, session_id, tool_name, agent_id } = input;
28
- const toolInput = input.tool_input ?? {};
29
- if (tool_name === "Read") {
30
- const filePath = toolInput.file_path;
31
- if (filePath && isWithinMemory(filePath, cwd)) {
32
- appendJsonLine(join(cwd, ".nexus/memory-access.jsonl"), {
33
- path: relative(cwd, resolve(filePath)),
34
- accessed_at: new Date().toISOString(),
35
- agent: agent_id ?? null
36
- });
37
- }
38
- }
39
- if (EDIT_TOOLS.has(tool_name) && agent_id) {
40
- const filePath = toolInput.file_path ?? toolInput.notebook_path;
41
- if (filePath) {
42
- appendJsonLine(join(cwd, ".nexus/state", session_id, "tool-log.jsonl"), {
43
- ts: new Date().toISOString(),
44
- agent_id,
45
- tool: tool_name,
46
- file: relative(cwd, resolve(filePath)),
47
- status: "ok"
48
- });
49
- }
50
- }
51
- };
52
- var handler_default = handler;
53
- export {
54
- handler_default as default
55
- };