@mawaru/sdk 0.40.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.
Files changed (56) hide show
  1. package/_schemas/graph.ts +0 -5
  2. package/_schemas/human-manifest.test.ts +39 -101
  3. package/_schemas/human-manifest.ts +25 -152
  4. package/_schemas/port-spec.test.ts +33 -92
  5. package/_schemas/port-spec.ts +35 -44
  6. package/dist/_cli/login.js +15 -1
  7. package/dist/_cli/previewApi.d.ts +1 -1
  8. package/dist/_cli/previewCommand.d.ts +0 -8
  9. package/dist/_cli/previewCommand.js +37 -95
  10. package/dist/_cli/previewFiles.d.ts +1 -2
  11. package/dist/_cli/previewFiles.js +18 -42
  12. package/dist/_schemas/graph.d.ts +0 -15
  13. package/dist/_schemas/graph.js +0 -5
  14. package/dist/_schemas/human-manifest.d.ts +6 -24
  15. package/dist/_schemas/human-manifest.js +24 -116
  16. package/dist/_schemas/port-spec.d.ts +1 -4
  17. package/dist/_schemas/port-spec.js +25 -34
  18. package/dist/cli.js +1 -2
  19. package/dist/typegen.d.ts +2 -2
  20. package/dist/typegen.js +21 -21
  21. package/dist/validate.js +9 -7
  22. package/dist/views/_humanProps.d.ts +10 -0
  23. package/dist/views/_humanProps.js +11 -0
  24. package/dist/views/{index.d.ts → components.d.ts} +0 -1
  25. package/dist/views/components.js +12 -0
  26. package/dist/views/host.d.ts +8 -3
  27. package/dist/views/host.js +5 -5
  28. package/dist/views/human.d.ts +49 -68
  29. package/dist/views/human.js +5 -6
  30. package/dist/views/protocol.d.ts +13 -3
  31. package/dist/views/protocol.js +2 -2
  32. package/dist/views/runtime.d.ts +2 -3
  33. package/dist/views/runtime.js +24 -24
  34. package/dist/views/sample.d.ts +22 -0
  35. package/dist/views/sample.js +38 -0
  36. package/docs/development.md +30 -31
  37. package/package.json +5 -1
  38. package/skills/create-loop/SKILL.md +3 -2
  39. package/templates/CLAUDE.md +2 -0
  40. package/views/_humanProps.ts +27 -0
  41. package/views/components.ts +16 -0
  42. package/views/host.ts +9 -5
  43. package/views/human.ts +71 -79
  44. package/views/protocol.ts +29 -6
  45. package/views/runtime.tsx +36 -45
  46. package/views/sample.tsx +94 -0
  47. package/dist/_cli/openBrowser.d.ts +0 -1
  48. package/dist/_cli/openBrowser.js +0 -15
  49. package/dist/views/_approval/ApprovalBar.d.ts +0 -20
  50. package/dist/views/_approval/ApprovalBar.js +0 -74
  51. package/dist/views/_approvalActions.d.ts +0 -18
  52. package/dist/views/_approvalActions.js +0 -10
  53. package/dist/views/index.js +0 -12
  54. package/views/_approval/ApprovalBar.tsx +0 -239
  55. package/views/_approvalActions.tsx +0 -31
  56. package/views/index.ts +0 -16
@@ -1,9 +1,8 @@
1
1
  import { z } from "zod"
2
- import type { HumanDeclaration } from "./human-manifest.js"
3
2
  import type { GraphPort, JsonSchema, PortIo } from "./node.js"
4
3
 
5
4
  // ポートの kind 別型システム(docs/tasks/done/ポートのkind別仕様.md)。
6
- // 型の決まり方は2分類:宣言(program / ai の in・out。human は実装(nodes/human/)の宣言を転写)/
5
+ // 型の決まり方は2分類:宣言(program / ai と、実装(nodes/human/<dir>/)を指す human の in・out)/
7
6
  // 導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)。
8
7
  // mawaru がコードで型を固定するポートは無い
9
8
  // (docs/tasks/done/ポート型固定の撤去とセッション再開のポート属性化.md)
