@mawaru/sdk 0.5.0 → 0.9.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.
@@ -2,14 +2,18 @@ import { z } from "zod";
2
2
  // ポートの kind 別型システム(docs/tasks/wip/ポートのkind別仕様.md)。
3
3
  // 型の決まり方は3分類:宣言(program / AI の in・out)/固定(human reject の
4
4
  // content 型)/導出(それ以外。接続元から伝播し、結果は ports.schema にキャッシュする)
5
- // content 型・未接続の導出ポートは any({})で表す。content の意味(エンジンが
6
- // content に変換して注入する口)はスキーマではなく kind が決める
7
- export const ANY_SCHEMA = {};
8
- export const isAnySchema = (schema) => Object.keys(schema).length === 0 || schema.type === undefined;
9
- // ---- file 型(AI ノードのファイル入力。docs/tasks/wip/AIノードのファイル入力(file型と添付).md) ----
5
+ // 「まだ型が決まっていない」=未宣言を {} で表す(未接続・上流が未宣言・fan-in の型割れ)。
6
+ // 「何でも受ける」型は存在しない:AI in は接続元の具体型を鏡映し、差し戻しの指示は
7
+ // instruction ポートの宣言型を持つ(docs/tasks/wip/ポートスキーマのany撲滅.md)
8
+ export const UNDECLARED_SCHEMA = {};
9
+ export const isUndeclaredSchema = (schema) => Object.keys(schema).length === 0 || schema.type === undefined;
10
+ // ---- file 型(docs/tasks/done/AIノードのファイル入力(file型と添付).md) ----
10
11
  // schema 上の file は x-mawaru-type マーカー付きの object(nominal に識別する)。値はバイト
11
- // ではなく storage path の参照(url startAiStep の dispatch 時に発行して client_payload に
12
- // のみ載せる)。ajv strict:false で未知キーワードを無視するので検証は素の JSON Schema のまま。
12
+ // ではなく storage path の参照で、署名 URL はどこにも焼き込まない。
13
+ // **file の位置は名前ではなくこのマーカーで決まる**:runner が宣言スキーマと値を並行走査して
14
+ // 入力はワークスペースへ materialize(パスに置換)、出力はアップロード(参照に置換)する
15
+ // (docs/tasks/wip/ポートスキーマ編集でファイル型を選べるようにする.md)。
16
+ // ajv は strict:false で未知キーワードを無視するので検証は素の JSON Schema のまま。
13
17
  export const FILE_SCHEMA_MARKER = "x-mawaru-type";
14
18
  export const FILE_SCHEMA_TYPE = "file";
