@mawaru/sdk 0.5.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.
Files changed (43) hide show
  1. package/_schemas/ai-manifest.test.ts +135 -0
  2. package/_schemas/ai-manifest.ts +76 -0
  3. package/_schemas/graph.test.ts +872 -0
  4. package/_schemas/graph.ts +439 -0
  5. package/_schemas/hook-manifest.test.ts +45 -0
  6. package/_schemas/hook-manifest.ts +25 -0
  7. package/_schemas/node.test.ts +76 -0
  8. package/_schemas/node.ts +54 -0
  9. package/_schemas/port-spec.test.ts +657 -0
  10. package/_schemas/port-spec.ts +405 -0
  11. package/_schemas/program-manifest.test.ts +126 -0
  12. package/_schemas/program-manifest.ts +43 -0
  13. package/dist/_schemas/ai-manifest.d.ts +25 -0
  14. package/dist/_schemas/ai-manifest.js +67 -0
  15. package/dist/_schemas/graph.d.ts +226 -0
  16. package/dist/_schemas/graph.js +372 -0
  17. package/dist/_schemas/hook-manifest.d.ts +18 -0
  18. package/dist/_schemas/hook-manifest.js +21 -0
  19. package/dist/_schemas/node.d.ts +47 -0
  20. package/dist/_schemas/node.js +42 -0
  21. package/dist/_schemas/port-spec.d.ts +62 -0
  22. package/dist/_schemas/port-spec.js +336 -0
  23. package/dist/_schemas/program-manifest.d.ts +10 -0
  24. package/dist/_schemas/program-manifest.js +41 -0
  25. package/dist/cli.d.ts +2 -0
  26. package/dist/cli.js +54 -0
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.js +9 -0
  29. package/dist/init.d.ts +6 -0
  30. package/dist/init.js +81 -0
  31. package/dist/typegen.d.ts +14 -0
  32. package/dist/typegen.js +206 -0
  33. package/dist/validate.d.ts +6 -0
  34. package/dist/validate.js +130 -0
  35. package/docs/development.md +59 -0
  36. package/index.ts +9 -0
  37. package/package.json +47 -0
  38. package/skills/create-loop/SKILL.md +87 -0
  39. package/templates/CLAUDE.md +23 -0
  40. package/templates/README.md +12 -0
  41. package/templates/echo/config.json +23 -0
  42. package/templates/echo/main.ts +4 -0
  43. package/templates/mawaru-runner.yml +71 -0
