makdoong2-team 2.1.0 → 2.2.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/README.md +48 -3
- package/bin/cli.js +0 -1
- package/bin/cli.ts +0 -1
- package/package.json +6 -6
- package/postinstall.mjs +0 -2
- package/scripts/install-lib.mjs +0 -2
- package/scripts/install-lib.mts +0 -2
- package/scripts/model-policy.mjs +0 -2
- package/scripts/model-policy.mts +0 -2
- package/scripts/run-tests.mts +2 -4
- package/scripts/smoke-test.mts +0 -1
- package/scripts/test-postinstall.mts +0 -1
- package/scripts/run-tests.mjs +0 -155
- package/scripts/smoke-test.mjs +0 -178
- package/scripts/test-postinstall.mjs +0 -144
package/README.md
CHANGED
|
@@ -250,7 +250,52 @@ Resolved chain (primary → fallbacks):
|
|
|
250
250
|
|
|
251
251
|
---
|
|
252
252
|
|
|
253
|
-
## 5.
|
|
253
|
+
## 5. 개발 환경과 빌드
|
|
254
|
+
|
|
255
|
+
이 저장소에는 **소스 파일만 커밋한다.** 손으로 쓴 JavaScript 도, 커밋된 빌드
|
|
256
|
+
산출물도 없다 — 구현은 TypeScript(`.ts`/`.mts`)와 셸(`.sh`)이고, `.js`/`.mjs`
|
|
257
|
+
와 `dist/` 는 소스에서 생성되며 `.gitignore` 대상이다.
|
|
258
|
+
|
|
259
|
+
### 요구 도구
|
|
260
|
+
|
|
261
|
+
| 도구 | 버전 | 용도 |
|
|
262
|
+
|---|---|---|
|
|
263
|
+
| **Node.js** | **≥ 22.18** | 진입점·테스트 실행 (네이티브 type-stripping). 클라이언트 환경은 node 24. |
|
|
264
|
+
| npm | Node 동봉 | 스크립트·의존성 |
|
|
265
|
+
| TypeScript | `devDependencies` 고정 (5.9.x) | 플러그인·진입점 빌드, 타입 검사 |
|
|
266
|
+
| jq, git | 시스템 | 런타임(state.sh·게이트) |
|
|
267
|
+
| tmux | ≥ 3.0 | 서브에이전트 pane (선택) |
|
|
268
|
+
| docker | 선택 | 비-Linux 호스트의 Ubuntu 교차 검증 |
|
|
269
|
+
|
|
270
|
+
> **왜 node ≥ 22.18 인가.** 개발 중에는 CLI(`bin/cli.ts`)·`postinstall.mts`·
|
|
271
|
+
> 테스트(`test/*.test.ts`)를 컴파일하지 않고 `node`가 확장자를 보고 타입을 지워
|
|
272
|
+
> 소스를 바로 실행한다(22.18/23.6부터 기본 활성). 배포된 패키지는 `node_modules`
|
|
273
|
+
> 안에서 실행되는데 node 는 거기서는 type-stripping 을 하지 않으므로, `bin`·
|
|
274
|
+
> `postinstall`은 배포 시 빌드된 `.js`가 실행된다. 플러그인 런타임은 opencode(Bun)가
|
|
275
|
+
> `dist/`를 로드하므로 이 하한과 무관하다.
|
|
276
|
+
|
|
277
|
+
### 빌드 — 개발엔 불필요, 배포 시 자동
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
npm run build # src/** → dist/** (opencode 플러그인. tsc)
|
|
281
|
+
npm run build:entry # 진입점 4개 → bin/cli.js · postinstall.mjs · scripts/{install-lib,model-policy}.mjs
|
|
282
|
+
npm run typecheck # 타입 검사만 (noEmit)
|
|
283
|
+
npm run clean # dist/ 제거
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**개발 중에는 빌드가 필요 없다** — 소스를 직접 실행한다:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
node bin/cli.ts doctor
|
|
290
|
+
node scripts/run-tests.mts
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
빌드 산출물은 커밋하지 않는다. 배포(`npm publish`) 시 `prepack`이 `build` +
|
|
294
|
+
`build:entry`를 돌려 tarball에만 담는다. 근거·경계는 ARCHITECTURE.md §12.6.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 6. 테스트
|
|
254
299
|
|
|
255
300
|
```bash
|
|
256
301
|
npm test # 전체
|
|
@@ -267,7 +312,7 @@ MAKDOONG2_SKIP_LINUX_CHECK=1 git push # 그 push 는 Linux 검증 없이 나
|
|
|
267
312
|
|
|
268
313
|
---
|
|
269
314
|
|
|
270
|
-
##
|
|
315
|
+
## 7. 배포 (Maintainer)
|
|
271
316
|
|
|
272
317
|
두 경로 모두 **승인 게이트 2회**를 통과해야 배포된다.
|
|
273
318
|
|
|
@@ -289,7 +334,7 @@ npm run release:major # 1.3.1 → 2.0.0 (breaking)
|
|
|
289
334
|
|
|
290
335
|
---
|
|
291
336
|
|
|
292
|
-
##
|
|
337
|
+
## 8. 문서
|
|
293
338
|
|
|
294
339
|
| 문서 | 내용 |
|
|
295
340
|
|---|---|
|
package/bin/cli.js
CHANGED
package/bin/cli.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makdoong2-team",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -65,21 +65,21 @@
|
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsc -p tsconfig.build.json",
|
|
68
|
-
"build:entry": "tsc -p tsconfig.entry.json && node -e \"import('node:fs').then(({chmodSync})=>{for(const f of ['bin/cli.js','postinstall.mjs','scripts/run-tests.mjs','scripts/test-postinstall.mjs']) chmodSync(f, 0o755)})\"",
|
|
69
68
|
"typecheck": "tsc -p tsconfig.json",
|
|
70
69
|
"clean": "node -e \"import('node:fs').then(({rmSync})=>rmSync('dist',{recursive:true,force:true}))\"",
|
|
71
|
-
"prepack": "npm run clean && npm run build &&
|
|
70
|
+
"prepack": "npm run clean && npm run build && npm run build:entry",
|
|
72
71
|
"postinstall": "node postinstall.mjs",
|
|
73
72
|
"prepare": "husky install",
|
|
74
73
|
"test": "XDG_CONFIG_HOME=\"$(mktemp -d)\" XDG_CACHE_HOME=\"$(mktemp -d)\" npm run test:all",
|
|
75
|
-
"test:all": "npm run build && node scripts/run-tests.
|
|
74
|
+
"test:all": "npm run build && node scripts/run-tests.mts",
|
|
76
75
|
"test:install": "node test/install-lib.test.ts",
|
|
77
|
-
"test:postinstall": "node scripts/test-postinstall.
|
|
76
|
+
"test:postinstall": "node scripts/test-postinstall.mts",
|
|
78
77
|
"test:ubuntu": "bash scripts/test-ubuntu.sh",
|
|
79
78
|
"release": "bash scripts/release.sh",
|
|
80
79
|
"release:patch": "bash scripts/release.sh patch",
|
|
81
80
|
"release:minor": "bash scripts/release.sh minor",
|
|
82
|
-
"release:major": "bash scripts/release.sh major"
|
|
81
|
+
"release:major": "bash scripts/release.sh major",
|
|
82
|
+
"build:entry": "tsc -p tsconfig.entry.json && node -e \"import('node:fs').then(({chmodSync})=>{chmodSync('bin/cli.js',0o755)})\""
|
|
83
83
|
},
|
|
84
84
|
"homepage": "https://github.com/y00njinuk/makdoong2-team#readme",
|
|
85
85
|
"bugs": {
|
package/postinstall.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// postinstall.mts — Auto-install on npm install -g
|
|
3
3
|
//
|
|
4
|
-
// 산출물은 같은 자리의 postinstall.mjs 다 (`npm run build:entry`). 그 .mjs 를
|
|
5
|
-
// 직접 편집하지 말 것 — 다음 빌드가 덮어쓴다.
|
|
6
4
|
//
|
|
7
5
|
// **이 파일은 반드시 패키지 루트에 있어야 한다.** pkgRoot = 자기 디렉토리
|
|
8
6
|
// (`..` 없음)가 계약이라, 한 단계라도 깊어지면 agents/skills 복사가 ENOENT 로
|
package/scripts/install-lib.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// scripts/install-lib.mts — Reusable install logic for CLI and postinstall.
|
|
2
2
|
//
|
|
3
|
-
// 산출물은 같은 자리의 scripts/install-lib.mjs 다 (`npm run build:entry`).
|
|
4
|
-
// 그 .mjs 를 직접 편집하지 말 것 — 다음 빌드가 덮어쓴다.
|
|
5
3
|
//
|
|
6
4
|
// Extracted from bin/cli doInstall() to enable both:
|
|
7
5
|
// 1) Manual install via `makdoong2-team install` (CLI)
|
package/scripts/install-lib.mts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// scripts/install-lib.mts — Reusable install logic for CLI and postinstall.
|
|
2
2
|
//
|
|
3
|
-
// 산출물은 같은 자리의 scripts/install-lib.mjs 다 (`npm run build:entry`).
|
|
4
|
-
// 그 .mjs 를 직접 편집하지 말 것 — 다음 빌드가 덮어쓴다.
|
|
5
3
|
//
|
|
6
4
|
// Extracted from bin/cli doInstall() to enable both:
|
|
7
5
|
// 1) Manual install via `makdoong2-team install` (CLI)
|
package/scripts/model-policy.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// model-policy.mts — pure mirror of src/model-fallback-policy.ts.
|
|
2
2
|
//
|
|
3
|
-
// 산출물은 같은 자리의 scripts/model-policy.mjs 다 (`npm run build:entry`).
|
|
4
|
-
// 그 .mjs 를 직접 편집하지 말 것 — 다음 빌드가 덮어쓴다.
|
|
5
3
|
//
|
|
6
4
|
// 왜 미러인가: bin/cli.js 는 dist/ 없이 돌아야 한다. doctor/validate 는 설치가
|
|
7
5
|
// 깨졌을 때 실행하는 진단 도구인데, dist/ 를 import 하면 (a) .gitignore 대상이라
|
package/scripts/model-policy.mts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// model-policy.mts — pure mirror of src/model-fallback-policy.ts.
|
|
2
2
|
//
|
|
3
|
-
// 산출물은 같은 자리의 scripts/model-policy.mjs 다 (`npm run build:entry`).
|
|
4
|
-
// 그 .mjs 를 직접 편집하지 말 것 — 다음 빌드가 덮어쓴다.
|
|
5
3
|
//
|
|
6
4
|
// 왜 미러인가: bin/cli.js 는 dist/ 없이 돌아야 한다. doctor/validate 는 설치가
|
|
7
5
|
// 깨졌을 때 실행하는 진단 도구인데, dist/ 를 import 하면 (a) .gitignore 대상이라
|
package/scripts/run-tests.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// scripts/run-tests.mts — 전체 테스트를 순차 실행하고 실패를 모아서 보고한다.
|
|
3
3
|
//
|
|
4
|
-
// 산출물은 같은 자리의 scripts/run-tests.mjs 다 (`npm run build:entry`).
|
|
5
4
|
// **새 테스트를 등록할 때는 이 .mts 의 STEPS 를 고친다** — .mjs 는 산출물이라
|
|
6
5
|
// 다음 빌드에 날아간다.
|
|
7
6
|
//
|
|
@@ -37,7 +36,7 @@ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
37
36
|
const STEPS = [
|
|
38
37
|
"bash scripts/lint-agent-prompts.sh",
|
|
39
38
|
"node --test test/shell-portability.test.ts",
|
|
40
|
-
"node scripts/smoke-test.
|
|
39
|
+
"node scripts/smoke-test.mts",
|
|
41
40
|
"bash scripts/gate-policy-test.sh",
|
|
42
41
|
"node test/install-lib.test.ts",
|
|
43
42
|
"node test/skill-mcp-registry.test.ts",
|
|
@@ -83,7 +82,6 @@ const STEPS = [
|
|
|
83
82
|
"node --test test/doctor-exit-code.test.ts",
|
|
84
83
|
"node --test test/example-config-portability.test.ts",
|
|
85
84
|
"node --test test/research-fanout.test.ts",
|
|
86
|
-
"node --test test/entry-artifacts.test.ts",
|
|
87
85
|
"node --test test/state-sh-write-atomicity.test.ts",
|
|
88
86
|
"node --test test/gate-locale-and-path.test.ts",
|
|
89
87
|
"node --test test/model-policy-parity.test.ts",
|
|
@@ -96,7 +94,7 @@ const STEPS = [
|
|
|
96
94
|
"node --test test/sync-state-commit-detection.test.ts",
|
|
97
95
|
"node --test test/skill-tls-config.test.ts",
|
|
98
96
|
"node --test test/opencode-json-read.test.ts",
|
|
99
|
-
"node scripts/test-postinstall.
|
|
97
|
+
"node scripts/test-postinstall.mts"
|
|
100
98
|
];
|
|
101
99
|
|
|
102
100
|
const CYAN = "\u001b[36m", GREEN = "\u001b[32m", RED = "\u001b[31m", YELLOW = "\u001b[33m", OFF = "\u001b[0m";
|
package/scripts/smoke-test.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// smoke-test.mts — smoke test for the model-fallback-policy chain logic.
|
|
2
2
|
//
|
|
3
|
-
// 산출물은 같은 자리의 scripts/smoke-test.mjs 다 (`npm run build:entry`).
|
|
4
3
|
//
|
|
5
4
|
// 주의: 이 파일은 **미러(scripts/model-policy.mts)만** 검증한다. 정본
|
|
6
5
|
// src/model-fallback-policy.ts 와의 동치는 test/model-policy-parity.test.ts 가
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// scripts/test-postinstall.mts — integration test for npm install -g postinstall hook
|
|
3
3
|
//
|
|
4
|
-
// 산출물은 같은 자리의 scripts/test-postinstall.mjs 다 (`npm run build:entry`).
|
|
5
4
|
//
|
|
6
5
|
// Simulates global install in isolated HOME directory:
|
|
7
6
|
// 1) npm pack → produces tarball
|
package/scripts/run-tests.mjs
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/run-tests.mts — 전체 테스트를 순차 실행하고 실패를 모아서 보고한다.
|
|
3
|
-
//
|
|
4
|
-
// 산출물은 같은 자리의 scripts/run-tests.mjs 다 (`npm run build:entry`).
|
|
5
|
-
// **새 테스트를 등록할 때는 이 .mts 의 STEPS 를 고친다** — .mjs 는 산출물이라
|
|
6
|
-
// 다음 빌드에 날아간다.
|
|
7
|
-
//
|
|
8
|
-
// 왜 `&&` 체인을 대체하는가:
|
|
9
|
-
// 기존 test:all 은 42개 단계를 `&&` 로 이어 붙였다. 앞 단계가 하나라도 실패하면
|
|
10
|
-
// 뒤 단계는 아예 실행되지 않는다. 실제로 tmux-monitor 실패가 그 뒤의
|
|
11
|
-
// rollback-commits / worktree-sync-gate 실패를 오래 가려왔고, gate-policy-test 가
|
|
12
|
-
// 깨져 있던 동안에는 사실상 전 구간이 미실행 상태였다.
|
|
13
|
-
//
|
|
14
|
-
// 순차 실행은 유지한다 (병렬화하지 않는다). npm test 가 XDG_CONFIG_HOME 을 단일
|
|
15
|
-
// 임시 디렉토리로 고정하므로, 설치 계열 테스트를 동시에 돌리면 서로의 config 를
|
|
16
|
-
// 덮어써 경합이 난다.
|
|
17
|
-
//
|
|
18
|
-
// 2단계 구성:
|
|
19
|
-
// 1) 호스트 단계 — STEPS 를 순차 실행한다.
|
|
20
|
-
// 2) Linux 교차 검증 — 호스트가 Linux 가 아니면 같은 스위트를 Ubuntu 컨테이너에서
|
|
21
|
-
// 한 번 더 돌린다. 별도 명령을 기억할 필요 없이 npm test 만으로 두 플랫폼이
|
|
22
|
-
// 함께 검증된다.
|
|
23
|
-
//
|
|
24
|
-
// 호스트 단계를 컨테이너 실행으로 "대체" 하지 않는 이유: 실제로 잡힌 결함 중
|
|
25
|
-
// 하나(rollback-commits.sh 의 unbraced 변수)는 Darwin libc 에서만 재현된다.
|
|
26
|
-
// 컨테이너로 갈아타면 그 부류가 영구히 은폐된다. 두 번 돌려야 둘 다 잡힌다.
|
|
27
|
-
//
|
|
28
|
-
// 종료 코드: 실패 단계가 하나라도 있으면 1.
|
|
29
|
-
import { spawnSync } from "node:child_process";
|
|
30
|
-
import { existsSync } from "node:fs";
|
|
31
|
-
import { dirname, join } from "node:path";
|
|
32
|
-
import { fileURLToPath } from "node:url";
|
|
33
|
-
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
34
|
-
const STEPS = [
|
|
35
|
-
"bash scripts/lint-agent-prompts.sh",
|
|
36
|
-
"node --test test/shell-portability.test.ts",
|
|
37
|
-
"node scripts/smoke-test.mjs",
|
|
38
|
-
"bash scripts/gate-policy-test.sh",
|
|
39
|
-
"node test/install-lib.test.ts",
|
|
40
|
-
"node test/skill-mcp-registry.test.ts",
|
|
41
|
-
"node --test test/state-write-guard.test.ts",
|
|
42
|
-
"node --test test/state-access-guard.test.ts",
|
|
43
|
-
"node --test test/git-exclude-registration.test.ts",
|
|
44
|
-
"node --test test/issue-reporter-guard.test.ts",
|
|
45
|
-
"node --test test/release-confirm.test.ts",
|
|
46
|
-
"node --test test/state-sh-schema.test.ts",
|
|
47
|
-
"node --test test/state-sh-init-review-shape.test.ts",
|
|
48
|
-
"node --test test/doctor-phantom-scan.test.ts",
|
|
49
|
-
"node --test test/mcp-secret-injector.test.ts",
|
|
50
|
-
"node --test test/poll-sub-session.test.ts",
|
|
51
|
-
"node --test test/tmux-monitor.test.ts",
|
|
52
|
-
"node --test test/gate-already-done-block.test.ts",
|
|
53
|
-
"node --test test/gate-hybrid-first-entry.test.ts",
|
|
54
|
-
"node --test test/gate-post-pr-verify.test.ts",
|
|
55
|
-
"node --test test/gate-post-review-verify.test.ts",
|
|
56
|
-
"node --test test/gate-requirements-quality.test.ts",
|
|
57
|
-
"node --test test/worktree-sync-gate.test.ts",
|
|
58
|
-
"node --test test/planner-prompt-early-exit.test.ts",
|
|
59
|
-
"node --test test/plugin-bug-fixes.test.ts",
|
|
60
|
-
"node --test test/omo-tmux-detection.test.ts",
|
|
61
|
-
"node --test test/empty-output-done-override.test.ts",
|
|
62
|
-
"node --test test/tmux-monitor-orphan.test.ts",
|
|
63
|
-
"node --test test/poll-permission-scope.test.ts",
|
|
64
|
-
"node --test test/dispatch-review-fixes.test.ts",
|
|
65
|
-
"node --test test/dispatch-stage-redispatch.test.ts",
|
|
66
|
-
"node --test test/dispatch-stage-dev-resume-prompt.test.ts",
|
|
67
|
-
"node --test test/logger.test.ts",
|
|
68
|
-
"node --test test/redact-secrets.test.ts",
|
|
69
|
-
"node --test test/commit-atomicity-verify.test.ts",
|
|
70
|
-
"node --test test/verdict-reason-injection.test.ts",
|
|
71
|
-
"node --test test/verdict-hash-normalize.test.ts",
|
|
72
|
-
"node --test test/session-index-fallback.test.ts",
|
|
73
|
-
"node --test test/plugin-exports-shape.test.ts",
|
|
74
|
-
"node --test test/stale-worktree-recovery.test.ts",
|
|
75
|
-
"node --test test/rollback-commits.test.ts",
|
|
76
|
-
"node --test test/config-dir-home-fallback.test.ts",
|
|
77
|
-
"node --test test/state-path-migration.test.ts",
|
|
78
|
-
"node --test test/state-path-relative.test.ts",
|
|
79
|
-
"node --test test/with-fallback-no-bun.test.ts",
|
|
80
|
-
"node --test test/doctor-exit-code.test.ts",
|
|
81
|
-
"node --test test/example-config-portability.test.ts",
|
|
82
|
-
"node --test test/research-fanout.test.ts",
|
|
83
|
-
"node --test test/entry-artifacts.test.ts",
|
|
84
|
-
"node --test test/state-sh-write-atomicity.test.ts",
|
|
85
|
-
"node --test test/gate-locale-and-path.test.ts",
|
|
86
|
-
"node --test test/model-policy-parity.test.ts",
|
|
87
|
-
// npm install -g 전 구간(pack → 전역 설치 → postinstall → doctor)을 검증하는
|
|
88
|
-
// 유일한 테스트다. 종전에는 STEPS 에 없어서 `npm test` 가 한 번도 실행하지
|
|
89
|
-
// 않았고, 그 사이 내용이 현행 구현과 반대로 썩어 있었다 (plugin ref 형식,
|
|
90
|
-
// secrets.env 처리). 등록하지 않으면 같은 일이 반복된다.
|
|
91
|
-
"node --test test/agent-permission-keys.test.ts",
|
|
92
|
-
"node --test test/gate-missing-state-sentinel.test.ts",
|
|
93
|
-
"node --test test/sync-state-commit-detection.test.ts",
|
|
94
|
-
"node --test test/skill-tls-config.test.ts",
|
|
95
|
-
"node --test test/opencode-json-read.test.ts",
|
|
96
|
-
"node scripts/test-postinstall.mjs"
|
|
97
|
-
];
|
|
98
|
-
const CYAN = "\u001b[36m", GREEN = "\u001b[32m", RED = "\u001b[31m", YELLOW = "\u001b[33m", OFF = "\u001b[0m";
|
|
99
|
-
const say = (s) => { process.stdout.write(s); };
|
|
100
|
-
// ── 1단계: 호스트 ──
|
|
101
|
-
const results = [];
|
|
102
|
-
for (const [i, step] of STEPS.entries()) {
|
|
103
|
-
const [cmd, ...args] = step.split(" ");
|
|
104
|
-
say(`\n${CYAN}[${String(i + 1).padStart(2)}/${STEPS.length}]${OFF} ${step}\n`);
|
|
105
|
-
const r = spawnSync(cmd, args, { stdio: "inherit" });
|
|
106
|
-
results.push({ step, code: r.status === null ? 1 : r.status });
|
|
107
|
-
}
|
|
108
|
-
// ── 2단계: Linux 교차 검증 ──
|
|
109
|
-
/**
|
|
110
|
-
* 컨테이너 교차 검증을 돌려야 하는지 판단한다.
|
|
111
|
-
* @returns {{run: true} | {run: false, reason: string, loud: boolean}}
|
|
112
|
-
*/
|
|
113
|
-
function linuxCheckPlan() {
|
|
114
|
-
// 컨테이너 안에서 다시 컨테이너를 띄우지 않는다 (무한 재귀 방지).
|
|
115
|
-
if (process.env.MAKDOONG2_IN_TEST_CONTAINER === "1") {
|
|
116
|
-
return { run: false, reason: "이미 테스트 컨테이너 안", loud: false };
|
|
117
|
-
}
|
|
118
|
-
// 호스트가 Linux 면 방금 돌린 것이 곧 Linux 결과다.
|
|
119
|
-
if (process.platform === "linux") {
|
|
120
|
-
return { run: false, reason: "호스트가 이미 Linux", loud: false };
|
|
121
|
-
}
|
|
122
|
-
if (process.env.MAKDOONG2_SKIP_LINUX_CHECK === "1") {
|
|
123
|
-
return { run: false, reason: "MAKDOONG2_SKIP_LINUX_CHECK=1", loud: true };
|
|
124
|
-
}
|
|
125
|
-
if (!existsSync(join(REPO_ROOT, "Dockerfile.test"))) {
|
|
126
|
-
return { run: false, reason: "Dockerfile.test 없음 (배포 tarball 등)", loud: false };
|
|
127
|
-
}
|
|
128
|
-
if (spawnSync("docker", ["--version"], { stdio: "ignore" }).status !== 0) {
|
|
129
|
-
return { run: false, reason: "docker 없음 — Linux 전용 회귀를 놓칠 수 있다", loud: true };
|
|
130
|
-
}
|
|
131
|
-
return { run: true };
|
|
132
|
-
}
|
|
133
|
-
const plan = linuxCheckPlan();
|
|
134
|
-
let linuxResult = null;
|
|
135
|
-
if (plan.run) {
|
|
136
|
-
say(`\n${CYAN}[linux]${OFF} Ubuntu 컨테이너에서 교차 검증 (${process.platform} 호스트)\n`);
|
|
137
|
-
const r = spawnSync("bash", [join(REPO_ROOT, "scripts", "test-ubuntu.sh")], { stdio: "inherit" });
|
|
138
|
-
linuxResult = { step: "linux 교차 검증 (scripts/test-ubuntu.sh)", code: r.status === null ? 1 : r.status };
|
|
139
|
-
results.push(linuxResult);
|
|
140
|
-
}
|
|
141
|
-
else if (plan.loud) {
|
|
142
|
-
say(`\n${YELLOW}[linux] 교차 검증 건너뜀 — ${plan.reason}${OFF}\n`);
|
|
143
|
-
}
|
|
144
|
-
// ── 요약 ──
|
|
145
|
-
const failed = results.filter((r) => r.code !== 0);
|
|
146
|
-
say(`\n${"=".repeat(70)}\n`);
|
|
147
|
-
if (failed.length === 0) {
|
|
148
|
-
const suffix = linuxResult ? " (호스트 + Ubuntu 교차 검증)" : "";
|
|
149
|
-
say(`${GREEN}[run-tests] ${results.length}개 단계 전부 통과${suffix}${OFF}\n`);
|
|
150
|
-
process.exit(0);
|
|
151
|
-
}
|
|
152
|
-
say(`${RED}[run-tests] ${failed.length}/${results.length} 단계 실패:${OFF}\n`);
|
|
153
|
-
for (const f of failed)
|
|
154
|
-
say(` ${RED}\u2716${OFF} ${f.step} (exit ${f.code})\n`);
|
|
155
|
-
process.exit(1);
|
package/scripts/smoke-test.mjs
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
// smoke-test.mts — smoke test for the model-fallback-policy chain logic.
|
|
2
|
-
//
|
|
3
|
-
// 산출물은 같은 자리의 scripts/smoke-test.mjs 다 (`npm run build:entry`).
|
|
4
|
-
//
|
|
5
|
-
// 주의: 이 파일은 **미러(scripts/model-policy.mts)만** 검증한다. 정본
|
|
6
|
-
// src/model-fallback-policy.ts 와의 동치는 test/model-policy-parity.test.ts 가
|
|
7
|
-
// 따로 강제한다 — 예전 주석은 이 파일이 정본까지 검증한다고 적었으나 거짓이었다.
|
|
8
|
-
//
|
|
9
|
-
// Run: node scripts/smoke-test.mjs
|
|
10
|
-
// Exits 0 on all-pass, 1 on any failure.
|
|
11
|
-
import { DEFAULT_ALLOWED_PRIMARIES, DEFAULT_POLICIES, nextModel, validatePolicies, buildPoliciesFromConfig, } from "./model-policy.mjs";
|
|
12
|
-
let passed = 0, failed = 0;
|
|
13
|
-
const t = (name, fn) => {
|
|
14
|
-
try {
|
|
15
|
-
fn();
|
|
16
|
-
console.log(` ✓ ${name}`);
|
|
17
|
-
passed++;
|
|
18
|
-
}
|
|
19
|
-
catch (e) {
|
|
20
|
-
console.error(` ✗ ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
21
|
-
failed++;
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
const eq = (a, b, msg) => {
|
|
25
|
-
if (JSON.stringify(a) !== JSON.stringify(b)) {
|
|
26
|
-
throw new Error(`${msg}\n expected: ${JSON.stringify(b)}\n got: ${JSON.stringify(a)}`);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
const assertThrows = (fn, matcher, msg) => {
|
|
30
|
-
let thrown = null;
|
|
31
|
-
try {
|
|
32
|
-
fn();
|
|
33
|
-
}
|
|
34
|
-
catch (e) {
|
|
35
|
-
thrown = e;
|
|
36
|
-
}
|
|
37
|
-
if (!thrown)
|
|
38
|
-
throw new Error(`${msg}: expected throw, got none`);
|
|
39
|
-
const message = thrown instanceof Error ? thrown.message : String(thrown);
|
|
40
|
-
if (matcher && !matcher.test(message)) {
|
|
41
|
-
throw new Error(`${msg}: message did not match ${matcher}\n got: ${message}`);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
/** `next` 가 null 이 아님을 단언하고 좁혀서 돌려준다. */
|
|
45
|
-
const expectNext = (r) => {
|
|
46
|
-
if (!r.next)
|
|
47
|
-
throw new Error("expected a next model, got null (chain exhausted)");
|
|
48
|
-
return r.next;
|
|
49
|
-
};
|
|
50
|
-
console.log("\n[smoke-test] model-fallback-policy chain logic\n");
|
|
51
|
-
// ── Default chain semantics ──
|
|
52
|
-
t("makdoong2-team-leader: gpt-5.6-luna → haiku on first failure", () => {
|
|
53
|
-
const r = nextModel({ agent: "makdoong2-team-leader", current: "github-copilot/gpt-5.6-luna", reason: "rate_limit" });
|
|
54
|
-
eq(r.exhausted, false, "should not be exhausted");
|
|
55
|
-
eq(expectNext(r).id, "github-copilot/claude-haiku-4.5", "next should be haiku-4.5");
|
|
56
|
-
});
|
|
57
|
-
t("makdoong2-team-leader: haiku → exhausted after second failure", () => {
|
|
58
|
-
const r = nextModel({ agent: "makdoong2-team-leader", current: "github-copilot/claude-haiku-4.5" });
|
|
59
|
-
eq(r.exhausted, true, "should be exhausted");
|
|
60
|
-
eq(r.next, null, "next should be null");
|
|
61
|
-
});
|
|
62
|
-
t("makdoong2-planner: gpt-5.6-luna → haiku", () => {
|
|
63
|
-
const r = nextModel({ agent: "makdoong2-planner", current: "github-copilot/gpt-5.6-luna", reason: "5xx" });
|
|
64
|
-
eq(expectNext(r).id, "github-copilot/claude-haiku-4.5", "planner fallback should be haiku");
|
|
65
|
-
});
|
|
66
|
-
t("makdoong2-engineer: gpt-5.6-luna → haiku (lower-tier degrade)", () => {
|
|
67
|
-
const r = nextModel({ agent: "makdoong2-engineer", current: "github-copilot/gpt-5.6-luna", reason: "context_exceeded" });
|
|
68
|
-
eq(expectNext(r).id, "github-copilot/claude-haiku-4.5", "engineer fallback should be haiku");
|
|
69
|
-
});
|
|
70
|
-
t("makdoong2-publisher: gpt-5.6-luna → haiku", () => {
|
|
71
|
-
const r = nextModel({ agent: "makdoong2-publisher", current: "github-copilot/gpt-5.6-luna" });
|
|
72
|
-
eq(expectNext(r).id, "github-copilot/claude-haiku-4.5", "publisher fallback should be haiku");
|
|
73
|
-
});
|
|
74
|
-
t("makdoong2-verifier: gpt-5.6-luna → haiku", () => {
|
|
75
|
-
const r = nextModel({ agent: "makdoong2-verifier", current: "github-copilot/gpt-5.6-luna" });
|
|
76
|
-
eq(expectNext(r).id, "github-copilot/claude-haiku-4.5", "verifier fallback should be haiku");
|
|
77
|
-
});
|
|
78
|
-
t("unknown agent returns exhausted", () => {
|
|
79
|
-
const r = nextModel({ agent: "stage99-fictional", current: "any" });
|
|
80
|
-
eq(r.exhausted, true, "unknown agent should be exhausted");
|
|
81
|
-
});
|
|
82
|
-
t("unknown current model starts from primary", () => {
|
|
83
|
-
const r = nextModel({ agent: "makdoong2-team-leader", current: "anthropic/claude-fictional" });
|
|
84
|
-
eq(expectNext(r).id, "github-copilot/gpt-5.6-luna", "unknown current should start at primary");
|
|
85
|
-
});
|
|
86
|
-
t("all default primaries are in DEFAULT_ALLOWED_PRIMARIES", () => {
|
|
87
|
-
for (const [agent, policy] of Object.entries(DEFAULT_POLICIES)) {
|
|
88
|
-
if (!DEFAULT_ALLOWED_PRIMARIES.has(policy.primary.id)) {
|
|
89
|
-
throw new Error(`'${agent}' primary '${policy.primary.id}' is not allowed`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
t("all fallback tiers are strictly lower than primary tier", () => {
|
|
94
|
-
validatePolicies(DEFAULT_POLICIES, DEFAULT_ALLOWED_PRIMARIES);
|
|
95
|
-
});
|
|
96
|
-
// ── Config-driven overrides ──
|
|
97
|
-
t("agents block: primary + fallback override (string form, backward-compat)", () => {
|
|
98
|
-
const { policies } = buildPoliciesFromConfig({
|
|
99
|
-
agents: {
|
|
100
|
-
"makdoong2-engineer": {
|
|
101
|
-
model: "github-copilot/claude-sonnet-4.6",
|
|
102
|
-
fallback_models: ["github-copilot/claude-haiku-4.5"],
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
eq(policies["makdoong2-engineer"].primary.id, "github-copilot/claude-sonnet-4.6", "primary updated");
|
|
107
|
-
eq(policies["makdoong2-engineer"].fallbacks.map(f => f.id), ["github-copilot/claude-haiku-4.5"], "fallbacks set");
|
|
108
|
-
eq(policies["makdoong2-engineer"].fallbacks[0].tier, "low", "string-form fallback defaults to tier=low");
|
|
109
|
-
});
|
|
110
|
-
t("agents block: fallback object form {id, tier} accepted", () => {
|
|
111
|
-
const { policies } = buildPoliciesFromConfig({
|
|
112
|
-
agents: {
|
|
113
|
-
"makdoong2-planner": {
|
|
114
|
-
model: "github-copilot/claude-sonnet-4.6",
|
|
115
|
-
fallback_models: [{ id: "github-copilot/claude-haiku-4.5", tier: "low" }],
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
eq(policies["makdoong2-planner"].fallbacks[0], { id: "github-copilot/claude-haiku-4.5", tier: "low" }, "object-form preserved");
|
|
120
|
-
});
|
|
121
|
-
t("model_policy.allowed_primaries: extends default allow-list", () => {
|
|
122
|
-
const { policies, allowed } = buildPoliciesFromConfig({
|
|
123
|
-
model_policy: { allowed_primaries: ["custom-provider/custom-model"] },
|
|
124
|
-
agents: {
|
|
125
|
-
"makdoong2-engineer": {
|
|
126
|
-
model: "custom-provider/custom-model",
|
|
127
|
-
fallback_models: ["github-copilot/claude-haiku-4.5"],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
eq(allowed.has("custom-provider/custom-model"), true, "extra primary allowed");
|
|
132
|
-
eq(allowed.has("local/qwen3.6-27b"), true, "built-in defaults preserved");
|
|
133
|
-
eq(policies["makdoong2-engineer"].primary.id, "custom-provider/custom-model", "custom primary set");
|
|
134
|
-
});
|
|
135
|
-
t("validation rejects primary outside allow-list (no model_policy extension)", () => {
|
|
136
|
-
assertThrows(() => buildPoliciesFromConfig({
|
|
137
|
-
agents: { "makdoong2-engineer": { model: "rogue-provider/rogue-model" } },
|
|
138
|
-
}), /violates policy/, "should reject unlisted primary");
|
|
139
|
-
});
|
|
140
|
-
t("validation rejects fallback tier >= primary tier", () => {
|
|
141
|
-
assertThrows(() => buildPoliciesFromConfig({
|
|
142
|
-
agents: {
|
|
143
|
-
"makdoong2-engineer": {
|
|
144
|
-
model: "local/qwen3.6-27b",
|
|
145
|
-
fallback_models: [{ id: "github-copilot/claude-haiku-4.5", tier: "medium" }],
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
}), /not strictly lower/, "fallback at primary tier must be rejected");
|
|
149
|
-
});
|
|
150
|
-
t("validation rejects invalid tier label", () => {
|
|
151
|
-
assertThrows(() => buildPoliciesFromConfig({
|
|
152
|
-
agents: {
|
|
153
|
-
"makdoong2-engineer": {
|
|
154
|
-
model: "local/qwen3.6-27b",
|
|
155
|
-
// 의도적으로 잘못된 tier 라벨 — 런타임 검증이 잡아야 한다.
|
|
156
|
-
fallback_models: [{ id: "github-copilot/claude-haiku-4.5", tier: "ultra" }],
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
}), /not a valid tier/, "unknown tier must be rejected");
|
|
160
|
-
});
|
|
161
|
-
t("build is pure: invalid config throws but does not mutate DEFAULT_POLICIES", () => {
|
|
162
|
-
const before = JSON.stringify(DEFAULT_POLICIES);
|
|
163
|
-
try {
|
|
164
|
-
buildPoliciesFromConfig({
|
|
165
|
-
agents: { "makdoong2-engineer": { model: "rogue-provider/rogue-model" } },
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
catch { /* expected */ }
|
|
169
|
-
const after = JSON.stringify(DEFAULT_POLICIES);
|
|
170
|
-
eq(after, before, "DEFAULT_POLICIES must remain untouched after a failed override");
|
|
171
|
-
});
|
|
172
|
-
t("empty config returns defaults verbatim", () => {
|
|
173
|
-
const { policies, allowed } = buildPoliciesFromConfig({});
|
|
174
|
-
eq(Object.keys(policies).sort(), Object.keys(DEFAULT_POLICIES).sort(), "same agent set");
|
|
175
|
-
eq(allowed.size, DEFAULT_ALLOWED_PRIMARIES.size, "no extra primaries added");
|
|
176
|
-
});
|
|
177
|
-
console.log(`\n[smoke-test] ${passed} passed, ${failed} failed\n`);
|
|
178
|
-
process.exit(failed === 0 ? 0 : 1);
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/test-postinstall.mts — integration test for npm install -g postinstall hook
|
|
3
|
-
//
|
|
4
|
-
// 산출물은 같은 자리의 scripts/test-postinstall.mjs 다 (`npm run build:entry`).
|
|
5
|
-
//
|
|
6
|
-
// Simulates global install in isolated HOME directory:
|
|
7
|
-
// 1) npm pack → produces tarball
|
|
8
|
-
// 2) npm install -g <tarball> with isolated HOME
|
|
9
|
-
// 3) Verify deployment artifacts
|
|
10
|
-
// 4) Verify idempotency (re-install doesn't create duplicate backups)
|
|
11
|
-
// 5) Verify secrets.env preservation
|
|
12
|
-
import { mkdtempSync, rmSync, existsSync, writeFileSync, readFileSync, readdirSync } from "node:fs";
|
|
13
|
-
import { tmpdir, platform } from "node:os";
|
|
14
|
-
import { join, dirname } from "node:path";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
-
import { spawnSync } from "node:child_process";
|
|
17
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
const PKG_ROOT = join(HERE, "..");
|
|
19
|
-
function run(cmd, args, opts = {}) {
|
|
20
|
-
const result = spawnSync(cmd, args, { ...opts, stdio: "pipe", encoding: "utf8" });
|
|
21
|
-
if (result.error)
|
|
22
|
-
throw result.error;
|
|
23
|
-
return result;
|
|
24
|
-
}
|
|
25
|
-
function assert(cond, msg) {
|
|
26
|
-
if (!cond) {
|
|
27
|
-
console.error(`✗ FAIL: ${msg}`);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
console.log(`✓ ${msg}`);
|
|
31
|
-
}
|
|
32
|
-
console.log("[test-postinstall] Starting npm install -g smoke test");
|
|
33
|
-
// 1) npm pack
|
|
34
|
-
console.log("\n[1/6] Running npm pack...");
|
|
35
|
-
const packResult = run("npm", ["pack"], { cwd: PKG_ROOT });
|
|
36
|
-
if (packResult.status !== 0) {
|
|
37
|
-
console.error("npm pack failed:", packResult.stderr);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
const tarballLine = packResult.stdout.trim().split("\n").pop();
|
|
41
|
-
if (!tarballLine) {
|
|
42
|
-
console.error("npm pack produced no tarball name on stdout");
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
const tarball = join(PKG_ROOT, tarballLine);
|
|
46
|
-
assert(existsSync(tarball), `tarball created: ${tarball}`);
|
|
47
|
-
try {
|
|
48
|
-
// 2) Create isolated HOME
|
|
49
|
-
console.log("\n[2/6] Setting up isolated HOME...");
|
|
50
|
-
const tmpHome = mkdtempSync(join(tmpdir(), "mkd2-postinstall-"));
|
|
51
|
-
const npmPrefix = join(tmpHome, "npm");
|
|
52
|
-
const configDir = join(tmpHome, ".config", "opencode");
|
|
53
|
-
console.log(` tmpHome: ${tmpHome}`);
|
|
54
|
-
console.log(` npmPrefix: ${npmPrefix}`);
|
|
55
|
-
console.log(` configDir: ${configDir}`);
|
|
56
|
-
// 3) npm install -g with isolated env
|
|
57
|
-
console.log("\n[3/6] Running npm install -g (first install)...");
|
|
58
|
-
// 격리는 HOME 만으로 부족하다. resolveConfigDir() 은 XDG_CONFIG_HOME 을 HOME
|
|
59
|
-
// 보다 **먼저** 보고, seedOpencodeCache() 는 XDG_CACHE_HOME 을 본다. `npm test`
|
|
60
|
-
// 가 두 변수를 임시 디렉토리로 고정하므로, 상속하면 배포가 tmpHome 이 아니라
|
|
61
|
-
// 그 임시 디렉토리로 가서 "npm install -g 는 성공했는데 configDir 이 비어 있다"
|
|
62
|
-
// 가 된다 — 단독 실행(`npm run test:postinstall`)에서는 두 변수가 없어 통과하고
|
|
63
|
-
// 스위트 안에서만 실패하는 형태라 원인을 찾기 어렵다.
|
|
64
|
-
const installEnv = {
|
|
65
|
-
...process.env,
|
|
66
|
-
HOME: tmpHome,
|
|
67
|
-
XDG_CONFIG_HOME: join(tmpHome, ".config"),
|
|
68
|
-
XDG_CACHE_HOME: join(tmpHome, ".cache"),
|
|
69
|
-
npm_config_prefix: npmPrefix,
|
|
70
|
-
npm_config_global: "true",
|
|
71
|
-
CI: undefined,
|
|
72
|
-
CONTINUOUS_INTEGRATION: undefined,
|
|
73
|
-
};
|
|
74
|
-
const installResult = run("npm", ["install", "-g", tarball], { env: installEnv });
|
|
75
|
-
if (installResult.status !== 0) {
|
|
76
|
-
console.error("npm install -g failed:", installResult.stderr);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
console.log(" npm install -g completed");
|
|
80
|
-
// 4) Verify deployment artifacts
|
|
81
|
-
console.log("\n[4/6] Verifying deployment artifacts...");
|
|
82
|
-
// Binary shim
|
|
83
|
-
const isWin = platform() === "win32";
|
|
84
|
-
const binName = isWin ? "makdoong2-team.cmd" : "makdoong2-team";
|
|
85
|
-
const binPath = join(npmPrefix, "bin", binName);
|
|
86
|
-
assert(existsSync(binPath), `binary shim exists: ${binPath}`);
|
|
87
|
-
// Agents in configDir
|
|
88
|
-
const agentPath = join(configDir, "agents", "makdoong2-team-leader.md");
|
|
89
|
-
assert(existsSync(agentPath), `agent deployed to configDir: ${agentPath}`);
|
|
90
|
-
// Skills in configDir
|
|
91
|
-
const skillPath = join(configDir, "skills", "jira-research", "SKILL.md");
|
|
92
|
-
assert(existsSync(skillPath), `skill deployed to configDir: ${skillPath}`);
|
|
93
|
-
// opencode.json plugin ref (relative path to npm module)
|
|
94
|
-
const ocPath = join(configDir, "opencode.json");
|
|
95
|
-
assert(existsSync(ocPath), `opencode.json created: ${ocPath}`);
|
|
96
|
-
const oc = JSON.parse(readFileSync(ocPath, "utf8"));
|
|
97
|
-
// 플러그인 ref 는 **npm 패키지 이름**이다. 예전에는 src/opencode-plugin.ts 를
|
|
98
|
-
// 가리키는 절대 경로였고 이 단언도 그 시절 형태로 남아 있었다 — 즉 이 테스트는
|
|
99
|
-
// 지금 돌리면 실패한다. STEPS 에 등록돼 있지 않아 아무도 몰랐다.
|
|
100
|
-
// (install-lib 의 pluginRef() / isLegacyPluginEntry() 가 계약의 출처다.)
|
|
101
|
-
const PKG_NAME = "makdoong2-team";
|
|
102
|
-
const matchesRef = (v) => typeof v === "string" && (v === PKG_NAME || v.startsWith(`${PKG_NAME}@`));
|
|
103
|
-
assert(Array.isArray(oc.plugin) && oc.plugin.some((p) => matchesRef(p) || (Array.isArray(p) && matchesRef(p[0]))), `opencode.json has plugin ref (${PKG_NAME})`);
|
|
104
|
-
assert(!JSON.stringify(oc.plugin).includes("opencode-plugin.ts"), "opencode.json has no legacy path-based plugin ref");
|
|
105
|
-
assert(oc.tools?.verify_stage === true, "opencode.json has tools");
|
|
106
|
-
// makdoong2-team doctor
|
|
107
|
-
console.log("\n[5/6] Running makdoong2-team doctor...");
|
|
108
|
-
const doctorResult = run(binPath, ["doctor"], { env: installEnv });
|
|
109
|
-
assert(doctorResult.status === 0, "makdoong2-team doctor exits 0");
|
|
110
|
-
// 5) Re-install (idempotency test)
|
|
111
|
-
console.log("\n[6/6] Re-installing (idempotency check)...");
|
|
112
|
-
const beforeOcBackups = readdirSync(configDir).filter((f) => f.startsWith("opencode.json.bak."));
|
|
113
|
-
const reinstallResult = run("npm", ["install", "-g", tarball], { env: installEnv });
|
|
114
|
-
assert(reinstallResult.status === 0, "re-install succeeds");
|
|
115
|
-
const afterOcBackups = readdirSync(configDir).filter((f) => f.startsWith("opencode.json.bak."));
|
|
116
|
-
assert(afterOcBackups.length === beforeOcBackups.length, `no new opencode.json backups on re-install (idempotent): ${beforeOcBackups.length} → ${afterOcBackups.length}`);
|
|
117
|
-
// 6) legacy secrets.env cleanup
|
|
118
|
-
//
|
|
119
|
-
// 종전 이 블록은 "secrets.env 가 보존된다" 를 단언했다. 현행 install() 은 정반대로
|
|
120
|
-
// 동작한다 — 자격증명은 makdoong2-team.json .secrets.* 하나로 옮겨졌고, 남아 있는
|
|
121
|
-
// 레거시 파일은 백업 후 **삭제**하는 것이 계약이다 (install-lib 의
|
|
122
|
-
// cleanupLegacySecretsEnv). 규약이 뒤집혔는데 테스트가 따라오지 않았다.
|
|
123
|
-
console.log("\n[Bonus] Testing legacy secrets.env cleanup...");
|
|
124
|
-
const secretsPath = join(configDir, "skills", "jira-research", "secrets.env");
|
|
125
|
-
writeFileSync(secretsPath, "JIRA_TOKEN=test123");
|
|
126
|
-
const reinstall2Result = run("npm", ["install", "-g", tarball], { env: installEnv });
|
|
127
|
-
assert(reinstall2Result.status === 0, "third install succeeds");
|
|
128
|
-
assert(!existsSync(secretsPath), "legacy secrets.env removed on re-install");
|
|
129
|
-
const backups = readdirSync(join(configDir, "skills", "jira-research"))
|
|
130
|
-
.filter((f) => f.startsWith("secrets.env.bak."));
|
|
131
|
-
assert(backups.length >= 1, "legacy secrets.env backed up before removal");
|
|
132
|
-
// 자격증명의 새 자리가 실제로 만들어져 있어야 한다.
|
|
133
|
-
const cfg = JSON.parse(readFileSync(join(configDir, "makdoong2-team.json"), "utf8"));
|
|
134
|
-
assert(cfg.secrets && typeof cfg.secrets === "object", "makdoong2-team.json has .secrets scaffolding");
|
|
135
|
-
assert("JIRA_API_TOKEN" in cfg.secrets, "makdoong2-team.json .secrets has JIRA_API_TOKEN key");
|
|
136
|
-
console.log("\n✅ [test-postinstall] All checks passed!");
|
|
137
|
-
// Cleanup
|
|
138
|
-
rmSync(tmpHome, { recursive: true, force: true });
|
|
139
|
-
}
|
|
140
|
-
finally {
|
|
141
|
-
// Cleanup tarball
|
|
142
|
-
if (existsSync(tarball))
|
|
143
|
-
rmSync(tarball);
|
|
144
|
-
}
|