@@ -266,9 +265,10 @@ export const kindPortsViolation = (
266
265
  // ---- 型伝播(導出ポートの解決) ----
267
266
  // 接続の両端は同じスキーマになる(値入力フォームのSchemaForm統一.md)。
268
267
  // wait は素通し(パラメトリック)ノード:in は接続元の out、out は in と同型。
269
- // human は実装(nodes/human/)の宣言を転写("T" は素通しと同じ挙動。reject もビューの宣言)。
270
- // 宣言 out(program / ai)は触らない。
271
- // program / ai の in は**全ての口が**接続鏡映(sticky):接続元 out が具体型のときだけ
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 が具体型のときだけ
272
272
  // 上書きし、未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の
273
273
  // config.json へも書き戻される。docs/tasks/done/ポート編集のrepo書き戻し同期.md)
274
274
 
@@ -329,19 +329,11 @@ type PropagationNode = {
329
329
  id: string
330
330
  kind: string
331
331
  ports: GraphPort[]
332
- // human のときだけ意味を持つ:実装(nodes/human/<dir>/)の宣言の写し(node_humans.declaration)。
333
- // 無ければ in・approve は上流のコピー、reject は触らない
334
- human?: { declaration?: HumanDeclaration } | null
332
+ // human のときだけ意味を持つ:実装(nodes/human/<dir>/)を指しているか
333
+ human?: { human_dir?: string | null } | null
335
334
  }
336
335
  type PortLink = { from_port_id: string; to_port_id: string }
337
336
 
338
- // 「上流の out をそのままコピーする」(in)/「in と同型」(approve)を表す指示。
339
- // 承認ビューのカタログ(@mawaru/common)の COPY_UPSTREAM と同じ値
340
- export const COPY_UPSTREAM_SCHEMA = "T"
341
-
342
- // ポートの型宣言:具体型か、上流からのコピー指示か
343
- export type DeclaredPortSchema = JsonSchema | typeof COPY_UPSTREAM_SCHEMA
344
-
345
337
  // end も導出ノード(各 in は上流から、out main は未宣言)。個別処理は
346
338
  // resolvePortSchemas 内で行うが、初期スキーマを未宣言にするためここに含める。
347
339
  // start も導出ノード:out main は接続先の in から逆向きに転写し、in は out と同型
@@ -352,10 +344,22 @@ const DERIVED_KINDS = new Set(["wait", "human", "end", "start", "join"])
352
344
  // in を接続鏡映(sticky)するノード:接続元 out が具体型のときだけ上書き
353
345
  const MIRROR_KINDS = new Set(["program", "ai"])
354
346
 
355
- const isDerivedPort = (kind: string, port: Pick<GraphPort, "io" | "key">) =>
356
- DERIVED_KINDS.has(kind) &&
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) &&
357
361
  (port.io === "in" ||
358
- (kind !== "human" && port.key === "main") ||
362
+ (node.kind !== "human" && port.key === "main") ||
359
363
  port.key === "approve" ||
360
364
  port.key === "reject")
361
365
 
@@ -375,7 +379,7 @@ export const resolvePortSchemas = <N extends PropagationNode>(
375
379
  kindByPortId.set(port.id, node.kind)
376
380
  resolved.set(
377
381
  port.id,
378
- isDerivedPort(node.kind, port) ? UNDECLARED_SCHEMA : port.schema,
382
+ isDerivedPort(node, port) ? UNDECLARED_SCHEMA : port.schema,
379
383
  )
380
384
  }
381
385
  }
@@ -466,9 +470,9 @@ export const resolvePortSchemas = <N extends PropagationNode>(
466
470
  }
467
471
  continue
468
472
  }
469
- const isMirror = MIRROR_KINDS.has(node.kind)
470
- if (!DERIVED_KINDS.has(node.kind) && !isMirror) continue
471
- // 鏡映(program / ai):全ての in が対象。具体型が流れてきたときだけ上書きする
473
+ const isMirror = isMirrorNode(node)
474
+ if (!isDerivedNode(node) && !isMirror) continue
475
+ // 鏡映(program / ai / 実装を指す human):全ての in が対象。具体型が流れてきたときだけ上書きする
472
476
  // (宣言を未宣言で潰さない)。ai の in はどの口も対等(instruction の特別扱いは無い)
