sonamu 0.8.24 → 0.8.26
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/api/__tests__/config.test.js +189 -0
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +7 -2
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +14 -10
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/knex-adapter.d.ts +23 -0
- package/dist/auth/knex-adapter.d.ts.map +1 -0
- package/dist/auth/knex-adapter.js +163 -0
- package/dist/auth/plugins/wrappers/admin.d.ts +2 -2
- package/dist/bin/__tests__/ts-loader-register.test.js +45 -0
- package/dist/bin/cli.js +47 -9
- package/dist/bin/ts-loader-register.js +3 -29
- package/dist/bin/ts-loader-registration.d.ts +2 -0
- package/dist/bin/ts-loader-registration.d.ts.map +1 -0
- package/dist/bin/ts-loader-registration.js +42 -0
- package/dist/cone/cone-generator.js +3 -3
- package/dist/database/puri-subset.test-d.js +9 -1
- package/dist/database/puri-subset.types.d.ts +1 -1
- package/dist/database/puri-subset.types.d.ts.map +1 -1
- package/dist/database/puri-subset.types.js +1 -1
- package/dist/testing/fixture-generator.js +5 -5
- package/dist/ui/ai-client.js +2 -2
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +14 -14
- package/dist/ui/cdd-service.d.ts +15 -18
- package/dist/ui/cdd-service.d.ts.map +1 -1
- package/dist/ui/cdd-service.js +246 -222
- package/dist/ui/cdd-types.d.ts +41 -68
- package/dist/ui/cdd-types.d.ts.map +1 -1
- package/dist/ui/cdd-types.js +2 -2
- package/dist/ui-web/assets/index-CKo0Z2Iu.css +1 -0
- package/dist/ui-web/assets/{index-CxiydzeC.js → index-DK-2aacv.js} +83 -83
- package/dist/ui-web/index.html +2 -2
- package/package.json +6 -2
- package/src/api/__tests__/config.test.ts +225 -0
- package/src/api/config.ts +10 -4
- package/src/api/sonamu.ts +16 -13
- package/src/auth/index.ts +1 -0
- package/src/auth/knex-adapter.ts +208 -0
- package/src/bin/__tests__/ts-loader-register.test.ts +62 -0
- package/src/bin/cli.ts +52 -9
- package/src/bin/ts-loader-register.ts +2 -32
- package/src/bin/ts-loader-registration.ts +55 -0
- package/src/cone/cone-generator.ts +2 -2
- package/src/database/puri-subset.test-d.ts +102 -0
- package/src/database/puri-subset.types.ts +1 -1
- package/src/skills/commands/sonamu-skills.md +20 -0
- package/src/skills/sonamu/SKILL.md +179 -137
- package/src/skills/sonamu/ai-agents.md +69 -69
- package/src/skills/sonamu/api.md +147 -147
- package/src/skills/sonamu/auth-migration.md +220 -220
- package/src/skills/sonamu/auth-plugins.md +83 -83
- package/src/skills/sonamu/auth.md +106 -106
- package/src/skills/sonamu/cdd.md +65 -200
- package/src/skills/sonamu/cone.md +138 -138
- package/src/skills/sonamu/config.md +191 -191
- package/src/skills/sonamu/create-sonamu.md +66 -66
- package/src/skills/sonamu/database.md +158 -158
- package/src/skills/sonamu/entity-basic.md +292 -293
- package/src/skills/sonamu/entity-relations.md +246 -246
- package/src/skills/sonamu/entity-validation-checklist.md +124 -124
- package/src/skills/sonamu/fixture-cli.md +231 -231
- package/src/skills/sonamu/framework-change.md +37 -37
- package/src/skills/sonamu/frontend.md +223 -223
- package/src/skills/sonamu/i18n.md +82 -82
- package/src/skills/sonamu/migration.md +77 -77
- package/src/skills/sonamu/model.md +222 -222
- package/src/skills/sonamu/naite.md +86 -86
- package/src/skills/sonamu/project-init.md +228 -228
- package/src/skills/sonamu/puri.md +122 -122
- package/src/skills/sonamu/scaffolding.md +154 -154
- package/src/skills/sonamu/skill-contribution.md +124 -124
- package/src/skills/sonamu/subset.md +46 -46
- package/src/skills/sonamu/tasks.md +82 -82
- package/src/skills/sonamu/testing-devrunner.md +147 -147
- package/src/skills/sonamu/testing.md +673 -673
- package/src/skills/sonamu/upsert.md +79 -79
- package/src/skills/sonamu/vector.md +67 -67
- package/src/testing/fixture-generator.ts +4 -4
- package/src/ui/ai-client.ts +1 -1
- package/src/ui/api.ts +18 -17
- package/src/ui/cdd-service.ts +264 -254
- package/src/ui/cdd-types.ts +40 -75
- package/dist/ui-web/assets/index-BrQKU3j9.css +0 -1
- package/src/skills/sonamu/workflow.md +0 -317
package/src/bin/cli.ts
CHANGED
|
@@ -159,6 +159,7 @@ async function bootstrap() {
|
|
|
159
159
|
["fixture", "explore"],
|
|
160
160
|
["migrate", "run"],
|
|
161
161
|
["migrate", "apply", "#targets"],
|
|
162
|
+
["migrate", "generate"],
|
|
162
163
|
["migrate", "status"],
|
|
163
164
|
["stub", "practice", "#name"],
|
|
164
165
|
["stub", "entity", "#name"],
|
|
@@ -185,6 +186,7 @@ async function bootstrap() {
|
|
|
185
186
|
migrate_status,
|
|
186
187
|
migrate_run,
|
|
187
188
|
migrate_apply,
|
|
189
|
+
migrate_generate,
|
|
188
190
|
fixture_init,
|
|
189
191
|
fixture_import,
|
|
190
192
|
fixture_sync,
|
|
@@ -546,6 +548,31 @@ async function migrate_run() {
|
|
|
546
548
|
await migrator.runAction("apply", targets as (keyof SonamuDBConfig)[]);
|
|
547
549
|
}
|
|
548
550
|
|
|
551
|
+
async function migrate_generate() {
|
|
552
|
+
await setupMigrator();
|
|
553
|
+
|
|
554
|
+
const { conns } = await migrator.getStatus();
|
|
555
|
+
const hasStatus0 = conns.some((conn) => conn.status === 0);
|
|
556
|
+
if (!hasStatus0) {
|
|
557
|
+
console.log(
|
|
558
|
+
chalk.red(
|
|
559
|
+
"마이그레이션 파일을 생성하려면 기존 마이그레이션이 최소 하나의 DB에 모두 적용되어 있어야 합니다.",
|
|
560
|
+
),
|
|
561
|
+
);
|
|
562
|
+
for (const conn of conns) {
|
|
563
|
+
if (conn.pending.length > 0) {
|
|
564
|
+
console.log(chalk.yellow(` ${conn.name}: pending ${conn.pending.length}개`));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const count = await migrator.generatePreparedCodes();
|
|
571
|
+
if (count > 0) {
|
|
572
|
+
console.log(chalk.green(`${count}개의 마이그레이션 파일이 생성되었습니다.`));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
549
576
|
async function migrate_status() {
|
|
550
577
|
await setupMigrator();
|
|
551
578
|
|
|
@@ -972,11 +999,23 @@ async function skills_sync() {
|
|
|
972
999
|
}
|
|
973
1000
|
|
|
974
1001
|
if (isGlobal) {
|
|
975
|
-
|
|
1002
|
+
const homeClaudeDir = path.join(os.homedir(), ".claude");
|
|
1003
|
+
await skills_sync_to(homeClaudeDir, sourceSkillsDir, sourceClaudeMd, {
|
|
976
1004
|
useSymlink: false,
|
|
977
1005
|
copyProjectTemplates: false,
|
|
978
1006
|
isGlobal: true,
|
|
979
1007
|
});
|
|
1008
|
+
|
|
1009
|
+
// ~/.claude/commands/sonamu-skills.md 설치
|
|
1010
|
+
const sourceCommandsDir = path.join(sourceBase, "commands");
|
|
1011
|
+
const sourceCommand = path.join(sourceCommandsDir, "sonamu-skills.md");
|
|
1012
|
+
if (await exists(sourceCommand)) {
|
|
1013
|
+
const targetCommandsDir = path.join(homeClaudeDir, "commands");
|
|
1014
|
+
await mkdir(targetCommandsDir, { recursive: true });
|
|
1015
|
+
await cp(sourceCommand, path.join(targetCommandsDir, "sonamu-skills.md"));
|
|
1016
|
+
console.log(chalk.green(`✓ /sonamu-skills command installed → ~/.claude/commands/`));
|
|
1017
|
+
}
|
|
1018
|
+
|
|
980
1019
|
console.log(chalk.cyan(`\n Global sync complete → ~/.claude/skills/sonamu/`));
|
|
981
1020
|
console.log(chalk.dim(` These skills are available in all Claude Code sessions.`));
|
|
982
1021
|
console.log(
|
|
@@ -1325,23 +1364,21 @@ async function auth_add_companions() {
|
|
|
1325
1364
|
|
|
1326
1365
|
/**
|
|
1327
1366
|
* 워크스페이스 루트를 찾습니다.
|
|
1328
|
-
* 우선순위: pnpm-workspace.yaml >
|
|
1367
|
+
* 우선순위: pnpm-workspace.yaml > package.json(workspaces) > .agents/
|
|
1368
|
+
*
|
|
1369
|
+
* CLAUDE.md는 서브패키지에도 존재할 수 있으므로 사용하지 않습니다.
|
|
1370
|
+
* .agents/는 agents init이 생성하는 디렉토리로, 워크스페이스 루트에만 존재합니다.
|
|
1329
1371
|
*/
|
|
1330
1372
|
async function findWorkspaceRoot() {
|
|
1331
1373
|
let dir = process.cwd();
|
|
1332
1374
|
|
|
1333
1375
|
while (dir !== path.dirname(dir)) {
|
|
1334
|
-
// 1. pnpm-workspace.yaml
|
|
1376
|
+
// 1. pnpm-workspace.yaml: 확실한 monorepo 루트.
|
|
1335
1377
|
if (await exists(path.join(dir, "pnpm-workspace.yaml"))) {
|
|
1336
1378
|
return dir;
|
|
1337
1379
|
}
|
|
1338
1380
|
|
|
1339
|
-
// 2.
|
|
1340
|
-
if (await exists(path.join(dir, "CLAUDE.md"))) {
|
|
1341
|
-
return dir;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
// 3. package.json에 workspaces 필드가 있으면 루트.
|
|
1381
|
+
// 2. package.json에 workspaces 필드가 있으면 monorepo 루트.
|
|
1345
1382
|
const packagePath = path.join(dir, "package.json");
|
|
1346
1383
|
if (await exists(packagePath)) {
|
|
1347
1384
|
try {
|
|
@@ -1353,6 +1390,12 @@ async function findWorkspaceRoot() {
|
|
|
1353
1390
|
// 파싱 실패시 무시
|
|
1354
1391
|
}
|
|
1355
1392
|
}
|
|
1393
|
+
|
|
1394
|
+
// 3. .agents/: agents init이 생성한 디렉토리. 서브패키지에는 존재하지 않음.
|
|
1395
|
+
if (await exists(path.join(dir, ".agents"))) {
|
|
1396
|
+
return dir;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1356
1399
|
dir = path.dirname(dir);
|
|
1357
1400
|
}
|
|
1358
1401
|
|
|
@@ -1,38 +1,8 @@
|
|
|
1
|
-
import { register } from "node:module";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { exists } from "../utils/fs-utils.js";
|
|
4
1
|
import { findApiRootPath } from "../utils/utils.js";
|
|
2
|
+
import { ensureTsLoaderRegistered } from "./ts-loader-registration.js";
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
5
|
* @sonamu-kit/ts-loader/loader를 등록하는 스크립트입니다.
|
|
8
6
|
* 이 스크립트는 sonamu cli로 dev 실행할 때 --import로 실행됩니다.
|
|
9
7
|
*/
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
const apiRoot = findApiRootPath();
|
|
13
|
-
|
|
14
|
-
// 프로젝트 루트에서 .swcrc 찾기
|
|
15
|
-
const projectSwcrcPath = path.join(apiRoot, ".swcrc");
|
|
16
|
-
if (await exists(projectSwcrcPath)) {
|
|
17
|
-
// 사용자 프로젝트에 .swcrc가 있으면 우선으로 사용합니다.
|
|
18
|
-
process.env.SWCRC_PATH = projectSwcrcPath;
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// 아니라면 sonamu가 관리하는 .swcrc.project-default를 가져다 씁니다.
|
|
23
|
-
const sonamuSwcrcPath = path.join(import.meta.dirname, "..", "..", ".swcrc.project-default");
|
|
24
|
-
if (await exists(sonamuSwcrcPath)) {
|
|
25
|
-
process.env.SWCRC_PATH = sonamuSwcrcPath;
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
} catch {
|
|
29
|
-
// 환경 변수 설정 실패는 무시 (loader가 기본 설정 사용)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// swc 설정 파일 경로를 환경 변수로 설정
|
|
34
|
-
await setupSwcConfig();
|
|
35
|
-
|
|
36
|
-
register("@sonamu-kit/ts-loader/loader", {
|
|
37
|
-
parentURL: import.meta.url,
|
|
38
|
-
});
|
|
8
|
+
await ensureTsLoaderRegistered(findApiRootPath());
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { register } from "node:module";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { exists } from "../utils/fs-utils.js";
|
|
4
|
+
|
|
5
|
+
const tsLoaderRegisterStateKey = Symbol.for("sonamu.ts-loader-register.state");
|
|
6
|
+
|
|
7
|
+
type TsLoaderRegisterState = {
|
|
8
|
+
registered: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type GlobalWithTsLoaderRegisterState = typeof globalThis & {
|
|
12
|
+
[tsLoaderRegisterStateKey]?: TsLoaderRegisterState;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function getTsLoaderRegisterState(): TsLoaderRegisterState {
|
|
16
|
+
const globalState = globalThis as GlobalWithTsLoaderRegisterState;
|
|
17
|
+
|
|
18
|
+
if (!globalState[tsLoaderRegisterStateKey]) {
|
|
19
|
+
globalState[tsLoaderRegisterStateKey] = { registered: false };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return globalState[tsLoaderRegisterStateKey];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function setupSwcConfig(apiRoot: string) {
|
|
26
|
+
try {
|
|
27
|
+
const projectSwcrcPath = path.join(apiRoot, ".swcrc");
|
|
28
|
+
if (await exists(projectSwcrcPath)) {
|
|
29
|
+
process.env.SWCRC_PATH = projectSwcrcPath;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sonamuSwcrcPath = path.join(import.meta.dirname, "..", "..", ".swcrc.project-default");
|
|
34
|
+
if (await exists(sonamuSwcrcPath)) {
|
|
35
|
+
process.env.SWCRC_PATH = sonamuSwcrcPath;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// 환경 변수 설정 실패는 무시 (loader가 기본 설정 사용)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function ensureTsLoaderRegistered(apiRoot: string): Promise<void> {
|
|
44
|
+
const state = getTsLoaderRegisterState();
|
|
45
|
+
if (state.registered) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await setupSwcConfig(apiRoot);
|
|
50
|
+
|
|
51
|
+
register("@sonamu-kit/ts-loader/loader", {
|
|
52
|
+
parentURL: import.meta.url,
|
|
53
|
+
});
|
|
54
|
+
state.registered = true;
|
|
55
|
+
}
|
|
@@ -63,7 +63,7 @@ function getApiKey(): string {
|
|
|
63
63
|
try {
|
|
64
64
|
// Sonamu가 초기화되어 있는 경우
|
|
65
65
|
const { Sonamu } = require("../api");
|
|
66
|
-
apiKey = Sonamu.
|
|
66
|
+
apiKey = Sonamu.secrets?.anthropic_api_key;
|
|
67
67
|
} catch {
|
|
68
68
|
// Sonamu가 초기화되지 않은 경우 (테스트 등)
|
|
69
69
|
apiKey = undefined;
|
|
@@ -294,7 +294,7 @@ async function callAnthropicAPI(
|
|
|
294
294
|
});
|
|
295
295
|
|
|
296
296
|
const { text, usage } = await generateText({
|
|
297
|
-
model: anthropic("claude-sonnet-4-
|
|
297
|
+
model: anthropic("claude-sonnet-4-6"),
|
|
298
298
|
prompt,
|
|
299
299
|
});
|
|
300
300
|
|
|
@@ -160,6 +160,51 @@ describe("LoadersResult", () => {
|
|
|
160
160
|
}>();
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
+
it("중첩 로더의 as 경로도 hydrate한다", () => {
|
|
164
|
+
type IngredientLoaderQb = (
|
|
165
|
+
qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
|
|
166
|
+
fromIds: number[] | string[],
|
|
167
|
+
) => MockPuri<{ id: number; name: string; refId: number }>;
|
|
168
|
+
|
|
169
|
+
type PrescriptionItemLoaderQb = (
|
|
170
|
+
qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
|
|
171
|
+
fromIds: number[] | string[],
|
|
172
|
+
) => MockPuri<{
|
|
173
|
+
id: number;
|
|
174
|
+
test__id: number;
|
|
175
|
+
test__name: string;
|
|
176
|
+
refId: number;
|
|
177
|
+
}>;
|
|
178
|
+
|
|
179
|
+
type Loaders = [
|
|
180
|
+
{
|
|
181
|
+
as: "items";
|
|
182
|
+
refId: "id";
|
|
183
|
+
qb: PrescriptionItemLoaderQb;
|
|
184
|
+
loaders: [
|
|
185
|
+
{
|
|
186
|
+
as: "test__items";
|
|
187
|
+
refId: "test__id";
|
|
188
|
+
qb: IngredientLoaderQb;
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
type Result = LoadersResult<Loaders>;
|
|
194
|
+
|
|
195
|
+
const result = {} as Result;
|
|
196
|
+
expectTypeOf(result).toEqualTypeOf<{
|
|
197
|
+
items: {
|
|
198
|
+
id: number;
|
|
199
|
+
test: {
|
|
200
|
+
id: number;
|
|
201
|
+
name: string;
|
|
202
|
+
items: { id: number; name: string }[];
|
|
203
|
+
};
|
|
204
|
+
}[];
|
|
205
|
+
}>();
|
|
206
|
+
});
|
|
207
|
+
|
|
163
208
|
it("여러 로더를 처리한다", () => {
|
|
164
209
|
type ProjectLoader = {
|
|
165
210
|
as: "projects";
|
|
@@ -351,6 +396,63 @@ describe("InferAllSubsets", () => {
|
|
|
351
396
|
};
|
|
352
397
|
}>();
|
|
353
398
|
});
|
|
399
|
+
|
|
400
|
+
it("중첩 로더 경로 alias를 포함한 Subset 결과를 추론한다", () => {
|
|
401
|
+
type SubsetFnA = (qbWrapper: PuriWrapper<DatabaseSchemaExtend>) => MockPuri<{
|
|
402
|
+
id: number;
|
|
403
|
+
root__id: number;
|
|
404
|
+
}>;
|
|
405
|
+
type SubsetQueries = {
|
|
406
|
+
A: SubsetFnA;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
type RootItemLoader = {
|
|
410
|
+
as: "root__items";
|
|
411
|
+
refId: "root__id";
|
|
412
|
+
qb: (
|
|
413
|
+
qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
|
|
414
|
+
fromIds: number[] | string[],
|
|
415
|
+
) => MockPuri<{
|
|
416
|
+
id: number;
|
|
417
|
+
test__id: number;
|
|
418
|
+
test__name: string;
|
|
419
|
+
refId: number;
|
|
420
|
+
}>;
|
|
421
|
+
loaders: [
|
|
422
|
+
{
|
|
423
|
+
as: "test__inner_items";
|
|
424
|
+
refId: "test__id";
|
|
425
|
+
qb: (
|
|
426
|
+
qbWrapper: PuriWrapper<DatabaseSchemaExtend>,
|
|
427
|
+
fromIds: number[] | string[],
|
|
428
|
+
) => MockPuri<{ id: number; name: string; refId: number }>;
|
|
429
|
+
},
|
|
430
|
+
];
|
|
431
|
+
};
|
|
432
|
+
type LoaderQueries = {
|
|
433
|
+
A: [RootItemLoader];
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
type Result = InferAllSubsets<SubsetQueries, LoaderQueries>;
|
|
437
|
+
|
|
438
|
+
const result = {} as Result;
|
|
439
|
+
expectTypeOf(result).toEqualTypeOf<{
|
|
440
|
+
A: {
|
|
441
|
+
id: number;
|
|
442
|
+
root: {
|
|
443
|
+
id: number;
|
|
444
|
+
items: {
|
|
445
|
+
id: number;
|
|
446
|
+
test: {
|
|
447
|
+
id: number;
|
|
448
|
+
name: string;
|
|
449
|
+
inner_items: { id: number; name: string }[];
|
|
450
|
+
};
|
|
451
|
+
}[];
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
}>();
|
|
455
|
+
});
|
|
354
456
|
});
|
|
355
457
|
|
|
356
458
|
describe("서브셋이 여러 개인 경우", () => {
|
|
@@ -134,7 +134,7 @@ export type LoadersResult<TLoaders extends GenericPuriLoader[]> = Expand<{
|
|
|
134
134
|
* 기본 결과와 Loader 결과 병합
|
|
135
135
|
*/
|
|
136
136
|
type WithLoaders<TBase, TLoaders> = TLoaders extends GenericPuriLoader[]
|
|
137
|
-
? Expand<TBase & LoadersResult<TLoaders
|
|
137
|
+
? Expand<Hydrate<TBase & LoadersResult<TLoaders>>>
|
|
138
138
|
: TBase;
|
|
139
139
|
|
|
140
140
|
// ============================================================================
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Read ~/.claude/skills/sonamu/SKILL.md and follow its instructions.
|
|
2
|
+
|
|
3
|
+
Then greet the user with the following format (exactly):
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
Sonamu 스킬 가이드가 로드됐습니다. 어떤 작업을 하실 건가요?
|
|
7
|
+
|
|
8
|
+
**Workflow** · CDD · project-init · config
|
|
9
|
+
**Entity/DB** · entity-basic · entity-relations · subset · migration · database
|
|
10
|
+
**API** · model · api · puri · upsert
|
|
11
|
+
**Testing** · testing · devrunner · naite · cone · fixture-cli
|
|
12
|
+
**Auth** · auth · auth-migration · auth-plugins
|
|
13
|
+
**Frontend** · frontend · scaffolding · i18n
|
|
14
|
+
**Advanced** · vector · ai-agents · tasks
|
|
15
|
+
**Meta** · skill-contribution · framework-change
|
|
16
|
+
|
|
17
|
+
어떤 스킬이나 작업을 도와드릴까요? (하고 싶은 작업을 자유롭게 설명해도 됩니다.)
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
If the user provides $ARGUMENTS, skip the greeting and directly help with: $ARGUMENTS
|