@team-semicolon/semo-cli 4.13.0 → 4.15.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/dist/commands/context.js +23 -3
- package/dist/commands/harness.d.ts +8 -0
- package/dist/commands/harness.js +412 -0
- package/dist/commands/incubator.d.ts +10 -0
- package/dist/commands/incubator.js +517 -0
- package/dist/commands/service.js +144 -3
- package/dist/commands/sessions.js +156 -0
- package/dist/commands/skill-sync.d.ts +2 -1
- package/dist/commands/skill-sync.js +88 -23
- package/dist/commands/skill-sync.test.js +78 -45
- package/dist/database.d.ts +2 -1
- package/dist/database.js +109 -27
- package/dist/global-cache.js +26 -14
- package/dist/index.js +576 -522
- package/dist/kb.d.ts +4 -4
- package/dist/kb.js +203 -103
- package/dist/semo-workspace.js +51 -0
- package/dist/service-migrate.d.ts +47 -2
- package/dist/service-migrate.js +188 -13
- package/dist/templates/harness/commit-msg +1 -0
- package/dist/templates/harness/commitlint.config.js +11 -0
- package/dist/templates/harness/eslint.config.mjs +17 -0
- package/dist/templates/harness/pr-quality-gate.yml +19 -0
- package/dist/templates/harness/pre-commit +1 -0
- package/dist/templates/harness/pre-push +1 -0
- package/dist/templates/harness/prettierignore +5 -0
- package/dist/templates/harness/prettierrc.json +7 -0
- package/package.json +8 -4
package/dist/commands/context.js
CHANGED
|
@@ -53,7 +53,8 @@ const path = __importStar(require("path"));
|
|
|
53
53
|
const os = __importStar(require("os"));
|
|
54
54
|
const database_1 = require("../database");
|
|
55
55
|
const kb_1 = require("../kb");
|
|
56
|
-
// [v4.
|
|
56
|
+
// [v4.7.0] syncSkillsToDB 복원 — 워크스페이스 → DB 동기화 경로 재활성화
|
|
57
|
+
const skill_sync_1 = require("./skill-sync");
|
|
57
58
|
const global_cache_1 = require("../global-cache");
|
|
58
59
|
const semo_workspace_1 = require("../semo-workspace");
|
|
59
60
|
// ============================================================
|
|
@@ -211,8 +212,27 @@ function registerContextCommands(program) {
|
|
|
211
212
|
// [v4.2.0] KB→md 파일 생성 제거 — semo CLI kb 명령어로 대체
|
|
212
213
|
// 기존 memory/*.md (team, projects, decisions, infra, process, bots, ontology) 파일은
|
|
213
214
|
// semo CLI가 실시간 DB 조회로 대체합니다.
|
|
214
|
-
// [v4.
|
|
215
|
-
//
|
|
215
|
+
// [v4.7.0] 워크스페이스 → DB 스킬 동기화 복원
|
|
216
|
+
// v4.4.0에서 제거했으나, 워크스페이스 스킬이 DB에 미반영되는 문제 발생.
|
|
217
|
+
// --no-skills 플래그로 스킵 가능.
|
|
218
|
+
if (options.skills !== false) {
|
|
219
|
+
spinner.text = "스킬 동기화 (워크스페이스 → DB)...";
|
|
220
|
+
try {
|
|
221
|
+
const client = await pool.connect();
|
|
222
|
+
try {
|
|
223
|
+
const skillResult = await (0, skill_sync_1.syncSkillsToDB)(client, pool);
|
|
224
|
+
if (skillResult.total > 0) {
|
|
225
|
+
console.log(chalk_1.default.green(` ✓ 스킬 DB 동기화: ${skillResult.total}개 스킬 upsert`));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
client.release();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (skillErr) {
|
|
233
|
+
console.log(chalk_1.default.yellow(` ⚠ 스킬 DB 동기화 실패 (비치명적): ${skillErr}`));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
216
236
|
// DB → 글로벌 캐시 (skills/commands/agents → ~/.claude/)
|
|
217
237
|
if (options.globalCache !== false) {
|
|
218
238
|
spinner.text = "글로벌 캐시 동기화 (skills/commands/agents)...";
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* semo harness — 프로젝트 하네스 관리
|
|
4
|
+
*
|
|
5
|
+
* 결정론적 하네스(Husky, ESLint, Prettier, commitlint)를
|
|
6
|
+
* 어떤 레포에서든 일관되게 적용·점검·갱신하는 CLI.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.registerHarnessCommands = registerHarnessCommands;
|
|
46
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
47
|
+
const ora_1 = __importDefault(require("ora"));
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
// ── 템플릿 경로 ──────────────────────────────────────────────
|
|
52
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates', 'harness');
|
|
53
|
+
const CHECKS = [
|
|
54
|
+
{
|
|
55
|
+
name: 'Husky installed (.husky/)',
|
|
56
|
+
check: (cwd) => fs.existsSync(path.join(cwd, '.husky')),
|
|
57
|
+
fix: 'npx husky init',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'pre-commit hook (lint-staged)',
|
|
61
|
+
check: (cwd) => {
|
|
62
|
+
const f = path.join(cwd, '.husky', 'pre-commit');
|
|
63
|
+
return fs.existsSync(f) && fs.readFileSync(f, 'utf8').includes('lint-staged');
|
|
64
|
+
},
|
|
65
|
+
fix: 'echo "npx lint-staged" > .husky/pre-commit',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'commit-msg hook (commitlint)',
|
|
69
|
+
check: (cwd) => {
|
|
70
|
+
const f = path.join(cwd, '.husky', 'commit-msg');
|
|
71
|
+
return fs.existsSync(f) && fs.readFileSync(f, 'utf8').includes('commitlint');
|
|
72
|
+
},
|
|
73
|
+
fix: 'echo "npx --no -- commitlint --edit \\$1" > .husky/commit-msg',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'pre-push hook (lint + tsc)',
|
|
77
|
+
check: (cwd) => {
|
|
78
|
+
const f = path.join(cwd, '.husky', 'pre-push');
|
|
79
|
+
return fs.existsSync(f) && fs.readFileSync(f, 'utf8').includes('lint');
|
|
80
|
+
},
|
|
81
|
+
fix: 'echo "npm run lint && npx tsc --noEmit" > .husky/pre-push',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Prettier configured (.prettierrc)',
|
|
85
|
+
check: (cwd) => fs.existsSync(path.join(cwd, '.prettierrc')) ||
|
|
86
|
+
fs.existsSync(path.join(cwd, '.prettierrc.json')) ||
|
|
87
|
+
fs.existsSync(path.join(cwd, 'prettier.config.js')),
|
|
88
|
+
fix: 'semo harness init',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'Prettier ignore (.prettierignore)',
|
|
92
|
+
check: (cwd) => fs.existsSync(path.join(cwd, '.prettierignore')),
|
|
93
|
+
fix: 'semo harness init',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'commitlint configured',
|
|
97
|
+
check: (cwd) => fs.existsSync(path.join(cwd, 'commitlint.config.js')) ||
|
|
98
|
+
fs.existsSync(path.join(cwd, 'commitlint.config.ts')) ||
|
|
99
|
+
fs.existsSync(path.join(cwd, '.commitlintrc.js')),
|
|
100
|
+
fix: 'semo harness init',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'ESLint configured',
|
|
104
|
+
check: (cwd) => {
|
|
105
|
+
// Root-level config
|
|
106
|
+
if (fs.existsSync(path.join(cwd, 'eslint.config.mjs')) ||
|
|
107
|
+
fs.existsSync(path.join(cwd, 'eslint.config.js')) ||
|
|
108
|
+
fs.existsSync(path.join(cwd, '.eslintrc.js')) ||
|
|
109
|
+
fs.existsSync(path.join(cwd, '.eslintrc.json')))
|
|
110
|
+
return true;
|
|
111
|
+
// Monorepo: check packages/*/eslint.config.*
|
|
112
|
+
const pkgsDir = path.join(cwd, 'packages');
|
|
113
|
+
if (fs.existsSync(pkgsDir)) {
|
|
114
|
+
const dirs = fs.readdirSync(pkgsDir, { withFileTypes: true });
|
|
115
|
+
return dirs.some((d) => d.isDirectory() &&
|
|
116
|
+
(fs.existsSync(path.join(pkgsDir, d.name, 'eslint.config.mjs')) ||
|
|
117
|
+
fs.existsSync(path.join(pkgsDir, d.name, 'eslint.config.js'))));
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
},
|
|
121
|
+
fix: 'semo harness init',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'lint-staged configured',
|
|
125
|
+
check: (cwd) => {
|
|
126
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
127
|
+
if (!fs.existsSync(pkgPath))
|
|
128
|
+
return false;
|
|
129
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
130
|
+
return !!pkg['lint-staged'];
|
|
131
|
+
},
|
|
132
|
+
fix: 'semo harness init',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'PR Quality Gate workflow',
|
|
136
|
+
check: (cwd) => {
|
|
137
|
+
const dir = path.join(cwd, '.github', 'workflows');
|
|
138
|
+
if (!fs.existsSync(dir))
|
|
139
|
+
return false;
|
|
140
|
+
const files = fs.readdirSync(dir);
|
|
141
|
+
return files.some((f) => f.includes('quality') ||
|
|
142
|
+
(fs.existsSync(path.join(dir, f)) &&
|
|
143
|
+
fs.readFileSync(path.join(dir, f), 'utf8').includes('tsc --noEmit')));
|
|
144
|
+
},
|
|
145
|
+
fix: 'semo harness init',
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
// ── 유틸 ─────────────────────────────────────────────────────
|
|
149
|
+
function copyTemplate(templateName, destPath) {
|
|
150
|
+
const src = path.join(TEMPLATES_DIR, templateName);
|
|
151
|
+
if (!fs.existsSync(src)) {
|
|
152
|
+
throw new Error(`Template not found: ${templateName}`);
|
|
153
|
+
}
|
|
154
|
+
const dir = path.dirname(destPath);
|
|
155
|
+
if (!fs.existsSync(dir)) {
|
|
156
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
157
|
+
}
|
|
158
|
+
fs.copyFileSync(src, destPath);
|
|
159
|
+
}
|
|
160
|
+
function fileExists(cwd, ...segments) {
|
|
161
|
+
return fs.existsSync(path.join(cwd, ...segments));
|
|
162
|
+
}
|
|
163
|
+
function readPkg(cwd) {
|
|
164
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
165
|
+
if (!fs.existsSync(pkgPath))
|
|
166
|
+
return {};
|
|
167
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
168
|
+
}
|
|
169
|
+
function writePkg(cwd, pkg) {
|
|
170
|
+
fs.writeFileSync(path.join(cwd, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
|
|
171
|
+
}
|
|
172
|
+
// ── 명령 등록 ────────────────────────────────────────────────
|
|
173
|
+
function registerHarnessCommands(program) {
|
|
174
|
+
const cmd = program
|
|
175
|
+
.command('harness')
|
|
176
|
+
.description('프로젝트 하네스 관리 — init / check / update');
|
|
177
|
+
// ── semo harness init ──────────────────────────────────────
|
|
178
|
+
cmd
|
|
179
|
+
.command('init')
|
|
180
|
+
.description('현재 레포에 하네스 스캐폴딩 (Husky, ESLint, Prettier, commitlint)')
|
|
181
|
+
.option('--skip-install', 'npm install 건너뛰기')
|
|
182
|
+
.option('--skip-eslint', 'ESLint 설정 건너뛰기 (기존 설정 있을 때)')
|
|
183
|
+
.option('--skip-ci', 'GitHub Actions 워크플로우 건너뛰기')
|
|
184
|
+
.action(async (options) => {
|
|
185
|
+
const cwd = process.cwd();
|
|
186
|
+
const spinner = (0, ora_1.default)();
|
|
187
|
+
// 사전 조건 확인
|
|
188
|
+
if (!fileExists(cwd, 'package.json')) {
|
|
189
|
+
console.error(chalk_1.default.red('✗ package.json이 없습니다. npm init 먼저 실행하세요.'));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
if (!fileExists(cwd, '.git')) {
|
|
193
|
+
console.error(chalk_1.default.red('✗ git 레포가 아닙니다. git init 먼저 실행하세요.'));
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
console.log(chalk_1.default.bold('\n🔧 SEMO Harness 초기화\n'));
|
|
197
|
+
// 1. devDependencies 설치
|
|
198
|
+
if (!options.skipInstall) {
|
|
199
|
+
spinner.start('devDependencies 설치 중...');
|
|
200
|
+
try {
|
|
201
|
+
(0, child_process_1.execSync)('npm install -D husky lint-staged prettier @commitlint/cli @commitlint/config-conventional', { cwd, stdio: 'pipe' });
|
|
202
|
+
spinner.succeed('devDependencies 설치 완료');
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
spinner.fail('devDependencies 설치 실패');
|
|
206
|
+
console.error(chalk_1.default.red(String(e)));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// 2. Husky 초기화
|
|
211
|
+
spinner.start('Husky 초기화 중...');
|
|
212
|
+
try {
|
|
213
|
+
(0, child_process_1.execSync)('npx husky init', { cwd, stdio: 'pipe' });
|
|
214
|
+
spinner.succeed('Husky 초기화 완료');
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// husky init은 이미 초기화되면 에러 — 무시
|
|
218
|
+
spinner.succeed('Husky 이미 초기화됨');
|
|
219
|
+
}
|
|
220
|
+
// 3. Husky 훅 생성
|
|
221
|
+
spinner.start('Git 훅 생성 중...');
|
|
222
|
+
const huskyDir = path.join(cwd, '.husky');
|
|
223
|
+
copyTemplate('pre-commit', path.join(huskyDir, 'pre-commit'));
|
|
224
|
+
copyTemplate('commit-msg', path.join(huskyDir, 'commit-msg'));
|
|
225
|
+
copyTemplate('pre-push', path.join(huskyDir, 'pre-push'));
|
|
226
|
+
spinner.succeed('Git 훅 3개 생성 (pre-commit, commit-msg, pre-push)');
|
|
227
|
+
// 4. 설정 파일 생성 (기존 파일이 없을 때만)
|
|
228
|
+
spinner.start('설정 파일 생성 중...');
|
|
229
|
+
const created = [];
|
|
230
|
+
if (!fileExists(cwd, '.prettierrc') && !fileExists(cwd, '.prettierrc.json')) {
|
|
231
|
+
copyTemplate('prettierrc.json', path.join(cwd, '.prettierrc'));
|
|
232
|
+
created.push('.prettierrc');
|
|
233
|
+
}
|
|
234
|
+
if (!fileExists(cwd, '.prettierignore')) {
|
|
235
|
+
copyTemplate('prettierignore', path.join(cwd, '.prettierignore'));
|
|
236
|
+
created.push('.prettierignore');
|
|
237
|
+
}
|
|
238
|
+
if (!fileExists(cwd, 'commitlint.config.js') && !fileExists(cwd, 'commitlint.config.ts')) {
|
|
239
|
+
copyTemplate('commitlint.config.js', path.join(cwd, 'commitlint.config.js'));
|
|
240
|
+
created.push('commitlint.config.js');
|
|
241
|
+
}
|
|
242
|
+
spinner.succeed(`설정 파일 생성: ${created.length > 0 ? created.join(', ') : '(모두 존재)'}`);
|
|
243
|
+
// 5. ESLint 설정 (기존 없을 때만)
|
|
244
|
+
if (!options.skipEslint) {
|
|
245
|
+
const hasEslint = fileExists(cwd, 'eslint.config.mjs') ||
|
|
246
|
+
fileExists(cwd, 'eslint.config.js') ||
|
|
247
|
+
fileExists(cwd, '.eslintrc.js') ||
|
|
248
|
+
fileExists(cwd, '.eslintrc.json');
|
|
249
|
+
if (!hasEslint) {
|
|
250
|
+
spinner.start('ESLint 설정 생성 중...');
|
|
251
|
+
try {
|
|
252
|
+
(0, child_process_1.execSync)('npm install -D eslint @eslint/js typescript-eslint', {
|
|
253
|
+
cwd,
|
|
254
|
+
stdio: 'pipe',
|
|
255
|
+
});
|
|
256
|
+
copyTemplate('eslint.config.mjs', path.join(cwd, 'eslint.config.mjs'));
|
|
257
|
+
spinner.succeed('ESLint 설정 생성 완료');
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
spinner.warn('ESLint 설정 생성 실패 — 수동 설정 필요');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
console.log(chalk_1.default.dim(' ℹ ESLint 설정 이미 존재 — 건너뜀'));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// 6. lint-staged 설정 추가
|
|
268
|
+
const pkg = readPkg(cwd);
|
|
269
|
+
if (!pkg['lint-staged']) {
|
|
270
|
+
spinner.start('lint-staged 설정 추가 중...');
|
|
271
|
+
pkg['lint-staged'] = {
|
|
272
|
+
'*.{ts,tsx,js,mjs}': ['prettier --write'],
|
|
273
|
+
'*.{json,yml,yaml}': ['prettier --write'],
|
|
274
|
+
'*.md': ['prettier --write'],
|
|
275
|
+
};
|
|
276
|
+
writePkg(cwd, pkg);
|
|
277
|
+
spinner.succeed('lint-staged 설정 추가 완료');
|
|
278
|
+
}
|
|
279
|
+
// 7. prepare 스크립트 추가
|
|
280
|
+
const scripts = pkg['scripts'] || {};
|
|
281
|
+
if (!scripts['prepare']) {
|
|
282
|
+
scripts['prepare'] = 'husky';
|
|
283
|
+
pkg['scripts'] = scripts;
|
|
284
|
+
writePkg(cwd, pkg);
|
|
285
|
+
}
|
|
286
|
+
// 8. GitHub Actions 워크플로우
|
|
287
|
+
if (!options.skipCi) {
|
|
288
|
+
const workflowDir = path.join(cwd, '.github', 'workflows');
|
|
289
|
+
const hasQualityGate = fs.existsSync(workflowDir) &&
|
|
290
|
+
fs.readdirSync(workflowDir).some((f) => f.includes('quality'));
|
|
291
|
+
if (!hasQualityGate) {
|
|
292
|
+
spinner.start('PR Quality Gate 워크플로우 생성 중...');
|
|
293
|
+
copyTemplate('pr-quality-gate.yml', path.join(workflowDir, 'pr-quality-gate.yml'));
|
|
294
|
+
spinner.succeed('PR Quality Gate 워크플로우 생성 완료');
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
console.log(chalk_1.default.dim(' ℹ PR Quality Gate 워크플로우 이미 존재 — 건너뜀'));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// 완료 리포트
|
|
301
|
+
console.log(chalk_1.default.bold.green('\n✅ 하네스 초기화 완료!\n'));
|
|
302
|
+
console.log(chalk_1.default.dim('검증: semo harness check'));
|
|
303
|
+
console.log(chalk_1.default.dim('갱신: semo harness update\n'));
|
|
304
|
+
});
|
|
305
|
+
// ── semo harness check ─────────────────────────────────────
|
|
306
|
+
cmd
|
|
307
|
+
.command('check')
|
|
308
|
+
.description('현재 레포의 하네스 건강도 검사')
|
|
309
|
+
.action(async () => {
|
|
310
|
+
const cwd = process.cwd();
|
|
311
|
+
console.log(chalk_1.default.bold('\n🔍 SEMO Harness 점검\n'));
|
|
312
|
+
let passed = 0;
|
|
313
|
+
let warned = 0;
|
|
314
|
+
let failed = 0;
|
|
315
|
+
for (const item of CHECKS) {
|
|
316
|
+
const ok = item.check(cwd);
|
|
317
|
+
if (ok) {
|
|
318
|
+
console.log(chalk_1.default.green(` ✅ ${item.name}`));
|
|
319
|
+
passed++;
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
console.log(chalk_1.default.red(` ❌ ${item.name}`));
|
|
323
|
+
if (item.fix) {
|
|
324
|
+
console.log(chalk_1.default.dim(` Fix: ${item.fix}`));
|
|
325
|
+
}
|
|
326
|
+
failed++;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const total = CHECKS.length;
|
|
330
|
+
const score = Math.round((passed / total) * 10);
|
|
331
|
+
const color = score >= 8 ? chalk_1.default.green : score >= 5 ? chalk_1.default.yellow : chalk_1.default.red;
|
|
332
|
+
console.log(color(`\n Score: ${score}/10 (${passed} passed, ${warned} warnings, ${failed} failed)\n`));
|
|
333
|
+
if (failed > 0) {
|
|
334
|
+
console.log(chalk_1.default.yellow(" 💡 Run 'semo harness init' to fix missing items.\n"));
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
// ── semo harness update ────────────────────────────────────
|
|
338
|
+
cmd
|
|
339
|
+
.command('update')
|
|
340
|
+
.description('하네스 설정을 최신 SEMO 표준으로 갱신')
|
|
341
|
+
.option('--apply', '차이점 자동 적용')
|
|
342
|
+
.option('--force', '기존 파일 강제 덮어쓰기')
|
|
343
|
+
.action(async (options) => {
|
|
344
|
+
const cwd = process.cwd();
|
|
345
|
+
console.log(chalk_1.default.bold('\n🔄 SEMO Harness 업데이트 점검\n'));
|
|
346
|
+
const targets = [
|
|
347
|
+
{
|
|
348
|
+
template: 'pre-commit',
|
|
349
|
+
dest: path.join('.husky', 'pre-commit'),
|
|
350
|
+
label: 'pre-commit hook',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
template: 'commit-msg',
|
|
354
|
+
dest: path.join('.husky', 'commit-msg'),
|
|
355
|
+
label: 'commit-msg hook',
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
template: 'pre-push',
|
|
359
|
+
dest: path.join('.husky', 'pre-push'),
|
|
360
|
+
label: 'pre-push hook',
|
|
361
|
+
},
|
|
362
|
+
{ template: 'prettierrc.json', dest: '.prettierrc', label: 'Prettier config' },
|
|
363
|
+
{ template: 'prettierignore', dest: '.prettierignore', label: 'Prettier ignore' },
|
|
364
|
+
{
|
|
365
|
+
template: 'commitlint.config.js',
|
|
366
|
+
dest: 'commitlint.config.js',
|
|
367
|
+
label: 'commitlint config',
|
|
368
|
+
},
|
|
369
|
+
];
|
|
370
|
+
let diffs = 0;
|
|
371
|
+
for (const t of targets) {
|
|
372
|
+
const templatePath = path.join(TEMPLATES_DIR, t.template);
|
|
373
|
+
const destPath = path.join(cwd, t.dest);
|
|
374
|
+
if (!fs.existsSync(templatePath))
|
|
375
|
+
continue;
|
|
376
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
377
|
+
if (!fs.existsSync(destPath)) {
|
|
378
|
+
console.log(chalk_1.default.yellow(` ⚠ ${t.label}: 파일 없음`));
|
|
379
|
+
if (options.apply || options.force) {
|
|
380
|
+
const dir = path.dirname(destPath);
|
|
381
|
+
if (!fs.existsSync(dir))
|
|
382
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
383
|
+
fs.writeFileSync(destPath, templateContent);
|
|
384
|
+
console.log(chalk_1.default.green(` → 생성됨`));
|
|
385
|
+
}
|
|
386
|
+
diffs++;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const currentContent = fs.readFileSync(destPath, 'utf8');
|
|
390
|
+
if (currentContent.trim() !== templateContent.trim()) {
|
|
391
|
+
console.log(chalk_1.default.yellow(` ⚠ ${t.label}: 표준과 다름`));
|
|
392
|
+
if (options.apply || options.force) {
|
|
393
|
+
fs.writeFileSync(destPath, templateContent);
|
|
394
|
+
console.log(chalk_1.default.green(` → 갱신됨`));
|
|
395
|
+
}
|
|
396
|
+
diffs++;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
console.log(chalk_1.default.green(` ✅ ${t.label}: 최신`));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (diffs === 0) {
|
|
403
|
+
console.log(chalk_1.default.green('\n 모든 설정이 최신 SEMO 표준과 일치합니다.\n'));
|
|
404
|
+
}
|
|
405
|
+
else if (!options.apply && !options.force) {
|
|
406
|
+
console.log(chalk_1.default.yellow(`\n ${diffs}개 항목 차이. 'semo harness update --apply'로 적용.\n`));
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
console.log(chalk_1.default.green(`\n ${diffs}개 항목 갱신 완료.\n`));
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* semo incubator — 인큐베이터 세션 관리
|
|
3
|
+
*
|
|
4
|
+
* semo incubator create --service-id <uuid> --channel <name> — 프로젝트 세션 생성
|
|
5
|
+
* semo incubator list — 활성 세션 목록
|
|
6
|
+
* semo incubator stop --service-id <uuid> — 세션 중지 + 아카이브
|
|
7
|
+
* semo incubator status --service-id <uuid> — 세션 상태 조회
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
export declare function registerIncubatorCommands(program: Command): void;
|