@mawaru/sdk 0.39.0 → 0.40.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 +20 -6
- package/_schemas/human-manifest.test.ts +129 -0
- package/_schemas/human-manifest.ts +158 -0
- package/_schemas/port-spec.test.ts +40 -40
- package/_schemas/port-spec.ts +10 -24
- package/dist/_cli/login.js +1 -15
- package/dist/_cli/openBrowser.d.ts +1 -0
- package/dist/_cli/openBrowser.js +15 -0
- package/dist/_cli/previewApi.d.ts +1 -1
- package/dist/_cli/previewCommand.d.ts +8 -0
- package/dist/_cli/previewCommand.js +95 -37
- package/dist/_cli/previewFiles.d.ts +2 -1
- package/dist/_cli/previewFiles.js +42 -19
- package/dist/_cli/previewSlack.d.ts +1 -1
- package/dist/_cli/previewSlack.js +1 -1
- package/dist/_schemas/graph.d.ts +21 -3
- package/dist/_schemas/graph.js +17 -6
- package/dist/_schemas/human-manifest.d.ts +27 -0
- package/dist/_schemas/human-manifest.js +121 -0
- package/dist/_schemas/port-spec.d.ts +6 -9
- package/dist/_schemas/port-spec.js +7 -7
- package/dist/cli.js +3 -2
- 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 +55 -0
- package/dist/views/_approval/ApprovalBar.d.ts +20 -0
- package/dist/views/_approval/ApprovalBar.js +74 -0
- package/dist/views/_approvalActions.d.ts +18 -0
- package/dist/views/_approvalActions.js +10 -0
- package/dist/views/_bridge.d.ts +12 -0
- package/dist/views/_bridge.js +10 -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/host.d.ts +18 -0
- package/dist/views/host.js +20 -0
- package/dist/views/human.d.ts +73 -0
- package/dist/views/human.js +6 -0
- package/dist/views/index.d.ts +8 -0
- package/dist/views/index.js +12 -0
- package/dist/views/protocol.d.ts +34 -0
- package/dist/views/protocol.js +5 -0
- package/dist/views/runtime.d.ts +3 -0
- package/dist/views/runtime.js +86 -0
- package/dist/{slack-view.d.ts → views/slack.d.ts} +1 -1
- package/docs/development.md +40 -2
- package/index.ts +1 -0
- package/package.json +36 -4
- package/skills/create-loop/SKILL.md +6 -3
- package/views/_approval/ApprovalBar.tsx +239 -0
- package/views/_approvalActions.tsx +31 -0
- package/views/_bridge.tsx +36 -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/host.ts +23 -0
- package/views/human.ts +91 -0
- package/views/index.ts +16 -0
- package/views/protocol.ts +40 -0
- package/views/runtime.tsx +132 -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
|
@@ -5,14 +5,22 @@ export type PreviewOptions = {
|
|
|
5
5
|
case?: string;
|
|
6
6
|
surface?: SlackSurface;
|
|
7
7
|
channel?: string;
|
|
8
|
+
once?: boolean;
|
|
8
9
|
};
|
|
9
10
|
export declare const parsePreviewArgs: (args: string[]) => PreviewOptions;
|
|
10
11
|
type Log = (line: string) => void;
|
|
12
|
+
export type PreviewEnv = {
|
|
13
|
+
tenantId: string;
|
|
14
|
+
appUrl: string;
|
|
15
|
+
openUrl: (url: string) => void;
|
|
16
|
+
watch: (dir: string, onChange: () => Promise<void>) => Promise<void>;
|
|
17
|
+
};
|
|
11
18
|
export declare const runPreviewWith: (params: {
|
|
12
19
|
root: string;
|
|
13
20
|
options: PreviewOptions;
|
|
14
21
|
api: MawaruApi;
|
|
15
22
|
log: Log;
|
|
23
|
+
env?: Partial<PreviewEnv>;
|
|
16
24
|
}) => Promise<number>;
|
|
17
25
|
export declare const runPreview: (args: string[]) => Promise<number>;
|
|
18
26
|
export {};
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, watch } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { requireCredentials } from "./credentials.js";
|
|
5
|
+
import { openBrowser } from "./openBrowser.js";
|
|
5
6
|
import { expectOk, mawaruApiFor } from "./previewApi.js";
|
|
6
|
-
import { readViewFiles, resolveView } from "./previewFiles.js";
|
|
7
|
+
import { kindRoot, readViewFiles, resolveView, } from "./previewFiles.js";
|
|
7
8
|
import { previewSlack, SLACK_SURFACES, } from "./previewSlack.js";
|
|
8
|
-
// `mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|ID>]`
|
|
9
|
+
// `mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|ID>] [--once]`
|
|
9
10
|
// (実行 repo のルートで実行)
|
|
10
11
|
//
|
|
11
12
|
// views/<種類>/<名前>/ のビューを、push せずに手元のファイルのまま確かめる。描画は mawaru API がやるので、
|
|
12
13
|
// `mawaru login` のセッション(web と同じ membership)と、どのテナントのつもりで描くかの
|
|
13
14
|
// MAWARU_TENANT_ID(repo の .env。create-loop スキルと同じ)が要る。
|
|
14
|
-
//
|
|
15
|
-
// (Slack
|
|
16
|
-
// (docs/tasks/
|
|
17
|
-
const USAGE = "使い方: npx mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|チャンネル ID>]\n" +
|
|
18
|
-
" 例: npx mawaru preview slack/pr-review(views/slack/pr-review/ の cases.ts の全ケースを Slack
|
|
15
|
+
// ファイルの集め方・認証は種類をまたいで共通で、種類ごとに「確かめ方」だけを差し替える
|
|
16
|
+
// (Slack は cases.ts の全ケースを Slack に送る。承認ビューは本番と同じ承認画面で開き、保存するたびに送り直す)
|
|
17
|
+
// (docs/tasks/done/Slackビューの顧客repo配置.md・docs/tasks/wip/承認ビューの顧客repo配置.md の「手元プレビュー」)
|
|
18
|
+
const USAGE = "使い方: npx mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|チャンネル ID>] [--once]\n" +
|
|
19
|
+
" 例: npx mawaru preview slack/pr-review(views/slack/pr-review/ の cases.ts の全ケースを Slack に送る)\n" +
|
|
20
|
+
" npx mawaru preview human/invoice-review(承認画面で開き、保存するたびに送り直す。--once は1回送って終わる)";
|
|
19
21
|
export const parsePreviewArgs = (args) => {
|
|
20
22
|
const { values, positionals } = parseArgs({
|
|
21
23
|
args,
|
|
@@ -24,6 +26,7 @@ export const parsePreviewArgs = (args) => {
|
|
|
24
26
|
case: { type: "string" },
|
|
25
27
|
surface: { type: "string" },
|
|
26
28
|
channel: { type: "string" },
|
|
29
|
+
once: { type: "boolean" },
|
|
27
30
|
},
|
|
28
31
|
});
|
|
29
32
|
const [target, ...extra] = positionals;
|
|
@@ -40,35 +43,12 @@ export const parsePreviewArgs = (args) => {
|
|
|
40
43
|
? { surface: values.surface }
|
|
41
44
|
: {}),
|
|
42
45
|
...(values.channel !== undefined ? { channel: values.channel } : {}),
|
|
46
|
+
...(values.once ? { once: true } : {}),
|
|
43
47
|
};
|
|
44
48
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
run: ({ name, files, cases, options, api, log }) => {
|
|
49
|
-
const channel = options.channel ?? process.env.MAWARU_PREVIEW_CHANNEL;
|
|
50
|
-
if (!channel) {
|
|
51
|
-
throw new Error("送り先のチャンネルを --channel か repo の .env の MAWARU_PREVIEW_CHANNEL で指定してください(#名前 かチャンネル ID)");
|
|
52
|
-
}
|
|
53
|
-
return previewSlack({
|
|
54
|
-
name,
|
|
55
|
-
files,
|
|
56
|
-
cases: cases,
|
|
57
|
-
surfaces: options.surface ? [options.surface] : SLACK_SURFACES,
|
|
58
|
-
channel,
|
|
59
|
-
api,
|
|
60
|
-
log,
|
|
61
|
-
});
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
// 終了コードを返す(描画に失敗したものがあれば 1)。テストはここを呼ぶ
|
|
66
|
-
export const runPreviewWith = async (params) => {
|
|
67
|
-
const { root, options, api, log } = params;
|
|
68
|
-
const view = resolveView(root, options.target);
|
|
69
|
-
const files = readViewFiles(root, view);
|
|
70
|
-
const previewer = PREVIEWERS[view.kind];
|
|
71
|
-
const { cases } = expectOk("ケースの読み込み", await api("POST", previewer.casesPath, { files }));
|
|
49
|
+
// Slack ビュー:cases.ts を mawaru API で評価し、絞り込んだケース×面を Slack に送る
|
|
50
|
+
const previewSlackView = async ({ view, files, options, api, log, }) => {
|
|
51
|
+
const { cases } = expectOk("ケースの読み込み", await api("POST", "/views/slack/cases", { files }));
|
|
72
52
|
const picked = options.case === undefined
|
|
73
53
|
? cases
|
|
74
54
|
: cases.filter((c) => c.name === options.case);
|
|
@@ -77,15 +57,92 @@ export const runPreviewWith = async (params) => {
|
|
|
77
57
|
? "cases.ts にケースがありません"
|
|
78
58
|
: `ケース「${options.case}」がありません(ある: ${cases.map((c) => c.name).join(", ")})`);
|
|
79
59
|
}
|
|
80
|
-
const
|
|
60
|
+
const channel = options.channel ?? process.env.MAWARU_PREVIEW_CHANNEL;
|
|
61
|
+
if (!channel) {
|
|
62
|
+
throw new Error("送り先のチャンネルを --channel か repo の .env の MAWARU_PREVIEW_CHANNEL で指定してください(#名前 かチャンネル ID)");
|
|
63
|
+
}
|
|
64
|
+
return previewSlack({
|
|
81
65
|
name: view.name,
|
|
82
66
|
files,
|
|
83
67
|
cases: picked,
|
|
68
|
+
surfaces: options.surface ? [options.surface] : SLACK_SURFACES,
|
|
69
|
+
channel,
|
|
70
|
+
api,
|
|
71
|
+
log,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
// human ノードの実装(承認画面):ファイルを mawaru に置いて、本番と同じ承認画面(sandbox iframe)で開く。
|
|
75
|
+
// cases.ts の評価は画面を開いたときに mawaru がやる。保存するたびに置き直し、開いている画面が読み直す
|
|
76
|
+
const previewHuman = async ({ root, view, files, options, api, log, env, }) => {
|
|
77
|
+
const created = expectOk("プレビューの用意", await api("POST", "/previews", {
|
|
78
|
+
kind: "human",
|
|
79
|
+
name: view.name,
|
|
80
|
+
files,
|
|
81
|
+
}));
|
|
82
|
+
if (created.error) {
|
|
83
|
+
log(`✘ human/${view.name} をまとめられません\n${created.error}`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
const url = `${env.appUrl.replace(/\/$/, "")}/previews/${created.id}?tenant=${env.tenantId}`;
|
|
87
|
+
log(`✔ human/${view.name} を開きます: ${url}`);
|
|
88
|
+
env.openUrl(url);
|
|
89
|
+
if (options.once)
|
|
90
|
+
return 0;
|
|
91
|
+
log("保存するたびに送り直します(Ctrl-C で終わる)");
|
|
92
|
+
await env.watch(kindRoot(root, "human"), async () => {
|
|
93
|
+
const res = await api("PUT", `/previews/${created.id}`, {
|
|
94
|
+
files: readViewFiles(root, view),
|
|
95
|
+
});
|
|
96
|
+
if (res.status >= 300) {
|
|
97
|
+
log(`✘ 送り直せませんでした(${res.status})`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const { error } = res.body;
|
|
101
|
+
log(error ? `✘ まとめられません\n${error}` : "✔ 送り直しました");
|
|
102
|
+
});
|
|
103
|
+
return 0;
|
|
104
|
+
};
|
|
105
|
+
const PREVIEWERS = {
|
|
106
|
+
slack: previewSlackView,
|
|
107
|
+
human: previewHuman,
|
|
108
|
+
};
|
|
109
|
+
// 保存の監視:まとめて保存されることがあるので少し待ってから1回送る
|
|
110
|
+
const watchFiles = (dir, onChange) => new Promise((resolve) => {
|
|
111
|
+
let timer;
|
|
112
|
+
let running = Promise.resolve();
|
|
113
|
+
const watcher = watch(dir, { recursive: true }, () => {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
timer = setTimeout(() => {
|
|
116
|
+
running = running.then(onChange);
|
|
117
|
+
}, 300);
|
|
118
|
+
});
|
|
119
|
+
process.once("SIGINT", () => {
|
|
120
|
+
watcher.close();
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
// 終了コードを返す(描画・まとめに失敗したものがあれば 1)。テストはここを呼ぶ
|
|
125
|
+
export const runPreviewWith = async (params) => {
|
|
126
|
+
const { root, options, api, log } = params;
|
|
127
|
+
const view = resolveView(root, options.target);
|
|
128
|
+
const files = readViewFiles(root, view);
|
|
129
|
+
const failed = await PREVIEWERS[view.kind]({
|
|
130
|
+
root,
|
|
131
|
+
view,
|
|
132
|
+
files,
|
|
84
133
|
options,
|
|
85
134
|
api,
|
|
86
135
|
log,
|
|
136
|
+
env: {
|
|
137
|
+
tenantId: params.env?.tenantId ?? "",
|
|
138
|
+
appUrl: params.env?.appUrl ??
|
|
139
|
+
process.env.MAWARU_APP_URL ??
|
|
140
|
+
"https://app.mawaru.ai",
|
|
141
|
+
openUrl: params.env?.openUrl ?? openBrowser,
|
|
142
|
+
watch: params.env?.watch ?? watchFiles,
|
|
143
|
+
},
|
|
87
144
|
});
|
|
88
|
-
if (failed > 0)
|
|
145
|
+
if (view.kind === "slack" && failed > 0)
|
|
89
146
|
log(`失敗 ${failed} 件`);
|
|
90
147
|
return failed > 0 ? 1 : 0;
|
|
91
148
|
};
|
|
@@ -105,5 +162,6 @@ export const runPreview = async (args) => {
|
|
|
105
162
|
options,
|
|
106
163
|
api: mawaruApiFor(credentials, tenantId),
|
|
107
164
|
log: (line) => console.log(line),
|
|
165
|
+
env: { tenantId },
|
|
108
166
|
});
|
|
109
167
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export declare const VIEW_KINDS: readonly ["slack"];
|
|
1
|
+
export declare const VIEW_KINDS: readonly ["slack", "human"];
|
|
2
2
|
export type ViewKind = (typeof VIEW_KINDS)[number];
|
|
3
|
+
export declare const kindRoot: (root: string, kind: ViewKind) => string;
|
|
3
4
|
export type ViewTarget = {
|
|
4
5
|
kind: ViewKind;
|
|
5
6
|
name: string;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
// 手元プレビュー(`mawaru preview`)が読む、実行 repo
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
export const VIEW_KINDS = ["slack"];
|
|
3
|
+
// 手元プレビュー(`mawaru preview`)が読む、実行 repo のファイル:Slack ビューは views/slack/<名前>/、
|
|
4
|
+
// human ノードの実装(承認画面)は nodes/human/<名前>/。描画は mawaru API がやるので、ここは
|
|
5
|
+
// 「対象を決めてファイルを集める」だけで、種類をまたいで共通
|
|
6
|
+
// (docs/tasks/wip/Slackビューの顧客repo配置.md・承認ビューの顧客repo配置.md の「手元プレビュー」)
|
|
7
|
+
// 手元プレビューできる種類(previewCommand.ts の振り分けと揃える)と、その置き場
|
|
8
|
+
export const VIEW_KINDS = ["slack", "human"];
|
|
9
|
+
const KIND_ROOTS = {
|
|
10
|
+
slack: ["views", "slack"],
|
|
11
|
+
human: ["nodes", "human"],
|
|
12
|
+
};
|
|
13
|
+
export const kindRoot = (root, kind) => join(root, ...KIND_ROOTS[kind]);
|
|
9
14
|
const dirsIn = (dir) => {
|
|
10
15
|
try {
|
|
11
16
|
return readdirSync(dir, { withFileTypes: true })
|
|
@@ -17,15 +22,17 @@ const dirsIn = (dir) => {
|
|
|
17
22
|
return [];
|
|
18
23
|
}
|
|
19
24
|
};
|
|
20
|
-
//
|
|
21
|
-
export const listViews = (root) => VIEW_KINDS.flatMap((kind) => dirsIn(
|
|
25
|
+
// 種類ごとの置き場の直下のディレクトリ(`_` 始まりは部品の置き場で対象ではない)。無ければ空
|
|
26
|
+
export const listViews = (root) => VIEW_KINDS.flatMap((kind) => dirsIn(kindRoot(root, kind))
|
|
27
|
+
.filter((name) => !name.startsWith("_"))
|
|
28
|
+
.map((name) => ({ kind, name })));
|
|
22
29
|
const labelOf = (view) => `${view.kind}/${view.name}`;
|
|
23
|
-
// 指定(views/slack/pr・slack/pr・pr
|
|
24
|
-
// (../
|
|
30
|
+
// 指定(views/slack/pr・slack/pr・nodes/human/review・human/review・pr)を対象に決める。一覧にあるものだけを受ける
|
|
31
|
+
// (../ でディレクトリの外を読ませない)。決まらなければ、ある対象を添えて失敗する
|
|
25
32
|
export const resolveView = (root, spec) => {
|
|
26
33
|
const normalized = spec
|
|
27
34
|
.replace(/^\.\//, "")
|
|
28
|
-
.replace(/^views\//, "")
|
|
35
|
+
.replace(/^(views|nodes)\//, "")
|
|
29
36
|
.replace(/\/+$/, "");
|
|
30
37
|
const views = listViews(root);
|
|
31
38
|
const found = normalized.includes("/")
|
|
@@ -34,17 +41,14 @@ export const resolveView = (root, spec) => {
|
|
|
34
41
|
if (found.length === 1 && found[0])
|
|
35
42
|
return found[0];
|
|
36
43
|
const known = views.length === 0
|
|
37
|
-
? "views/
|
|
44
|
+
? "views/slack/・nodes/human/ に対象がありません"
|
|
38
45
|
: `ある対象: ${views.map(labelOf).join(", ")}`;
|
|
39
46
|
throw new Error(found.length > 1
|
|
40
47
|
? `${spec} は複数の種類にあります。<種類>/<名前> で指定してください(${found.map(labelOf).join(", ")})`
|
|
41
48
|
: `${spec} が見つかりません(${known})`);
|
|
42
49
|
};
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
export const readViewFiles = (root, view) => {
|
|
46
|
-
const dir = join(root, "views", view.kind, view.name);
|
|
47
|
-
const files = {};
|
|
50
|
+
// dir の下の、拡張子が extensions のファイル(サブディレクトリも)。キーは prefix からの相対パス
|
|
51
|
+
const collect = (dir, prefix, extensions, files) => {
|
|
48
52
|
const walk = (relative) => {
|
|
49
53
|
for (const entry of readdirSync(join(dir, relative), {
|
|
50
54
|
withFileTypes: true,
|
|
@@ -54,11 +58,30 @@ export const readViewFiles = (root, view) => {
|
|
|
54
58
|
const path = relative ? `${relative}/${entry.name}` : entry.name;
|
|
55
59
|
if (entry.isDirectory())
|
|
56
60
|
walk(path);
|
|
57
|
-
else if (entry.isFile() &&
|
|
58
|
-
|
|
61
|
+
else if (entry.isFile() &&
|
|
62
|
+
extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
63
|
+
files[`${prefix}${path}`] = readFileSync(join(dir, path), "utf8");
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
};
|
|
62
67
|
walk("");
|
|
68
|
+
};
|
|
69
|
+
// mawaru API へ送る files(上限の検証は API 側)。
|
|
70
|
+
// - Slack ビュー:ビューのディレクトリの .ts(キーはビューのディレクトリからの相対パス)
|
|
71
|
+
// - human ノードの実装:nodes/human/<名前>/ と nodes/human/_<部品>/ の .ts / .tsx(キーは nodes/human/ からの相対パス。
|
|
72
|
+
// 本番と同じく、部品は複数の human ノードで共有できる)
|
|
73
|
+
export const readViewFiles = (root, view) => {
|
|
74
|
+
const kindDir = kindRoot(root, view.kind);
|
|
75
|
+
const files = {};
|
|
76
|
+
if (view.kind === "slack") {
|
|
77
|
+
collect(join(kindDir, view.name), "", [".ts"], files);
|
|
78
|
+
return files;
|
|
79
|
+
}
|
|
80
|
+
for (const dir of [
|
|
81
|
+
view.name,
|
|
82
|
+
...dirsIn(kindDir).filter((name) => name.startsWith("_")),
|
|
83
|
+
]) {
|
|
84
|
+
collect(join(kindDir, dir), `${dir}/`, [".ts", ".tsx"], files);
|
|
85
|
+
}
|
|
63
86
|
return files;
|
|
64
87
|
};
|
|
@@ -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,13 @@ 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>;
|
|
56
|
+
declaration: z.ZodOptional<z.ZodObject<{
|
|
57
|
+
in: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
58
|
+
approve: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
59
|
+
reject: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
60
|
+
}, z.core.$strip>>;
|
|
55
61
|
assignment: z.ZodObject<{
|
|
56
62
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
57
63
|
fixed: "fixed";
|
|
@@ -194,7 +200,13 @@ export declare const graphNodeSchema: z.ZodObject<{
|
|
|
194
200
|
ref: z.ZodOptional<z.ZodString>;
|
|
195
201
|
}, z.core.$strip>>;
|
|
196
202
|
human: z.ZodOptional<z.ZodObject<{
|
|
197
|
-
|
|
203
|
+
human_dir: z.ZodOptional<z.ZodString>;
|
|
204
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
205
|
+
declaration: z.ZodOptional<z.ZodObject<{
|
|
206
|
+
in: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
207
|
+
approve: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
208
|
+
reject: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
209
|
+
}, z.core.$strip>>;
|
|
198
210
|
assignment: z.ZodObject<{
|
|
199
211
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
200
212
|
fixed: "fixed";
|
|
@@ -295,7 +307,13 @@ export declare const saveGraphBodySchema: z.ZodObject<{
|
|
|
295
307
|
ref: z.ZodOptional<z.ZodString>;
|
|
296
308
|
}, z.core.$strip>>;
|
|
297
309
|
human: z.ZodOptional<z.ZodObject<{
|
|
298
|
-
|
|
310
|
+
human_dir: z.ZodOptional<z.ZodString>;
|
|
311
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
312
|
+
declaration: z.ZodOptional<z.ZodObject<{
|
|
313
|
+
in: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
314
|
+
approve: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
315
|
+
reject: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
316
|
+
}, z.core.$strip>>;
|
|
299
317
|
assignment: z.ZodObject<{
|
|
300
318
|
strategy: z.ZodDefault<z.ZodEnum<{
|
|
301
319
|
fixed: "fixed";
|
package/dist/_schemas/graph.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { humanDeclarationSchema } from "./human-manifest.js";
|
|
2
3
|
import { graphPortSchema, jsonSchemaSchema, nodeKindSchema, portKeySchema, } from "./node.js";
|
|
3
4
|
import { connectionAccepted, hasInboundConflict, kindPortsViolation, resolvePortSchemas, UNDECLARED_SCHEMA, } from "./port-spec.js";
|
|
4
5
|
// refs=ループ変数の袋(key→任意の JSON 値。上流出力の参照(refs).md)。
|
|
@@ -116,10 +117,20 @@ export const assignMemberSchema = z.union([z.uuid(), z.literal(RUN_STARTER)]);
|
|
|
116
117
|
// メンバーが tenant の membership であることの検証は保存 API 側で行う
|
|
117
118
|
// (docs/tasks/backlog/Human担当者の高度化(複数人承認とラウンドロビン).md)
|
|
118
119
|
export const humanConfigSchema = z.object({
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
120
|
+
// 実装(承認画面と入出力の宣言)の置き場所:実行 repo の nodes/human/ 直下のディレクトリ名。
|
|
121
|
+
// program_dir / ai_dir と同じ。省略時は mawaru 内蔵の標準の承認画面
|
|
122
|
+
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「human ノードの実装として置く」)
|
|
123
|
+
human_dir: z
|
|
124
|
+
.string()
|
|
125
|
+
.min(1)
|
|
126
|
+
.refine((v) => !v.includes("/") && !v.includes("\\") && v !== "." && v !== "..", "human_dir は nodes/human/ 直下のディレクトリ名で指定してください")
|
|
127
|
+
.optional(),
|
|
128
|
+
// 読むブランチ(省略時はデフォルトブランチ)。publish でその先頭の commit に固定する
|
|
129
|
+
ref: z.string().min(1).optional(),
|
|
130
|
+
// 宣言の写し(in / approve / reject の型)。伝播はこれを読む。human_dir を選んだときに
|
|
131
|
+
// 一覧 API(GET /tenants/:id/github/humans)の宣言を入れる。省略すると保存時に mawaru が repo の
|
|
132
|
+
// config.json から読む(下書きに同じ human_dir の同じノードがあればその写しを保つ)
|
|
133
|
+
declaration: humanDeclarationSchema.optional(),
|
|
123
134
|
assignment: z
|
|
124
135
|
.object({
|
|
125
136
|
strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
|
|
@@ -236,8 +247,8 @@ export const graphNodeSchema = z.object({
|
|
|
236
247
|
// 表示アイコン(全 kind 共通の任意設定。human はフロントが担当から導出するので通常設定しない)
|
|
237
248
|
icon: nodeIconSchema.optional(),
|
|
238
249
|
// Slack ビュー(全 kind 共通の任意設定。実行中(human は承認待ち)・完了メッセージの見せ方)。
|
|
239
|
-
// human の view と同じ「不透明なキー」で、選べる key は GET slack
|
|
240
|
-
// <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/slack
|
|
250
|
+
// human の view と同じ「不透明なキー」で、選べる key は GET views/slack が返す。
|
|
251
|
+
// <slug>/<name> は実行 repo の views/slack/<name>/(@mawaru/sdk/views/slack の契約で書く)。
|
|
241
252
|
// 保存時に mawaru が見るのは common/* の実在と <slug>/* の可視性だけ。
|
|
242
253
|
// 省略=承認待ちは承認ボタンだけ・完了は ✅ の1行(データは出さない)。
|
|
243
254
|
// ポートの型には一切影響しない
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type JsonSchema } from "./node.js";
|
|
3
|
+
import { type DeclaredPortSchema } from "./port-spec.js";
|
|
4
|
+
export type HumanDeclaration = {
|
|
5
|
+
in: DeclaredPortSchema;
|
|
6
|
+
approve: DeclaredPortSchema;
|
|
7
|
+
reject: JsonSchema;
|
|
8
|
+
};
|
|
9
|
+
export declare const humanDeclarationSchema: z.ZodObject<{
|
|
10
|
+
in: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
11
|
+
approve: z.ZodUnion<readonly [z.ZodLiteral<"T">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
|
|
12
|
+
reject: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export declare const STANDARD_HUMAN_DECLARATION: HumanDeclaration;
|
|
15
|
+
export declare const humanManifestSchema: z.ZodObject<{
|
|
16
|
+
inputs: z.ZodOptional<z.ZodObject<{
|
|
17
|
+
in: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
19
|
+
}, z.core.$strict>>;
|
|
20
|
+
}, z.core.$strict>>;
|
|
21
|
+
outputs: z.ZodOptional<z.ZodObject<{
|
|
22
|
+
approve: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
23
|
+
reject: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
24
|
+
}, z.core.$strict>>;
|
|
25
|
+
}, z.core.$strict>;
|
|
26
|
+
export type HumanManifest = z.infer<typeof humanManifestSchema>;
|
|
27
|
+
export declare const humanDeclarationOfManifest: (config: HumanManifest) => HumanDeclaration;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { jsonSchemaSchema } from "./node.js";
|
|
3
|
+
import { COPY_UPSTREAM_SCHEMA, instructionSchema, } from "./port-spec.js";
|
|
4
|
+
// 宣言に書ける type。file は x-mawaru-type マーカー付きの object
|
|
5
|
+
const DECLARED_TYPES = [
|
|
6
|
+
"object",
|
|
7
|
+
"array",
|
|
8
|
+
"string",
|
|
9
|
+
"number",
|
|
10
|
+
"integer",
|
|
11
|
+
"boolean",
|
|
12
|
+
];
|
|
13
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
// JSON Schema の形の崩れを場所つきで返す。接続の比較(schemaFit)が扱える形だけを通す:
|
|
15
|
+
// 顧客 repo の宣言は mawaru のレビューを通らないので、壊れた型がノードに写る前に止める
|
|
16
|
+
const schemaIssues = (schema, path) => {
|
|
17
|
+
const issues = [];
|
|
18
|
+
const { type, properties, required, items } = schema;
|
|
19
|
+
if (Array.isArray(type)) {
|
|
20
|
+
issues.push({
|
|
21
|
+
path: [...path, "type"],
|
|
22
|
+
message: "配列(nullable)は使えません。任意の値は required から外してください",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else if (type !== undefined &&
|
|
26
|
+
!DECLARED_TYPES.includes(type)) {
|
|
27
|
+
issues.push({
|
|
28
|
+
path: [...path, "type"],
|
|
29
|
+
message: `type は ${DECLARED_TYPES.join(" / ")} のどれかにしてください(${String(type)})`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else if (type === undefined &&
|
|
33
|
+
(properties !== undefined || items !== undefined)) {
|
|
34
|
+
issues.push({
|
|
35
|
+
path: [...path, "type"],
|
|
36
|
+
message: "properties / items を書くときは type も書いてください",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (properties !== undefined) {
|
|
40
|
+
if (!isPlainObject(properties)) {
|
|
41
|
+
issues.push({
|
|
42
|
+
path: [...path, "properties"],
|
|
43
|
+
message: "properties はオブジェクトで書いてください",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
48
|
+
if (!isPlainObject(child)) {
|
|
49
|
+
issues.push({
|
|
50
|
+
path: [...path, "properties", key],
|
|
51
|
+
message: "スキーマはオブジェクトで書いてください",
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
issues.push(...schemaIssues(child, [...path, "properties", key]));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (required !== undefined &&
|
|
60
|
+
!(Array.isArray(required) && required.every((r) => typeof r === "string"))) {
|
|
61
|
+
issues.push({
|
|
62
|
+
path: [...path, "required"],
|
|
63
|
+
message: "required は文字列の配列で書いてください",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (items !== undefined) {
|
|
67
|
+
if (!isPlainObject(items)) {
|
|
68
|
+
issues.push({
|
|
69
|
+
path: [...path, "items"],
|
|
70
|
+
message: "items はスキーマ(オブジェクト)で書いてください",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
issues.push(...schemaIssues(items, [...path, "items"]));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return issues;
|
|
78
|
+
};
|
|
79
|
+
// 宣言に書く1つの型(検査つき)
|
|
80
|
+
const checkedSchema = jsonSchemaSchema.superRefine((schema, ctx) => {
|
|
81
|
+
for (const issue of schemaIssues(schema, []))
|
|
82
|
+
ctx.addIssue({ code: "custom", ...issue });
|
|
83
|
+
});
|
|
84
|
+
const declaredPortSchema = z.union([
|
|
85
|
+
z.literal(COPY_UPSTREAM_SCHEMA),
|
|
86
|
+
checkedSchema,
|
|
87
|
+
]);
|
|
88
|
+
export const humanDeclarationSchema = z.object({
|
|
89
|
+
in: declaredPortSchema,
|
|
90
|
+
approve: declaredPortSchema,
|
|
91
|
+
reject: checkedSchema,
|
|
92
|
+
});
|
|
93
|
+
// 標準ビュー(common/standard)の宣言:上流の型をそのまま受けて出し、差し戻しは標準の指示フォーム
|
|
94
|
+
export const STANDARD_HUMAN_DECLARATION = {
|
|
95
|
+
in: COPY_UPSTREAM_SCHEMA,
|
|
96
|
+
approve: COPY_UPSTREAM_SCHEMA,
|
|
97
|
+
reject: instructionSchema(),
|
|
98
|
+
};
|
|
99
|
+
// ---- 顧客 repo の nodes/human/<dir>/config.json ----
|
|
100
|
+
// 形は AI の config.json に揃える(inputs の値は { schema }、outputs の値は素の JSON Schema)。
|
|
101
|
+
// 口は in / approve / reject 固定。省略した口は STANDARD_HUMAN_DECLARATION と同じ扱い
|
|
102
|
+
export const humanManifestSchema = z
|
|
103
|
+
.object({
|
|
104
|
+
inputs: z
|
|
105
|
+
.object({ in: z.object({ schema: checkedSchema }).strict().optional() })
|
|
106
|
+
.strict()
|
|
107
|
+
.optional(),
|
|
108
|
+
outputs: z
|
|
109
|
+
.object({
|
|
110
|
+
approve: checkedSchema.optional(),
|
|
111
|
+
reject: checkedSchema.optional(),
|
|
112
|
+
})
|
|
113
|
+
.strict()
|
|
114
|
+
.optional(),
|
|
115
|
+
})
|
|
116
|
+
.strict();
|
|
117
|
+
export const humanDeclarationOfManifest = (config) => ({
|
|
118
|
+
in: config.inputs?.in?.schema ?? COPY_UPSTREAM_SCHEMA,
|
|
119
|
+
approve: config.outputs?.approve ?? COPY_UPSTREAM_SCHEMA,
|
|
120
|
+
reject: config.outputs?.reject ?? instructionSchema(),
|
|
121
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { HumanDeclaration } from "./human-manifest.js";
|
|
2
3
|
import type { GraphPort, JsonSchema } from "./node.js";
|
|
3
4
|
export declare const UNDECLARED_SCHEMA: JsonSchema;
|
|
4
5
|
export declare const isUndeclaredSchema: (schema: JsonSchema) => boolean;
|
|
@@ -55,6 +56,9 @@ type PropagationNode = {
|
|
|
55
56
|
id: string;
|
|
56
57
|
kind: string;
|
|
57
58
|
ports: GraphPort[];
|
|
59
|
+
human?: {
|
|
60
|
+
declaration?: HumanDeclaration;
|
|
61
|
+
} | null;
|
|
58
62
|
};
|
|
59
63
|
type PortLink = {
|
|
60
64
|
from_port_id: string;
|
|
@@ -62,17 +66,10 @@ type PortLink = {
|
|
|
62
66
|
};
|
|
63
67
|
export declare const COPY_UPSTREAM_SCHEMA = "T";
|
|
64
68
|
export type DeclaredPortSchema = JsonSchema | typeof COPY_UPSTREAM_SCHEMA;
|
|
65
|
-
export
|
|
66
|
-
viewPortSchemasOf?: (node: N) => {
|
|
67
|
-
in: DeclaredPortSchema;
|
|
68
|
-
approve: DeclaredPortSchema;
|
|
69
|
-
reject: JsonSchema;
|
|
70
|
-
} | null | undefined;
|
|
71
|
-
};
|
|
72
|
-
export declare const resolvePortSchemas: <N extends PropagationNode>(nodes: N[], connections: PortLink[], options?: PropagationOptions<N>) => Map<string, JsonSchema>;
|
|
69
|
+
export declare const resolvePortSchemas: <N extends PropagationNode>(nodes: N[], connections: PortLink[]) => Map<string, JsonSchema>;
|
|
73
70
|
export declare const resolveFlexiblePorts: (nodes: PropagationNode[], connections: PortLink[]) => Set<string>;
|
|
74
71
|
export declare const connectionAccepted: (from: JsonSchema, to: Pick<GraphPort, "schema" | "flexible">) => boolean;
|
|
75
|
-
export declare const propagateDerivedPorts: <N extends PropagationNode>(nodes: N[], connections: PortLink[]
|
|
72
|
+
export declare const propagateDerivedPorts: <N extends PropagationNode>(nodes: N[], connections: PortLink[]) => N[];
|
|
76
73
|
export type SchemaFit = "ok" | "mismatch" | "unknown";
|
|
77
74
|
export declare const schemaFit: (sub: JsonSchema, sup: JsonSchema) => SchemaFit;
|
|
78
75
|
export declare const isSchemaSubset: (sub: JsonSchema, sup: JsonSchema) => boolean;
|