473
477
  if (isMirror) {
474
478
  for (const port of node.ports) {
@@ -483,37 +487,24 @@ export const resolvePortSchemas = <N extends PropagationNode>(
483
487
  }
484
488
  continue
485
489
  }
486
- // 導出(wait / human):in は上流から
490
+ // 導出(wait / 実装を指さない human):in は上流から、out(main・approve)は in と同型。
491
+ // human の reject は標準の承認画面の差し戻し(指示+参考ファイル)。
492
+ // 上流の out は書き換えない(逆伝播すると fan-out 先の型割れが config.json へ書き戻され、
493
+ // 元の出力宣言を黙って壊す。ズレは接続の型エラーで見せる)
487
494
  const inPort = node.ports.find((p) => p.io === "in")
488
495
  if (!inPort) continue
489
496
  const sources = inbound.get(inPort.id) ?? []
490
497
  const inSchema = mergeInboundSchemas(
491
498
  sources.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA),
492
499
  )
493
- // 実装(nodes/human/<dir>/)の宣言を human のポートへそのまま転写する(上流ノードの kind は見ない)。
494
- // "T" は「上流の out をコピー」(in)/「in と同型」(approve)。reject は具体型の宣言
495
- // のみで、宣言が無ければ未宣言のまま(mawaru が型を焼き込むことはしない)。
496
- // 上流の out は書き換えない:逆伝播すると fan-out 先のビューが食い違ったとき
497
- // 型割れが config.json へ write-through され、元の出力宣言を黙って壊す。
498
- // ズレは接続の型エラーで見せてユーザーの [修正する] で直す
499
- const declared =
500
- node.kind === "human" ? node.human?.declaration : undefined
501
- const effectiveIn =
502
- declared && declared.in !== COPY_UPSTREAM_SCHEMA
503
- ? declared.in
504
- : inSchema
505
- assign(inPort.id, effectiveIn)
500
+ assign(inPort.id, inSchema)
506
501
  for (const port of node.ports) {
507
- if (port.io !== "out" || !isDerivedPort(node.kind, port)) continue
508
- if (node.kind === "human" && port.key === "reject") {
509
- if (declared) assign(port.id, declared.reject)
510
- continue
511
- }
502
+ if (port.io !== "out" || !isDerivedPort(node, port)) continue
512
503
  assign(
513
504
  port.id,
514
- declared && declared.approve !== COPY_UPSTREAM_SCHEMA
515
- ? declared.approve
516
- : effectiveIn,
505
+ node.kind === "human" && port.key === "reject"
506
+ ? instructionSchema()
507
+ : inSchema,
517
508
  )
518
509
  }
519
510
  }
@@ -1,7 +1,7 @@
1
+ import { spawn } from "node:child_process";
1
2
  import { randomUUID } from "node:crypto";
2
3
  import { createServer } from "node:http";
3
4
  import { saveCredentials } from "./credentials.js";
4
- import { openBrowser } from "./openBrowser.js";
5
5
  // `gh auth login` と同じループバック方式(RFC 8252)。
6
6
  // CLI が 127.0.0.1 の一時ポートで待ち受け、ブラウザで mawaru の /cli-auth を開く。
7
7
  // 承認するとブラウザがループバック URL へ**遷移**してトークンを渡す
