@mawaru/sdk 0.39.0 → 0.41.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/graph.test.ts +15 -9
- package/_schemas/graph.ts +15 -6
- package/_schemas/human-manifest.test.ts +67 -0
- package/_schemas/human-manifest.ts +31 -0
- package/_schemas/port-spec.test.ts +33 -92
- package/_schemas/port-spec.ts +36 -59
- package/dist/_cli/previewCommand.js +3 -3
- package/dist/_cli/previewFiles.js +2 -3
- package/dist/_cli/previewSlack.d.ts +1 -1
- package/dist/_cli/previewSlack.js +1 -1
- package/dist/_schemas/graph.d.ts +6 -3
- package/dist/_schemas/graph.js +12 -6
- package/dist/_schemas/human-manifest.d.ts +9 -0
- package/dist/_schemas/human-manifest.js +29 -0
- package/dist/_schemas/port-spec.d.ts +5 -11
- package/dist/_schemas/port-spec.js +28 -37
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init.js +22 -3
- package/dist/typegen.d.ts +3 -1
- package/dist/typegen.js +96 -14
- package/dist/validate.js +57 -0
- package/dist/views/_bridge.d.ts +12 -0
- package/dist/views/_bridge.js +10 -0
- package/dist/views/_humanProps.d.ts +10 -0
- package/dist/views/_humanProps.js +11 -0
- package/dist/views/_parts/DocumentCard.d.ts +2 -0
- package/dist/views/_parts/DocumentCard.js +16 -0
- package/dist/views/_parts/FilePreview.d.ts +5 -0
- package/dist/views/_parts/FilePreview.js +101 -0
- package/dist/views/_parts/Linkify.d.ts +7 -0
- package/dist/views/_parts/Linkify.js +28 -0
- package/dist/views/_parts/Markdown.d.ts +44 -0
- package/dist/views/_parts/Markdown.js +313 -0
- package/dist/views/_parts/SchemaForm.d.ts +19 -0
- package/dist/views/_parts/SchemaForm.js +310 -0
- package/dist/views/_parts/fileRef.d.ts +7 -0
- package/dist/views/_parts/fileRef.js +11 -0
- package/dist/views/_parts/useNarrow.d.ts +5 -0
- package/dist/views/_parts/useNarrow.js +23 -0
- package/dist/views/components.d.ts +7 -0
- package/dist/views/components.js +12 -0
- package/dist/views/host.d.ts +23 -0
- package/dist/views/host.js +20 -0
- package/dist/views/human.d.ts +54 -0
- package/dist/views/human.js +5 -0
- package/dist/views/protocol.d.ts +44 -0
- package/dist/views/protocol.js +5 -0
- package/dist/views/runtime.d.ts +2 -0
- package/dist/views/runtime.js +86 -0
- package/dist/views/sample.d.ts +22 -0
- package/dist/views/sample.js +38 -0
- package/dist/{slack-view.d.ts → views/slack.d.ts} +1 -1
- package/docs/development.md +39 -2
- package/index.ts +1 -0
- package/package.json +40 -4
- package/skills/create-loop/SKILL.md +7 -3
- package/templates/CLAUDE.md +2 -0
- package/views/_bridge.tsx +36 -0
- package/views/_humanProps.ts +27 -0
- package/views/_parts/DocumentCard.ts +18 -0
- package/views/_parts/FilePreview.tsx +180 -0
- package/views/_parts/Linkify.test.ts +40 -0
- package/views/_parts/Linkify.tsx +40 -0
- package/views/_parts/Markdown.test.ts +131 -0
- package/views/_parts/Markdown.tsx +472 -0
- package/views/_parts/SchemaForm.tsx +742 -0
- package/views/_parts/fileRef.ts +21 -0
- package/views/_parts/useNarrow.ts +24 -0
- package/views/components.ts +16 -0
- package/views/host.ts +27 -0
- package/views/human.ts +83 -0
- package/views/protocol.ts +63 -0
- package/views/runtime.tsx +123 -0
- package/views/sample.tsx +94 -0
- package/views/slack.test.ts +40 -0
- package/{slack-view.ts → views/slack.ts} +3 -3
- /package/dist/{slack-view.js → views/slack.js} +0 -0
package/_schemas/graph.test.ts
CHANGED
|
@@ -174,20 +174,26 @@ describe("humanConfigSchema", () => {
|
|
|
174
174
|
).toThrow(/担当/)
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
it("承認ビューを受け付ける", () => {
|
|
177
|
+
// 実装は nodes/human/ 直下のディレクトリ名(program_dir / ai_dir と同じ)。repo にあるかは保存 API が見る
|
|
178
|
+
it("実装(human_dir・ref)を受け付ける", () => {
|
|
180
179
|
const parsed = humanConfigSchema.parse({
|
|
181
180
|
assignment: { units: [[uuid()]] },
|
|
182
|
-
|
|
181
|
+
human_dir: "invoice-review",
|
|
182
|
+
ref: "try-view",
|
|
183
183
|
})
|
|
184
|
-
expect(parsed.
|
|
184
|
+
expect(parsed.human_dir).toBe("invoice-review")
|
|
185
|
+
expect(parsed.ref).toBe("try-view")
|
|
185
186
|
})
|
|
186
187
|
|
|
187
|
-
it("
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
it("空・パスの human_dir は拒否する", () => {
|
|
189
|
+
for (const human_dir of ["", "a/b", "..", "a\\b"]) {
|
|
190
|
+
expect(() =>
|
|
191
|
+
humanConfigSchema.parse({
|
|
192
|
+
assignment: { units: [[uuid()]] },
|
|
193
|
+
human_dir,
|
|
194
|
+
}),
|
|
195
|
+
).toThrow()
|
|
196
|
+
}
|
|
191
197
|
})
|
|
192
198
|
|
|
193
199
|
it("fixed:1ユニット(複数人の and/or)を受け付ける", () => {
|
package/_schemas/graph.ts
CHANGED
|
@@ -156,10 +156,19 @@ export type AssignMember = z.infer<typeof assignMemberSchema>
|
|
|
156
156
|
// メンバーが tenant の membership であることの検証は保存 API 側で行う
|
|
157
157
|
// (docs/tasks/backlog/Human担当者の高度化(複数人承認とラウンドロビン).md)
|
|
158
158
|
export const humanConfigSchema = z.object({
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
159
|
+
// 実装(承認画面と入出力の宣言)の置き場所:実行 repo の nodes/human/ 直下のディレクトリ名。
|
|
160
|
+
// program_dir / ai_dir と同じ。省略時は mawaru 内蔵の標準の承認画面
|
|
161
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「human ノードの実装として置く」)
|
|
162
|
+
human_dir: z
|
|
163
|
+
.string()
|
|
164
|
+
.min(1)
|
|
165
|
+
.refine(
|
|
166
|
+
(v) => !v.includes("/") && !v.includes("\\") && v !== "." && v !== "..",
|
|
167
|
+
"human_dir は nodes/human/ 直下のディレクトリ名で指定してください",
|
|
168
|
+
)
|
|
169
|
+
.optional(),
|
|
170
|
+
// 読むブランチ(省略時はデフォルトブランチ)。publish でその先頭の commit に固定する
|
|
171
|
+
ref: z.string().min(1).optional(),
|
|
163
172
|
assignment: z
|
|
164
173
|
.object({
|
|
165
174
|
strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
|
|
@@ -293,8 +302,8 @@ export const graphNodeSchema = z.object({
|
|
|
293
302
|
// 表示アイコン(全 kind 共通の任意設定。human はフロントが担当から導出するので通常設定しない)
|
|
294
303
|
icon: nodeIconSchema.optional(),
|
|
295
304
|
// Slack ビュー(全 kind 共通の任意設定。実行中(human は承認待ち)・完了メッセージの見せ方)。
|
|
296
|
-
// human の view と同じ「不透明なキー」で、選べる key は GET slack
|
|
297
|
-
// <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/slack
|
|
305
|
+
// human の view と同じ「不透明なキー」で、選べる key は GET views/slack が返す。
|
|
306
|
+
// <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/views/slack の契約で書く)。
|
|
298
307
|
// 保存時に mawaru が見るのは common/* の実在と <slug>/* の可視性だけ。
|
|
299
308
|
// 省略=承認待ちは承認ボタンだけ・完了は ✅ の1行(データは出さない)。
|
|
300
309
|
// ポートの型には一切影響しない
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { humanManifestSchema } from "./human-manifest.js"
|
|
3
|
+
import { fileSchema, instructionSchema } from "./port-spec.js"
|
|
4
|
+
|
|
5
|
+
const pr = {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
title: { type: "string", description: "PR のタイトル" },
|
|
9
|
+
gifs: { type: "array", items: fileSchema() },
|
|
10
|
+
},
|
|
11
|
+
required: ["title"],
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const messagesOf = (value: unknown) => {
|
|
15
|
+
const parsed = humanManifestSchema.safeParse(value)
|
|
16
|
+
return parsed.success ? [] : parsed.error.issues.map((i) => i.message)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("humanManifestSchema(nodes/human/<dir>/config.json)", () => {
|
|
20
|
+
it("in・approve・reject を書いた宣言を通す(追加の出口も書ける)", () => {
|
|
21
|
+
const config = {
|
|
22
|
+
inputs: { in: { schema: pr } },
|
|
23
|
+
outputs: {
|
|
24
|
+
approve: { type: "array", items: pr },
|
|
25
|
+
reject: instructionSchema(),
|
|
26
|
+
notion: pr,
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
// refs はエディタが転写する生成物なので、無ければ空になる
|
|
30
|
+
expect(humanManifestSchema.parse(config)).toEqual({ ...config, refs: {} })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("in・approve・reject のどれかが無ければ弾く(省略したときの決まりは無い)", () => {
|
|
34
|
+
const full = {
|
|
35
|
+
inputs: { in: { schema: pr } },
|
|
36
|
+
outputs: { approve: pr, reject: instructionSchema() },
|
|
37
|
+
}
|
|
38
|
+
const violation =
|
|
39
|
+
"human のポート構成は in(in) と out(approve / reject) が必須です(out は追加できます)"
|
|
40
|
+
expect(messagesOf({ ...full, inputs: {} })).toEqual([violation])
|
|
41
|
+
expect(messagesOf({ ...full, outputs: { approve: pr } })).toEqual([
|
|
42
|
+
violation,
|
|
43
|
+
])
|
|
44
|
+
expect(
|
|
45
|
+
messagesOf({ ...full, outputs: { reject: instructionSchema() } }),
|
|
46
|
+
).toEqual([violation])
|
|
47
|
+
expect(humanManifestSchema.safeParse({}).success).toBe(false)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("in 以外の入口は弾く", () => {
|
|
51
|
+
expect(
|
|
52
|
+
humanManifestSchema.safeParse({
|
|
53
|
+
inputs: { in: { schema: pr }, instruction: { schema: pr } },
|
|
54
|
+
outputs: { approve: pr, reject: instructionSchema() },
|
|
55
|
+
}).success,
|
|
56
|
+
).toBe(false)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('上流のコピー("T")は書けない(型は具体的に書く)', () => {
|
|
60
|
+
expect(
|
|
61
|
+
humanManifestSchema.safeParse({
|
|
62
|
+
inputs: { in: { schema: "T" } },
|
|
63
|
+
outputs: { approve: "T", reject: instructionSchema() },
|
|
64
|
+
}).success,
|
|
65
|
+
).toBe(false)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { jsonSchemaSchema, portKeySchema } from "./node.js"
|
|
3
|
+
import { kindPortsViolation } from "./port-spec.js"
|
|
4
|
+
|
|
5
|
+
// 顧客 repo の nodes/human/<dir>/config.json(human ノードの実装の入出力の宣言)。
|
|
6
|
+
// program / AI の config.json と同じく、エディタがディレクトリを選んだときに inputs / outputs から
|
|
7
|
+
// ports を起こし、ポートの編集は保存時にここへ書き戻される(実行時の正は ports テーブルのまま)。
|
|
8
|
+
// 形は AI に揃える(inputs の値は { schema }、outputs の値は素の JSON Schema)。
|
|
9
|
+
// 口の構成(in 1つ・approve / reject は必須・追加の出口は自由)は kind のポート構成の規則をそのまま使う
|
|
10
|
+
export const humanManifestSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
inputs: z.record(z.string(), z.object({ schema: jsonSchemaSchema })),
|
|
13
|
+
outputs: z.record(z.string(), jsonSchemaSchema),
|
|
14
|
+
// refs=ループ変数の定義の転写(program と同じ。エディタのグラフ保存が一方向に書く生成物)
|
|
15
|
+
refs: z.record(portKeySchema, jsonSchemaSchema).default({}),
|
|
16
|
+
})
|
|
17
|
+
.superRefine((manifest, ctx) => {
|
|
18
|
+
const violation = kindPortsViolation("human", [
|
|
19
|
+
...Object.keys(manifest.inputs).map((key) => ({
|
|
20
|
+
io: "in" as const,
|
|
21
|
+
key,
|
|
22
|
+
})),
|
|
23
|
+
...Object.keys(manifest.outputs).map((key) => ({
|
|
24
|
+
io: "out" as const,
|
|
25
|
+
key,
|
|
26
|
+
})),
|
|
27
|
+
])
|
|
28
|
+
if (violation) ctx.addIssue({ code: "custom", message: violation })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export type HumanManifest = z.infer<typeof humanManifestSchema>
|
|
@@ -153,11 +153,17 @@ describe("kindPortsViolation(kind 別ポート構成)", () => {
|
|
|
153
153
|
})
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
+
// 実装(nodes/human/<dir>/)を指す human にする
|
|
157
|
+
const withHumanDir = <N extends { kind: string }>(nodes: N[]) =>
|
|
158
|
+
nodes.map((node) =>
|
|
159
|
+
node.kind === "human" ? { ...node, human: { human_dir: "review" } } : node,
|
|
160
|
+
)
|
|
161
|
+
|
|
156
162
|
describe("型伝播(導出ポートの解決)", () => {
|
|
157
163
|
it.each([
|
|
158
164
|
"notion_task",
|
|
159
165
|
"main",
|
|
160
|
-
])("Humanの追加出口 %s
|
|
166
|
+
])("Humanの追加出口 %s の宣言型は伝播で上書きしない", (key) => {
|
|
161
167
|
const extra = port("out", key, {
|
|
162
168
|
type: "object",
|
|
163
169
|
properties: { title: { type: "string" } },
|
|
@@ -173,14 +179,10 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
173
179
|
extra,
|
|
174
180
|
],
|
|
175
181
|
}
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
reject: { type: "string" },
|
|
181
|
-
}),
|
|
182
|
-
})
|
|
183
|
-
expect(resolved.get(extra.id)).toEqual(extra.schema)
|
|
182
|
+
for (const nodes of [[human], withHumanDir([human])]) {
|
|
183
|
+
const resolved = resolvePortSchemas(nodes, [])
|
|
184
|
+
expect(resolved.get(extra.id)).toEqual(extra.schema)
|
|
185
|
+
}
|
|
184
186
|
})
|
|
185
187
|
|
|
186
188
|
// 伝播テスト用の任意の宣言型(中身の形に意味はない)
|
|
@@ -190,10 +192,9 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
190
192
|
required: ["text"],
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
// human
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
it("ai → human 直結(コピー宣言):in も approve も上流の out と同型、reject はビューの宣言型", () => {
|
|
195
|
+
// 実装を指さない human(標準の承認画面)は wait と同じ素通し。上流の kind は見ない
|
|
196
|
+
// → docs/tasks/wip/承認ビューの顧客repo配置.md の「ポート型」
|
|
197
|
+
it("実装を指さない human:in も approve も上流の out と同型、reject は標準の差し戻し", () => {
|
|
197
198
|
const aiOut = port("out", "main", declaredOut)
|
|
198
199
|
const ai = {
|
|
199
200
|
id: uuid(),
|
|
@@ -211,13 +212,6 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
211
212
|
const resolved = resolvePortSchemas(
|
|
212
213
|
[ai, human],
|
|
213
214
|
[{ from_port_id: aiOut.id, to_port_id: humanIn.id }],
|
|
214
|
-
{
|
|
215
|
-
viewPortSchemasOf: () => ({
|
|
216
|
-
in: "T",
|
|
217
|
-
approve: "T",
|
|
218
|
-
reject: instructionSchema(),
|
|
219
|
-
}),
|
|
220
|
-
},
|
|
221
215
|
)
|
|
222
216
|
// 上流が AI でも中身を取り出さない:AI の出力形式がそのまま下流へ流れる
|
|
223
217
|
expect(resolved.get(humanIn.id)).toEqual(declaredOut)
|
|
@@ -225,25 +219,17 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
225
219
|
expect(resolved.get(reject.id)).toEqual(instructionSchema())
|
|
226
220
|
})
|
|
227
221
|
|
|
228
|
-
describe("
|
|
222
|
+
describe("実装(nodes/human/<dir>/)を指す human は program / ai と同じ", () => {
|
|
229
223
|
const viewIn = {
|
|
230
224
|
type: "object",
|
|
231
|
-
properties: {
|
|
232
|
-
|
|
233
|
-
type: "object",
|
|
234
|
-
properties: { prs: { type: "array", items: { type: "object" } } },
|
|
235
|
-
required: ["prs"],
|
|
236
|
-
},
|
|
237
|
-
description: { type: "string" },
|
|
238
|
-
},
|
|
239
|
-
required: ["data", "description"],
|
|
225
|
+
properties: { prs: { type: "array", items: { type: "object" } } },
|
|
226
|
+
required: ["prs"],
|
|
240
227
|
}
|
|
241
228
|
const viewOut = {
|
|
242
229
|
type: "object",
|
|
243
|
-
properties: {
|
|
244
|
-
required: ["
|
|
230
|
+
properties: { merged: { type: "boolean" } },
|
|
231
|
+
required: ["merged"],
|
|
245
232
|
}
|
|
246
|
-
// reject もビューの宣言(具体型のみ。コピー指示は意味を持たない)
|
|
247
233
|
const viewReject = {
|
|
248
234
|
type: "object",
|
|
249
235
|
properties: { reason: { type: "string" } },
|
|
@@ -256,18 +242,19 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
256
242
|
kind: upstreamKind,
|
|
257
243
|
ports: [port("in", "in"), out],
|
|
258
244
|
}
|
|
259
|
-
const humanIn = port("in", "in")
|
|
260
|
-
const approve = port("out", "approve")
|
|
261
|
-
const reject = port("out", "reject")
|
|
245
|
+
const humanIn = port("in", "in", viewIn)
|
|
246
|
+
const approve = port("out", "approve", viewOut)
|
|
247
|
+
const reject = port("out", "reject", viewReject)
|
|
262
248
|
const human = {
|
|
263
249
|
id: uuid(),
|
|
264
250
|
kind: "human",
|
|
251
|
+
human: { human_dir: "review" },
|
|
265
252
|
ports: [humanIn, approve, reject],
|
|
266
253
|
}
|
|
267
254
|
return { upstream, human, out, humanIn, approve, reject }
|
|
268
255
|
}
|
|
269
256
|
|
|
270
|
-
it("
|
|
257
|
+
it("out(approve・reject)は宣言のまま(上流の kind に依らない)", () => {
|
|
271
258
|
for (const kind of ["ai", "program"]) {
|
|
272
259
|
const { upstream, human, out, humanIn, approve, reject } = build(
|
|
273
260
|
kind,
|
|
@@ -276,79 +263,33 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
276
263
|
const resolved = resolvePortSchemas(
|
|
277
264
|
[upstream, human],
|
|
278
265
|
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
279
|
-
{
|
|
280
|
-
viewPortSchemasOf: () => ({
|
|
281
|
-
in: viewIn,
|
|
282
|
-
approve: viewOut,
|
|
283
|
-
reject: viewReject,
|
|
284
|
-
}),
|
|
285
|
-
},
|
|
286
266
|
)
|
|
287
|
-
expect(resolved.get(humanIn.id)).toEqual(viewIn)
|
|
288
267
|
expect(resolved.get(approve.id)).toEqual(viewOut)
|
|
289
268
|
expect(resolved.get(reject.id)).toEqual(viewReject)
|
|
290
269
|
}
|
|
291
270
|
})
|
|
292
271
|
|
|
293
|
-
it("
|
|
272
|
+
it("in は上流の具体型を写す(program / ai の in と同じ鏡映)", () => {
|
|
294
273
|
const { upstream, human, out, humanIn } = build("ai", declaredOut)
|
|
295
274
|
const resolved = resolvePortSchemas(
|
|
296
275
|
[upstream, human],
|
|
297
276
|
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
298
|
-
{
|
|
299
|
-
viewPortSchemasOf: () => ({
|
|
300
|
-
in: viewIn,
|
|
301
|
-
approve: viewOut,
|
|
302
|
-
reject: viewReject,
|
|
303
|
-
}),
|
|
304
|
-
},
|
|
305
|
-
)
|
|
306
|
-
expect(resolved.get(out.id)).toEqual(declaredOut)
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
it("コピー宣言(標準ビュー)は上流の型をそのまま in と approve へ流す", () => {
|
|
310
|
-
const { upstream, human, out, humanIn, approve } = build(
|
|
311
|
-
"ai",
|
|
312
|
-
declaredOut,
|
|
313
|
-
)
|
|
314
|
-
const resolved = resolvePortSchemas(
|
|
315
|
-
[upstream, human],
|
|
316
|
-
[{ from_port_id: out.id, to_port_id: humanIn.id }],
|
|
317
|
-
{
|
|
318
|
-
viewPortSchemasOf: () => ({
|
|
319
|
-
in: "T",
|
|
320
|
-
approve: "T",
|
|
321
|
-
reject: viewReject,
|
|
322
|
-
}),
|
|
323
|
-
},
|
|
324
277
|
)
|
|
325
278
|
expect(resolved.get(humanIn.id)).toEqual(declaredOut)
|
|
326
|
-
|
|
279
|
+
// 上流の out は書き換えない(逆伝播しない)
|
|
280
|
+
expect(resolved.get(out.id)).toEqual(declaredOut)
|
|
327
281
|
})
|
|
328
282
|
|
|
329
|
-
it("
|
|
330
|
-
const humanIn =
|
|
331
|
-
const
|
|
332
|
-
const reject = port("out", "reject")
|
|
333
|
-
const human = {
|
|
334
|
-
id: uuid(),
|
|
335
|
-
kind: "human",
|
|
336
|
-
ports: [humanIn, approve, reject],
|
|
337
|
-
}
|
|
338
|
-
const resolved = resolvePortSchemas([human], [], {
|
|
339
|
-
viewPortSchemasOf: () => ({
|
|
340
|
-
in: viewIn,
|
|
341
|
-
approve: viewOut,
|
|
342
|
-
reject: viewReject,
|
|
343
|
-
}),
|
|
344
|
-
})
|
|
283
|
+
it("未接続の in は宣言のまま", () => {
|
|
284
|
+
const { human, humanIn, approve, reject } = build("ai", declaredOut)
|
|
285
|
+
const resolved = resolvePortSchemas([human], [])
|
|
345
286
|
expect(resolved.get(humanIn.id)).toEqual(viewIn)
|
|
346
287
|
expect(resolved.get(approve.id)).toEqual(viewOut)
|
|
347
288
|
expect(resolved.get(reject.id)).toEqual(viewReject)
|
|
348
289
|
})
|
|
349
290
|
})
|
|
350
291
|
|
|
351
|
-
it("ai → wait → human:素通しで上流の宣言型が伝播し、reject
|
|
292
|
+
it("ai → wait → human:素通しで上流の宣言型が伝播し、reject は標準の差し戻し", () => {
|
|
352
293
|
// ai の in は宣言型(nodes/ai/<dir>/config.json の inputs.in 由来。実行repoの構造見直し)
|
|
353
294
|
const aiInSchema = {
|
|
354
295
|
type: "object",
|
|
@@ -383,8 +324,8 @@ describe("型伝播(導出ポートの解決)", () => {
|
|
|
383
324
|
expect(resolved.get(humanIn.id)).toEqual(declaredOut)
|
|
384
325
|
// approve は in と同型(上流の kind による中身の取り出しはしない)
|
|
385
326
|
expect(resolved.get(approve.id)).toEqual(declaredOut)
|
|
386
|
-
// reject
|
|
387
|
-
expect(resolved.get(reject.id)).toEqual(
|
|
327
|
+
// reject は標準の承認画面の差し戻し(指示+参考ファイル)
|
|
328
|
+
expect(resolved.get(reject.id)).toEqual(instructionSchema())
|
|
388
329
|
// ai の in は宣言型がそのまま残る(const 扱いは廃止)
|
|
389
330
|
expect(resolved.get(aiIn.id)).toEqual(aiInSchema)
|
|
390
331
|
})
|
package/_schemas/port-spec.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod"
|
|
|
2
2
|
import type { GraphPort, JsonSchema, PortIo } from "./node.js"
|
|
3
3
|
|
|
4
4
|
// ポートの kind 別型システム(docs/tasks/done/ポートのkind別仕様.md)。
|
|
5
|
-
// 型の決まり方は2分類:宣言(program / ai の in・out
|
|
5
|
+
// 型の決まり方は2分類:宣言(program / ai と、実装(nodes/human/<dir>/)を指す human の in・out)/
|
|
6
6
|
// 導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)。
|
|
7
7
|
// mawaru がコードで型を固定するポートは無い
|
|
8
8
|
// (docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md)
|
|
@@ -265,9 +265,10 @@ export const kindPortsViolation = (
|
|
|
265
265
|
// ---- 型伝播(導出ポートの解決) ----
|
|
266
266
|
// 接続の両端は同じスキーマになる(値入力フォームのSchemaForm統一.md)。
|
|
267
267
|
// wait は素通し(パラメトリック)ノード:in は接続元の out、out は in と同型。
|
|
268
|
-
// human
|
|
269
|
-
//
|
|
270
|
-
// program / ai
|
|
268
|
+
// human は実装(nodes/human/<dir>/)を指していれば program / ai と同じ宣言ノード、指していなければ
|
|
269
|
+
// (標準の承認画面)wait と同じ素通し(approve は in と同型、reject は標準の差し戻し instructionSchema)。
|
|
270
|
+
// 宣言 out(program / ai / 実装を指す human)は触らない。
|
|
271
|
+
// program / ai(と実装を指す human)の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
|
|
271
272
|
// 上書きし、未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の
|
|
272
273
|
// config.json へも書き戻される。docs/tasks/done/ポート編集のrepo書き戻し同期.md)
|
|
273
274
|
|
|
@@ -328,33 +329,11 @@ type PropagationNode = {
|
|
|
328
329
|
id: string
|
|
329
330
|
kind: string
|
|
330
331
|
ports: GraphPort[]
|
|
332
|
+
// human のときだけ意味を持つ:実装(nodes/human/<dir>/)を指しているか
|
|
333
|
+
human?: { human_dir?: string | null } | null
|
|
331
334
|
}
|
|
332
335
|
type PortLink = { from_port_id: string; to_port_id: string }
|
|
333
336
|
|
|
334
|
-
// 「上流の out をそのままコピーする」(in)/「in と同型」(approve)を表す指示。
|
|
335
|
-
// 承認ビューのカタログ(@mawaru/common)の COPY_UPSTREAM と同じ値
|
|
336
|
-
export const COPY_UPSTREAM_SCHEMA = "T"
|
|
337
|
-
|
|
338
|
-
// ポートの型宣言:具体型か、上流からのコピー指示か
|
|
339
|
-
export type DeclaredPortSchema = JsonSchema | typeof COPY_UPSTREAM_SCHEMA
|
|
340
|
-
|
|
341
|
-
// 伝播のオプション。sdk は承認ビューのカタログを知らない(非公開。企業向けビューの
|
|
342
|
-
// キーが npm 公開物に載るのを避ける)ので、呼び出し元が解決して渡す
|
|
343
|
-
// → docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md
|
|
344
|
-
export type PropagationOptions<N> = {
|
|
345
|
-
// human の in / approve / reject に転写する型(承認ビューの宣言)。null / undefined
|
|
346
|
-
// ならその node は対象外(human 以外)。上流ノードの kind は一切見ない。
|
|
347
|
-
// reject は具体型のみ(上流からのコピー指示は意味を持たない)
|
|
348
|
-
viewPortSchemasOf?: (node: N) =>
|
|
349
|
-
| {
|
|
350
|
-
in: DeclaredPortSchema
|
|
351
|
-
approve: DeclaredPortSchema
|
|
352
|
-
reject: JsonSchema
|
|
353
|
-
}
|
|
354
|
-
| null
|
|
355
|
-
| undefined
|
|
356
|
-
}
|
|
357
|
-
|
|
358
337
|
// end も導出ノード(各 in は上流から、out main は未宣言)。個別処理は
|
|
359
338
|
// resolvePortSchemas 内で行うが、初期スキーマを未宣言にするためここに含める。
|
|
360
339
|
// start も導出ノード:out main は接続先の in から逆向きに転写し、in は out と同型
|
|
@@ -365,10 +344,22 @@ const DERIVED_KINDS = new Set(["wait", "human", "end", "start", "join"])
|
|
|
365
344
|
// in を接続鏡映(sticky)するノード:接続元 out が具体型のときだけ上書き
|
|
366
345
|
const MIRROR_KINDS = new Set(["program", "ai"])
|
|
367
346
|
|
|
368
|
-
|
|
369
|
-
|
|
347
|
+
// 実装(nodes/human/<dir>/)を指す human は program / ai と同じ(in は鏡映・out は宣言。
|
|
348
|
+
// 型は config.json へ書き戻される)。指していない human は導出(標準の承認画面の素通し)
|
|
349
|
+
const isMirrorNode = (node: PropagationNode): boolean =>
|
|
350
|
+
MIRROR_KINDS.has(node.kind) ||
|
|
351
|
+
(node.kind === "human" && Boolean(node.human?.human_dir))
|
|
352
|
+
|
|
353
|
+
const isDerivedNode = (node: PropagationNode): boolean =>
|
|
354
|
+
DERIVED_KINDS.has(node.kind) && !isMirrorNode(node)
|
|
355
|
+
|
|
356
|
+
const isDerivedPort = (
|
|
357
|
+
node: PropagationNode,
|
|
358
|
+
port: Pick<GraphPort, "io" | "key">,
|
|
359
|
+
) =>
|
|
360
|
+
isDerivedNode(node) &&
|
|
370
361
|
(port.io === "in" ||
|
|
371
|
-
(kind !== "human" && port.key === "main") ||
|
|
362
|
+
(node.kind !== "human" && port.key === "main") ||
|
|
372
363
|
port.key === "approve" ||
|
|
373
364
|
port.key === "reject")
|
|
374
365
|
|
|
@@ -380,7 +371,6 @@ const isDerivedPort = (kind: string, port: Pick<GraphPort, "io" | "key">) =>
|
|
|
380
371
|
export const resolvePortSchemas = <N extends PropagationNode>(
|
|
381
372
|
nodes: N[],
|
|
382
373
|
connections: PortLink[],
|
|
383
|
-
options?: PropagationOptions<N>,
|
|
384
374
|
): Map<string, JsonSchema> => {
|
|
385
375
|
const resolved = new Map<string, JsonSchema>()
|
|
386
376
|
const kindByPortId = new Map<string, string>()
|
|
@@ -389,7 +379,7 @@ export const resolvePortSchemas = <N extends PropagationNode>(
|
|
|
389
379
|
kindByPortId.set(port.id, node.kind)
|
|
390
380
|
resolved.set(
|
|
391
381
|
port.id,
|
|
392
|
-
isDerivedPort(node
|
|
382
|
+
isDerivedPort(node, port) ? UNDECLARED_SCHEMA : port.schema,
|
|
393
383
|
)
|
|
394
384
|
}
|
|
395
385
|
}
|
|
@@ -480,9 +470,9 @@ export const resolvePortSchemas = <N extends PropagationNode>(
|
|
|
480
470
|
}
|
|
481
471
|
continue
|
|
482
472
|
}
|
|
483
|
-
const isMirror =
|
|
484
|
-
if (!
|
|
485
|
-
// 鏡映(program / ai):全ての in が対象。具体型が流れてきたときだけ上書きする
|
|
473
|
+
const isMirror = isMirrorNode(node)
|
|
474
|
+
if (!isDerivedNode(node) && !isMirror) continue
|
|
475
|
+
// 鏡映(program / ai / 実装を指す human):全ての in が対象。具体型が流れてきたときだけ上書きする
|
|
486
476
|
// (宣言を未宣言で潰さない)。ai の in はどの口も対等(instruction の特別扱いは無い)
|
|
487
477
|
if (isMirror) {
|
|
488
478
|
for (const port of node.ports) {
|
|
@@ -497,36 +487,24 @@ export const resolvePortSchemas = <N extends PropagationNode>(
|
|
|
497
487
|
}
|
|
498
488
|
continue
|
|
499
489
|
}
|
|
500
|
-
// 導出(wait / human):in
|
|
490
|
+
// 導出(wait / 実装を指さない human):in は上流から、out(main・approve)は in と同型。
|
|
491
|
+
// human の reject は標準の承認画面の差し戻し(指示+参考ファイル)。
|
|
492
|
+
// 上流の out は書き換えない(逆伝播すると fan-out 先の型割れが config.json へ書き戻され、
|
|
493
|
+
// 元の出力宣言を黙って壊す。ズレは接続の型エラーで見せる)
|
|
501
494
|
const inPort = node.ports.find((p) => p.io === "in")
|
|
502
495
|
if (!inPort) continue
|
|
503
496
|
const sources = inbound.get(inPort.id) ?? []
|
|
504
497
|
const inSchema = mergeInboundSchemas(
|
|
505
498
|
sources.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA),
|
|
506
499
|
)
|
|
507
|
-
|
|
508
|
-
// "T" は「上流の out をコピー」(in)/「in と同型」(approve)。reject は具体型の宣言
|
|
509
|
-
// のみで、宣言が無ければ未宣言のまま(mawaru が型を焼き込むことはしない)。
|
|
510
|
-
// 上流の out は書き換えない:逆伝播すると fan-out 先のビューが食い違ったとき
|
|
511
|
-
// 型割れが config.json へ write-through され、元の出力宣言を黙って壊す。
|
|
512
|
-
// ズレは接続の型エラーで見せてユーザーの [修正する] で直す
|
|
513
|
-
const declared = options?.viewPortSchemasOf?.(node)
|
|
514
|
-
const effectiveIn =
|
|
515
|
-
declared && declared.in !== COPY_UPSTREAM_SCHEMA
|
|
516
|
-
? declared.in
|
|
517
|
-
: inSchema
|
|
518
|
-
assign(inPort.id, effectiveIn)
|
|
500
|
+
assign(inPort.id, inSchema)
|
|
519
501
|
for (const port of node.ports) {
|
|
520
|
-
if (port.io !== "out" || !isDerivedPort(node
|
|
521
|
-
if (node.kind === "human" && port.key === "reject") {
|
|
522
|
-
if (declared) assign(port.id, declared.reject)
|
|
523
|
-
continue
|
|
524
|
-
}
|
|
502
|
+
if (port.io !== "out" || !isDerivedPort(node, port)) continue
|
|
525
503
|
assign(
|
|
526
504
|
port.id,
|
|
527
|
-
|
|
528
|
-
?
|
|
529
|
-
:
|
|
505
|
+
node.kind === "human" && port.key === "reject"
|
|
506
|
+
? instructionSchema()
|
|
507
|
+
: inSchema,
|
|
530
508
|
)
|
|
531
509
|
}
|
|
532
510
|
}
|
|
@@ -588,9 +566,8 @@ export const connectionAccepted = (
|
|
|
588
566
|
export const propagateDerivedPorts = <N extends PropagationNode>(
|
|
589
567
|
nodes: N[],
|
|
590
568
|
connections: PortLink[],
|
|
591
|
-
options?: PropagationOptions<N>,
|
|
592
569
|
): N[] => {
|
|
593
|
-
const resolved = resolvePortSchemas(nodes, connections
|
|
570
|
+
const resolved = resolvePortSchemas(nodes, connections)
|
|
594
571
|
const flexiblePorts = resolveFlexiblePorts(nodes, connections)
|
|
595
572
|
return nodes.map((node) => {
|
|
596
573
|
let touched = false
|
|
@@ -12,8 +12,8 @@ import { previewSlack, SLACK_SURFACES, } from "./previewSlack.js";
|
|
|
12
12
|
// `mawaru login` のセッション(web と同じ membership)と、どのテナントのつもりで描くかの
|
|
13
13
|
// MAWARU_TENANT_ID(repo の .env。create-loop スキルと同じ)が要る。
|
|
14
14
|
// ファイルの集め方・cases.ts の評価・ケースの絞り込みは種類をまたいで共通で、種類ごとに「確かめ方」
|
|
15
|
-
// (Slack
|
|
16
|
-
// (docs/tasks/
|
|
15
|
+
// (Slack は送る)だけを差し替える
|
|
16
|
+
// (docs/tasks/done/Slackビューの顧客repo配置.md の「手元プレビュー」)
|
|
17
17
|
const USAGE = "使い方: npx mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|チャンネル ID>]\n" +
|
|
18
18
|
" 例: npx mawaru preview slack/pr-review(views/slack/pr-review/ の cases.ts の全ケースを Slack に送る)";
|
|
19
19
|
export const parsePreviewArgs = (args) => {
|
|
@@ -44,7 +44,7 @@ export const parsePreviewArgs = (args) => {
|
|
|
44
44
|
};
|
|
45
45
|
const PREVIEWERS = {
|
|
46
46
|
slack: {
|
|
47
|
-
casesPath: "/
|
|
47
|
+
casesPath: "/views/slack/cases",
|
|
48
48
|
run: ({ name, files, cases, options, api, log }) => {
|
|
49
49
|
const channel = options.channel ?? process.env.MAWARU_PREVIEW_CHANNEL;
|
|
50
50
|
if (!channel) {
|
|
@@ -2,9 +2,8 @@ import { readdirSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
// 手元プレビュー(`mawaru preview`)が読む、実行 repo の views/<種類>/<名前>/ のファイル。
|
|
4
4
|
// 描画は mawaru API がやるので、ここは「対象を決めてファイルを集める」だけで、種類をまたいで共通
|
|
5
|
-
// (docs/tasks/
|
|
6
|
-
// 手元プレビューできるビューの種類(views/
|
|
7
|
-
// "approval" を足す(previewCommand.ts の振り分けにも足す)
|
|
5
|
+
// (docs/tasks/done/Slackビューの顧客repo配置.md の「手元プレビュー」)
|
|
6
|
+
// 手元プレビューできるビューの種類(views/ 直下のディレクトリ名。previewCommand.ts の振り分けと揃える)
|
|
8
7
|
export const VIEW_KINDS = ["slack"];
|
|
9
8
|
const dirsIn = (dir) => {
|
|
10
9
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SlackViewCase } from "../slack
|
|
1
|
+
import type { SlackViewCase } from "../views/slack.js";
|
|
2
2
|
import { type MawaruApi } from "./previewApi.js";
|
|
3
3
|
export type SlackSurface = "running" | "completed";
|
|
4
4
|
export declare const SLACK_SURFACES: SlackSurface[];
|
|
@@ -42,7 +42,7 @@ export const previewSlack = async (params) => {
|
|
|
42
42
|
let failed = 0;
|
|
43
43
|
for (const testCase of cases) {
|
|
44
44
|
for (const surface of surfaces) {
|
|
45
|
-
const result = expectOk("Slack への送信", await api("POST", "/
|
|
45
|
+
const result = expectOk("Slack への送信", await api("POST", "/views/slack/test-post", {
|
|
46
46
|
...previewBodyOf(files, testCase, surface),
|
|
47
47
|
channel_id: channelId,
|
|
48
48
|
...(thread
|
package/dist/_schemas/graph.d.ts
CHANGED
|
@@ -51,7 +51,8 @@ export declare const RUN_STARTER = "run_starter";
|
|
|
51
51
|
export declare const assignMemberSchema: z.ZodUnion<readonly [z.ZodUUID, z.ZodLiteral<"run_starter">]>;
|
|
52
52
|
export type AssignMember = z.infer<typeof assignMemberSchema>;
|
|
53
53
|
export declare const humanConfigSchema: z.ZodObject<{
|
|
54
|
-
|
|
54
|
+
human_dir: z.ZodOptional<z.ZodString>;
|
|
55
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
55
56
|
assignment: z.ZodObject<{
|
|
56
57
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
57
58
|
fixed: "fixed";
|
|
@@ -194,7 +195,8 @@ export declare const graphNodeSchema: z.ZodObject<{
|
|
|
194
195
|
ref: z.ZodOptional<z.ZodString>;
|
|
195
196
|
}, z.core.$strip>>;
|
|
196
197
|
human: z.ZodOptional<z.ZodObject<{
|
|
197
|
-
|
|
198
|
+
human_dir: z.ZodOptional<z.ZodString>;
|
|
199
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
198
200
|
assignment: z.ZodObject<{
|
|
199
201
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
200
202
|
fixed: "fixed";
|
|
@@ -295,7 +297,8 @@ export declare const saveGraphBodySchema: z.ZodObject<{
|
|
|
295
297
|
ref: z.ZodOptional<z.ZodString>;
|
|
296
298
|
}, z.core.$strip>>;
|
|
297
299
|
human: z.ZodOptional<z.ZodObject<{
|
|
298
|
-
|
|
300
|
+
human_dir: z.ZodOptional<z.ZodString>;
|
|
301
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
299
302
|
assignment: z.ZodObject<{
|
|
300
303
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
301
304
|
fixed: "fixed";
|