15
19
  export const fileSchema = () => ({
@@ -24,6 +28,25 @@ export const fileSchema = () => ({
24
28
  required: ["path", "name"],
25
29
  });
26
30
  export const isFileSchema = (schema) => schema[FILE_SCHEMA_MARKER] === FILE_SCHEMA_TYPE;
31
+ // ---- 差し戻しの指示(AI の instruction ポート / human の reject ポート) ----
32
+ // AI 入力の既存エンベロープ { prompt, files } と同じ形にする。配管が効く根拠は
33
+ // files が file 型として宣言されていることであって、キー名ではない
34
+ // (runner がマーカーで位置を見つけて materialize する)。
35
+ // スクリーンショットを添えて差し戻すケースがあるので文章だけにしない
36
+ // (docs/tasks/wip/ポートスキーマのany撲滅.md)
37
+ export const AI_INSTRUCTION_PORT_KEY = "instruction";
38
+ export const instructionSchema = () => ({
39
+ type: "object",
40
+ properties: {
41
+ prompt: { type: "string", description: "差し戻しの理由(AI への指示)" },
42
+ files: {
43
+ type: "array",
44
+ items: fileSchema(),
45
+ description: "スクリーンショットなどの参考資料",
46
+ },
47
+ },
48
+ required: ["prompt"],
49
+ });
27
50
  // ---- データフォーマットカタログ ----
28
51
  // human 承認 UI の表示・編集フォームの出し分けに使う(テキスト=string(markdown)/
29
52
  // リスト=Array<Row>(列定義つき))。バリデーションには使わない(data は任意の型)
@@ -131,8 +154,34 @@ const FIXED_PORT_KEYS = {
131
154
  wait: { in: ["in"], out: ["main"] },
132
155
  // start は in 1つ(run 入力契約)+ out main 1つの恒等パススルー
133
156
  start: { in: ["in"], out: ["main"] },
157
+ // component は in(子 loop の start.in 契約)+ out main(end.out の戻り値)。
158
+ // スキーマは保存時にサーバが子 loop の契約から焼き込む宣言型
159
+ // (DERIVED/MIRROR には入れない=接続で潰さない)
160
+ component: { in: ["in"], out: ["main"] },
134
161
  };
135
162
  const FREE_OUT_KINDS = new Set(["program", "ai"]);
163
+ // out は自由(複数出口=分岐)だが in の構成は固定。ai は上流データを受ける in と、
164
+ // 差し戻しの指示を受ける instruction の2口(意味の違う入力を1つのポートに
165
+ // fan-in させない。docs/tasks/wip/ポートスキーマのany撲滅.md)
166
+ const FIXED_IN_KEYS = {
167
+ program: ["in"],
168
+ ai: ["in", AI_INSTRUCTION_PORT_KEY],
169
+ };
170
+ export const fixedInKeysOf = (kind) => FIXED_IN_KEYS[kind] ?? null;
171
+ // config.json(manifest)の inputs 検査(適合なら null)。同じルールを二重に持たないため
172
+ // manifest 側はここを参照する(docs/tasks/wip/manifestのin複数対応(instructionポート).md)。
173
+ // **必須は "in" だけ**で、kind が固定する他の in(ai の instruction)は任意:スキーマは
174
+ // constSchemaOf が焼き込む固定型なのでエディタ側で補え、in を2口に分ける前に同期された
175
+ // config.json もそのまま読める
176
+ export const manifestInKeysViolation = (kind, keys) => {
177
+ const allowed = fixedInKeysOf(kind) ?? ["in"];
178
+ if (!keys.includes("in"))
179
+ return 'inputs は "in" を宣言してください';
180
+ const unknown = keys.filter((key) => !allowed.includes(key));
181
+ return unknown.length > 0
182
+ ? `inputs に宣言できない key があります: ${unknown.join(" / ")}(${allowed.join(" / ")} のみ)`
183
+ : null;
184
+ };
136
185
  // kind 別のポート構成違反を返す(適合なら null)。in=1/out>=1 の構造ルールは
137
186
  // graph.ts 側の既存チェックが担い、ここは key の固定だけ見る
138
187
  export const kindPortsViolation = (kind, ports) => {
@@ -150,10 +199,17 @@ export const kindPortsViolation = (kind, ports) => {
150
199
  }
151
200
  const fixed = FIXED_PORT_KEYS[kind];
152
201
  if (!fixed) {
153
- // program / ai(自由 out):in の key だけ "in" 固定
154
- if (FREE_OUT_KINDS.has(kind) &&
155
- ports.some((p) => p.io === "in" && p.key !== "in"))
156
- return `${kind} in ポートの key は "in" です`;
202
+ // program / ai(自由 out):in の構成だけ固定
203
+ const expectIn = FIXED_IN_KEYS[kind];
204
+ if (FREE_OUT_KINDS.has(kind) && expectIn) {
205
+ const inKeys = ports
206
+ .filter((p) => p.io === "in")
207
+ .map((p) => p.key)
208
+ .sort();
209
+ if (inKeys.join(",") !== [...expectIn].sort().join(",")) {
210
+ return `${kind} の in ポートは ${expectIn.join(" / ")} です`;
211
+ }
212
+ }
157
213
  return null;
158
214
  }
159
215
  const keysOf = (io) => ports
@@ -167,8 +223,32 @@ export const kindPortsViolation = (kind, ports) => {
167
223
  }
168
224
  return null;
169
225
  };
170
- // end も導出ノード(各 in は上流から、out main は any)。個別処理は
171
- // resolvePortSchemas 内で行うが、初期スキーマを any にするためここに含める。
226
+ // ---- 型伝播(導出ポートの解決) ----
227
+ // 接続の両端は同じスキーマになる(値入力フォームのSchemaForm統一.md)。
228
+ // wait / human は素通し(パラメトリック)ノード:in は接続元の out、out は in と同型。
229
+ // 例外は human の approve:接続元が AI ノードのときだけ封筒を剥がして data 型になる。
230
+ // human の reject は未宣言のまま(段階2 で instruction エンベロープを宣言する)。
231
+ // 宣言 out(program / ai)は触らない。
232
+ // program / ai の in は接続鏡映(sticky):接続元 out が具体型のときだけ上書きし、
233
+ // 未接続・型割れ・未宣言は現スキーマ(宣言)を維持する(鏡映結果は repo の config.json
234
+ // へも書き戻される。docs/tasks/wip/ポート編集のrepo書き戻し同期.md)
235
+ // 複数の流入(fan-in)から型を1つに決める:未宣言を除いて具体型が1種類ならその型、
236
+ // 0種類なら未宣言。2種類以上は型が割れているので未宣言に落とす
237
+ // (保存時に graph.ts が 422 で弾く。伝播は止めずに固定点へ収束させる)
238
+ export const mergeInboundSchemas = (schemas) => {
239
+ const uniq = [
240
+ ...new Set(schemas
241
+ .filter((s) => !isUndeclaredSchema(s))
242
+ .map((s) => JSON.stringify(s))),
243
+ ];
244
+ return uniq.length === 1 && uniq[0] !== undefined
245
+ ? JSON.parse(uniq[0])
246
+ : UNDECLARED_SCHEMA;
247
+ };
248
+ // 流入の具体型が2種類以上あるか(=同じポートに違う型を流し込んでいる)
249
+ export const hasInboundConflict = (schemas) => new Set(schemas.filter((s) => !isUndeclaredSchema(s)).map((s) => JSON.stringify(s))).size > 1;
250
+ // end も導出ノード(各 in は上流から、out main は未宣言)。個別処理は
251
+ // resolvePortSchemas 内で行うが、初期スキーマを未宣言にするためここに含める。
172
252
  // start も導出ノード:out main は接続先の in から逆向きに転写し、in は out と同型
173
253
  // (run 入力契約=接続先の入力宣言。開始ノードの入力契約を接続先から導出.md)
174
254
  const DERIVED_KINDS = new Set(["wait", "human", "end", "start"]);
@@ -179,18 +259,30 @@ const isDerivedPort = (kind, port) => DERIVED_KINDS.has(kind) &&
179
259
  // AI の in は nodes/ai/<dir>/config.json の inputs.in 由来の宣言型が初期値で、
180
260
  // 接続時は program と同じ鏡映で接続元と同値に同期される(const 扱い廃止。
181
261
  // 接続バリデーションの「ai の in は常に許可」は維持)
182
- const isConstPort = (kind, port) => kind === "human" && port.io === "out" && port.key === "reject";
262
+ // 宣言型で固定されるポート(導出も鏡映もしない)。差し戻しの指示エンベロープを
263
+ // human の出口と AI の入口の両方に焼き込むことで、両端が同じ具体型で一致する
264
+ const constSchemaOf = (kind, port) => {
265
+ if (kind === "human" && port.io === "out" && port.key === "reject") {
266
+ return instructionSchema();
267
+ }
268
+ if (kind === "ai" &&
269
+ port.io === "in" &&
270
+ port.key === AI_INSTRUCTION_PORT_KEY) {
271
+ return instructionSchema();
272
+ }
273
+ return null;
274
+ };
183
275
  // port_id → 解決済みスキーマ。導出は接続を遡って固定点まで反復し、
184
- // 未接続・複数流入・純パラメトリック閉路は any に落とす(MVP の割り切り)
276
+ // 未接続・純パラメトリック閉路・fan-in の型割れは未宣言に落とす
185
277
  export const resolvePortSchemas = (nodes, connections) => {
186
278
  const resolved = new Map();
187
279
  const kindByPortId = new Map();
188
280
  for (const node of nodes) {
189
281
  for (const port of node.ports) {
190
282
  kindByPortId.set(port.id, node.kind);
191
- resolved.set(port.id, isDerivedPort(node.kind, port) || isConstPort(node.kind, port)
192
- ? ANY_SCHEMA
193
- : port.schema);
283
+ const constSchema = constSchemaOf(node.kind, port);
284
+ resolved.set(port.id, constSchema ??
285
+ (isDerivedPort(node.kind, port) ? UNDECLARED_SCHEMA : port.schema));
194
286
  }
195
287
  }
196
288
  // in ポートへの流入(差し戻しの fan-in があり得る。1本のときだけ型を採用)
@@ -215,52 +307,42 @@ export const resolvePortSchemas = (nodes, connections) => {
215
307
  }
216
308
  };
217
309
  // start:out main は接続先 in の解決値を転写(逆向き導出)。fan-out は
218
- // 非 any が1種類(JSON 等値)のときだけ採用し、未接続・型割れは any。
310
+ // 非未宣言が1種類(JSON 等値)のときだけ採用し、未接続・型割れは未宣言。
219
311
  // in は out main と同型(恒等の向きを out→in に逆転。run 入力契約は
220
312
  // 接続先の入力宣言から自動で決まる)
221
313
  if (node.kind === "start") {
222
314
  const inPort = node.ports.find((p) => p.io === "in");
223
315
  const outMain = node.ports.find((p) => p.io === "out" && p.key === "main");
224
316
  if (inPort && outMain) {
225
- const targets = (outbound.get(outMain.id) ?? [])
226
- .map((id) => resolved.get(id) ?? ANY_SCHEMA)
227
- .filter((s) => !isAnySchema(s))
228
- .map((s) => JSON.stringify(s));
229
- const uniq = [...new Set(targets)];
230
- const schema = uniq.length === 1 && uniq[0] !== undefined
231
- ? JSON.parse(uniq[0])
232
- : ANY_SCHEMA;
317
+ const schema = mergeInboundSchemas((outbound.get(outMain.id) ?? []).map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA));
233
318
  assign(outMain.id, schema);
234
319
  assign(inPort.id, schema);
235
320
  }
236
321
  continue;
237
322
  }
238
323
  // end:各 in を自分の単一上流から導出(複数ルートを別 in で集約)。
239
- // out main は any 固定(ルートごとに型が違いうる。Loop-in-Loop で詰める)
324
+ // out main は未宣言固定(ルートごとに型が違いうる。Loop-in-Loop で詰める)
240
325
  if (node.kind === "end") {
241
326
  for (const port of node.ports) {
242
327
  if (port.io !== "in")
243
328
  continue;
244
329
  const srcs = inbound.get(port.id) ?? [];
245
- assign(port.id, srcs.length === 1 && srcs[0] !== undefined
246
- ? (resolved.get(srcs[0]) ?? ANY_SCHEMA)
247
- : ANY_SCHEMA);
330
+ assign(port.id, mergeInboundSchemas(srcs.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA)));
248
331
  }
249
332
  continue;
250
333
  }