@@ -11,6 +11,20 @@ const page = (title, message) => `<!doctype html><meta charset="utf-8"><title>${
11
11
  `<body style="font-family:system-ui;display:grid;place-items:center;height:100vh;margin:0">` +
12
12
  `<div style="text-align:center"><h1 style="font-size:1.25rem">${title}</h1>` +
13
13
  `<p style="color:#666">${message}</p></div></body>`;
14
+ // ブラウザを開く(開けなければ URL を出して手で開いてもらう。依存は足さない)
15
+ const openBrowser = (url) => {
16
+ const [command, args] = process.platform === "darwin"
17
+ ? ["open", [url]]
18
+ : process.platform === "win32"
19
+ ? ["cmd", ["/c", "start", "", url]]
20
+ : ["xdg-open", [url]];
21
+ try {
22
+ spawn(command, args, { stdio: "ignore", detached: true }).unref();
23
+ }
24
+ catch {
25
+ // 何もしない(URL は既に印字済み)
26
+ }
27
+ };
14
28
  export const runLogin = async (timeoutMs = 5 * 60 * 1000) => {
15
29
  // 別のローカルプロセスが投げ込んだ応答を受け付けないための照合値
16
30
  const state = randomUUID();
@@ -1,5 +1,5 @@
1
1
  import { type Credentials } from "./credentials.js";
2
- export type MawaruApi = (method: "GET" | "POST" | "PUT", path: string, body?: unknown) => Promise<{
2
+ export type MawaruApi = (method: "GET" | "POST", path: string, body?: unknown) => Promise<{
3
3
  status: number;
4
4
  body: unknown;
5
5
  }>;
@@ -5,22 +5,14 @@ export type PreviewOptions = {
5
5
  case?: string;
6
6
  surface?: SlackSurface;
7
7
  channel?: string;
8
- once?: boolean;
9
8
  };
10
9
  export declare const parsePreviewArgs: (args: string[]) => PreviewOptions;
11
10
  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
- };
18
11
  export declare const runPreviewWith: (params: {
19
12
  root: string;
20
13
  options: PreviewOptions;
21
14
  api: MawaruApi;
22
15
  log: Log;
23
- env?: Partial<PreviewEnv>;
24
16
  }) => Promise<number>;
25
17
  export declare const runPreview: (args: string[]) => Promise<number>;
26
18
  export {};
@@ -1,23 +1,21 @@
1
- import { existsSync, watch } from "node:fs";
1
+ import { existsSync } 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";
6
5
  import { expectOk, mawaruApiFor } from "./previewApi.js";
7
- import { kindRoot, readViewFiles, resolveView, } from "./previewFiles.js";
6
+ import { readViewFiles, resolveView } from "./previewFiles.js";
8
7
  import { previewSlack, SLACK_SURFACES, } from "./previewSlack.js";
9
- // `mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|ID>] [--once]`
8
+ // `mawaru preview <種類>/<名前> [--case <ケース名>] [--surface running|completed] [--channel <#名前|ID>]`
10
9
  // (実行 repo のルートで実行)
11
10
  //
12
11
  // views/<種類>/<名前>/ のビューを、push せずに手元のファイルのまま確かめる。描画は mawaru API がやるので、
13
12
  // `mawaru login` のセッション(web と同じ membership)と、どのテナントのつもりで描くかの
14
13
  // MAWARU_TENANT_ID(repo の .env。create-loop スキルと同じ)が要る。
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回送って終わる)";
14
+ // ファイルの集め方・cases.ts の評価・ケースの絞り込みは種類をまたいで共通で、種類ごとに「確かめ方」
15
+ // (Slack は送る)だけを差し替える
16
+ // (docs/tasks/done/Slackビューの顧客repo配置.md の「手元プレビュー」)
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 に送る)";
21
19
  export const parsePreviewArgs = (args) => {
22
20
  const { values, positionals } = parseArgs({
23
21
  args,
@@ -26,7 +24,6 @@ export const parsePreviewArgs = (args) => {
26
24
  case: { type: "string" },
27
25
  surface: { type: "string" },
28
26
  channel: { type: "string" },
29
- once: { type: "boolean" },
30
27
  },
31
28
  });
32
29
  const [target, ...extra] = positionals;
@@ -43,12 +40,35 @@ export const parsePreviewArgs = (args) => {
43
40
  ? { surface: values.surface }
44
41
  : {}),
45
42
  ...(values.channel !== undefined ? { channel: values.channel } : {}),
46
- ...(values.once ? { once: true } : {}),
47
43
  };
48
44
  };
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 }));
45
+ const PREVIEWERS = {
46
+ slack: {
47
+ casesPath: "/views/slack/cases",
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 }));
52
72
  const picked = options.case === undefined
53
73
  ? cases
54
74
  : cases.filter((c) => c.name === options.case);
@@ -57,92 +77,15 @@ const previewSlackView = async ({ view, files, options, api, log, }) => {
57
77
  ? "cases.ts にケースがありません"
58
78
  : `ケース「${options.case}」がありません(ある: ${cases.map((c) => c.name).join(", ")})`);
