@mawaru/sdk 0.8.0 → 0.11.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/_schemas/ai-manifest.test.ts +9 -2
- package/_schemas/ai-manifest.ts +5 -2
- package/_schemas/graph.test.ts +31 -0
- package/_schemas/graph.ts +22 -3
- package/dist/_schemas/ai-manifest.d.ts +4 -2
- package/dist/_schemas/ai-manifest.js +5 -2
- package/dist/_schemas/graph.d.ts +6 -3
- package/dist/_schemas/graph.js +18 -3
- package/dist/typegen.js +8 -1
- package/docs/development.md +9 -2
- package/package.json +3 -2
- package/skills/create-loop/SKILL.md +48 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { aiManifestSchema } from "./ai-manifest.js"
|
|
2
|
+
import { AI_MODELS, aiManifestSchema } from "./ai-manifest.js"
|
|
3
3
|
import { envelopeSchemaOf, instructionSchema } from "./port-spec.js"
|
|
4
4
|
|
|
5
5
|
const envelope = envelopeSchemaOf({ type: "string" })
|
|
@@ -32,11 +32,18 @@ describe("aiManifestSchema", () => {
|
|
|
32
32
|
outputs: { main: envelope },
|
|
33
33
|
})
|
|
34
34
|
expect(manifest.name).toBeUndefined()
|
|
35
|
-
expect(manifest.model).toBe("claude-opus-
|
|
35
|
+
expect(manifest.model).toBe("claude-opus-5")
|
|
36
36
|
expect(manifest.skills).toEqual([])
|
|
37
37
|
expect(manifest.env).toEqual([])
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
+
it("model は AI_MODELS の enum(バージョン固定。外の値は拒否)", () => {
|
|
41
|
+
for (const model of AI_MODELS) {
|
|
42
|
+
expect(aiManifestSchema.parse({ ...valid, model }).model).toBe(model)
|
|
43
|
+
}
|
|
44
|
+
expect(() => aiManifestSchema.parse({ ...valid, model: "opus" })).toThrow()
|
|
45
|
+
})
|
|
46
|
+
|
|
40
47
|
it("prompt は必須・非空(repo に置く時点で完成品)", () => {
|
|
41
48
|
expect(() => aiManifestSchema.parse({ ...valid, prompt: "" })).toThrow()
|
|
42
49
|
const { prompt: _prompt, ...rest } = valid
|
package/_schemas/ai-manifest.ts
CHANGED
|
@@ -2,15 +2,18 @@ import { z } from "zod"
|
|
|
2
2
|
import { jsonSchemaSchema } from "./node.js"
|
|
3
3
|
import { isAiEnvelopeSchema, manifestInKeysViolation } from "./port-spec.js"
|
|
4
4
|
|
|
5
|
-
// AI ノードのモデル選択肢(既定は Opus
|
|
5
|
+
// AI ノードのモデル選択肢(既定は Opus:下書き品質が価値の中心。費用はユーザー持ち)。
|
|
6
|
+
// エイリアス("opus" 等)は採らずバージョンを固定する:実行のたびに黙って別モデルへ
|
|
7
|
+
// 乗り換わると品質も費用も再現しないため。新モデルが出たらこの配列を足して SDK を publish する
|
|
6
8
|
export const AI_MODELS = [
|
|
9
|
+
"claude-opus-5",
|
|
7
10
|
"claude-opus-4-8",
|
|
8
11
|
"claude-sonnet-5",
|
|
9
12
|
"claude-haiku-4-5",
|
|
10
13
|
] as const
|
|
11
14
|
export const aiModelSchema = z.enum(AI_MODELS)
|
|
12
15
|
export type AiModel = z.infer<typeof aiModelSchema>
|
|
13
|
-
export const DEFAULT_AI_MODEL = "claude-opus-
|
|
16
|
+
export const DEFAULT_AI_MODEL = "claude-opus-5" satisfies AiModel
|
|
14
17
|
|
|
15
18
|
// nodes/ai/<dir>/config.json(実行repoの構造見直し)。AI ノードの定義(prompt/model/skills/
|
|
16
19
|
// 入出力スキーマ/env)は repo 側が正で、エディタが取り込んで ports を自動生成する
|
package/_schemas/graph.test.ts
CHANGED
|
@@ -261,6 +261,37 @@ describe("humanConfigSchema", () => {
|
|
|
261
261
|
humanConfigSchema.parse({ assignment: { units: [["not-a-uuid"]] } }),
|
|
262
262
|
).toThrow()
|
|
263
263
|
})
|
|
264
|
+
|
|
265
|
+
it("run_starter:固定メンバーと混ぜて受け付ける(実行時に start_by で解決)", () => {
|
|
266
|
+
const u1 = uuid()
|
|
267
|
+
const parsed = humanConfigSchema.parse({
|
|
268
|
+
assignment: {
|
|
269
|
+
strategy: "fixed",
|
|
270
|
+
approval_mode: "all",
|
|
271
|
+
units: [["run_starter", u1]],
|
|
272
|
+
},
|
|
273
|
+
})
|
|
274
|
+
expect(parsed.assignment.units).toEqual([["run_starter", u1]])
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it("run_starter:交代制(round_robin)との併用を拒否する", () => {
|
|
278
|
+
expect(() =>
|
|
279
|
+
humanConfigSchema.parse({
|
|
280
|
+
assignment: {
|
|
281
|
+
strategy: "round_robin",
|
|
282
|
+
units: [["run_starter"], [uuid()]],
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
).toThrow()
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it("run_starter:ユニット内の重複を拒否する", () => {
|
|
289
|
+
expect(() =>
|
|
290
|
+
humanConfigSchema.parse({
|
|
291
|
+
assignment: { units: [["run_starter", "run_starter"]] },
|
|
292
|
+
}),
|
|
293
|
+
).toThrow()
|
|
294
|
+
})
|
|
264
295
|
})
|
|
265
296
|
|
|
266
297
|
describe("saveGraphBodySchema", () => {
|
package/_schemas/graph.ts
CHANGED
|
@@ -130,9 +130,16 @@ export const aiConfigSchema = z.object({
|
|
|
130
130
|
})
|
|
131
131
|
export type AiConfig = z.infer<typeof aiConfigSchema>
|
|
132
132
|
|
|
133
|
+
// 担当ユニットのメンバー:user_id の名指し、または動的な担当「ループを実行した人」
|
|
134
|
+
// (run の起動者。実行時に runs.start_by で解決し、解決できなければその1人だけ落ちる。
|
|
135
|
+
// docs/tasks/wip/Human担当に「ループを実行した人」を追加する.md)
|
|
136
|
+
export const RUN_STARTER = "run_starter"
|
|
137
|
+
export const assignMemberSchema = z.union([z.uuid(), z.literal(RUN_STARTER)])
|
|
138
|
+
export type AssignMember = z.infer<typeof assignMemberSchema>
|
|
139
|
+
|
|
133
140
|
// Human ノードの kind 別設定(node_humans + node_human_assignees に対応)。
|
|
134
141
|
// 担当 = ユニット(1人以上のメンバー)の順序付きリスト + 承認モード + 選出戦略。
|
|
135
|
-
// units[i]
|
|
142
|
+
// units[i] はメンバーの配列(1人=個人・複数=グループ)。units 空 = tenant の誰でも。
|
|
136
143
|
// approval_mode はノード単位(any=誰か1人 / all=全員。差し戻しは1人で成立=veto)。
|
|
137
144
|
// メンバーが tenant の membership であることの検証は保存 API 側で行う
|
|
138
145
|
// (docs/tasks/backlog/Human担当者の高度化(複数人承認とラウンドロビン).md)
|
|
@@ -147,7 +154,7 @@ export const humanConfigSchema = z.object({
|
|
|
147
154
|
.object({
|
|
148
155
|
strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
|
|
149
156
|
approval_mode: z.enum(["any", "all"]).default("any"),
|
|
150
|
-
units: z.array(z.array(
|
|
157
|
+
units: z.array(z.array(assignMemberSchema).min(1)).default([]),
|
|
151
158
|
})
|
|
152
159
|
.superRefine((assignment, ctx) => {
|
|
153
160
|
if (assignment.strategy === "fixed" && assignment.units.length > 1) {
|
|
@@ -167,13 +174,25 @@ export const humanConfigSchema = z.object({
|
|
|
167
174
|
message: "round_robin にはユニットが2つ以上必要です",
|
|
168
175
|
})
|
|
169
176
|
}
|
|
177
|
+
// 交代制と「ループを実行した人」は排他:どの回に誰が担当かが起動者と交代位置の
|
|
178
|
+
// 掛け算になって読めなくなるため、設定できる形の方を減らす
|
|
179
|
+
if (
|
|
180
|
+
assignment.strategy === "round_robin" &&
|
|
181
|
+
assignment.units.some((unit) => unit.includes(RUN_STARTER))
|
|
182
|
+
) {
|
|
183
|
+
ctx.addIssue({
|
|
184
|
+
code: "custom",
|
|
185
|
+
path: ["units"],
|
|
186
|
+
message: "round_robin の担当に「ループを実行した人」は入れられません",
|
|
187
|
+
})
|
|
188
|
+
}
|
|
170
189
|
// ユニット内の重複は拒否(またぐ重複は「Aは毎回・相手が交代」の形があるので許す)
|
|
171
190
|
assignment.units.forEach((unit, i) => {
|
|
172
191
|
if (new Set(unit).size !== unit.length) {
|
|
173
192
|
ctx.addIssue({
|
|
174
193
|
code: "custom",
|
|
175
194
|
path: ["units", i],
|
|
176
|
-
message: "
|
|
195
|
+
message: "ユニット内で担当が重複しています",
|
|
177
196
|
})
|
|
178
197
|
}
|
|
179
198
|
})
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export declare const AI_MODELS: readonly ["claude-opus-4-8", "claude-sonnet-5", "claude-haiku-4-5"];
|
|
2
|
+
export declare const AI_MODELS: readonly ["claude-opus-5", "claude-opus-4-8", "claude-sonnet-5", "claude-haiku-4-5"];
|
|
3
3
|
export declare const aiModelSchema: z.ZodEnum<{
|
|
4
|
+
"claude-opus-5": "claude-opus-5";
|
|
4
5
|
"claude-opus-4-8": "claude-opus-4-8";
|
|
5
6
|
"claude-sonnet-5": "claude-sonnet-5";
|
|
6
7
|
"claude-haiku-4-5": "claude-haiku-4-5";
|
|
7
8
|
}>;
|
|
8
9
|
export type AiModel = z.infer<typeof aiModelSchema>;
|
|
9
|
-
export declare const DEFAULT_AI_MODEL = "claude-opus-
|
|
10
|
+
export declare const DEFAULT_AI_MODEL = "claude-opus-5";
|
|
10
11
|
export declare const aiManifestSchema: z.ZodObject<{
|
|
11
12
|
name: z.ZodOptional<z.ZodString>;
|
|
12
13
|
description: z.ZodOptional<z.ZodString>;
|
|
13
14
|
prompt: z.ZodString;
|
|
14
15
|
model: z.ZodDefault<z.ZodEnum<{
|
|
16
|
+
"claude-opus-5": "claude-opus-5";
|
|
15
17
|
"claude-opus-4-8": "claude-opus-4-8";
|
|
16
18
|
"claude-sonnet-5": "claude-sonnet-5";
|
|
17
19
|
"claude-haiku-4-5": "claude-haiku-4-5";
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonSchemaSchema } from "./node.js";
|
|
3
3
|
import { isAiEnvelopeSchema, manifestInKeysViolation } from "./port-spec.js";
|
|
4
|
-
// AI ノードのモデル選択肢(既定は Opus
|
|
4
|
+
// AI ノードのモデル選択肢(既定は Opus:下書き品質が価値の中心。費用はユーザー持ち)。
|
|
5
|
+
// エイリアス("opus" 等)は採らずバージョンを固定する:実行のたびに黙って別モデルへ
|
|
6
|
+
// 乗り換わると品質も費用も再現しないため。新モデルが出たらこの配列を足して SDK を publish する
|
|
5
7
|
export const AI_MODELS = [
|
|
8
|
+
"claude-opus-5",
|
|
6
9
|
"claude-opus-4-8",
|
|
7
10
|
"claude-sonnet-5",
|
|
8
11
|
"claude-haiku-4-5",
|
|
9
12
|
];
|
|
10
13
|
export const aiModelSchema = z.enum(AI_MODELS);
|
|
11
|
-
export const DEFAULT_AI_MODEL = "claude-opus-
|
|
14
|
+
export const DEFAULT_AI_MODEL = "claude-opus-5";
|
|
12
15
|
// nodes/ai/<dir>/config.json(実行repoの構造見直し)。AI ノードの定義(prompt/model/skills/
|
|
13
16
|
// 入出力スキーマ/env)は repo 側が正で、エディタが取り込んで ports を自動生成する
|
|
14
17
|
// (実行時の正は ports テーブルのまま。プログラム規約 v2 と同じ関係)。
|
package/dist/_schemas/graph.d.ts
CHANGED
|
@@ -47,6 +47,9 @@ export declare const aiConfigSchema: z.ZodObject<{
|
|
|
47
47
|
ref: z.ZodOptional<z.ZodString>;
|
|
48
48
|
}, z.core.$strip>;
|
|
49
49
|
export type AiConfig = z.infer<typeof aiConfigSchema>;
|
|
50
|
+
export declare const RUN_STARTER = "run_starter";
|
|
51
|
+
export declare const assignMemberSchema: z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>;
|
|
52
|
+
export type AssignMember = z.infer<typeof assignMemberSchema>;
|
|
50
53
|
export declare const humanConfigSchema: z.ZodObject<{
|
|
51
54
|
view: z.ZodOptional<z.ZodString>;
|
|
52
55
|
view_config: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
|
|
@@ -59,7 +62,7 @@ export declare const humanConfigSchema: z.ZodObject<{
|
|
|
59
62
|
any: "any";
|
|
60
63
|
all: "all";
|
|
61
64
|
}>>;
|
|
62
|
-
units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUUID
|
|
65
|
+
units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>>>>;
|
|
63
66
|
}, z.core.$strip>;
|
|
64
67
|
}, z.core.$strip>;
|
|
65
68
|
export type HumanConfig = z.infer<typeof humanConfigSchema>;
|
|
@@ -143,7 +146,7 @@ export declare const graphNodeSchema: z.ZodObject<{
|
|
|
143
146
|
any: "any";
|
|
144
147
|
all: "all";
|
|
145
148
|
}>>;
|
|
146
|
-
units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUUID
|
|
149
|
+
units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>>>>;
|
|
147
150
|
}, z.core.$strip>;
|
|
148
151
|
}, z.core.$strip>>;
|
|
149
152
|
component: z.ZodOptional<z.ZodObject<{
|
|
@@ -225,7 +228,7 @@ export declare const saveGraphBodySchema: z.ZodObject<{
|
|
|
225
228
|
any: "any";
|
|
226
229
|
all: "all";
|
|
227
230
|
}>>;
|
|
228
|
-
units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUUID
|
|
231
|
+
units: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>>>>;
|
|
229
232
|
}, z.core.$strip>;
|
|
230
233
|
}, z.core.$strip>>;
|
|
231
234
|
component: z.ZodOptional<z.ZodObject<{
|
package/dist/_schemas/graph.js
CHANGED
|
@@ -91,9 +91,14 @@ export const aiConfigSchema = z.object({
|
|
|
91
91
|
.refine((v) => !v.includes("/") && !v.includes("\\") && v !== "." && v !== "..", "ai_dir は nodes/ai/ 直下のディレクトリ名で指定してください"),
|
|
92
92
|
ref: z.string().min(1).optional(),
|
|
93
93
|
});
|
|
94
|
+
// 担当ユニットのメンバー:user_id の名指し、または動的な担当「ループを実行した人」
|
|
95
|
+
// (run の起動者。実行時に runs.start_by で解決し、解決できなければその1人だけ落ちる。
|
|
96
|
+
// docs/tasks/wip/Human担当に「ループを実行した人」を追加する.md)
|
|
97
|
+
export const RUN_STARTER = "run_starter";
|
|
98
|
+
export const assignMemberSchema = z.union([z.uuid(), z.literal(RUN_STARTER)]);
|
|
94
99
|
// Human ノードの kind 別設定(node_humans + node_human_assignees に対応)。
|
|
95
100
|
// 担当 = ユニット(1人以上のメンバー)の順序付きリスト + 承認モード + 選出戦略。
|
|
96
|
-
// units[i]
|
|
101
|
+
// units[i] はメンバーの配列(1人=個人・複数=グループ)。units 空 = tenant の誰でも。
|
|
97
102
|
// approval_mode はノード単位(any=誰か1人 / all=全員。差し戻しは1人で成立=veto)。
|
|
98
103
|
// メンバーが tenant の membership であることの検証は保存 API 側で行う
|
|
99
104
|
// (docs/tasks/backlog/Human担当者の高度化(複数人承認とラウンドロビン).md)
|
|
@@ -108,7 +113,7 @@ export const humanConfigSchema = z.object({
|
|
|
108
113
|
.object({
|
|
109
114
|
strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
|
|
110
115
|
approval_mode: z.enum(["any", "all"]).default("any"),
|
|
111
|
-
units: z.array(z.array(
|
|
116
|
+
units: z.array(z.array(assignMemberSchema).min(1)).default([]),
|
|
112
117
|
})
|
|
113
118
|
.superRefine((assignment, ctx) => {
|
|
114
119
|
if (assignment.strategy === "fixed" && assignment.units.length > 1) {
|
|
@@ -126,13 +131,23 @@ export const humanConfigSchema = z.object({
|
|
|
126
131
|
message: "round_robin にはユニットが2つ以上必要です",
|
|
127
132
|
});
|
|
128
133
|
}
|
|
134
|
+
// 交代制と「ループを実行した人」は排他:どの回に誰が担当かが起動者と交代位置の
|
|
135
|
+
// 掛け算になって読めなくなるため、設定できる形の方を減らす
|
|
136
|
+
if (assignment.strategy === "round_robin" &&
|
|
137
|
+
assignment.units.some((unit) => unit.includes(RUN_STARTER))) {
|
|
138
|
+
ctx.addIssue({
|
|
139
|
+
code: "custom",
|
|
140
|
+
path: ["units"],
|
|
141
|
+
message: "round_robin の担当に「ループを実行した人」は入れられません",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
129
144
|
// ユニット内の重複は拒否(またぐ重複は「Aは毎回・相手が交代」の形があるので許す)
|
|
130
145
|
assignment.units.forEach((unit, i) => {
|
|
131
146
|
if (new Set(unit).size !== unit.length) {
|
|
132
147
|
ctx.addIssue({
|
|
133
148
|
code: "custom",
|
|
134
149
|
path: ["units", i],
|
|
135
|
-
message: "
|
|
150
|
+
message: "ユニット内で担当が重複しています",
|
|
136
151
|
});
|
|
137
152
|
}
|
|
138
153
|
});
|
package/dist/typegen.js
CHANGED
|
@@ -146,7 +146,14 @@ export const renderTypesFile = (manifest, rawConfig) => {
|
|
|
146
146
|
envelopeUnionType(Object.keys(manifest.outputs)),
|
|
147
147
|
"",
|
|
148
148
|
...(refEntries.length > 0
|
|
149
|
-
? [
|
|
149
|
+
? [
|
|
150
|
+
`export type Refs = {\n${refEntries.join("\n")}\n}`,
|
|
151
|
+
"",
|
|
152
|
+
"// run の第2引数:refs=届いた袋の読み、setRefs=袋へのパッチの宣言",
|
|
153
|
+
"// (書いたキーだけが完了時に袋へマージされ、下流のノードから読める)",
|
|
154
|
+
"export type Ctx = { refs: Refs; setRefs: (patch: Partial<Refs>) => void }",
|
|
155
|
+
"",
|
|
156
|
+
]
|
|
150
157
|
: []),
|
|
151
158
|
].join("\n");
|
|
152
159
|
};
|
package/docs/development.md
CHANGED
|
@@ -26,10 +26,17 @@ skills/<dir>/ # AI ノードが参照する skill(SKILL.md)
|
|
|
26
26
|
|
|
27
27
|
- `config.json` — 入出力スキーマ等の宣言。正は `_schemas/program-manifest.ts`
|
|
28
28
|
(`inputs` は `"in"` の1件だけ・`outputs` は1件以上・`env` は必要な secret 名の宣言)
|
|
29
|
-
- `main.ts` — `run(input, ctx)` を export
|
|
29
|
+
- `main.ts` — `run(input, ctx)` を export する。戻り値:
|
|
30
30
|
- `{ <出口ポートのkey>: <データ> }` の**単一キー封筒**(どの出口から出るかを戻り値自身が運ぶ)
|
|
31
31
|
- `"pending"` — 完了保留(外部イベントで後から end が届く)
|
|
32
|
-
-
|
|
32
|
+
- `ctx` — ループ変数の袋の読み書き:
|
|
33
|
+
- `ctx.refs` — 届いた袋(loop に定義された変数の、この step 開始時点の値)
|
|
34
|
+
- `ctx.setRefs({ key: value })` — 袋へのパッチの宣言。**完了時にだけ**反映され、以降の
|
|
35
|
+
ノードが `ctx.refs` で読める(動的な値を下流へ渡す唯一の経路)。何度呼んでもトップレベル
|
|
36
|
+
キー後勝ちで畳まれる。`"pending"` を返す実行では反映されない。loop に定義の無いキーを
|
|
37
|
+
書くと step は failed になる
|
|
38
|
+
- 型生成:`npx mawaru typegen` が config.json から `types.d.ts`(Input / Outputs / Refs / Ctx 型)を
|
|
39
|
+
生成する
|
|
33
40
|
- **ノード単位の npm 依存**:`<dir>/package.json` を置けば実行前にそのディレクトリで
|
|
34
41
|
`npm ci` される。`package-lock.json` の commit が必須(無いと明瞭に失敗する)
|
|
35
42
|
- 環境変数:`config.json` の `env` に宣言した secret(repo の Actions secrets)だけが
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mawaru/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "mawaru 実行 repo の開発 SDK。契約スキーマ(config.json 規約・ループ graph API)の正 + typegen / validate CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"prepack": "pnpm build",
|
|
34
34
|
"typecheck": "tsc --noEmit",
|
|
35
35
|
"test": "vitest run",
|
|
36
|
-
"test:watch": "vitest"
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"release": "sh -c 'pnpm typecheck && pnpm test && npm version --no-git-tag-version ${1:-minor} && npm publish --access public' release"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
40
|
"zod": "^4.4.3",
|
|
@@ -14,19 +14,33 @@ repo ルートの `.env` に以下の4変数が揃っているか確認する。
|
|
|
14
14
|
セットアップまで面倒を見る**(ユーザーに手作業のセットアップを求めない):
|
|
15
15
|
|
|
16
16
|
1. `.gitignore` に `.env` が入っていることを確認する。無ければ追加する(API キーをコミットさせない)。
|
|
17
|
-
2. `.env`
|
|
17
|
+
2. `.env` を作成・追記する。**足りない変数は推測で埋めず、その場でユーザーに聞く**(下の
|
|
18
|
+
「環境変数が足りないとき」の原則に従う):
|
|
18
19
|
- `MAWARU_API_URL` — API のベース URL(例: `https://api.mawaru.ai`。ローカル開発は `http://localhost:9000`)
|
|
19
20
|
- `MAWARU_APP_URL` — フロントの URL(例: `https://app.mawaru.ai`。ローカル開発は `http://localhost:3000`)
|
|
20
21
|
- `MAWARU_TENANT_ID` — tenant の ID
|
|
21
22
|
- `MAWARU_API_KEY` — tenant の API キー
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
接続先が本番かローカルかはユーザーに確認する(URL 2つはこれで決まる)。API キーは mawaru の
|
|
24
|
+
テナント設定画面(`{MAWARU_APP_URL}/settings` の「API キー」。admin のみ)で発行・コピーできるので、
|
|
25
|
+
発行手順を案内したうえで `.env` に入れてもらう。**キーの値をチャット・ログ・コミットに出さない。**
|
|
25
26
|
3. 疎通確認:`GET {MAWARU_API_URL}/tenants/{MAWARU_TENANT_ID}/loops` が 200 を返せば完了。
|
|
26
27
|
401 なら API キー、404 なら tenant ID を疑う。
|
|
27
28
|
|
|
28
29
|
認証は全リクエスト共通でヘッダ `x-api-key: $MAWARU_API_KEY`。
|
|
29
30
|
|
|
31
|
+
## 環境変数が足りないとき(全体の原則)
|
|
32
|
+
|
|
33
|
+
このスキルの `.env` でも、ノードの `env` 宣言(program / ai の handler・setup.sh・prompt が使う
|
|
34
|
+
secret)でも、**足りない値を推測・生成・空文字で代用しない。作業を止めてユーザーに対話的に聞く。**
|
|
35
|
+
|
|
36
|
+
- 足りないものは**まとめて1回で聞く**:変数名・何に使う値か・どこで取れるか(画面・発行手順)・
|
|
37
|
+
形式の例を添える。答えを待ってから作業を再開する(タイムアウトを承認扱いにしない)。
|
|
38
|
+
- **秘密の値はチャットに書かせない・出力しない**。`.env` へはユーザー自身の手で追記してもらう
|
|
39
|
+
(例: `echo 'MAWARU_API_KEY=xxxx' >> .env` を実行してもらう)。ノードの `env` 宣言分は
|
|
40
|
+
実行 repo の Settings → Secrets and variables → Actions に登録してもらい、こちらは**名前だけ**扱う。
|
|
41
|
+
- 受け取ったら「効いていること」まで確かめてから次へ進む(`.env` なら疎通確認、
|
|
42
|
+
Actions secrets なら名前が config.json の `env` と一致していること)。
|
|
43
|
+
|
|
30
44
|
## 契約の読み方(最重要)
|
|
31
45
|
|
|
32
46
|
**API のボディ形をこの文書は説明しない。正はこのパッケージ内の Zod スキーマなので、必ずソースを読むこと**:
|
|
@@ -62,6 +76,7 @@ repo との矛盾・使用中ノードの削除は 409。**graph PUT は冪等
|
|
|
62
76
|
1. **設計**:要件から必要なノード(program / ai / human / wait)と流れを決め、ユーザーに一言で確認する。
|
|
63
77
|
2. **repo 側の準備**:足りない program / ai は `nodes/program/<dir>/` / `nodes/ai/<dir>/` に作る
|
|
64
78
|
(config.json → `npx mawaru typegen` → main.ts 実装 → `npx mawaru validate`)。
|
|
79
|
+
**AI ノードの決定論的な処理は prompt ではなく `setup.sh` に書く**(下記)。
|
|
65
80
|
**作った・変えたものは GitHub へ push してから次へ進む**(mawaru は repo を GitHub 経由で
|
|
66
81
|
読むため、未 push だと graph 保存の検証・書き戻しと食い違って 409 になる)。
|
|
67
82
|
3. **ループ作成**:`POST loops`。
|
|
@@ -78,6 +93,35 @@ repo との矛盾・使用中ノードの削除は 409。**graph PUT は冪等
|
|
|
78
93
|
6. **報告**:エディタ URL `{MAWARU_APP_URL}/loops/{loopId}/edit` をユーザーに渡し、
|
|
79
94
|
見た目の確認と run はユーザーに委ねる。
|
|
80
95
|
|
|
96
|
+
## AI ノード:決定論的な処理は prompt でなく setup.sh に書く
|
|
97
|
+
|
|
98
|
+
やることが決まっている準備=**外部 repo の clone / skill の持ち込み / 認証キーのファイル化 /
|
|
99
|
+
ツールの用意**は、prompt で AI にやらせず `nodes/ai/<dir>/setup.sh` に書く。prompt には
|
|
100
|
+
「どこに何が置いてあるか」と、そこから先の判断だけを書く。
|
|
101
|
+
|
|
102
|
+
理由:LLM にやらせるとトークンを食い flaky で、失敗が「セットアップの失敗」として明瞭に出ない。
|
|
103
|
+
skill の配置は Claude Code 起動前に完了している必要もある。
|
|
104
|
+
|
|
105
|
+
規約(実行 repo 側。config.json への宣言は不要、ファイルの存在が正):
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# nodes/ai/<dir>/setup.sh
|
|
109
|
+
set -euo pipefail
|
|
110
|
+
git clone --depth 1 "https://x-access-token:${SOME_REPO_TOKEN}@github.com/owner/repo.git" workspace/repo
|
|
111
|
+
cp -r workspace/repo/.claude/skills/some-skill skills/ # copySkills の前に走るので自動発見に乗る
|
|
112
|
+
printf '%s' "$SOME_SA_KEY" > workspace/key.json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- 実行順は `env 注入 → input.json 書き出し → setup.sh → copySkills → 本体(Claude Code)`。
|
|
116
|
+
`bash nodes/ai/<dir>/setup.sh "$INPUT_JSON"`(実行ビット不要)、cwd は checkout ルート。
|
|
117
|
+
- **入力は `$1`(JSON 文字列)か `input.json`** で参照できる(`jq` はランナー同梱)。
|
|
118
|
+
- **環境変数は config.json の `env` 宣言分だけ**が注入される。必要な secret は `env` に宣言し、
|
|
119
|
+
実行 repo の Actions secrets に登録してもらう(→「環境変数が足りないとき」)。
|
|
120
|
+
- **受け渡しはファイルで**。別プロセスなので `export` は本体に引き継がれない。clone 先・キーは
|
|
121
|
+
ワークスペース内の既知パス(`workspace/` 配下を推奨)に置き、prompt / skill はそのパスを参照する。
|
|
122
|
+
- **非 0 exit は step failed**(本体は実行されない)。`set -euo pipefail` を付ける。
|
|
123
|
+
- program ノードにも同じ規約が効く(`nodes/program/<dir>/setup.sh`)。
|
|
124
|
+
|
|
81
125
|
## 注意
|
|
82
126
|
|
|
83
127
|
- **graph PUT は全量置換**。既存ループを更新するときは、必ず先に `GET loops/{loopId}` で
|