251
334
  const isMirror = MIRROR_KINDS.has(node.kind);
252
335
  if (!DERIVED_KINDS.has(node.kind) && !isMirror)
253
336
  continue;
254
- const inPort = node.ports.find((p) => p.io === "in");
337
+ // ai in が2口あるので、上流データの口(key="in")だけを鏡映・導出の対象にする
338
+ const inPort = node.ports.find((p) => p.io === "in" && p.key === "in");
255
339
  if (!inPort)
256
340
  continue;
257
341
  const sources = inbound.get(inPort.id) ?? [];
258
- const inSchema = sources.length === 1 && sources[0] !== undefined
259
- ? (resolved.get(sources[0]) ?? ANY_SCHEMA)
260
- : ANY_SCHEMA;
261
- // 鏡映(program)は具体型が流れてきたときだけ上書き(宣言を any で潰さない)
342
+ const inSchema = mergeInboundSchemas(sources.map((id) => resolved.get(id) ?? UNDECLARED_SCHEMA));
343
+ // 鏡映(program / ai)は具体型が流れてきたときだけ上書き(宣言を未宣言で潰さない)
262
344
  if (isMirror) {
263
- if (!isAnySchema(inSchema))
345
+ if (!isUndeclaredSchema(inSchema))
264
346
  assign(inPort.id, inSchema);
265
347
  continue;
266
348
  }
