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.
Files changed (88) hide show
  1. package/dist/api/__tests__/config.test.js +189 -0
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +7 -2
  4. package/dist/api/sonamu.d.ts.map +1 -1
  5. package/dist/api/sonamu.js +14 -10
  6. package/dist/auth/index.d.ts +1 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +2 -1
  9. package/dist/auth/knex-adapter.d.ts +23 -0
  10. package/dist/auth/knex-adapter.d.ts.map +1 -0
  11. package/dist/auth/knex-adapter.js +163 -0
  12. package/dist/auth/plugins/wrappers/admin.d.ts +2 -2
  13. package/dist/bin/__tests__/ts-loader-register.test.js +45 -0
  14. package/dist/bin/cli.js +47 -9
  15. package/dist/bin/ts-loader-register.js +3 -29
  16. package/dist/bin/ts-loader-registration.d.ts +2 -0
  17. package/dist/bin/ts-loader-registration.d.ts.map +1 -0
  18. package/dist/bin/ts-loader-registration.js +42 -0
  19. package/dist/cone/cone-generator.js +3 -3
  20. package/dist/database/puri-subset.test-d.js +9 -1
  21. package/dist/database/puri-subset.types.d.ts +1 -1
  22. package/dist/database/puri-subset.types.d.ts.map +1 -1
  23. package/dist/database/puri-subset.types.js +1 -1
  24. package/dist/testing/fixture-generator.js +5 -5
  25. package/dist/ui/ai-client.js +2 -2
  26. package/dist/ui/api.d.ts.map +1 -1
  27. package/dist/ui/api.js +14 -14
  28. package/dist/ui/cdd-service.d.ts +15 -18
  29. package/dist/ui/cdd-service.d.ts.map +1 -1
  30. package/dist/ui/cdd-service.js +246 -222
  31. package/dist/ui/cdd-types.d.ts +41 -68
  32. package/dist/ui/cdd-types.d.ts.map +1 -1
  33. package/dist/ui/cdd-types.js +2 -2
  34. package/dist/ui-web/assets/index-CKo0Z2Iu.css +1 -0
  35. package/dist/ui-web/assets/{index-CxiydzeC.js → index-DK-2aacv.js} +83 -83
  36. package/dist/ui-web/index.html +2 -2
  37. package/package.json +6 -2
  38. package/src/api/__tests__/config.test.ts +225 -0
  39. package/src/api/config.ts +10 -4
  40. package/src/api/sonamu.ts +16 -13
  41. package/src/auth/index.ts +1 -0
  42. package/src/auth/knex-adapter.ts +208 -0
  43. package/src/bin/__tests__/ts-loader-register.test.ts +62 -0
  44. package/src/bin/cli.ts +52 -9
  45. package/src/bin/ts-loader-register.ts +2 -32
  46. package/src/bin/ts-loader-registration.ts +55 -0
  47. package/src/cone/cone-generator.ts +2 -2
  48. package/src/database/puri-subset.test-d.ts +102 -0
  49. package/src/database/puri-subset.types.ts +1 -1
  50. package/src/skills/commands/sonamu-skills.md +20 -0
  51. package/src/skills/sonamu/SKILL.md +179 -137
  52. package/src/skills/sonamu/ai-agents.md +69 -69
  53. package/src/skills/sonamu/api.md +147 -147
  54. package/src/skills/sonamu/auth-migration.md +220 -220
  55. package/src/skills/sonamu/auth-plugins.md +83 -83
  56. package/src/skills/sonamu/auth.md +106 -106
  57. package/src/skills/sonamu/cdd.md +65 -200
  58. package/src/skills/sonamu/cone.md +138 -138
  59. package/src/skills/sonamu/config.md +191 -191
  60. package/src/skills/sonamu/create-sonamu.md +66 -66
  61. package/src/skills/sonamu/database.md +158 -158
  62. package/src/skills/sonamu/entity-basic.md +292 -293
  63. package/src/skills/sonamu/entity-relations.md +246 -246
  64. package/src/skills/sonamu/entity-validation-checklist.md +124 -124
  65. package/src/skills/sonamu/fixture-cli.md +231 -231
  66. package/src/skills/sonamu/framework-change.md +37 -37
  67. package/src/skills/sonamu/frontend.md +223 -223
  68. package/src/skills/sonamu/i18n.md +82 -82
  69. package/src/skills/sonamu/migration.md +77 -77
  70. package/src/skills/sonamu/model.md +222 -222
  71. package/src/skills/sonamu/naite.md +86 -86
  72. package/src/skills/sonamu/project-init.md +228 -228
  73. package/src/skills/sonamu/puri.md +122 -122
  74. package/src/skills/sonamu/scaffolding.md +154 -154
  75. package/src/skills/sonamu/skill-contribution.md +124 -124
  76. package/src/skills/sonamu/subset.md +46 -46
  77. package/src/skills/sonamu/tasks.md +82 -82
  78. package/src/skills/sonamu/testing-devrunner.md +147 -147
  79. package/src/skills/sonamu/testing.md +673 -673
  80. package/src/skills/sonamu/upsert.md +79 -79
  81. package/src/skills/sonamu/vector.md +67 -67
  82. package/src/testing/fixture-generator.ts +4 -4
  83. package/src/ui/ai-client.ts +1 -1
  84. package/src/ui/api.ts +18 -17
  85. package/src/ui/cdd-service.ts +264 -254
  86. package/src/ui/cdd-types.ts +40 -75
  87. package/dist/ui-web/assets/index-BrQKU3j9.css +0 -1
  88. 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
- await skills_sync_to(path.join(os.homedir(), ".claude"), sourceSkillsDir, sourceClaudeMd, {
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 > CLAUDE.md > 루트 package.json (workspaces 필드)
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 파일이 있는지 확인. 있으면 확실한 monorepo 루트.
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. CLAUDE.md 파일이 있는지 확인. 있으면 프로젝트 루트로 간주함.
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
- async function setupSwcConfig() {
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.secret?.anthropic_api_key;
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-5"),
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