59
79
  }
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({
80
+ const failed = await previewer.run({
65
81
  name: view.name,
66
82
  files,
67
83
  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,
133
84
  options,
134
85
  api,
135
86
  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
- },
144
87
  });
145
- if (view.kind === "slack" && failed > 0)
88
+ if (failed > 0)
146
89
  log(`失敗 ${failed} 件`);
147
90
  return failed > 0 ? 1 : 0;
148
91
  };
@@ -162,6 +105,5 @@ export const runPreview = async (args) => {
162
105
  options,
163
106
  api: mawaruApiFor(credentials, tenantId),
164
107
  log: (line) => console.log(line),
165
- env: { tenantId },
166
108
  });
167
109
  };
@@ -1,6 +1,5 @@
1
- export declare const VIEW_KINDS: readonly ["slack", "human"];
1
+ export declare const VIEW_KINDS: readonly ["slack"];
2
2
  export type ViewKind = (typeof VIEW_KINDS)[number];
3
- export declare const kindRoot: (root: string, kind: ViewKind) => string;
4
3
  export type ViewTarget = {
5
4
  kind: ViewKind;
6
5
  name: string;
@@ -1,16 +1,10 @@
1
1
  import { readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
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]);
3
+ // 手元プレビュー(`mawaru preview`)が読む、実行 repo の views/<種類>/<名前>/ のファイル。
4
+ // 描画は mawaru API がやるので、ここは「対象を決めてファイルを集める」だけで、種類をまたいで共通
5
+ // (docs/tasks/done/Slackビューの顧客repo配置.md の「手元プレビュー」)
6
+ // 手元プレビューできるビューの種類(views/ 直下のディレクトリ名。previewCommand.ts の振り分けと揃える)
7
+ export const VIEW_KINDS = ["slack"];
14
8
  const dirsIn = (dir) => {
15
9
  try {
16
10
  return readdirSync(dir, { withFileTypes: true })
@@ -22,17 +16,15 @@ const dirsIn = (dir) => {
22
16
  return [];
23
17
  }
24
18
  };
25
- // 種類ごとの置き場の直下のディレクトリ(`_` 始まりは部品の置き場で対象ではない)。無ければ空
26
- export const listViews = (root) => VIEW_KINDS.flatMap((kind) => dirsIn(kindRoot(root, kind))
27
- .filter((name) => !name.startsWith("_"))
28
- .map((name) => ({ kind, name })));
19
+ // views/<種類>/ 直下のディレクトリ(=ビュー)。無ければ空
20
+ export const listViews = (root) => VIEW_KINDS.flatMap((kind) => dirsIn(join(root, "views", kind)).map((name) => ({ kind, name })));
29
21
  const labelOf = (view) => `${view.kind}/${view.name}`;
30
- // 指定(views/slack/pr・slack/pr・nodes/human/review・human/review・pr)を対象に決める。一覧にあるものだけを受ける
31
- // (../ でディレクトリの外を読ませない)。決まらなければ、ある対象を添えて失敗する
22
+ // 指定(views/slack/pr・slack/pr・pr)をビューに決める。一覧にあるものだけを受ける
23
+ // (../ でディレクトリの外を読ませない)。決まらなければ、あるビューを添えて失敗する
32
24
  export const resolveView = (root, spec) => {
33
25
  const normalized = spec
34
26
  .replace(/^\.\//, "")
35
- .replace(/^(views|nodes)\//, "")
27
+ .replace(/^views\//, "")
36
28
  .replace(/\/+$/, "");
37
29
  const views = listViews(root);
38
30
  const found = normalized.includes("/")
@@ -41,14 +33,17 @@ export const resolveView = (root, spec) => {
41
33
  if (found.length === 1 && found[0])
42
34
  return found[0];
43
35
  const known = views.length === 0
44
- ? "views/slack/・nodes/human/ に対象がありません"
36
+ ? "views/ にビューがありません"
45
37
  : `ある対象: ${views.map(labelOf).join(", ")}`;
46
38
  throw new Error(found.length > 1
47
39
  ? `${spec} は複数の種類にあります。<種類>/<名前> で指定してください(${found.map(labelOf).join(", ")})`
48
40
  : `${spec} が見つかりません(${known})`);
49
41
  };
50
- // dir の下の、拡張子が extensions のファイル(サブディレクトリも)。キーは prefix からの相対パス
51
- const collect = (dir, prefix, extensions, files) => {
42
+ // mawaru API へ送る files({ ビューのディレクトリからの相対パス(/ 区切り): 中身 })。
43
+ // .ts だけをサブディレクトリまで集める(上限の検証は API 側)
44
+ export const readViewFiles = (root, view) => {
45
+ const dir = join(root, "views", view.kind, view.name);
46
+ const files = {};
52
47
  const walk = (relative) => {
53
48
  for (const entry of readdirSync(join(dir, relative), {
54
49
  withFileTypes: true,
@@ -58,30 +53,11 @@ const collect = (dir, prefix, extensions, files) => {
58
53
  const path = relative ? `${relative}/${entry.name}` : entry.name;
59
54
  if (entry.isDirectory())
60
55
  walk(path);
61
- else if (entry.isFile() &&
62
- extensions.some((ext) => entry.name.endsWith(ext))) {
63
- files[`${prefix}${path}`] = readFileSync(join(dir, path), "utf8");
56
+ else if (entry.isFile() && entry.name.endsWith(".ts")) {
57
+ files[path] = readFileSync(join(dir, path), "utf8");
64
58
  }
65
59
  }
66
60
  };
67
61
  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
- }
86
62
  return files;
87
63
  };
@@ -53,11 +53,6 @@ export type AssignMember = z.infer<typeof assignMemberSchema>;
53
53
  export declare const humanConfigSchema: z.ZodObject<{
54
54
  human_dir: z.ZodOptional<z.ZodString>;
55
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>>;
61
56
  assignment: z.ZodObject<{
62
57
  strategy: z.ZodDefault<z.ZodEnum<{
63
58
  fixed: "fixed";
@@ -202,11 +197,6 @@ export declare const graphNodeSchema: z.ZodObject<{
202
197
  human: z.ZodOptional<z.ZodObject<{
203
198
  human_dir: z.ZodOptional<z.ZodString>;
204
199
  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>>;
210
200
  assignment: z.ZodObject<{
211
201
  strategy: z.ZodDefault<z.ZodEnum<{
212
202
  fixed: "fixed";
@@ -309,11 +299,6 @@ export declare const saveGraphBodySchema: z.ZodObject<{
309
299
  human: z.ZodOptional<z.ZodObject<{
310
300
  human_dir: z.ZodOptional<z.ZodString>;
311
301
  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>>;
317
302
  assignment: z.ZodObject<{
318
303
  strategy: z.ZodDefault<z.ZodEnum<{
319
304
  fixed: "fixed";
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
- import { humanDeclarationSchema } from "./human-manifest.js";
3
2
  import { graphPortSchema, jsonSchemaSchema, nodeKindSchema, portKeySchema, } from "./node.js";
4
3
  import { connectionAccepted, hasInboundConflict, kindPortsViolation, resolvePortSchemas, UNDECLARED_SCHEMA, } from "./port-spec.js";
5
4
  // refs=ループ変数の袋(key→任意の JSON 値。上流出力の参照(refs).md)。
@@ -127,10 +126,6 @@ export const humanConfigSchema = z.object({
127
126
  .optional(),
128
127
  // 読むブランチ(省略時はデフォルトブランチ)。publish でその先頭の commit に固定する
129
128
  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(),
134
129
  assignment: z
135
130
  .object({
136
131
  strategy: z.enum(["fixed", "round_robin"]).default("fixed"),
@@ -1,27 +1,9 @@
1
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
2
  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>;
3
+ inputs: z.ZodRecord<z.ZodString, z.ZodObject<{
4
+ schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
5
+ }, z.core.$strip>>;
6
+ outputs: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
7
+ refs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
8
+ }, z.core.$strip>;
26
9
  export type HumanManifest = z.infer<typeof humanManifestSchema>;
27
- export declare const humanDeclarationOfManifest: (config: HumanManifest) => HumanDeclaration;