@@ -0,0 +1,135 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { aiManifestSchema } from "./ai-manifest.js"
3
+ import { envelopeSchemaOf } from "./port-spec.js"
4
+
5
+ const envelope = envelopeSchemaOf({ type: "string" })
6
+
7
+ const valid = {
8
+ name: "返信下書き",
9
+ description: "問い合わせに FAQ を根拠に返信下書きを作る",
10
+ prompt: "問い合わせ内容を読み、FAQ を根拠に返信の下書きを作ってください。",
11
+ model: "claude-opus-4-8",
12
+ skills: ["answer-faq"],
13
+ env: ["ANTHROPIC_API_KEY"],
14
+ inputs: { in: {} },
15
+ outputs: { main: envelope },
16
+ }
17
+
18
+ describe("aiManifestSchema", () => {
19
+ it("フル指定の manifest を受け付ける", () => {
20
+ const manifest = aiManifestSchema.parse(valid)
21
+ expect(manifest.name).toBe("返信下書き")
22
+ expect(manifest.prompt).toContain("下書き")
23
+ expect(manifest.model).toBe("claude-opus-4-8")
24
+ expect(manifest.skills).toEqual(["answer-faq"])
25
+ expect(manifest.env).toEqual(["ANTHROPIC_API_KEY"])
26
+ })
27
+
28
+ it("name / description / model / skills / env は省略できる(既定値が補われる)", () => {
29
+ const manifest = aiManifestSchema.parse({
30
+ prompt: valid.prompt,
31
+ inputs: { in: {} },
32
+ outputs: { main: envelope },
33
+ })
34
+ expect(manifest.name).toBeUndefined()
35
+ expect(manifest.model).toBe("claude-opus-4-8")
36
+ expect(manifest.skills).toEqual([])
37
+ expect(manifest.env).toEqual([])
38
+ })
39
+
40
+ it("prompt は必須・非空(repo に置く時点で完成品)", () => {
41
+ expect(() => aiManifestSchema.parse({ ...valid, prompt: "" })).toThrow()
42
+ const { prompt: _prompt, ...rest } = valid
43
+ expect(() => aiManifestSchema.parse(rest)).toThrow()
44
+ })
45
+
46
+ it("選択肢にないモデルを拒否する", () => {
47
+ expect(() =>
48
+ aiManifestSchema.parse({ ...valid, model: "gpt-4o" }),
49
+ ).toThrow()
50
+ })
51
+
52
+ it('inputs は "in" の1件だけ', () => {
53
+ expect(() => aiManifestSchema.parse({ ...valid, inputs: {} })).toThrow()
54
+ expect(() =>
55
+ aiManifestSchema.parse({ ...valid, inputs: { data: {} } }),
56
+ ).toThrow()
57
+ expect(() =>
58
+ aiManifestSchema.parse({ ...valid, inputs: { in: {}, extra: {} } }),
59
+ ).toThrow()
60
+ })
61
+
62
+ it("outputs は1件以上・key 自由(AI の複数出口=分岐)", () => {
63
+ expect(() => aiManifestSchema.parse({ ...valid, outputs: {} })).toThrow()
64
+ expect(() =>
65
+ aiManifestSchema.parse({ ...valid, outputs: { draft: envelope } }),
66
+ ).not.toThrow()
67
+ expect(() =>
68
+ aiManifestSchema.parse({
69
+ ...valid,
70
+ outputs: { spam: envelope, sales: envelope, support: envelope },
71
+ }),
72
+ ).not.toThrow()
73
+ })
74
+
75
+ it("outputs は全出口に封筒型 { data, description } を強制する(data は任意の型)", () => {
76
+ for (const schema of [
77
+ {}, // any
78
+ { type: "string" },
79
+ { type: "object", properties: { text: { type: "string" } } }, // data キーなし
80
+ {
81
+ type: "object",
82
+ properties: { data: { type: "string" } }, // description なし
83
+ required: ["data"],
84
+ },
85
+ {
86
+ type: "object",
87
+ properties: {
88
+ data: { type: "string" },
89
+ description: { type: "string" }, // description が optional
90
+ },
91
+ required: ["data"],
92
+ },
93
+ ]) {
94
+ expect(() =>
95
+ aiManifestSchema.parse({ ...valid, outputs: { main: schema } }),
96
+ ).toThrow()
97
+ // 妥当な出口に混ざっていても、封筒型でない出口があれば弾く
98
+ expect(() =>
99
+ aiManifestSchema.parse({
100
+ ...valid,
101
+ outputs: { main: envelope, other: schema },
102
+ }),
103
+ ).toThrow()
104
+ }
105
+ // data が任意の構造(カタログ外の object)でも通る
106
+ expect(() =>
107
+ aiManifestSchema.parse({
108
+ ...valid,
109
+ outputs: {
110
+ main: envelopeSchemaOf({
111
+ type: "object",
112
+ properties: {
113
+ title: { type: "string" },
114
+ count: { type: "number" },
115
+ },
116
+ required: ["title"],
117
+ }),
118
+ },
119
+ }),
120
+ ).not.toThrow()
121
+ })
122
+
123
+ it("ディレクトリ名として不正な skill 名を拒否する", () => {
124
+ for (const skill of ["", "../etc", ".hidden", "a/b", "a b"]) {
125
+ expect(() =>
126
+ aiManifestSchema.parse({ ...valid, skills: [skill] }),
127
+ ).toThrow()
128
+ }
129
+ })
130
+
131
+ it("env は非空文字列の配列", () => {
132
+ expect(() => aiManifestSchema.parse({ ...valid, env: [""] })).toThrow()
133
+ expect(() => aiManifestSchema.parse({ ...valid, env: "X" })).toThrow()
134
+ })
135
+ })
@@ -0,0 +1,76 @@
1
+ import { z } from "zod"
2
+ import { jsonSchemaSchema } from "./node.js"
3
+ import { isAiEnvelopeSchema } from "./port-spec.js"
4
+
5
+ // AI ノードのモデル選択肢(既定は Opus:下書き品質が価値の中心。費用はユーザー持ち)
6
+ export const AI_MODELS = [
7
+ "claude-opus-4-8",
8
+ "claude-sonnet-5",
9
+ "claude-haiku-4-5",
10
+ ] as const
11
+ export const aiModelSchema = z.enum(AI_MODELS)
12
+ export type AiModel = z.infer<typeof aiModelSchema>
13
+ export const DEFAULT_AI_MODEL = "claude-opus-4-8" satisfies AiModel
14
+
15
+ // nodes/ai/<dir>/config.json(実行repoの構造見直し)。AI ノードの定義(prompt/model/skills/
16
+ // 入出力スキーマ/env)は repo 側が正で、エディタが取り込んで ports を自動生成する
17
+ // (実行時の正は ports テーブルのまま。プログラム規約 v2 と同じ関係)。
18
+ // env は実行に必要な secret 名の宣言(runner が宣言分だけを子プロセスへ渡す)
19
+ export const aiManifestSchema = z
20
+ .object({
21
+ // 表示名(省略時はディレクトリ名)
22
+ name: z.string().min(1).optional(),
23
+ description: z.string().optional(),
24
+ // repo に置く時点で完成品なので必須(DB 時代の「書きかけ空文字」は廃止)
25
+ prompt: z.string().min(1),
26
+ model: aiModelSchema.default(DEFAULT_AI_MODEL),
27
+ // 同 repo のトップレベル skills/ 配下のディレクトリ名
28
+ skills: z
29
+ .array(
30
+ z
31
+ .string()
32
+ .regex(
33
+ /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/,
34
+ "skill 名はディレクトリ名(英数字始まり)で指定してください",
35
+ ),
36
+ )
37
+ .default([]),
38
+ env: z.array(z.string().min(1)).default([]),
39
+ // 実行中の経過(発話・ツール呼び出し)を Actions ログへ逐次出すデバッグフラグ。
40
+ // 通常時は config.json にキーを書かない(インスペクタも false ならキーを消す)
41
+ verbose: z.boolean().optional(),
42
+ inputs: z.record(z.string(), jsonSchemaSchema),
43
+ outputs: z.record(z.string(), jsonSchemaSchema),
44
+ })
45
+ .superRefine((manifest, ctx) => {
46
+ // グラフ側の既存制約(in ちょうど1つ・key "in")に整合させる
47
+ const inKeys = Object.keys(manifest.inputs)
48
+ if (inKeys.length !== 1 || inKeys[0] !== "in") {
49
+ ctx.addIssue({
50
+ code: "custom",
51
+ path: ["inputs"],
52
+ message: 'inputs は "in" の1件だけ宣言してください(join 導入まで)',
53
+ })
54
+ }
55
+ // outputs は1件以上・key 自由(複数出口=分岐。AIノードの複数出口(分岐).md)
56
+ if (Object.keys(manifest.outputs).length < 1) {
57
+ ctx.addIssue({
58
+ code: "custom",
59
+ path: ["outputs"],
60
+ message: "outputs は1件以上宣言してください",
61
+ })
62
+ }
63
+ // Human 承認 UI が依存する封筒型 { data: T, description: string } を全出口に強制
64
+ // (どの出口も human に接続できる、という AI ノードの不変条件。saveGraph と同じ判定。
65
+ // data=T は任意の JSON Schema)
66
+ for (const [key, schema] of Object.entries(manifest.outputs)) {
67
+ if (!isAiEnvelopeSchema(schema)) {
68
+ ctx.addIssue({
69
+ code: "custom",
70
+ path: ["outputs", key],
71
+ message: `outputs.${key} は封筒型 { data, description } で宣言してください(両方必須・description は string)`,
72
+ })
73
+ }
74
+ }
75
+ })
76
+ export type AiManifest = z.infer<typeof aiManifestSchema>