@@ -273,7 +355,7 @@ export const resolvePortSchemas = (nodes, connections) => {
273
355
  for (const port of node.ports) {
274
356
  if (port.io === "out" && isDerivedPort(node.kind, port)) {
275
357
  assign(port.id, node.kind === "human" && port.key === "approve" && fromAiNode
276
- ? (envelopeDataSchemaOf(inSchema) ?? ANY_SCHEMA)
358
+ ? (envelopeDataSchemaOf(inSchema) ?? UNDECLARED_SCHEMA)
277
359
  : inSchema);
278
360
  }
279
361
  }
@@ -299,18 +381,15 @@ export const propagateDerivedPorts = (nodes, connections) => {
299
381
  return touched ? { ...node, ports } : node;
300
382
  });
301
383
  };
302
- // ---- 接続バリデーション(伝播型 宣言スキーマ) ----
303
- // 完全な JSON Schema 包含ではなく実用的な構造チェック:any は素通し、type 一致、
304
- // object は宣言側 required の存在と型互換、array は items 互換
305
- export const isSchemaSubset = (sub, sup) => {
306
- if (isAnySchema(sup) || isAnySchema(sub))
307
- return true;
308
- // file は nominal:マーカー一致の file 同士だけ可(file → any は上の any 判定で許可)。
384
+ export const schemaFit = (sub, sup) => {
385
+ if (isUndeclaredSchema(sup) || isUndeclaredSchema(sub))
386
+ return "unknown";
387
+ // file nominal:マーカー一致の file 同士だけ可。
309
388
  // 構造が同じだけの普通の object は file に繋がない/file は普通の object に繋がない
310
389
  if (isFileSchema(sub) || isFileSchema(sup))
311
- return isFileSchema(sub) && isFileSchema(sup);
390
+ return isFileSchema(sub) && isFileSchema(sup) ? "ok" : "mismatch";
312
391
  if (sub.type !== sup.type)
313
- return false;
392
+ return "mismatch";
314
393
  if (sup.type === "object") {
315
394
  const subProps = (sub.properties ?? {});
316
395
  const subRequired = (sub.required ?? []);
@@ -318,19 +397,22 @@ export const isSchemaSubset = (sub, sup) => {
318
397
  for (const key of (sup.required ?? [])) {
319
398
  const subProp = subProps[key];
320
399
  if (!subProp || !subRequired.includes(key))
321
- return false;
400
+ return "mismatch";
322
401
  const supProp = supProps[key];
323
- if (supProp && !isSchemaSubset(subProp, supProp))
324
- return false;
402
+ // ネストは「不適合でなければよい」(未宣言のプロパティで親を落とさない)
403
+ if (supProp && schemaFit(subProp, supProp) === "mismatch")
404
+ return "mismatch";
325
405
  }
326
- return true;
406
+ return "ok";
327
407
  }
328
408
  if (sup.type === "array") {
329
409
  const supItems = sup.items;
330
410
  const subItems = sub.items;
331
411
  if (!supItems || !subItems)
332
- return true;
333
- return isSchemaSubset(subItems, supItems);
412
+ return "ok";
413
+ return schemaFit(subItems, supItems) === "mismatch" ? "mismatch" : "ok";
334
414
  }
335
- return true;
415
+ return "ok";
336
416
  };
417
+ // 保存時の接続チェック用:判断できない(未宣言)ものは通す
418
+ export const isSchemaSubset = (sub, sup) => schemaFit(sub, sup) !== "mismatch";
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { jsonSchemaSchema, portKeySchema } from "./node.js";
3
+ import { manifestInKeysViolation } from "./port-spec.js";
3
4
  // nodes/program/<dir>/config.json(リポジトリ規約 v2)。プログラム自身が入出力スキーマを宣言し、
4
5
  // エディタが取り込んで ports を自動生成する(実行時の正は ports テーブルのまま。
5
6
  // docs/tasks/wip/プログラムのリポジトリ規約v2(ディレクトリ+config.json).md)。
@@ -22,14 +23,11 @@ export const programManifestSchema = z
22
23
  refs: z.record(portKeySchema, jsonSchemaSchema).default({}),
23
24
  })
24
25
  .superRefine((manifest, ctx) => {
25
- // グラフ側の既存制約(in ポートちょうど1つ・key "in" 固定)に整合させる
26
- const inKeys = Object.keys(manifest.inputs);
27
- if (inKeys.length !== 1 || inKeys[0] !== "in") {
28
- ctx.addIssue({
29
- code: "custom",
30
- path: ["inputs"],
31
- message: 'inputs は "in" の1件だけ宣言してください(join 導入まで)',
32
- });
26
+ // in の構成は kind が固定する(program は "in" の1口)。ルールの正は
27
+ // port-spec 側で、ここは参照するだけ
28
+ const inViolation = manifestInKeysViolation("program", Object.keys(manifest.inputs));
29
+ if (inViolation) {
30
+ ctx.addIssue({ code: "custom", path: ["inputs"], message: inViolation });
33
31
  }
34
32
  if (Object.keys(manifest.outputs).length < 1) {
35
33
  ctx.addIssue({
package/dist/cli.js CHANGED
File without changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./_schemas/ai-manifest.js";
2
+ export * from "./_schemas/extract-component.js";
2
3
  export * from "./_schemas/graph.js";
3
4
  export * from "./_schemas/hook-manifest.js";
4
5
  export * from "./_schemas/node.js";
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // 正はここ(npm 公開)で、@mawaru/common(private)が re-export して
3
3
  // backend / frontend が使う。CLI(typegen / validate)の実装は export しない
4
4
  export * from "./_schemas/ai-manifest.js";
5
+ export * from "./_schemas/extract-component.js";
5
6
  export * from "./_schemas/graph.js";
6
7
  export * from "./_schemas/hook-manifest.js";
7
8
  export * from "./_schemas/node.js";
package/dist/init.js CHANGED
@@ -64,6 +64,9 @@ export const runInit = (root) => {
64
64
  }
65
65
  writeIfAbsent("CLAUDE.md", template("CLAUDE.md"));
66
66
  writeIfAbsent("README.md", template("README.md"));
67
+ // テンプレートのファイル名を .gitignore にすると npm pack 時に無視ルールとして
68
+ // 作用してしまうため、ドット無しの "gitignore" で持つ
69
+ writeIfAbsent(".gitignore", template("gitignore"));
67
70
  // package.json:@mawaru/sdk を devDependencies に(ポインタ・docs の参照解決と
68
71
  // typegen / validate CLI のため)。バージョンはこのパッケージ自身から取る
69
72
  const sdkVersion = JSON.parse(readFileSync(join(packageRoot(), "package.json"), "utf-8")).version;
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
- ? [`export type Refs = {\n${refEntries.join("\n")}\n}`, ""]
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
  };
@@ -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 する。`ctx.refs` にループ変数が入る。戻り値:
29
+ - `main.ts` — `run(input, ctx)` を export する。戻り値:
30
30
  - `{ <出口ポートのkey>: <データ> }` の**単一キー封筒**(どの出口から出るかを戻り値自身が運ぶ)
31
31
  - `"pending"` — 完了保留(外部イベントで後から end が届く)
32
- - 型生成:`npx mawaru typegen` が config.json から `types.d.ts`(Input / Outputs 型)を生成する
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/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  // 正はここ(npm 公開)で、@mawaru/common(private)が re-export して
3
3
  // backend / frontend が使う。CLI(typegen / validate)の実装は export しない
4
4
  export * from "./_schemas/ai-manifest.js"
5
+ export * from "./_schemas/extract-component.js"
5
6
  export * from "./_schemas/graph.js"
6
7
  export * from "./_schemas/hook-manifest.js"
7
8
  export * from "./_schemas/node.js"
package/package.json CHANGED
@@ -1,20 +1,15 @@
1
1
  {
2
2
  "name": "@mawaru/sdk",
3
- "version": "0.5.0",
3
+ "version": "0.9.0",
4
4
  "description": "mawaru 実行 repo の開発 SDK。契約スキーマ(config.json 規約・ループ graph API)の正 + typegen / validate CLI",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mawaru": "dist/cli.js"
8
8
  },
9
9
  "exports": {
10
- ".": "./index.ts"
11
- },
12
- "publishConfig": {
13
- "exports": {
14
- ".": {
15
- "types": "./dist/index.d.ts",
16
- "default": "./dist/index.js"
17
- }
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
18
13
  }
19
14
  },
20
15
  "files": [
@@ -28,13 +23,6 @@
28
23
  "engines": {
29
24
  "node": ">=20"
30
25
  },
31
- "scripts": {
32
- "build": "tsc -p tsconfig.build.json",
33
- "prepack": "pnpm build",
34
- "typecheck": "tsc --noEmit",
35
- "test": "vitest run",
36
- "test:watch": "vitest"
37
- },
38
26
  "dependencies": {
39
27
  "zod": "^4.4.3",
40
28
  "zod-from-json-schema": "^0.5.3"
@@ -43,5 +31,11 @@
43
31
  "@types/node": "^26.1.0",
44
32
  "typescript": "^6.0.3",
45
33
  "vitest": "^4.1.9"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest"
46
40
  }
47
- }
41
+ }
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ # mawaru の API キー等(create-loop スキルが生成する。コミット禁止)
3
+ .env
4
+ .DS_Store