boxglow 0.1.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/bin/boxglow.js ADDED
@@ -0,0 +1,2834 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cli/main.ts
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ // cli/mcp.ts
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { z } from "zod";
10
+ var text = (s) => ({ content: [{ type: "text", text: s.trim() || "(\u51FA\u529B\u306A\u3057)" }] });
11
+ var safe = (run, argv2) => {
12
+ try {
13
+ return text(run(argv2));
14
+ } catch (e) {
15
+ return { ...text(`[boxglow] ${e instanceof Error ? e.message : String(e)}`), isError: true };
16
+ }
17
+ };
18
+ function opt(argv2, name, value) {
19
+ if (value === void 0 || value === false || value === "") return;
20
+ if (value === true) {
21
+ argv2.push(`--${name}`);
22
+ return;
23
+ }
24
+ for (const v of Array.isArray(value) ? value : [value]) argv2.push(`--${name}`, String(v));
25
+ }
26
+ async function startMcp(run) {
27
+ const server = new McpServer({ name: "boxglow", version: "0.1.0" });
28
+ const block = z.string().describe("\u7BB1\u306E B \u756A\u53F7 (B12) \u304B\u984C\u540D");
29
+ server.registerTool("boxglow_status", { description: "\u8A08\u753B\u306E\u4ECA\u306E\u72B6\u6CC1: \u5224\u65AD\u5F85\u3061\u30FB\u4F5C\u696D\u4E2D\u30FB\u6B21\u306E\u5019\u88DC\u30FB\u968E\u5C64\u306E\u4E00\u89A7 (\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6700\u521D\u306B\u8AAD\u3080)", inputSchema: {} }, async () => safe(run, ["status"]));
30
+ server.registerTool("boxglow_show", { description: "\u7BB1\u306E\u8A73\u7D30 (\u5165\u51FA\u529B\u30FB\u7DDA\u30FB\u5224\u65AD\u30FB\u6210\u679C\u7269)", inputSchema: { block } }, async ({ block: b }) => safe(run, ["show", b]));
31
+ server.registerTool("boxglow_add", {
32
+ description: "\u7BB1\u3092\u8DB3\u3059\u3002\u51FA\u529B (\u6210\u679C\u7269\u306E\u540D\u524D) \u3092\u5FC5\u305A\u6C7A\u3081\u308B\u3002parent \u3092\u7701\u304F\u3068\u6700\u521D\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1\u306E\u76F4\u4E0B (\u5927\u9805\u76EE) \u306B\u5165\u308B",
33
+ inputSchema: { title: z.string(), out: z.string().describe("\u51FA\u529B\u306E\u540D\u524D (\u5177\u4F53\u7684\u306A\u6210\u679C\u7269: \u30D5\u30A1\u30A4\u30EB\u30FBURL\u30FBPR)"), parent: block.optional(), in: z.array(z.string()).optional().describe("\u5165\u529B\u306E\u540D\u524D"), category: z.string().optional().describe("design/build/verify/evaluate/study/research/ui/improve/fix/docs/ops/other"), note: z.string().optional() }
34
+ }, async (a) => {
35
+ const argv2 = ["add", a.title];
36
+ opt(argv2, "out", a.out);
37
+ opt(argv2, "parent", a.parent);
38
+ opt(argv2, "in", a.in);
39
+ opt(argv2, "category", a.category);
40
+ opt(argv2, "note", a.note);
41
+ return safe(run, argv2);
42
+ });
43
+ server.registerTool("boxglow_split", {
44
+ description: '\u5927\u304D\u3044\u7BB1\u3092\u4E2D\u306E\u7BB1\u306B\u5206\u89E3\u3059\u308B\u3002spec \u306F { blocks: [{ title, in: [\u540D\u524D], out: \u540D\u524D, category? }], wires: ["A.\u51FA\u529B -> B.\u5165\u529B"] } \u306E\u5F62',
45
+ inputSchema: { block, spec: z.record(z.string(), z.unknown()).describe("\u5206\u89E3\u306E\u6307\u5B9A (JSON)") }
46
+ }, async ({ block: b, spec }) => safe(run, ["split", b, "--spec", JSON.stringify(spec)]));
47
+ server.registerTool("boxglow_connect", { description: "\u7DDA\u3092\u3064\u306A\u3050: <\u984C\u540D.\u51FA\u529B\u540D> \u304B\u3089 <\u984C\u540D.\u5165\u529B\u540D> \u3078 (\u89AA\u306E\u5165\u529B\u306F project.\u5165\u529B\u540D\u3001\u89AA\u306E\u51FA\u529B\u3078\u306F \u984C\u540D.\u51FA\u529B\u540D -> project.\u51FA\u529B\u540D)", inputSchema: { from: z.string(), to: z.string() } }, async ({ from, to }) => safe(run, ["connect", from, to]));
48
+ server.registerTool("boxglow_disconnect", { description: "\u7DDA\u3092\u5916\u3059", inputSchema: { from: z.string(), to: z.string() } }, async ({ from, to }) => safe(run, ["disconnect", from, to]));
49
+ server.registerTool("boxglow_port", { description: "\u7BB1\u306B\u5165\u529B / \u51FA\u529B\u3092\u8DB3\u3059\u3001\u540D\u524D\u3092\u5909\u3048\u308B", inputSchema: { block, in: z.array(z.string()).optional(), out: z.array(z.string()).optional(), rename: z.string().optional().describe("\u65E7=\u65B0") } }, async (a) => {
50
+ const argv2 = ["port", a.block];
51
+ opt(argv2, "in", a.in);
52
+ opt(argv2, "out", a.out);
53
+ opt(argv2, "rename", a.rename);
54
+ return safe(run, argv2);
55
+ });
56
+ server.registerTool("boxglow_move", { description: "\u7BB1\u3092\u5225\u306E\u89AA\u306E\u4E2D\u3078\u79FB\u3059 (\u7DDA\u306F\u3064\u306A\u304E\u76F4\u3055\u308C\u308B)", inputSchema: { block, parent: z.string().describe("\u79FB\u3059\u5148\u306E\u7BB1\u304B project") } }, async ({ block: b, parent }) => safe(run, ["move", b, "--parent", parent]));
57
+ server.registerTool("boxglow_remove", { description: "\u7BB1\u3092\u6D88\u3059 (\u4E2D\u306B\u7BB1\u304C\u3042\u308C\u3070 force)", inputSchema: { block, force: z.boolean().optional() } }, async ({ block: b, force }) => {
58
+ const argv2 = ["remove", b];
59
+ opt(argv2, "force", force);
60
+ return safe(run, argv2);
61
+ });
62
+ server.registerTool("boxglow_start", { description: "\u4F5C\u696D\u3092\u59CB\u3081\u308B (\u4F5C\u696D\u4E2D\u306E\u672D\u304C\u4ED8\u304F)\u3002\u540C\u6642\u306B\u4F5C\u696D\u4E2D\u306B\u3059\u308B\u7BB1\u306F 1\u301C2 \u500B\u307E\u3067", inputSchema: { block, note: z.string().optional().describe("\u4F55\u3092\u3059\u308B\u304B") } }, async ({ block: b, note }) => {
63
+ const argv2 = ["start", b];
64
+ opt(argv2, "note", note);
65
+ return safe(run, argv2);
66
+ });
67
+ server.registerTool("boxglow_done", { description: "\u5B8C\u4E86\u306B\u3059\u308B\u3002\u6210\u679C\u7269 (\u540D\u524D=\u30D1\u30B9\u304B URL) \u3092\u4ED8\u3051\u308B\u3002Git \u306E\u30D5\u30A1\u30A4\u30EB\u306A\u3089 commit+path+blob \u304C\u8A18\u9332\u3055\u308C\u308B", inputSchema: { block, artifact: z.array(z.string()).optional().describe("\u540D\u524D=\u30D1\u30B9 \u307E\u305F\u306F \u540D\u524D=URL"), note: z.string().optional(), output: z.string().optional().describe("\u51FA\u529B\u306E\u540D\u524D\u3092\u5909\u3048\u308B\u3068\u304D") } }, async (a) => {
68
+ const argv2 = ["done", a.block];
69
+ opt(argv2, "artifact", a.artifact);
70
+ opt(argv2, "note", a.note);
71
+ opt(argv2, "output", a.output);
72
+ return safe(run, argv2);
73
+ });
74
+ server.registerTool("boxglow_blocked", { description: "\u8A70\u307E\u3063\u305F (\u56F0\u3063\u3066\u3044\u308B\u3053\u3068\u3092\u66F8\u3044\u3066\u4ED6\u306E\u7BB1\u3078\u79FB\u308B)", inputSchema: { block, note: z.string() } }, async ({ block: b, note }) => safe(run, ["blocked", b, "--note", note]));
75
+ server.registerTool("boxglow_ask", {
76
+ description: "\u4EBA\u306E\u5224\u65AD\u304C\u8981\u308B\u8CEA\u554F\u3092\u6B8B\u3059 (\u9078\u629E\u80A2\u4ED8\u304D)\u3002\u8CEA\u554F\u3060\u3051\u3067\u5224\u65AD\u3067\u304D\u308B\u3088\u3046\u306B\u3001\u524D\u63D0\u30FB\u6BD4\u8F03\u30FB\u5F71\u97FF\u3092 context \u306B\u66F8\u304F\u3002AI \u304C\u81EA\u5206\u3067\u9078\u3076\u3068\u304D\u3082 ask \u3057\u3066 answer (by=claude-code) \u3067\u8A18\u9332\u3059\u308B",
77
+ inputSchema: { block, question: z.string(), options: z.array(z.string()).optional(), context: z.string().optional() }
78
+ }, async (a) => {
79
+ const argv2 = ["ask", a.block, a.question];
80
+ if (a.options?.length) argv2.push("--options", a.options.join("|"));
81
+ opt(argv2, "context", a.context);
82
+ return safe(run, argv2);
83
+ });
84
+ server.registerTool("boxglow_answer", { description: "\u5224\u65AD\u306B\u7B54\u3048\u308B (\u4EBA\u304C\u30C1\u30E3\u30C3\u30C8\u3067\u7B54\u3048\u305F\u3089 by=human \u3067\u8A18\u9332)", inputSchema: { block, answer: z.string(), by: z.string().optional().describe("human / claude-code \u306A\u3069") } }, async (a) => {
85
+ const argv2 = ["answer", a.block, a.answer];
86
+ opt(argv2, "by", a.by);
87
+ return safe(run, argv2);
88
+ });
89
+ server.registerTool("boxglow_reopen", { description: "\u5224\u65AD\u3092\u3084\u308A\u76F4\u3059 (\u524D\u306E\u7B54\u3048\u306F\u5C65\u6B74\u306B\u6B8B\u308B)", inputSchema: { block, note: z.string().optional() } }, async ({ block: b, note }) => {
90
+ const argv2 = ["reopen", b];
91
+ opt(argv2, "note", note);
92
+ return safe(run, argv2);
93
+ });
94
+ server.registerTool("boxglow_set", { description: "\u7BB1\u306E\u5C5E\u6027\u3092\u5909\u3048\u308B (\u984C\u540D\u30FB\u30E1\u30E2\u30FB\u30AB\u30C6\u30B4\u30EA\u30FB\u671F\u65E5\u30FB\u30EA\u30DD\u30B8\u30C8\u30EA\u30FB\u8AB2\u984C URL)", inputSchema: { block, title: z.string().optional(), note: z.string().optional(), category: z.string().optional(), start: z.string().optional().describe("YYYY-MM-DD"), due: z.string().optional().describe("YYYY-MM-DD"), repo: z.string().optional(), issue: z.string().optional() } }, async (a) => {
95
+ const argv2 = ["set", a.block];
96
+ for (const k of ["title", "note", "category", "start", "due", "repo", "issue"]) opt(argv2, k, a[k]);
97
+ return safe(run, argv2);
98
+ });
99
+ server.registerTool("boxglow_export", { description: "\u8A08\u753B\u3092 Markdown \u306B\u66F8\u304D\u51FA\u3059 (docs/ROADMAP.md \u306A\u3069)", inputSchema: { out: z.string().optional() } }, async ({ out: o }) => {
100
+ const argv2 = ["export"];
101
+ opt(argv2, "out", o);
102
+ return safe(run, argv2);
103
+ });
104
+ server.registerTool("boxglow_validate", { description: "\u8A08\u753B\u306E\u554F\u984C (\u3064\u306A\u304C\u3063\u3066\u3044\u306A\u3044\u5165\u51FA\u529B\u306A\u3069) \u3092\u691C\u67FB\u3059\u308B", inputSchema: {} }, async () => safe(run, ["validate"]));
105
+ server.registerTool("boxglow_run", { description: '\u305D\u306E\u307B\u304B\u306E CLI \u30B3\u30DE\u30F3\u30C9\u3092\u305D\u306E\u307E\u307E\u5B9F\u884C\u3059\u308B (\u4F8B: ["log", "--n", "20"]\u3001["layout"]\u3001["help"])', inputSchema: { args: z.array(z.string()) } }, async ({ args }) => safe(run, args));
106
+ const transport = new StdioServerTransport();
107
+ await server.connect(transport);
108
+ }
109
+
110
+ // cli/serve.ts
111
+ import { createServer } from "node:http";
112
+ import { existsSync, readFileSync, statSync, watch, writeFileSync } from "node:fs";
113
+ import { basename, dirname, extname, join, normalize, resolve } from "node:path";
114
+ import { spawn } from "node:child_process";
115
+ var MIME = {
116
+ ".html": "text/html; charset=utf-8",
117
+ ".js": "text/javascript; charset=utf-8",
118
+ ".css": "text/css; charset=utf-8",
119
+ ".json": "application/json; charset=utf-8",
120
+ ".svg": "image/svg+xml",
121
+ ".png": "image/png",
122
+ ".ico": "image/x-icon",
123
+ ".webp": "image/webp",
124
+ ".woff2": "font/woff2",
125
+ ".map": "application/json"
126
+ };
127
+ function startServe(opts) {
128
+ const { file, port, dist, log } = opts;
129
+ if (!existsSync(join(dist, "index.html"))) throw new Error(`Web \u30A2\u30D7\u30EA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${dist} (npm run build \u3067 dist/ \u3092\u4F5C\u3063\u3066\u304F\u3060\u3055\u3044)`);
130
+ const clients = /* @__PURE__ */ new Set();
131
+ let lastWritten = "";
132
+ let timer = null;
133
+ const broadcast = () => {
134
+ for (const res of clients) res.write("event: change\ndata: {}\n\n");
135
+ };
136
+ try {
137
+ watch(dirname(file), (_ev, name) => {
138
+ if (name && name !== basename(file)) return;
139
+ if (timer) clearTimeout(timer);
140
+ timer = setTimeout(() => {
141
+ timer = null;
142
+ try {
143
+ const text2 = readFileSync(file, "utf8");
144
+ if (text2 === lastWritten) return;
145
+ } catch {
146
+ return;
147
+ }
148
+ broadcast();
149
+ }, 250);
150
+ });
151
+ } catch (e) {
152
+ log(`[serve] \u30D5\u30A1\u30A4\u30EB\u306E\u76E3\u8996\u3092\u59CB\u3081\u3089\u308C\u307E\u305B\u3093: ${e instanceof Error ? e.message : String(e)}`);
153
+ }
154
+ const send = (res, status, body, type) => {
155
+ res.writeHead(status, { "content-type": type, "cache-control": "no-store" });
156
+ res.end(body);
157
+ };
158
+ const readBody = (req) => new Promise((ok, ng) => {
159
+ const chunks = [];
160
+ req.on("data", (c) => chunks.push(c));
161
+ req.on("end", () => ok(Buffer.concat(chunks).toString("utf8")));
162
+ req.on("error", ng);
163
+ });
164
+ const server = createServer(async (req, res) => {
165
+ const url = new URL(req.url ?? "/", "http://localhost");
166
+ try {
167
+ if (url.pathname === "/api/project") {
168
+ if (req.method === "GET") {
169
+ const text2 = readFileSync(file, "utf8");
170
+ res.writeHead(200, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-boxglow-file": encodeURIComponent(basename(file)) });
171
+ res.end(text2);
172
+ return;
173
+ }
174
+ if (req.method === "PUT") {
175
+ const text2 = await readBody(req);
176
+ try {
177
+ JSON.parse(text2);
178
+ } catch {
179
+ send(res, 400, JSON.stringify({ ok: false, error: "JSON \u3067\u306F\u3042\u308A\u307E\u305B\u3093" }), "application/json");
180
+ return;
181
+ }
182
+ lastWritten = text2;
183
+ writeFileSync(file, text2, "utf8");
184
+ send(res, 200, JSON.stringify({ ok: true }), "application/json");
185
+ return;
186
+ }
187
+ send(res, 405, "method not allowed", "text/plain");
188
+ return;
189
+ }
190
+ if (url.pathname === "/api/events") {
191
+ res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-store", connection: "keep-alive" });
192
+ res.write("event: hello\ndata: {}\n\n");
193
+ clients.add(res);
194
+ const ping = setInterval(() => res.write(": ping\n\n"), 25e3);
195
+ req.on("close", () => {
196
+ clearInterval(ping);
197
+ clients.delete(res);
198
+ });
199
+ return;
200
+ }
201
+ let rel = decodeURIComponent(url.pathname);
202
+ if (rel.endsWith("/")) rel += "index.html";
203
+ const target = normalize(join(dist, rel));
204
+ if (!target.startsWith(resolve(dist))) {
205
+ send(res, 403, "forbidden", "text/plain");
206
+ return;
207
+ }
208
+ const path = existsSync(target) && statSync(target).isFile() ? target : join(dist, "index.html");
209
+ send(res, 200, readFileSync(path), MIME[extname(path)] ?? "application/octet-stream");
210
+ } catch (e) {
211
+ send(res, 500, `error: ${e instanceof Error ? e.message : String(e)}`, "text/plain");
212
+ }
213
+ });
214
+ server.listen(port, "127.0.0.1", () => {
215
+ const addr = `http://localhost:${port}/?serve=1`;
216
+ log(`[serve] ${file}
217
+ [serve] ${addr} (Ctrl+C \u3067\u7D42\u4E86)`);
218
+ if (opts.open) {
219
+ const cmd = process.platform === "win32" ? ["cmd", "/c", "start", "", addr] : process.platform === "darwin" ? ["open", addr] : ["xdg-open", addr];
220
+ try {
221
+ spawn(cmd[0], cmd.slice(1), { stdio: "ignore", detached: true }).unref();
222
+ } catch {
223
+ }
224
+ }
225
+ });
226
+ }
227
+
228
+ // cli/main.ts
229
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
230
+
231
+ // docs/AGENTS_SNIPPET.md
232
+ var AGENTS_SNIPPET_default = '# Boxglow \u3092\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u4F7F\u308F\u305B\u308B\u6307\u793A\u66F8 (AGENTS.md / CLAUDE.md \u306B\u8CBC\u308B)\n\n\u4E0B\u306E\u300C---\u300D\u306E\u9593\u3092\u305D\u306E\u307E\u307E\u30EA\u30DD\u30B8\u30C8\u30EA\u306E AGENTS.md (Codex) \u3084 CLAUDE.md (Claude Code) \u306B\u8CBC\u308B\u3002\nboxglow.json \u306F\u30EA\u30DD\u30B8\u30C8\u30EA\u76F4\u4E0B\u306B\u7F6E\u304F (`npx boxglow init --name "<\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D>"`)\u3002\n\n---\n\n## Boxglow (\u8A08\u753B\u3068\u9032\u6357\u306E\u5171\u6709)\n\n\u3053\u306E\u30EA\u30DD\u30B8\u30C8\u30EA\u306E\u8A08\u753B\u3068\u9032\u6357\u306F `boxglow.json` \u304C\u6B63\u672C\u3002\u4EBA\u9593\u306F Boxglow \u306E\u753B\u9762\u3067\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u898B\u3066\u3044\u308B\u3002\n\u30D5\u30A1\u30A4\u30EB\u3092\u76F4\u63A5\u7DE8\u96C6\u305B\u305A\u3001\u5FC5\u305A `npx boxglow` \u30B3\u30DE\u30F3\u30C9\u3067\u66F4\u65B0\u3059\u308B\u3053\u3068 (\u7D50\u7DDA\u30EB\u30FC\u30EB\u3068\u5C65\u6B74\u304C\u4FDD\u305F\u308C\u308B)\u3002\n\n\u30D6\u30ED\u30C3\u30AF = \u300C\u5165\u529B\u304B\u3089\u51FA\u529B (\u6210\u679C\u7269) \u3092\u4F5C\u308B\u30BF\u30B9\u30AF\u300D\u3002\u51FA\u529B\u306E\u6210\u679C\u7269\u304C\u78BA\u5B9A\u3057\u305F\u3089\u5B8C\u4E86 (WhiteBox)\u3002\n\u72B6\u614B: BlackBox (\u51FA\u529B\u3060\u3051\u6C7A\u3081\u305F) \u2192 GrayBox (\u5206\u89E3\u4E2D\u30FB\u7740\u624B\u4E2D) \u2192 WhiteBox (\u5B8C\u4E86)\u3002\n\n### \u4F5C\u696D\u306E\u305F\u3073\u306B\u5B88\u308B\u3053\u3068\n\n1. **\u6700\u521D\u306B\u8AAD\u3080**: `npx boxglow status` \u3067\u5168\u4F53\u30FB\u5224\u65AD\u5F85\u3061\u30FB\u4F5C\u696D\u4E2D\u30FB\u6B21\u306E\u5019\u88DC\u3092\u8AAD\u3080\u3002`npx boxglow show <block>` \u3067\u62C5\u5F53\u3059\u308B\u7BB1\u306E\u5165\u51FA\u529B\u3092\u78BA\u8A8D\u3059\u308B\n2. **\u7740\u624B\u3092\u8A18\u9332**: \u7BB1\u306B\u53D6\u308A\u304B\u304B\u308B\u3068\u304D `npx boxglow start <block> --note "<\u4F55\u3092\u3059\u308B\u304B>"`\u3002\u540C\u6642\u306B\u9032\u3081\u308B\u7BB1\u306F 1\u301C2 \u500B\u307E\u3067\n3. **\u5206\u89E3**: 1 \u3064\u306E\u7BB1\u3067\u51FA\u529B\u3092\u4F5C\u308B\u898B\u901A\u3057\u304C\u7ACB\u305F\u306A\u3044\u3068\u304D\u306F\u3001`npx boxglow split <block> --spec \'<JSON>\'` \u3067\u4E2D\u306B\u5C0F\u3055\u306A\u7BB1\u3092\u7F6E\u304F\u3002\u5404\u7BB1\u306B\u51FA\u529B (\u6210\u679C\u7269) \u3092\u5FC5\u305A\u6C7A\u3081\u308B\u3002\u5165\u529B\u306F\u4E0D\u660E\u306A\u3089\u7701\u7565\u3057\u3066\u3088\u3044 (\u81EA\u52D5\u3067\u4E0A\u306E\u968E\u5C64\u306E\u5165\u529B\u306B\u306A\u308B)\n4. **\u5B8C\u4E86\u3092\u8A18\u9332**: \u51FA\u529B\u306E\u6210\u679C\u7269\u304C\u3067\u304D\u305F\u3089 `npx boxglow done <block> --artifact "<\u540D\u524D>=<\u30EA\u30DD\u30B8\u30C8\u30EA\u5185\u306E\u30D1\u30B9>"`\u3002\n Git \u7BA1\u7406\u4E0B\u306E\u30D1\u30B9\u306F\u300C\u30B3\u30DF\u30C3\u30C8 + \u30D1\u30B9 + \u4E2D\u8EAB\u306E\u30CF\u30C3\u30B7\u30E5\u300D\u3067\u8A18\u9332\u3055\u308C\u308B (\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306F\u3057\u306A\u3044)\u3002\u30B3\u30DF\u30C3\u30C8\u3057\u3066\u304B\u3089 done \u3059\u308B\u3002PR \u3084\u5916\u90E8\u8CC7\u6599\u306F URL \u3067\u3082\u3088\u3044\u3002\n \u6210\u679C\u7269\u3060\u3051\u5148\u306B\u4ED8\u3051\u308B\u306A\u3089 `npx boxglow artifact <block> <\u30D1\u30B9>`\u3002\u9032\u6357\u306E\u9014\u4E2D\u7D4C\u904E\u306F `npx boxglow set <block> --progress 60`\n5. **\u4EBA\u9593\u306E\u5224\u65AD\u304C\u8981\u308B**: `npx boxglow ask <block> "<\u8CEA\u554F>" --options "A|B"`\u3002\u56DE\u7B54\u304C\u3042\u308B\u307E\u3067\u305D\u306E\u7BB1\u306F\u9032\u3081\u305A\u3001\u4ED6\u306E\u7BB1\u3078\u79FB\u308B\u3002\u56DE\u7B54\u306F `status` \u306E\u300C\u5224\u65AD\u5F85\u3061\u300D\u304C\u6D88\u3048\u305F\u3089 `show` \u3067\u8AAD\u3080\n6. **\u8A70\u307E\u3063\u305F\u3089**: `npx boxglow blocked <block> --note "<\u56F0\u3063\u3066\u3044\u308B\u3053\u3068>"`\n7. **\u6210\u679C\u7269\u306E\u78BA\u8A8D**: \u30D5\u30A1\u30A4\u30EB\u3092\u79FB\u52D5\u30FB\u6539\u540D\u3057\u305F\u3089 `npx boxglow check` \u3092\u5B9F\u884C\u3059\u308B (\u79FB\u52D5\u3092\u691C\u51FA\u3057\u3066\u30D1\u30B9\u3092\u4ED8\u3051\u66FF\u3048\u308B\u3002\u898B\u3064\u304B\u3089\u306A\u3051\u308C\u3070\u5370\u304C\u4ED8\u304F)\n8. **\u5831\u544A**: \u4F5C\u696D\u306E\u6700\u5F8C\u306B `npx boxglow status` \u306E\u5185\u5BB9\u3092\u8981\u7D04\u3057\u3066\u5831\u544A\u3059\u308B\n\n### \u69CB\u9020\u306E\u7D04\u675F\n\n- \u6700\u4E0A\u4F4D\u306B\u306F\u300C\u5165\u529B\u30CE\u30FC\u30C9\u300D\u300C\u6700\u7D42\u6210\u679C\u7269\u30CE\u30FC\u30C9\u300D\u3068\u300C\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1\u300D\u3060\u3051\u304C\u3042\u308B\u3002\u30BF\u30B9\u30AF\u306F\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1\u306E\u4E2D\u306B\u7F6E\u304F (`add` \u306E\u65E2\u5B9A\u306E\u89AA\u306F\u6700\u521D\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1)\n- \u968E\u5C64\u306E\u6DF1\u3055\u306B\u5236\u9650\u306F\u306A\u3044\u3002`split` \u306F\u4F55\u6BB5\u3067\u3082\u4F7F\u3048\u308B\n- \u5225\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u3082\u4F7F\u3048\u305D\u3046\u306A\u7BB1 (\u4F8B: \u5165\u529B\u753B\u50CF\u306E\u30E2\u30CE\u30AF\u30ED\u5316) \u306F `npx boxglow export-block <block> --out <\u540D\u524D>.boxglow-block.json` \u3067\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306B\u3057\u3001\n `npx boxglow import-block <path> --parent <block>` \u3067\u633F\u5165\u3059\u308B\u3002\u30EA\u30DD\u30B8\u30C8\u30EA\u306E `boxglow-blocks/` \u306B\u7F6E\u3044\u3066\u5171\u6709\u3057\u3066\u3088\u3044\n\n\u6700\u4E0A\u4F4D\u306E\u5165\u529B\u304C\u591A\u3044\u3068\u304D\u306F `npx boxglow group "<\u30B0\u30EB\u30FC\u30D7\u540D>"` \u3067\u30B0\u30EB\u30FC\u30D7\u3092\u4F5C\u308A\u3001`npx boxglow group-set <\u5165\u529B\u540D> <\u30B0\u30EB\u30FC\u30D7\u540D>` \u3067\u5206\u3051\u308B (\u4F8B: PCIe \u4ED5\u69D8\u66F8 / DDR \u4ED5\u69D8\u66F8)\u3002\n\n### split \u306E JSON \u5F62\u5F0F\n\n```json\n{\n "blocks": [\n { "title": "\u8A2D\u8A08", "description": "\u3084\u308B\u3053\u3068", "inputs": ["\u4ED5\u69D8"], "outputs": ["\u8A2D\u8A08\u66F8"] },\n { "title": "\u5B9F\u88C5", "inputs": ["\u8A2D\u8A08\u66F8"], "outputs": ["\u30B3\u30FC\u30C9"] }\n ],\n "connections": [\n { "from": "parent.\u4ED5\u69D8", "to": "\u8A2D\u8A08.\u4ED5\u69D8" },\n { "from": "\u8A2D\u8A08.\u8A2D\u8A08\u66F8", "to": "\u5B9F\u88C5.\u8A2D\u8A08\u66F8" },\n { "from": "\u5B9F\u88C5.\u30B3\u30FC\u30C9", "to": "parent.\u51FA\u529B" }\n ]\n}\n```\n\n`parent.<\u540D\u524D>` \u306F\u5206\u89E3\u3059\u308B\u7BB1\u81EA\u8EAB\u306E\u5165\u529B / \u51FA\u529B\u3002`<block>` \u306F\u77ED\u3044 ID (B12 \u306A\u3069\u3002status \u306B\u51FA\u308B) \u304B\u984C\u540D\u3002\n\u4E0B\u306E\u968E\u5C64\u3092\u6301\u305F\u306A\u3044\u7BB1\u306E\u51FA\u529B\u306F 1 \u672C (outputs \u306F 1 \u3064\u3060\u3051\u66F8\u304F)\u3002\u984C\u540D\u306F\u300C\u4F55\u3092\u4F5C\u308B\u304B\u300D\u304C\u5206\u304B\u308B\u77ED\u3044\u8A00\u8449\u306B\u3059\u308B\u3002\n\u671F\u65E5\u3084\u6642\u9593\u306F `npx boxglow set <block> --due 2026-10-15 --start 2026-10-01 --estimate 8 --hours 3.5` \u3067\u8A18\u9332\u3059\u308B\u3002\n\u7BB1\u306B\u306F\u4ED5\u4E8B\u306E\u7A2E\u985E (\u30AB\u30C6\u30B4\u30EA) \u3092\u4ED8\u3051\u308B: `--category design` (\u8A2D\u8A08) / build (\u5B9F\u88C5) / verify (\u691C\u8A3C) / evaluate (\u8A55\u4FA1) / study (\u691C\u8A0E) / research (\u8ABF\u67FB) / ui (\u30C7\u30B6\u30A4\u30F3) / improve (\u6539\u5584) / fix (\u8AB2\u984C\u89E3\u6C7A) / docs (\u6587\u66F8) / ops (\u904B\u7528) / other (\u305D\u306E\u4ED6: \u8FF7\u3063\u305F\u3068\u304D)\u3002add \u306E\u3068\u304D\u306B\u4ED8\u3051\u3001\u5909\u3048\u308B\u306A\u3089 `npx boxglow set <block> --category verify`\u3002\n\u65E2\u5B58\u306E\u7BB1\u306B\u5165\u51FA\u529B\u3092\u8DB3\u3059 / \u540D\u524D\u3092\u5909\u3048\u308B\u306E\u306F `npx boxglow port <block> --in <\u540D\u524D> --out <\u540D\u524D> --rename <\u65E7>=<\u65B0>` (`project` = \u6700\u521D\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1\u3002\u6700\u7D42\u6210\u679C\u7269\u306E\u540D\u524D\u3082\u3053\u308C\u3067\u5909\u3048\u308B)\u3002\u7DDA\u3092\u5916\u3059\u306E\u306F `npx boxglow disconnect <\u984C\u540D.\u51FA\u529B\u540D> <\u984C\u540D.\u5165\u529B\u540D>`\u3001\u7BB1\u305D\u306E\u3082\u306E\u3092\u6D88\u3059\u306E\u306F `npx boxglow remove <block>` (\u4E2D\u306B\u7BB1\u304C\u3042\u308C\u3070 `--force`)\u3002MCP \u30C4\u30FC\u30EB (`boxglow_status` / `boxglow_start` / `boxglow_done` / `boxglow_ask` \u306A\u3069\u3002`.mcp.json` \u306B\u767B\u9332\u6E08\u307F\u306A\u3089\u4F7F\u3048\u308B) \u306F\u540C\u3058\u30B3\u30DE\u30F3\u30C9\u306E\u5225\u306E\u5165\u53E3\u3067\u3001\u3069\u3061\u3089\u3067\u66F8\u3044\u3066\u3082\u540C\u3058\u30D5\u30A1\u30A4\u30EB\u304C\u66F4\u65B0\u3055\u308C\u308B\u3002\u8A08\u753B\u306E\u6587\u66F8\u306F `npx boxglow export --out docs/ROADMAP.md`\u3002\n\u65B0\u3057\u3044\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u306F\u3001\u65B9\u91DD (README \u3084\u4F9D\u983C\u6587) \u304B\u3089\u6700\u4E0A\u4F4D\u306E\u5927\u9805\u76EE 3\u301C7 \u500B\u3092\u5148\u306B\u4F5C\u308A\u3001\u4EBA\u306B `ask` \u3067\u78BA\u8A8D\u3057\u3066\u304B\u3089\u3001\u7740\u624B\u3059\u308B\u5927\u9805\u76EE\u3060\u3051\u3092 `split` \u3067\u5206\u89E3\u3059\u308B (\u5148\u306E\u6BB5\u968E\u306F\u7C97\u3044\u307E\u307E)\u3002`ask` \u306E\u8CEA\u554F\u306F\u305D\u308C\u3060\u3051\u3067\u5224\u65AD\u3067\u304D\u308B\u3088\u3046\u306B\u66F8\u304D\u3001\u524D\u63D0\u30FB\u6BD4\u8F03\u30FB\u5F71\u97FF\u306F `--context` \u306B\u5165\u308C\u308B (\u300C\u3053\u308C\u3067\u3088\u3044\u3067\u3059\u304B\u300D\u306E\u3088\u3046\u306B\u5916\u3092\u6307\u3055\u306A\u3044)\u3002\u5019\u88DC\u304B\u3089 1 \u3064\u3092\u9078\u3076\u5834\u9762\u3067\u306F\u3001\u81EA\u5206\u3067\u6C7A\u3081\u308B\u3068\u304D\u3082 `ask ... --options "A|B|C"` \u3068 `answer --by <\u81EA\u5206>` \u3067\u8A18\u9332\u3057\u3001\u9078\u3070\u306A\u304B\u3063\u305F\u5019\u88DC\u3092\u6B8B\u3059\u3002\u65B9\u91DD\u8EE2\u63DB\u306F `reopen <block> --note <\u7406\u7531>`\u3002\u3059\u3079\u3066 CLI \u3060\u3051\u3067\u6210\u7ACB\u3059\u308B (\u753B\u9762\u306F\u4EBA\u304C\u898B\u308B\u305F\u3081\u306E\u3082\u306E)\u3002\u4EBA\u304C\u3044\u306A\u3044\u904B\u7528\u306A\u3089\u3001AI \u304C `answer --by <\u81EA\u5206>` \u3067\u5224\u65AD\u3092\u8A18\u9332\u3057\u3066\u9032\u3081\u308B\u3002\n\u51FA\u529B\u306E\u540D\u524D\u306F\u300C\u5177\u4F53\u7684\u306A\u6210\u679C\u7269\u300D\u306B\u3059\u308B (\u4F8B: `\u8A2D\u8A08\u66F8 docs/design.md`\u3001`PR #12`\u3001`\u516C\u958B URL`\u3001`\u30C6\u30B9\u30C8\u7D50\u679C (vitest 53 \u4EF6)`)\u3002\u300C\u6A5F\u80FD\u4E00\u5F0F\u300D\u300C\u6240\u898B\u300D\u306E\u3088\u3046\u306A\u62BD\u8C61\u7684\u306A\u540D\u524D\u306F\u907F\u3051\u3001done \u306E\u3068\u304D\u306F\u5FC5\u305A --artifact \u3067\u30D5\u30A1\u30A4\u30EB\u30FBURL\u30FB\u30B3\u30DF\u30C3\u30C8\u3092\u4ED8\u3051\u308B\u3002\n\u3059\u3079\u3066\u306E\u7BB1\u306F\u300C\u5165\u529B \u2192 \u51FA\u529B\u300D\u304C\u4F55\u304B\u306B\u3064\u306A\u304C\u3063\u3066\u3044\u308B\u3088\u3046\u306B\u3059\u308B (\u3064\u306A\u304C\u3063\u3066\u3044\u306A\u3044\u7BB1\u306F\u8A08\u753B\u306E\u7A74\u3002\u6700\u4E0A\u4F4D\u306E\u5165\u529B\u30CE\u30FC\u30C9\u306F\u3001\u672A\u63A5\u7D9A\u306E\u5165\u529B\u304C\u81EA\u52D5\u3067\u4E0A\u304C\u308B)\u3002\n\u8907\u6570\u4EBA\u3067\u540C\u3058 boxglow.json \u3092\u7DE8\u96C6\u3059\u308B\u306A\u3089\u3001\u5404\u81EA\u306E clone \u3067\u4E00\u5EA6 `npx boxglow git-setup` \u3092\u5B9F\u884C\u3059\u308B (\u7BB1\u306E\u5358\u4F4D\u3067\u81EA\u52D5\u30DE\u30FC\u30B8\u3055\u308C\u3001\u5225\u306E\u7BB1\u306E\u5909\u66F4\u306F\u885D\u7A81\u3057\u306A\u3044)\u3002\n\u8907\u6570\u30EA\u30DD\u30B8\u30C8\u30EA\u3092 1 \u3064\u306E\u8A08\u753B\u3067\u7BA1\u7406\u3059\u308B\u3068\u304D\u306F\u3001boxglow.json \u3092\u4E0A\u306E\u30D5\u30A9\u30EB\u30C0\u306B\u7F6E\u304D\u3001\u5404\u30EA\u30DD\u30B8\u30C8\u30EA\u3067\u306F\u74B0\u5883\u5909\u6570 BOXGLOW_FILE \u306B\u305D\u306E\u5834\u6240\u3092\u5165\u308C\u308B (\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1\u306F\u30EA\u30DD\u30B8\u30C8\u30EA\u3054\u3068\u306B 1 \u3064\u3002`npx boxglow project "\u540D\u524D" --repo <\u30D1\u30B9>`)\u3002\n\u5165\u529B\u306F\u65E2\u5B9A\u3067\u5FC5\u9808\u3002\u7121\u304F\u3066\u3082\u7740\u624B\u3067\u304D\u308B\u5165\u529B\u306F\u753B\u9762\u3067\u300C\u4EFB\u610F\u300D\u306B\u3059\u308B\u3002status \u306E\u300C\u6B21\u306E\u5019\u88DC\u300D\u306F\u5FC5\u9808\u306E\u5165\u529B\u304C\u305D\u308D\u3063\u305F\u7BB1\u304B\u3089\u4E26\u3076 (\u300C\u7740\u624B\u3067\u304D\u308B\u300D/\u300C\u5FC5\u9808\u306E\u5165\u529B\u5F85\u3061\u300D\u304C\u4ED8\u304F)\u3002\n\u5165\u529B\u306E\u8AAC\u660E\u306F\u66F8\u304B\u306A\u3044 (\u3064\u306A\u304C\u3063\u305F\u51FA\u529B\u5074\u306E\u8AAC\u660E\u304C\u4F7F\u308F\u308C\u308B)\u3002\u51FA\u529B\u306E\u8AAC\u660E\u306B\u5F62\u5F0F\u30FB\u5236\u7D04\u3092\u66F8\u304F\u3002\n\n---\n';
233
+
234
+ // .claude/skills/boxglow/SKILL.md
235
+ var SKILL_default = '---\nname: boxglow\ndescription: Boxglow (boxglow.json) \u3067\u8A08\u753B\u3068\u9032\u6357\u3092\u4EBA\u9593\u3068\u5171\u6709\u3059\u308B\u3002\u4F5C\u696D\u306E\u958B\u59CB\u30FB\u5206\u89E3\u30FB\u5B8C\u4E86\u30FB\u5224\u65AD\u5F85\u3061\u3092 npx boxglow \u3067\u8A18\u9332\u3059\u308B\u3002\u300C\u9032\u6357\u3092 Boxglow \u306B\u8A18\u9332\u3057\u3066\u300D\u300Cboxglow \u3067\u72B6\u6CC1\u3092\u898B\u3066\u300D\u300C/boxglow\u300D\u3067\u4F7F\u7528\u3002\n---\n\n# Boxglow \u3067\u8A08\u753B\u3068\u9032\u6357\u3092\u5171\u6709\u3059\u308B\n\n\u30EA\u30DD\u30B8\u30C8\u30EA\u76F4\u4E0B\u306E `boxglow.json` \u304C\u8A08\u753B\u306E\u6B63\u672C\u3002\u4EBA\u9593\u306F Boxglow \u306E\u753B\u9762 (https://boxglow.pages.dev/ \u3067\u300Cboxglow.json \u3092\u958B\u304F\u300D) \u3067\u898B\u3066\u3044\u308B\u3002\n\u76F4\u63A5\u7DE8\u96C6\u305B\u305A `npx boxglow` \u3067\u66F4\u65B0\u3059\u308B\u3002\n\n## \u6700\u521D\u306E\u8A08\u753B\u3065\u304F\u308A (\u65B0\u3057\u3044\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u3068\u304D)\n\n1. \u65B9\u91DD (README\u3001\u4F9D\u983C\u6587\u3001\u8981\u4EF6\u30E1\u30E2) \u3092\u8AAD\u307F\u3001`npx boxglow init --name "<\u540D\u524D>"` \u306E\u3042\u3068\u3001\u6700\u4E0A\u4F4D\u306B\u5927\u9805\u76EE (3\u301C7 \u500B) \u3092 `add` \u3067\u7F6E\u304F\u3002\n \u5404\u5927\u9805\u76EE\u306B\u306F\u300C\u5165\u529B (\u4F55\u3092\u4F7F\u3046\u304B)\u300D\u3068\u300C\u51FA\u529B (\u5177\u4F53\u7684\u306A\u6210\u679C\u7269: \u30D5\u30A1\u30A4\u30EB\u30FBURL\u30FBPR)\u300D\u3092\u5FC5\u305A\u66F8\u304D\u3001\u6700\u7D42\u6210\u679C\u7269\u307E\u3067\u7DDA\u3067\u3064\u306A\u3050 (`connect`)\n2. \u4F5C\u3063\u305F\u3089 `npx boxglow ask <\u6700\u521D\u306E\u5927\u9805\u76EE> "\u3053\u306E\u5927\u9805\u76EE\u306E\u5206\u3051\u65B9\u3067\u3088\u3044\u3067\u3059\u304B?" --options "\u3088\u3044|\u76F4\u3057\u305F\u3044"` \u3067\u4EBA\u306E\u78BA\u8A8D\u3092\u53D6\u308B\u3002\n \u4EBA\u306F\u753B\u9762\u3067\u3082\u3001`npx boxglow answer <block> "<\u56DE\u7B54>"` \u3067\u3082\u3001\u30C1\u30E3\u30C3\u30C8\u3067\u3082\u7B54\u3048\u3089\u308C\u308B\u3002\u30C1\u30E3\u30C3\u30C8\u3067\u7B54\u3048\u304C\u8FD4\u3063\u305F\u3089 AI \u304C `answer --by human` \u3067\u8A18\u9332\u3059\u308B\u3002\n \u4EBA\u304C\u753B\u9762\u3092\u4F7F\u308F\u306A\u3044\u904B\u7528 (AI \u3060\u3051\u3067\u9032\u3081\u308B\u6307\u793A\u304C\u3042\u308B\u3068\u304D) \u306F\u3001AI \u81EA\u8EAB\u304C `answer --by claude-code "<\u5224\u65AD\u3068\u7406\u7531>"` \u3067\u8A18\u9332\u3057\u3066\u5148\u3078\u9032\u3080\u3002\u5224\u65AD\u306E\u8A18\u9332\u3092\u6B8B\u3059\u3053\u3068\u304C\u76EE\u7684\u3067\u3001\u753B\u9762\u306F\u5FC5\u9808\u3067\u306F\u306A\u3044\n3. \u7740\u624B\u3059\u308B\u5927\u9805\u76EE\u3060\u3051\u3092 `split` \u3067\u5206\u89E3\u3059\u308B (\u5148\u306E\u6BB5\u968E\u306F\u7C97\u3044\u307E\u307E\u3002\u9032\u3081\u306A\u304C\u3089\u7D30\u304B\u304F\u3059\u308B)\n\n## \u624B\u9806\n\n1. `npx boxglow status` \u3092\u8AAD\u3080\u3002\u5224\u65AD\u5F85\u3061\u306E\u56DE\u7B54\u3001\u4F5C\u696D\u4E2D\u306E\u7BB1\u3001\u6B21\u306E\u5019\u88DC\u3092\u628A\u63E1\u3059\u308B\n2. \u62C5\u5F53\u3059\u308B\u7BB1\u3092\u6C7A\u3081\u305F\u3089 `npx boxglow show <block>` \u3067\u5165\u51FA\u529B\u3092\u78BA\u8A8D\u3057\u3001`npx boxglow start <block> --note "<\u4F55\u3092\u3059\u308B\u304B>"`\n3. \u5927\u304D\u3044\u7BB1\u306F `npx boxglow split <block> --spec \'<JSON>\'` \u3067\u5206\u89E3\u3059\u308B (\u5F62\u5F0F\u306F `npx boxglow help`)\u3002\u5404\u7BB1\u306E\u51FA\u529B\u3092\u5FC5\u305A\u6C7A\u3081\u308B\n4. \u6210\u679C\u7269\u304C\u3067\u304D\u305F\u3089 `npx boxglow done <block> --artifact "<\u540D\u524D>=<URL \u307E\u305F\u306F\u30D1\u30B9>"`\n5. \u4EBA\u9593\u306E\u5224\u65AD\u304C\u8981\u308B\u3068\u304D\u306F `npx boxglow ask <block> "<\u8CEA\u554F>" --options "A|B"` \u3057\u3066\u4ED6\u306E\u7BB1\u3078\u79FB\u308B\n6. \u8A70\u307E\u3063\u305F\u3089 `npx boxglow blocked <block> --note "<\u56F0\u3063\u3066\u3044\u308B\u3053\u3068>"`\n7. \u6700\u5F8C\u306B `npx boxglow status` \u3092\u8981\u7D04\u3057\u3066\u5831\u544A\u3059\u308B\n\n## \u7D04\u675F\n\n- `ask` \u306E\u8CEA\u554F\u306F\u3001\u305D\u308C\u3060\u3051\u3067\u5224\u65AD\u3067\u304D\u308B\u3088\u3046\u306B\u66F8\u304F\u3002\u300C\u3053\u308C\u3067\u3088\u3044\u3067\u3059\u304B\u300D\u300C\u4E0A\u8A18\u306E\u6848\u300D\u306E\u3088\u3046\u306B\u5916\u3092\u6307\u3055\u305A\u3001\u524D\u63D0\u30FB\u6BD4\u8F03\u30FB\u5F71\u97FF\u3092 `--context` \u306B\u66F8\u304F (\u4EBA\u306F\u4E00\u89A7\u306E\u4E2D\u306E\u8CEA\u554F\u3060\u3051\u3092\u8AAD\u3093\u3067\u7B54\u3048\u308B)\n- \u5019\u88DC\u304B\u3089 1 \u3064\u3092\u9078\u3076\u5834\u9762 (\u65B9\u5F0F\u30FB\u30E9\u30A4\u30D6\u30E9\u30EA\u30FB\u8A2D\u8A08\u306E\u9078\u629E) \u3067\u306F\u3001AI \u304C\u81EA\u5206\u3067\u6C7A\u3081\u308B\u3068\u304D\u3082 `ask <block> "<\u554F\u3044>" --options "A|B|C"` \u2192 `answer --by claude-code "<\u9078\u3093\u3060\u3082\u306E>\u3002\u7406\u7531: ..."` \u306E\u5F62\u3067\u8A18\u9332\u3057\u3001\u9078\u3070\u306A\u304B\u3063\u305F\u5019\u88DC\u3092\u7BB1\u306B\u6B8B\u3059 (\u65B9\u91DD\u8EE2\u63DB\u306E\u3068\u304D\u306B\u623B\u308C\u308B)\n- \u65B9\u91DD\u8EE2\u63DB\u306F `reopen <block> --note "<\u7406\u7531>"` \u3067\u524D\u306E\u7B54\u3048\u3092\u5C65\u6B74\u306B\u6B8B\u3057\u3066\u9078\u3073\u76F4\u3059 (\u5019\u88DC\u306F\u6D88\u3055\u306A\u3044)\n\n- \u540C\u6642\u306B\u4F5C\u696D\u4E2D\u306B\u3059\u308B\u7BB1\u306F 1\u301C2 \u500B\u307E\u3067\n- \u6210\u679C\u7269 (artifact) \u306F\u5F8C\u304B\u3089\u4EBA\u304C\u958B\u3051\u308B\u3082\u306E (\u30B3\u30DF\u30C3\u30C8\u3001PR\u3001\u30D5\u30A1\u30A4\u30EB\u306E\u30D1\u30B9) \u306B\u3059\u308B\n- \u51FA\u529B\u306F\u5177\u4F53\u7684\u306A\u6210\u679C\u7269\u306E\u540D\u524D\u306B\u3059\u308B (\u30D5\u30A1\u30A4\u30EB\u30FBPR\u30FBURL\u30FB\u30C6\u30B9\u30C8\u7D50\u679C)\u3002\u62BD\u8C61\u7684\u306A\u540D\u524D (\u6A5F\u80FD\u4E00\u5F0F\u3001\u6240\u898B) \u306F\u907F\u3051\u308B\n- \u7BB1\u306F\u5FC5\u305A\u300C\u5165\u529B \u2192 \u51FA\u529B\u300D\u3067\u3064\u306A\u3050\u3002\u8DB3\u308A\u306A\u3044\u5165\u51FA\u529B\u306F `port`\u3001\u5916\u3059\u306E\u306F `disconnect`\u3001\u7BB1\u305D\u306E\u3082\u306E\u3092\u6D88\u3059\u306E\u306F `remove`\u3002\u6587\u66F8\u5316\u306F `export --out docs/ROADMAP.md`\n- \u7BB1\u3092 add \u3059\u308B\u3068\u304D\u306F\u4ED5\u4E8B\u306E\u7A2E\u985E\u3092 `--category` \u3067\u4ED8\u3051\u308B (design \u8A2D\u8A08 / build \u5B9F\u88C5 / verify \u691C\u8A3C / evaluate \u8A55\u4FA1 / study \u691C\u8A0E / research \u8ABF\u67FB / ui \u30C7\u30B6\u30A4\u30F3 / improve \u6539\u5584 / fix \u8AB2\u984C\u89E3\u6C7A / docs \u6587\u66F8 / ops \u904B\u7528 / other \u305D\u306E\u4ED6)\u3002\u753B\u9762\u3067\u306F\u8272\u306E\u5E2F\u3068\u672D\u306B\u306A\u308B\n- boxglow.json \u304C\u307E\u3060\u7121\u3051\u308C\u3070 `npx boxglow init --name "<\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D>"` \u3067\u4F5C\u308A\u3001\u6700\u7D42\u6210\u679C\u7269\u3068\u6700\u4E0A\u4F4D\u306E\u7BB1\u3092 `add` \u3067\u7F6E\u304F\n';
236
+
237
+ // src/model/categories.ts
238
+ var CATEGORIES = [
239
+ { key: "study", label: "\u691C\u8A0E", en: "study", color: "#6b7075" },
240
+ { key: "research", label: "\u8ABF\u67FB", en: "research", color: "#7a5230" },
241
+ { key: "design", label: "\u8A2D\u8A08", en: "design", color: "#2f6fb3" },
242
+ { key: "ui", label: "\u30C7\u30B6\u30A4\u30F3", en: "ui", color: "#c0427a" },
243
+ { key: "build", label: "\u5B9F\u88C5", en: "build", color: "#5a4fcf" },
244
+ { key: "verify", label: "\u691C\u8A3C", en: "verify", color: "#26753d" },
245
+ { key: "evaluate", label: "\u8A55\u4FA1", en: "evaluate", color: "#8a6508" },
246
+ { key: "improve", label: "\u6539\u5584", en: "improve", color: "#b5581a" },
247
+ { key: "fix", label: "\u8AB2\u984C\u89E3\u6C7A", en: "fix", color: "#c0392b" },
248
+ { key: "docs", label: "\u6587\u66F8", en: "docs", color: "#5f6f3a" },
249
+ { key: "ops", label: "\u904B\u7528", en: "ops", color: "#1f7a8c" },
250
+ { key: "other", label: "\u305D\u306E\u4ED6", en: "other", color: "#8a8f94", neutral: true }
251
+ ];
252
+ function categoryOf(key) {
253
+ if (!key) return void 0;
254
+ return CATEGORIES.find((c) => c.key === key);
255
+ }
256
+ function findCategory(text2) {
257
+ const q = text2.trim().toLowerCase();
258
+ if (!q) return void 0;
259
+ return CATEGORIES.find((c) => c.key === q || c.en.toLowerCase() === q || c.label === text2.trim());
260
+ }
261
+
262
+ // src/model/types.ts
263
+ var ROOT_ID = "root";
264
+ var SCHEMA_VERSION = 5;
265
+
266
+ // node_modules/nanoid/url-alphabet/index.js
267
+ var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
268
+
269
+ // node_modules/nanoid/index.js
270
+ var GET_RANDOM_LIMIT = 65536;
271
+ function fillRandom(buffer) {
272
+ let from = 0;
273
+ while (from < buffer.length) {
274
+ let to = Math.min(from + GET_RANDOM_LIMIT, buffer.length);
275
+ crypto.getRandomValues(buffer.subarray(from, to));
276
+ from = to;
277
+ }
278
+ }
279
+ function random(bytes) {
280
+ bytes |= 0;
281
+ if (bytes < 0) throw new RangeError("Wrong ID size");
282
+ let buffer = Buffer.allocUnsafe(bytes);
283
+ fillRandom(buffer);
284
+ return buffer;
285
+ }
286
+ function customRandom(alphabet, defaultSize, getRandom) {
287
+ let safeByteCutoff = 256 - 256 % alphabet.length;
288
+ if (safeByteCutoff === 256) {
289
+ let mask = alphabet.length - 1;
290
+ return (size = defaultSize) => {
291
+ if (!size) return "";
292
+ let id = "";
293
+ while (true) {
294
+ let bytes = getRandom(size);
295
+ let i = size;
296
+ while (i--) {
297
+ id += alphabet[bytes[i] & mask];
298
+ if (id.length >= size) return id;
299
+ }
300
+ }
301
+ };
302
+ }
303
+ let step = Math.ceil(1.6 * 256 * defaultSize / safeByteCutoff);
304
+ return (size = defaultSize) => {
305
+ if (!size) return "";
306
+ let id = "";
307
+ while (true) {
308
+ let bytes = getRandom(step);
309
+ let i = step;
310
+ while (i--) {
311
+ if (bytes[i] < safeByteCutoff) {
312
+ id += alphabet[bytes[i] % alphabet.length];
313
+ if (id.length >= size) return id;
314
+ }
315
+ }
316
+ }
317
+ };
318
+ }
319
+ var POOL_MAX = GET_RANDOM_LIMIT / 2;
320
+ function customAlphabet(alphabet, defaultSize = 21) {
321
+ if (typeof alphabet !== "string" || !alphabet || alphabet.length > 256) {
322
+ return customRandom(alphabet, defaultSize, random);
323
+ }
324
+ for (let i = 0; i < alphabet.length; i++) {
325
+ if (alphabet.charCodeAt(i) > 255) {
326
+ return customRandom(alphabet, defaultSize, random);
327
+ }
328
+ }
329
+ let charCodes = Uint8Array.from(alphabet, (str2) => {
330
+ return str2.charCodeAt(0);
331
+ });
332
+ let alphabetLen = alphabet.length;
333
+ let mask = (2 << 31 - Math.clz32(alphabetLen - 1 | 1)) - 1;
334
+ let pool = "";
335
+ let poolOffset = 0;
336
+ let poolNext = 0;
337
+ return (size = defaultSize) => {
338
+ size |= 0;
339
+ if (size < 0) throw new RangeError("Wrong ID size");
340
+ if (size === 0) return "";
341
+ if (poolOffset + size > pool.length) {
342
+ let target = Math.max(poolNext, size);
343
+ poolNext = Math.min(target * 16, POOL_MAX);
344
+ let buffer = Buffer.allocUnsafe(target);
345
+ if (mask === alphabetLen - 1) {
346
+ fillRandom(buffer);
347
+ for (let i = 0; i < target; i++) {
348
+ buffer[i] = charCodes[buffer[i] & mask];
349
+ }
350
+ } else {
351
+ let randomBytes = Buffer.allocUnsafe(
352
+ Math.ceil(1.6 * (mask + 1) * target / alphabetLen)
353
+ );
354
+ let accepted = 0;
355
+ while (accepted < target) {
356
+ fillRandom(randomBytes);
357
+ for (let i = 0; i < randomBytes.length; i++) {
358
+ let index = randomBytes[i] & mask;
359
+ if (index < alphabetLen) {
360
+ buffer[accepted++] = charCodes[index];
361
+ if (accepted === target) break;
362
+ }
363
+ }
364
+ }
365
+ }
366
+ pool = buffer.toString("latin1");
367
+ poolOffset = 0;
368
+ }
369
+ poolOffset += size;
370
+ return pool.substring(poolOffset - size, poolOffset);
371
+ };
372
+ }
373
+ var nanoid = customAlphabet(urlAlphabet);
374
+
375
+ // src/model/graph.ts
376
+ var LOG_LIMIT = 500;
377
+ var newId = () => nanoid(10);
378
+ var now = () => (/* @__PURE__ */ new Date()).toISOString();
379
+ var clone = (value) => structuredClone(value);
380
+ function nextKey(q) {
381
+ const n = q.nextKey ?? 1;
382
+ q.nextKey = n + 1;
383
+ return `B${n}`;
384
+ }
385
+ function ensureKeys(q) {
386
+ const used = /* @__PURE__ */ new Set();
387
+ for (const b of Object.values(q.blocks)) if (b.key) used.add(b.key);
388
+ let n = q.nextKey ?? 1;
389
+ for (const b of Object.values(q.blocks)) {
390
+ if (b.id === ROOT_ID || b.key) continue;
391
+ while (used.has(`B${n}`)) n++;
392
+ b.key = `B${n}`;
393
+ used.add(b.key);
394
+ n++;
395
+ }
396
+ q.nextKey = Math.max(n, q.nextKey ?? 1);
397
+ }
398
+ var CHILD_PADDING = { left: 120, top: 76 };
399
+ var DEFAULT_HEADER_H = 60;
400
+ function childTop(p, parentId) {
401
+ const b = p.blocks[parentId];
402
+ if (!b) return CHILD_PADDING.top;
403
+ const lines = Math.min(3, Math.max(1, Math.ceil(b.title.length * 14 / 400)));
404
+ return DEFAULT_HEADER_H + (lines - 1) * 18 + 16;
405
+ }
406
+ function createProject(name) {
407
+ const t = now();
408
+ const root = {
409
+ id: ROOT_ID,
410
+ parentId: null,
411
+ title: name,
412
+ description: "",
413
+ status: "black",
414
+ assigneeIds: [],
415
+ position: { x: 0, y: 0 },
416
+ collapsed: false,
417
+ artifacts: [],
418
+ activity: null,
419
+ decisions: []
420
+ };
421
+ const outPort = {
422
+ id: newId(),
423
+ blockId: ROOT_ID,
424
+ direction: "out",
425
+ name: "\u6700\u7D42\u6210\u679C\u7269",
426
+ description: "",
427
+ required: true,
428
+ artifacts: []
429
+ };
430
+ const base = {
431
+ schemaVersion: SCHEMA_VERSION,
432
+ id: newId(),
433
+ name,
434
+ description: "",
435
+ createdAt: t,
436
+ visibility: "private",
437
+ members: [],
438
+ blocks: { [ROOT_ID]: root },
439
+ ports: { [outPort.id]: outPort },
440
+ edges: {},
441
+ terminals: { in: { x: 0, y: 200 }, out: { x: 1100, y: 200 } },
442
+ log: [],
443
+ agents: {},
444
+ nextKey: 1
445
+ };
446
+ return addProjectBlock(base, name).project;
447
+ }
448
+ function addProjectBlock(p, name) {
449
+ const q = clone(p);
450
+ const existing = projectBlocks(q);
451
+ const id = newId();
452
+ const y = existing.length === 0 ? 40 : Math.max(...existing.map((b) => b.position.y)) + 400;
453
+ q.blocks[id] = {
454
+ id,
455
+ key: nextKey(q),
456
+ kind: "project",
457
+ parentId: ROOT_ID,
458
+ title: name,
459
+ description: "",
460
+ status: "black",
461
+ assigneeIds: [],
462
+ position: { x: 300, y },
463
+ collapsed: false,
464
+ artifacts: [],
465
+ activity: null,
466
+ decisions: []
467
+ };
468
+ const out2 = { id: newId(), blockId: id, direction: "out", name: "\u6700\u7D42\u6210\u679C\u7269", description: "", required: true, artifacts: [] };
469
+ q.ports[out2.id] = out2;
470
+ let rootOut = portsOf(q, ROOT_ID, "out").find((o) => incomingEdges(q, { portId: o.id, side: "inner" }).length === 0);
471
+ if (!rootOut) {
472
+ rootOut = { id: newId(), blockId: ROOT_ID, direction: "out", name: existing.length === 0 ? "\u6700\u7D42\u6210\u679C\u7269" : `${name} \u306E\u6210\u679C\u7269`, description: "", required: true, artifacts: [] };
473
+ q.ports[rootOut.id] = rootOut;
474
+ }
475
+ const eid = newId();
476
+ q.edges[eid] = { id: eid, from: { portId: out2.id, side: "outer" }, to: { portId: rootOut.id, side: "inner" }, kind: "up", auto: false };
477
+ return { project: q, blockId: id };
478
+ }
479
+ function projectBlocks(p) {
480
+ return childrenOf(p, ROOT_ID).filter((b) => b.kind === "project");
481
+ }
482
+ function defaultTaskParent(p) {
483
+ return projectBlocks(p)[0]?.id ?? ROOT_ID;
484
+ }
485
+ function normalizeCollapsed(p) {
486
+ const ids = Object.values(p.blocks).filter((b) => b.id !== ROOT_ID && kindOf(b) !== "project" && !b.collapsed).map((b) => b.id);
487
+ if (ids.length === 0) return p;
488
+ const blocks = { ...p.blocks };
489
+ for (const id of ids) blocks[id] = { ...blocks[id], collapsed: true };
490
+ return { ...p, blocks };
491
+ }
492
+ var kindOf = (b) => b.kind ?? "task";
493
+ function createArtifact(title, url = "") {
494
+ return { id: newId(), title, url, kind: url ? "url" : "note", note: "" };
495
+ }
496
+ function createGitArtifact(title, git2) {
497
+ return { id: newId(), title, url: git2.url ?? "", kind: "git", note: "", repo: git2.repo, path: git2.path, commit: git2.commit, blob: git2.blob, state: "ok", checkedAt: now() };
498
+ }
499
+ function childrenOf(p, blockId) {
500
+ return Object.values(p.blocks).filter((b) => b.parentId === blockId);
501
+ }
502
+ function descendantsOf(p, blockId) {
503
+ const out2 = [];
504
+ const stack = [blockId];
505
+ while (stack.length > 0) {
506
+ const id = stack.pop();
507
+ for (const c of childrenOf(p, id)) {
508
+ out2.push(c);
509
+ stack.push(c.id);
510
+ }
511
+ }
512
+ return out2;
513
+ }
514
+ function ancestorsOf(p, blockId) {
515
+ const out2 = [];
516
+ let cur = p.blocks[blockId]?.parentId ?? null;
517
+ while (cur !== null) {
518
+ const b = p.blocks[cur];
519
+ if (!b) break;
520
+ out2.push(b);
521
+ cur = b.parentId;
522
+ }
523
+ return out2;
524
+ }
525
+ function portsOf(p, blockId, direction) {
526
+ return Object.values(p.ports).filter((q) => q.blockId === blockId && (direction === void 0 || q.direction === direction));
527
+ }
528
+ var sameEndpoint = (a, b) => a.portId === b.portId && a.side === b.side;
529
+ function incomingEdges(p, ep) {
530
+ return Object.values(p.edges).filter((e) => sameEndpoint(e.to, ep));
531
+ }
532
+ function outgoingEdges(p, ep) {
533
+ return Object.values(p.edges).filter((e) => sameEndpoint(e.from, ep));
534
+ }
535
+ function scopeOf(p, ep) {
536
+ const port = p.ports[ep.portId];
537
+ if (!port) return null;
538
+ const block = p.blocks[port.blockId];
539
+ if (!block) return null;
540
+ return ep.side === "inner" ? block.id : block.parentId;
541
+ }
542
+ function validateConnection(p, from, to) {
543
+ const fp = p.ports[from.portId];
544
+ const tp = p.ports[to.portId];
545
+ if (!fp || !tp) return { ok: false, reason: "\u30DD\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" };
546
+ if (from.portId === to.portId) return { ok: false, reason: "\u540C\u3058\u30DD\u30FC\u30C8\u3069\u3046\u3057\u306F\u3064\u306A\u3052\u307E\u305B\u3093" };
547
+ const fromIsSource = fp.direction === "out" && from.side === "outer" || fp.direction === "in" && from.side === "inner";
548
+ const toIsTarget = tp.direction === "in" && to.side === "outer" || tp.direction === "out" && to.side === "inner";
549
+ if (!fromIsSource) return { ok: false, reason: "\u7DDA\u306F\u51FA\u529B\u30DD\u30FC\u30C8 (\u307E\u305F\u306F\u89AA\u306E\u5165\u529B) \u304B\u3089\u5F15\u3044\u3066\u304F\u3060\u3055\u3044" };
550
+ if (!toIsTarget) return { ok: false, reason: "\u7DDA\u306F\u5165\u529B\u30DD\u30FC\u30C8 (\u307E\u305F\u306F\u89AA\u306E\u51FA\u529B) \u306B\u3064\u306A\u3044\u3067\u304F\u3060\u3055\u3044" };
551
+ const sf = scopeOf(p, from);
552
+ const st = scopeOf(p, to);
553
+ if (sf === null || st === null || sf !== st) return { ok: false, reason: "\u540C\u3058\u968E\u5C64\u306E\u30DD\u30FC\u30C8\u3069\u3046\u3057\u3060\u3051\u3064\u306A\u3052\u307E\u3059" };
554
+ let kind;
555
+ if (fp.direction === "out" && tp.direction === "in") kind = "sibling";
556
+ else if (fp.direction === "out" && tp.direction === "out") kind = "up";
557
+ else if (fp.direction === "in" && tp.direction === "in") kind = "down";
558
+ else kind = "through";
559
+ if (kind === "sibling" && wouldCreateCycle(p, fp.blockId, tp.blockId)) {
560
+ return { ok: false, reason: "\u5FAA\u74B0\u3059\u308B\u7D50\u7DDA\u306F\u3067\u304D\u307E\u305B\u3093" };
561
+ }
562
+ return { ok: true, kind };
563
+ }
564
+ function wouldCreateCycle(p, fromBlock, toBlock) {
565
+ if (fromBlock === toBlock) return true;
566
+ const visited = /* @__PURE__ */ new Set();
567
+ const stack = [toBlock];
568
+ while (stack.length > 0) {
569
+ const cur = stack.pop();
570
+ if (cur === fromBlock) return true;
571
+ if (visited.has(cur)) continue;
572
+ visited.add(cur);
573
+ for (const port of portsOf(p, cur, "out")) {
574
+ for (const e of outgoingEdges(p, { portId: port.id, side: "outer" })) {
575
+ if (e.kind !== "sibling") continue;
576
+ const next = p.ports[e.to.portId]?.blockId;
577
+ if (next) stack.push(next);
578
+ }
579
+ }
580
+ }
581
+ return false;
582
+ }
583
+ function touch(p) {
584
+ return clone(p);
585
+ }
586
+ function addBlock(p, args) {
587
+ const q = touch(p);
588
+ const id = newId();
589
+ const siblings = childrenOf(q, args.parentId);
590
+ let defaultPos;
591
+ if (args.parentId === ROOT_ID) {
592
+ defaultPos = siblings.length === 0 ? { x: 260, y: 80 } : { x: Math.max(...siblings.map((s) => s.position.x)) + 300, y: 80 };
593
+ } else {
594
+ defaultPos = siblings.length === 0 ? { x: CHILD_PADDING.left, y: childTop(p, args.parentId) } : { x: CHILD_PADDING.left, y: Math.max(...siblings.map((s) => s.position.y)) + 110 };
595
+ }
596
+ q.blocks[id] = {
597
+ id,
598
+ key: nextKey(q),
599
+ parentId: args.parentId,
600
+ title: args.title,
601
+ description: "",
602
+ status: "black",
603
+ assigneeIds: [],
604
+ position: args.position ?? defaultPos,
605
+ collapsed: false,
606
+ artifacts: [],
607
+ activity: null,
608
+ decisions: []
609
+ };
610
+ const out2 = { id: newId(), blockId: id, direction: "out", name: args.outputName ?? "\u51FA\u529B", description: "", required: true, artifacts: [] };
611
+ q.ports[out2.id] = out2;
612
+ if (args.actor) appendLog(q, { actor: args.actor, kind: "added", blockId: id, message: `\u300C${args.title}\u300D\u3092\u8FFD\u52A0` });
613
+ const parent = q.blocks[args.parentId];
614
+ if (parent && parent.id !== ROOT_ID && parent.status === "black") parent.status = "gray";
615
+ return { project: normalizePromotions(q), blockId: id };
616
+ }
617
+ function updateBlock(p, blockId, patch) {
618
+ const q = touch(p);
619
+ const b = q.blocks[blockId];
620
+ if (!b) return p;
621
+ Object.assign(b, patch);
622
+ return q;
623
+ }
624
+ function removeBlock(p, blockId) {
625
+ if (blockId === ROOT_ID) return p;
626
+ const q = touch(p);
627
+ const ids = /* @__PURE__ */ new Set([blockId, ...descendantsOf(q, blockId).map((b) => b.id)]);
628
+ const portIds = new Set(Object.values(q.ports).filter((x) => ids.has(x.blockId)).map((x) => x.id));
629
+ for (const e of Object.values(q.edges)) {
630
+ if (portIds.has(e.from.portId) || portIds.has(e.to.portId)) delete q.edges[e.id];
631
+ }
632
+ for (const id of portIds) delete q.ports[id];
633
+ for (const id of ids) delete q.blocks[id];
634
+ return normalizePromotions(q);
635
+ }
636
+ function addPort(p, args) {
637
+ if (args.direction === "out" && args.blockId !== ROOT_ID && !canAddOutput(p, args.blockId)) {
638
+ return { project: p, portId: portsOf(p, args.blockId, "out")[0]?.id ?? "" };
639
+ }
640
+ const q = touch(p);
641
+ const id = newId();
642
+ q.ports[id] = {
643
+ id,
644
+ blockId: args.blockId,
645
+ direction: args.direction,
646
+ name: args.name,
647
+ description: "",
648
+ required: true,
649
+ artifacts: []
650
+ };
651
+ if (args.direction === "out" && kindOf(q.blocks[args.blockId]) === "project") mirrorProjectOutput(q, id);
652
+ return { project: normalizePromotions(q), portId: id };
653
+ }
654
+ function mirrorProjectOutput(q, portId) {
655
+ const port = q.ports[portId];
656
+ if (!port || port.direction !== "out") return;
657
+ const already = Object.values(q.edges).some((e) => e.from.portId === portId && e.from.side === "outer" && q.ports[e.to.portId]?.blockId === ROOT_ID);
658
+ if (already) return;
659
+ const rootOut = { id: newId(), blockId: ROOT_ID, direction: "out", name: port.name, description: port.description, required: true, artifacts: [] };
660
+ q.ports[rootOut.id] = rootOut;
661
+ const eid = newId();
662
+ q.edges[eid] = { id: eid, from: { portId, side: "outer" }, to: { portId: rootOut.id, side: "inner" }, kind: "up", auto: true };
663
+ }
664
+ function mirroredRootOutput(p, portId) {
665
+ const e = Object.values(p.edges).find((x) => x.from.portId === portId && x.from.side === "outer" && p.ports[x.to.portId]?.blockId === ROOT_ID);
666
+ return e ? p.ports[e.to.portId] ?? null : null;
667
+ }
668
+ function canAddOutput(p, blockId) {
669
+ if (kindOf(p.blocks[blockId]) === "project") return true;
670
+ if (blockId === ROOT_ID) return true;
671
+ if (portsOf(p, blockId, "out").length === 0) return true;
672
+ return childrenOf(p, blockId).length > 0;
673
+ }
674
+ function updatePort(p, portId, patch) {
675
+ const q = touch(p);
676
+ const x = q.ports[portId];
677
+ if (!x) return p;
678
+ Object.assign(x, patch);
679
+ if (patch.name !== void 0 && x.direction === "out" && kindOf(q.blocks[x.blockId]) === "project") {
680
+ const mirror = mirroredRootOutput(q, portId);
681
+ if (mirror) mirror.name = patch.name;
682
+ }
683
+ return q;
684
+ }
685
+ function connect(p, from, to) {
686
+ const check = validateConnection(p, from, to);
687
+ if (!check.ok) return { project: p, error: check.reason };
688
+ const q = touch(p);
689
+ for (const e of incomingEdges(q, to)) delete q.edges[e.id];
690
+ const id = newId();
691
+ q.edges[id] = { id, from, to, kind: check.kind, auto: false };
692
+ return { project: normalizePromotions(q), edgeId: id };
693
+ }
694
+ function disconnect(p, edgeId) {
695
+ if (!p.edges[edgeId]) return p;
696
+ const q = touch(p);
697
+ delete q.edges[edgeId];
698
+ return normalizePromotions(q);
699
+ }
700
+ function normalizePromotions(p) {
701
+ const q = clone(p);
702
+ for (let guard = 0; guard < 64; guard++) {
703
+ let changed = false;
704
+ for (const e of Object.values(q.edges)) {
705
+ if (!e.auto) continue;
706
+ const manual = incomingEdges(q, e.to).some((o) => !o.auto);
707
+ if (manual) {
708
+ delete q.edges[e.id];
709
+ changed = true;
710
+ }
711
+ }
712
+ for (const port of Object.values(q.ports)) {
713
+ if (!port.promotedFrom) continue;
714
+ const used = outgoingEdges(q, { portId: port.id, side: "inner" }).length > 0;
715
+ if (!used) {
716
+ for (const e of Object.values(q.edges)) {
717
+ if (e.from.portId === port.id || e.to.portId === port.id) delete q.edges[e.id];
718
+ }
719
+ delete q.ports[port.id];
720
+ changed = true;
721
+ }
722
+ }
723
+ for (const port of Object.values(q.ports)) {
724
+ if (!port.promotedFrom || !q.ports[port.id]) continue;
725
+ const twin = Object.values(q.ports).find((x) => x.id !== port.id && x.blockId === port.blockId && x.direction === "in" && x.promotedFrom && x.name === port.name);
726
+ if (!twin) continue;
727
+ for (const e of Object.values(q.edges)) {
728
+ if (e.from.portId === twin.id) e.from = { ...e.from, portId: port.id };
729
+ if (e.to.portId === twin.id) e.to = { ...e.to, portId: port.id };
730
+ }
731
+ delete q.ports[twin.id];
732
+ changed = true;
733
+ }
734
+ for (const port of Object.values(q.ports)) {
735
+ if (port.direction !== "in") continue;
736
+ const block = q.blocks[port.blockId];
737
+ if (!block || block.parentId === null) continue;
738
+ if (incomingEdges(q, { portId: port.id, side: "outer" }).length > 0) continue;
739
+ let promoted = Object.values(q.ports).find((x) => x.blockId === block.parentId && x.direction === "in" && x.promotedFrom && x.name === port.name);
740
+ if (!promoted) {
741
+ promoted = {
742
+ id: newId(),
743
+ blockId: block.parentId,
744
+ direction: "in",
745
+ name: port.name,
746
+ description: port.description,
747
+ required: port.required,
748
+ artifacts: [],
749
+ promotedFrom: port.id
750
+ };
751
+ q.ports[promoted.id] = promoted;
752
+ }
753
+ const id = newId();
754
+ q.edges[id] = { id, from: { portId: promoted.id, side: "inner" }, to: { portId: port.id, side: "outer" }, kind: "down", auto: true };
755
+ changed = true;
756
+ }
757
+ if (!changed) break;
758
+ }
759
+ return q;
760
+ }
761
+ function effectiveProgress(p, blockId) {
762
+ const b = p.blocks[blockId];
763
+ if (!b) return 0;
764
+ if (b.status === "white") return 100;
765
+ if (typeof b.progress === "number") return Math.max(0, Math.min(100, b.progress));
766
+ const kids = childrenOf(p, blockId);
767
+ if (kids.length === 0) return 0;
768
+ return Math.round(kids.reduce((acc, k) => acc + effectiveProgress(p, k.id), 0) / kids.length);
769
+ }
770
+ function computeProgress(p, blockId) {
771
+ const targets = descendantsOf(p, blockId).filter((b) => kindOf(b) !== "project");
772
+ const list2 = targets.length > 0 ? targets : blockId === ROOT_ID ? [] : [p.blocks[blockId]].filter((b) => b && kindOf(b) !== "project");
773
+ const count = (s) => list2.filter((b) => b.status === s).length;
774
+ const white = count("white");
775
+ const total = list2.length;
776
+ const percent = blockId === ROOT_ID ? (() => {
777
+ const pj = projectBlocks(p);
778
+ return pj.length === 0 ? 0 : Math.round(pj.reduce((acc, b) => acc + effectiveProgress(p, b.id), 0) / pj.length);
779
+ })() : effectiveProgress(p, blockId);
780
+ return { total, white, gray: count("gray"), black: count("black"), ratio: total === 0 ? 0 : white / total, percent };
781
+ }
782
+ function setProgress(p, blockId, value, actor) {
783
+ const b = p.blocks[blockId];
784
+ if (!b) return p;
785
+ const q = touch(p);
786
+ if (value === null) delete q.blocks[blockId].progress;
787
+ else q.blocks[blockId].progress = Math.max(0, Math.min(100, Math.round(value)));
788
+ if (value !== null && b.status === "black" && value > 0) q.blocks[blockId].status = "gray";
789
+ appendLog(q, { actor, kind: "note", blockId, message: `\u300C${b.title}\u300D\u306E\u9032\u6357 ${value === null ? "\u81EA\u52D5" : value + "%"}` });
790
+ return q;
791
+ }
792
+ function isSourceReady(p, ep, seen = /* @__PURE__ */ new Set()) {
793
+ const key = `${ep.portId}:${ep.side}`;
794
+ if (seen.has(key)) return false;
795
+ seen.add(key);
796
+ const port = p.ports[ep.portId];
797
+ if (!port) return false;
798
+ if (port.direction === "out") {
799
+ const b = p.blocks[port.blockId];
800
+ return port.artifacts.length > 0 || b?.status === "white";
801
+ }
802
+ if (port.artifacts.length > 0) return true;
803
+ const incoming = incomingEdges(p, { portId: port.id, side: "outer" });
804
+ return incoming.some((e) => isSourceReady(p, e.from, seen));
805
+ }
806
+ function isEdgeReady(p, e) {
807
+ return isSourceReady(p, e.from);
808
+ }
809
+ function isInputReady(p, portId) {
810
+ const port = p.ports[portId];
811
+ if (!port || port.direction !== "in") return false;
812
+ if (port.artifacts.length > 0) return true;
813
+ return incomingEdges(p, { portId, side: "outer" }).some((e) => isEdgeReady(p, e));
814
+ }
815
+ function missingRequiredInputs(p, blockId) {
816
+ return portsOf(p, blockId, "in").filter((q) => q.required && !isInputReady(p, q.id));
817
+ }
818
+ function toJSON(p) {
819
+ const { updatedAt: _u, version: _v, ...rest } = p;
820
+ return JSON.stringify(rest, null, 2);
821
+ }
822
+ function fromJSON(text2) {
823
+ let data;
824
+ try {
825
+ data = JSON.parse(text2);
826
+ } catch {
827
+ throw new Error("JSON \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093");
828
+ }
829
+ if (typeof data !== "object" || data === null) throw new Error("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093");
830
+ const d = data;
831
+ const ver = Number(d.schemaVersion);
832
+ if (![1, 2, 3, 4, 5].includes(ver)) throw new Error(`\u5BFE\u5FDC\u3057\u3066\u3044\u306A\u3044\u30C7\u30FC\u30BF\u5F62\u5F0F\u306E\u7248\u3067\u3059 (schemaVersion=${String(d.schemaVersion)})`);
833
+ if (!d.blocks || !d.ports || !d.edges || !d.blocks[ROOT_ID]) throw new Error("\u30D6\u30ED\u30C3\u30AF\u30FB\u30DD\u30FC\u30C8\u30FB\u7DDA\u306E\u30C7\u30FC\u30BF\u304C\u8DB3\u308A\u307E\u305B\u3093");
834
+ const q = {
835
+ schemaVersion: SCHEMA_VERSION,
836
+ id: typeof d.id === "string" ? d.id : newId(),
837
+ name: typeof d.name === "string" ? d.name : "\u7121\u984C",
838
+ description: typeof d.description === "string" ? d.description : "",
839
+ createdAt: typeof d.createdAt === "string" ? d.createdAt : now(),
840
+ visibility: d.visibility ?? "private",
841
+ members: Array.isArray(d.members) ? d.members : [],
842
+ blocks: d.blocks,
843
+ ports: d.ports,
844
+ edges: d.edges,
845
+ terminals: d.terminals ?? { in: { x: 0, y: 200 }, out: { x: 1100, y: 200 } },
846
+ log: Array.isArray(d.log) ? d.log : [],
847
+ agents: d.agents && typeof d.agents === "object" ? d.agents : {},
848
+ nextKey: typeof d.nextKey === "number" ? d.nextKey : 1,
849
+ inputGroups: Array.isArray(d.inputGroups) ? d.inputGroups : []
850
+ };
851
+ for (const x of Object.values(q.ports)) if (x.groupId && !q.inputGroups.some((gp) => gp.id === x.groupId)) delete x.groupId;
852
+ for (const b of Object.values(q.blocks)) {
853
+ if (b.activity === void 0) b.activity = null;
854
+ if (!Array.isArray(b.decisions)) b.decisions = [];
855
+ if (!Array.isArray(b.artifacts)) b.artifacts = [];
856
+ if (!Array.isArray(b.assigneeIds)) b.assigneeIds = [];
857
+ if (b.progress !== void 0 && typeof b.progress !== "number") delete b.progress;
858
+ }
859
+ if (ver < 4) {
860
+ for (const x of Object.values(q.ports)) if (x.direction === "in") x.required = true;
861
+ }
862
+ ensureKeys(q);
863
+ for (const x of Object.values(q.ports)) if (!q.blocks[x.blockId]) delete q.ports[x.id];
864
+ for (const e of Object.values(q.edges)) if (!q.ports[e.from.portId] || !q.ports[e.to.portId]) delete q.edges[e.id];
865
+ return normalizePromotions(wrapIntoProject(q));
866
+ }
867
+ function wrapIntoProject(p) {
868
+ const topTasks = childrenOf(p, ROOT_ID).filter((b) => kindOf(b) !== "project");
869
+ if (topTasks.length === 0) return p;
870
+ const q = clone(p);
871
+ const pid = newId();
872
+ q.blocks[pid] = {
873
+ id: pid,
874
+ key: nextKey(q),
875
+ kind: "project",
876
+ parentId: ROOT_ID,
877
+ title: q.name,
878
+ description: q.description,
879
+ status: "gray",
880
+ assigneeIds: [],
881
+ position: { x: 300, y: 40 },
882
+ collapsed: false,
883
+ artifacts: [],
884
+ activity: null,
885
+ decisions: []
886
+ };
887
+ const minX = Math.min(...topTasks.map((b) => b.position.x));
888
+ const minY = Math.min(...topTasks.map((b) => b.position.y));
889
+ for (const b of topTasks) {
890
+ q.blocks[b.id].parentId = pid;
891
+ q.blocks[b.id].position = { x: b.position.x - minX + CHILD_PADDING.left, y: b.position.y - minY + childTop(q, pid) };
892
+ }
893
+ for (const e of Object.values(q.edges)) {
894
+ const tp = q.ports[e.to.portId];
895
+ const fp = q.ports[e.from.portId];
896
+ if (!tp || !fp) continue;
897
+ if (e.kind === "up" && tp.blockId === ROOT_ID && e.to.side === "inner") {
898
+ let mid = portsOf(q, pid, "out").find((o) => o.name === tp.name);
899
+ if (!mid) {
900
+ mid = { id: newId(), blockId: pid, direction: "out", name: tp.name, description: tp.description, required: true, artifacts: [] };
901
+ q.ports[mid.id] = mid;
902
+ const up = newId();
903
+ q.edges[up] = { id: up, from: { portId: mid.id, side: "outer" }, to: { portId: tp.id, side: "inner" }, kind: "up", auto: false };
904
+ }
905
+ e.to = { portId: mid.id, side: "inner" };
906
+ }
907
+ if (e.kind === "down" && fp.blockId === ROOT_ID && e.from.side === "inner") {
908
+ if (e.auto) {
909
+ delete q.edges[e.id];
910
+ continue;
911
+ }
912
+ let mid = portsOf(q, pid, "in").find((o) => o.name === fp.name);
913
+ if (!mid) {
914
+ mid = { id: newId(), blockId: pid, direction: "in", name: fp.name, description: fp.description, required: fp.required, artifacts: [] };
915
+ q.ports[mid.id] = mid;
916
+ const down = newId();
917
+ q.edges[down] = { id: down, from: { portId: fp.id, side: "inner" }, to: { portId: mid.id, side: "outer" }, kind: "down", auto: false };
918
+ }
919
+ e.from = { portId: mid.id, side: "inner" };
920
+ }
921
+ }
922
+ for (const x of portsOf(q, ROOT_ID, "in")) if (x.promotedFrom) delete q.ports[x.id];
923
+ for (const e of Object.values(q.edges)) if (!q.ports[e.from.portId] || !q.ports[e.to.portId]) delete q.edges[e.id];
924
+ if (portsOf(q, pid, "out").length === 0) {
925
+ const out2 = { id: newId(), blockId: pid, direction: "out", name: "\u6700\u7D42\u6210\u679C\u7269", description: "", required: true, artifacts: [] };
926
+ q.ports[out2.id] = out2;
927
+ const rootOut = portsOf(q, ROOT_ID, "out")[0];
928
+ if (rootOut && incomingEdges(q, { portId: rootOut.id, side: "inner" }).length === 0) {
929
+ const up = newId();
930
+ q.edges[up] = { id: up, from: { portId: out2.id, side: "outer" }, to: { portId: rootOut.id, side: "inner" }, kind: "up", auto: false };
931
+ }
932
+ }
933
+ return q;
934
+ }
935
+ function appendLog(q, ev) {
936
+ q.log.push({ id: newId(), at: now(), ...ev });
937
+ if (q.log.length > LOG_LIMIT) q.log.splice(0, q.log.length - LOG_LIMIT);
938
+ q.agents[ev.actor] = { lastSeen: now() };
939
+ }
940
+ function setActivity(p, blockId, actor, state, note) {
941
+ const q = touch(p);
942
+ const b = q.blocks[blockId];
943
+ if (!b || blockId === ROOT_ID) return p;
944
+ b.activity = { actor, state, note, since: now() };
945
+ if (state === "working" && b.status === "black") b.status = "gray";
946
+ const kind = state === "working" ? "started" : state === "blocked" ? "blocked" : "note";
947
+ const label = state === "working" ? "\u958B\u59CB" : state === "blocked" ? "\u8A70\u307E\u308A" : state === "waiting_review" ? "\u78BA\u8A8D\u5F85\u3061" : "\u5224\u65AD\u5F85\u3061";
948
+ appendLog(q, { actor, kind, blockId, message: `\u300C${b.title}\u300D${label}${note ? ": " + note : ""}` });
949
+ return q;
950
+ }
951
+ function clearActivity(p, blockId) {
952
+ const q = touch(p);
953
+ const b = q.blocks[blockId];
954
+ if (!b) return p;
955
+ b.activity = null;
956
+ return q;
957
+ }
958
+ function finishBlock(p, blockId, actor, args = {}) {
959
+ const b = p.blocks[blockId];
960
+ if (!b || blockId === ROOT_ID) return { project: p, error: "\u30D6\u30ED\u30C3\u30AF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" };
961
+ const outs = portsOf(p, blockId, "out");
962
+ let target = outs[0];
963
+ if (args.outputName) {
964
+ const found = outs.find((o) => o.name === args.outputName);
965
+ if (!found) return { project: p, error: `\u51FA\u529B\u300C${args.outputName}\u300D\u304C\u3042\u308A\u307E\u305B\u3093 (${outs.map((o) => o.name).join(", ")})` };
966
+ target = found;
967
+ }
968
+ const q = touch(p);
969
+ if (target && args.artifacts) {
970
+ for (const a of args.artifacts) q.ports[target.id].artifacts.push("id" in a && "kind" in a ? a : createArtifact(a.title, a.url ?? ""));
971
+ }
972
+ q.blocks[blockId].activity = null;
973
+ q.blocks[blockId].status = "white";
974
+ const arts = (args.artifacts ?? []).map((a) => a.title).join(", ");
975
+ appendLog(q, { actor, kind: "done", blockId, message: `\u300C${b.title}\u300D\u5B8C\u4E86${arts ? " (" + arts + ")" : ""}${args.note ? ": " + args.note : ""}` });
976
+ return { project: q };
977
+ }
978
+ function askDecision(p, blockId, actor, question, options = [], context = "") {
979
+ const b = p.blocks[blockId];
980
+ if (!b || blockId === ROOT_ID) return { project: p };
981
+ const q = touch(p);
982
+ const d = { id: newId(), question, options, askedBy: actor, askedAt: now(), ...context ? { context } : {} };
983
+ q.blocks[blockId].decisions.push(d);
984
+ q.blocks[blockId].activity = { actor, state: "needs_decision", note: question, since: now() };
985
+ appendLog(q, { actor, kind: "asked", blockId, message: `\u300C${b.title}\u300D\u3067\u5224\u65AD\u5F85\u3061: ${question}` });
986
+ return { project: q, decisionId: d.id };
987
+ }
988
+ function updateDecision(p, blockId, decisionId, patch) {
989
+ const b = p.blocks[blockId];
990
+ const d = b?.decisions.find((x) => x.id === decisionId);
991
+ if (!b || !d || d.answer !== void 0) return p;
992
+ const q = touch(p);
993
+ const qd = q.blocks[blockId].decisions.find((x) => x.id === decisionId);
994
+ if (patch.question !== void 0) qd.question = patch.question;
995
+ if (patch.options !== void 0) qd.options = patch.options;
996
+ if (patch.context !== void 0) {
997
+ if (patch.context) qd.context = patch.context;
998
+ else delete qd.context;
999
+ }
1000
+ if (q.blocks[blockId].activity?.state === "needs_decision") q.blocks[blockId].activity = { ...q.blocks[blockId].activity, note: qd.question };
1001
+ return q;
1002
+ }
1003
+ function answerDecision(p, blockId, decisionId, answer, by) {
1004
+ const b = p.blocks[blockId];
1005
+ const d = b?.decisions.find((x) => x.id === decisionId);
1006
+ if (!b || !d) return p;
1007
+ const q = touch(p);
1008
+ const qd = q.blocks[blockId].decisions.find((x) => x.id === decisionId);
1009
+ qd.answer = answer;
1010
+ qd.answeredBy = by;
1011
+ qd.answeredAt = now();
1012
+ if (q.blocks[blockId].activity?.state === "needs_decision") q.blocks[blockId].activity = null;
1013
+ appendLog(q, { actor: by, kind: "answered", blockId, message: `\u300C${b.title}\u300D\u306E\u5224\u65AD: ${d.question} \u2192 ${answer}` });
1014
+ return q;
1015
+ }
1016
+ function candidatesOf(d) {
1017
+ if (d.answer === void 0) return { rejected: [...d.options] };
1018
+ return { chosen: d.answer, rejected: d.options.filter((o) => o !== d.answer) };
1019
+ }
1020
+ function reopenDecision(p, blockId, decisionId, by, note = "") {
1021
+ const b = p.blocks[blockId];
1022
+ const d = b?.decisions.find((x) => x.id === decisionId);
1023
+ if (!b || !d || d.answer === void 0) return p;
1024
+ const q = touch(p);
1025
+ const qd = q.blocks[blockId].decisions.find((x) => x.id === decisionId);
1026
+ qd.history = [...qd.history ?? [], { answer: d.answer, by: d.answeredBy ?? "", at: d.answeredAt ?? now(), note: note || void 0 }];
1027
+ delete qd.answer;
1028
+ delete qd.answeredBy;
1029
+ delete qd.answeredAt;
1030
+ q.blocks[blockId].activity = { actor: by, state: "needs_decision", note: d.question, since: now() };
1031
+ appendLog(q, { actor: by, kind: "asked", blockId, message: `\u300C${b.title}\u300D\u306E\u5224\u65AD\u3092\u3084\u308A\u76F4\u3057: ${d.question} (\u524D\u306E\u7B54\u3048: ${d.answer}${note ? "\u3002\u7406\u7531: " + note : ""})` });
1032
+ return q;
1033
+ }
1034
+ function pendingDecisions(p) {
1035
+ const out2 = [];
1036
+ for (const b of Object.values(p.blocks)) {
1037
+ for (const d of b.decisions) if (d.answer === void 0) out2.push({ block: b, decision: d });
1038
+ }
1039
+ return out2;
1040
+ }
1041
+ function setStatus(p, blockId, status, actor) {
1042
+ const b = p.blocks[blockId];
1043
+ if (!b || b.status === status) return p;
1044
+ const q = updateBlock(p, blockId, { status });
1045
+ if (status === "white") q.blocks[blockId].activity = null;
1046
+ appendLog(q, { actor, kind: "status", blockId, message: `\u300C${b.title}\u300D\u3092 ${status} \u306B` });
1047
+ return q;
1048
+ }
1049
+ function summarize(p) {
1050
+ const blocks = Object.values(p.blocks).filter((b) => b.id !== ROOT_ID && kindOf(b) !== "project");
1051
+ const working = blocks.filter((b) => b.activity?.state === "working").map((b) => ({ block: b, actor: b.activity.actor, note: b.activity.note, since: b.activity.since }));
1052
+ const blocked = blocks.filter((b) => b.activity?.state === "blocked" || b.activity?.state === "waiting_review").map((b) => ({ block: b, actor: b.activity.actor, note: b.activity.note }));
1053
+ const leafBlack = blocks.filter((b) => b.status === "black" && !b.activity && childrenOf(p, b.id).length === 0).sort((a, b) => Number(missingRequiredInputs(p, a.id).length > 0) - Number(missingRequiredInputs(p, b.id).length > 0));
1054
+ return {
1055
+ total: blocks.length,
1056
+ white: blocks.filter((b) => b.status === "white").length,
1057
+ gray: blocks.filter((b) => b.status === "gray").length,
1058
+ black: blocks.filter((b) => b.status === "black").length,
1059
+ working,
1060
+ blocked,
1061
+ decisions: pendingDecisions(p),
1062
+ next: leafBlack,
1063
+ overdue: blocks.filter((b) => isOverdue(b))
1064
+ };
1065
+ }
1066
+ function findBlock(p, ref) {
1067
+ if (p.blocks[ref] && ref !== ROOT_ID) return { block: p.blocks[ref], candidates: [] };
1068
+ const blocks = Object.values(p.blocks).filter((b) => b.id !== ROOT_ID);
1069
+ const byKey = blocks.find((b) => b.key && b.key.toLowerCase() === ref.toLowerCase());
1070
+ if (byKey) return { block: byKey, candidates: [] };
1071
+ const exact = blocks.filter((b) => b.title === ref);
1072
+ if (exact.length === 1) return { block: exact[0], candidates: [] };
1073
+ const partial = blocks.filter((b) => b.title.includes(ref));
1074
+ if (partial.length === 1) return { block: partial[0], candidates: [] };
1075
+ return { block: null, candidates: exact.length > 0 ? exact : partial };
1076
+ }
1077
+ function splitBlock(p, parentId, spec, actor) {
1078
+ const errors = [];
1079
+ const parent = p.blocks[parentId];
1080
+ if (!parent) return { project: p, errors: ["\u89AA\u30D6\u30ED\u30C3\u30AF\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093"] };
1081
+ let q = clone(p);
1082
+ const made = {};
1083
+ for (const b of spec.blocks) {
1084
+ const r = addBlock(q, { parentId, title: b.title, outputName: b.outputs?.[0] });
1085
+ q = r.project;
1086
+ made[b.title] = r.blockId;
1087
+ if (b.description) q.blocks[r.blockId].description = b.description;
1088
+ if ((b.outputs ?? []).length > 1) errors.push(`\u300C${b.title}\u300D\u306E\u51FA\u529B\u306F 1 \u672C\u306B\u3057\u307E\u3057\u305F (\u4E0B\u306E\u968E\u5C64\u3092\u6301\u305F\u306A\u3044\u7BB1\u306E\u51FA\u529B\u306F 1 \u672C\u3002${b.outputs.slice(1).join(", ")} \u306F\u7701\u7565)`);
1089
+ for (const name of b.inputs ?? []) q = addPort(q, { blockId: r.blockId, direction: "in", name }).project;
1090
+ }
1091
+ const resolve4 = (ref, dir) => {
1092
+ let why = "\u7BB1\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093";
1093
+ for (const { title, portName } of refSplits(ref)) {
1094
+ const isParent = title === "parent" || title === parent.title || title === parentId;
1095
+ const blockId = isParent ? parentId : made[title] ?? findBlock(q, title).block?.id;
1096
+ if (!blockId) continue;
1097
+ const want = isParent ? dir === "out" ? "in" : "out" : dir;
1098
+ const port = portsOf(q, blockId, want).find((x) => x.name === portName);
1099
+ if (!port) {
1100
+ why = `\u300C${title}\u300D\u306B${want === "in" ? "\u5165\u529B" : "\u51FA\u529B"}\u300C${portName}\u300D\u304C\u3042\u308A\u307E\u305B\u3093`;
1101
+ continue;
1102
+ }
1103
+ return { ep: { portId: port.id, side: isParent ? "inner" : "outer" }, why: "" };
1104
+ }
1105
+ return { ep: null, why };
1106
+ };
1107
+ for (const c of spec.connections ?? []) {
1108
+ const from = resolve4(c.from, "out");
1109
+ const to = resolve4(c.to, "in");
1110
+ if (!from.ep || !to.ep) {
1111
+ errors.push(`\u7D50\u7DDA\u3067\u304D\u307E\u305B\u3093: ${c.from} -> ${c.to} (${!from.ep ? from.why : to.why})`);
1112
+ continue;
1113
+ }
1114
+ const r = connect(q, from.ep, to.ep);
1115
+ if (r.error) errors.push(`\u7D50\u7DDA\u3067\u304D\u307E\u305B\u3093: ${c.from} -> ${c.to} (${r.error})`);
1116
+ q = r.project;
1117
+ }
1118
+ if (q.blocks[parentId].status === "black") q.blocks[parentId].status = "gray";
1119
+ q.blocks[parentId].collapsed = false;
1120
+ appendLog(q, { actor, kind: "split", blockId: parentId, message: `\u300C${parent.title}\u300D\u3092 ${spec.blocks.length} \u500B\u306B\u5206\u89E3: ${spec.blocks.map((b) => b.title).join(", ")}` });
1121
+ return { project: q, errors };
1122
+ }
1123
+ function toTemplateNode(p, blockId) {
1124
+ const b = p.blocks[blockId];
1125
+ const kids = childrenOf(p, blockId);
1126
+ const name = (id) => id === blockId ? "parent" : p.blocks[id]?.title ?? "?";
1127
+ const connections = [];
1128
+ const kidIds = new Set(kids.map((k) => k.id));
1129
+ for (const e of Object.values(p.edges)) {
1130
+ if (e.auto) continue;
1131
+ const fp = p.ports[e.from.portId];
1132
+ const tp = p.ports[e.to.portId];
1133
+ if (!fp || !tp) continue;
1134
+ const inScope = (port, side) => side === "inner" ? port.blockId === blockId : kidIds.has(port.blockId);
1135
+ if (!inScope(fp, e.from.side) || !inScope(tp, e.to.side)) continue;
1136
+ connections.push({ from: `${name(fp.blockId)}.${fp.name}`, to: `${name(tp.blockId)}.${tp.name}` });
1137
+ }
1138
+ return {
1139
+ title: b.title,
1140
+ category: b.category,
1141
+ description: b.description,
1142
+ inputs: portsOf(p, blockId, "in").filter((x) => !x.promotedFrom).map((x) => ({ name: x.name, description: x.description, required: x.required })),
1143
+ outputs: portsOf(p, blockId, "out").map((x) => ({ name: x.name, description: x.description })),
1144
+ children: kids.sort((a, c) => a.position.y - c.position.y || a.position.x - c.position.x).map((k) => toTemplateNode(p, k.id)),
1145
+ connections
1146
+ };
1147
+ }
1148
+ function extractTemplate(p, blockId, opts = {}) {
1149
+ const b = p.blocks[blockId];
1150
+ const t = now();
1151
+ return {
1152
+ schema: "boxglow-block",
1153
+ schemaVersion: 1,
1154
+ id: b.template?.id ?? newId(),
1155
+ name: opts.name ?? b.title,
1156
+ version: (b.template?.version ?? 0) + 1,
1157
+ description: opts.description ?? b.description,
1158
+ tags: opts.tags ?? [],
1159
+ createdAt: t,
1160
+ updatedAt: t,
1161
+ root: toTemplateNode(p, blockId)
1162
+ };
1163
+ }
1164
+ function instantiateNode(p, parentId, node, origin, position) {
1165
+ const r = addBlock(p, { parentId, title: node.title, outputName: node.outputs[0]?.name, position });
1166
+ let q = r.project;
1167
+ const id = r.blockId;
1168
+ q.blocks[id].description = node.description;
1169
+ q.blocks[id].template = origin;
1170
+ if (node.category) q.blocks[id].category = node.category;
1171
+ if (node.outputs[0]) q = updatePort(q, portsOf(q, id, "out")[0].id, { description: node.outputs[0].description });
1172
+ for (const o of node.outputs.slice(1)) {
1173
+ const a = addPort(q, { blockId: id, direction: "out", name: o.name });
1174
+ q = updatePort(a.project, a.portId, { description: o.description });
1175
+ }
1176
+ for (const i of node.inputs) {
1177
+ const a = addPort(q, { blockId: id, direction: "in", name: i.name });
1178
+ q = updatePort(a.project, a.portId, { description: i.description, required: i.required });
1179
+ }
1180
+ if (node.children.length > 0) {
1181
+ const made = {};
1182
+ let y = childTop(q, id);
1183
+ for (const c of node.children) {
1184
+ const rc = instantiateNode(q, id, c, origin, { x: CHILD_PADDING.left, y });
1185
+ q = rc.project;
1186
+ made[c.title] = rc.blockId;
1187
+ y += 120;
1188
+ }
1189
+ const resolve4 = (ref, dir) => {
1190
+ for (const { title, portName } of refSplits(ref)) {
1191
+ const isParent = title === "parent";
1192
+ const blockId = isParent ? id : made[title];
1193
+ if (!blockId) continue;
1194
+ const want = isParent ? dir === "out" ? "in" : "out" : dir;
1195
+ const port = portsOf(q, blockId, want).find((x) => x.name === portName);
1196
+ if (port) return { portId: port.id, side: isParent ? "inner" : "outer" };
1197
+ }
1198
+ return null;
1199
+ };
1200
+ for (const c of node.connections) {
1201
+ const from = resolve4(c.from, "out");
1202
+ const to = resolve4(c.to, "in");
1203
+ if (from && to) q = connect(q, from, to).project;
1204
+ }
1205
+ q.blocks[id].status = "gray";
1206
+ }
1207
+ return { project: q, blockId: id };
1208
+ }
1209
+ function instantiateTemplate(p, parentId, template, actor, position) {
1210
+ const origin = { id: template.id, name: template.name, version: template.version };
1211
+ const r = instantiateNode(p, parentId, template.root, origin, position);
1212
+ const q = r.project;
1213
+ appendLog(q, { actor, kind: "added", blockId: r.blockId, message: `\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u300C${template.name}\u300Dv${template.version} \u3092\u633F\u5165: ${template.root.title}` });
1214
+ return { project: normalizePromotions(q), blockId: r.blockId };
1215
+ }
1216
+ function parseTemplate(text2) {
1217
+ let d;
1218
+ try {
1219
+ d = JSON.parse(text2);
1220
+ } catch {
1221
+ throw new Error("JSON \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093");
1222
+ }
1223
+ const t = d;
1224
+ if (!t || t.schema !== "boxglow-block" || !t.root || typeof t.root.title !== "string") throw new Error("Boxglow \u306E\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8 (boxglow-block) \u3067\u306F\u3042\u308A\u307E\u305B\u3093");
1225
+ const fix = (n) => ({
1226
+ title: n.title ?? "\u7121\u984C",
1227
+ description: n.description ?? "",
1228
+ category: typeof n.category === "string" ? n.category : void 0,
1229
+ inputs: (n.inputs ?? []).map((i) => ({ name: i.name, description: i.description ?? "", required: i.required ?? true })),
1230
+ outputs: (n.outputs ?? []).map((o) => ({ name: o.name, description: o.description ?? "" })),
1231
+ children: (n.children ?? []).map(fix),
1232
+ connections: n.connections ?? []
1233
+ });
1234
+ return {
1235
+ schema: "boxglow-block",
1236
+ schemaVersion: 1,
1237
+ id: t.id ?? newId(),
1238
+ name: t.name ?? t.root.title,
1239
+ version: t.version ?? 1,
1240
+ description: t.description ?? "",
1241
+ tags: t.tags ?? [],
1242
+ createdAt: t.createdAt ?? now(),
1243
+ updatedAt: t.updatedAt ?? now(),
1244
+ root: fix(t.root)
1245
+ };
1246
+ }
1247
+ function setCategory(p, blockId, key) {
1248
+ const b = p.blocks[blockId];
1249
+ if (!b) return p;
1250
+ if (key && !categoryOf(key)) throw new Error(`\u77E5\u3089\u306A\u3044\u30AB\u30C6\u30B4\u30EA\u3067\u3059: ${key}`);
1251
+ const q = touch(p);
1252
+ if (key) q.blocks[blockId].category = key;
1253
+ else delete q.blocks[blockId].category;
1254
+ return q;
1255
+ }
1256
+ function setSchedule(p, blockId, args, actor) {
1257
+ const b = p.blocks[blockId];
1258
+ if (!b) return p;
1259
+ const q = touch(p);
1260
+ const t = q.blocks[blockId];
1261
+ const put = (k, v) => {
1262
+ if (v === void 0) return;
1263
+ if (v === null || v === "" || typeof v === "number" && Number.isNaN(v)) delete t[k];
1264
+ else t[k] = v;
1265
+ };
1266
+ put("startDate", args.startDate);
1267
+ put("dueDate", args.dueDate);
1268
+ put("estimateHours", args.estimateHours);
1269
+ put("actualHours", args.actualHours);
1270
+ const parts = [];
1271
+ if (args.dueDate !== void 0) parts.push(`\u671F\u65E5 ${args.dueDate ?? "\u306A\u3057"}`);
1272
+ if (args.startDate !== void 0) parts.push(`\u958B\u59CB ${args.startDate ?? "\u306A\u3057"}`);
1273
+ if (args.actualHours !== void 0) parts.push(`\u5B9F\u7E3E ${args.actualHours ?? 0}h`);
1274
+ if (args.estimateHours !== void 0) parts.push(`\u898B\u7A4D ${args.estimateHours ?? 0}h`);
1275
+ if (parts.length > 0) appendLog(q, { actor, kind: "note", blockId, message: `\u300C${b.title}\u300D${parts.join(", ")}` });
1276
+ return q;
1277
+ }
1278
+ function isOverdue(b, today = /* @__PURE__ */ new Date()) {
1279
+ if (!b.dueDate || b.status === "white") return false;
1280
+ const d = /* @__PURE__ */ new Date(b.dueDate + "T23:59:59");
1281
+ return d.getTime() < today.getTime();
1282
+ }
1283
+ function searchBlocks(p, text2, limit = 20) {
1284
+ const q = text2.trim().toLowerCase();
1285
+ if (!q) return [];
1286
+ const blocks = Object.values(p.blocks).filter((b) => b.id !== ROOT_ID);
1287
+ const exactKey = blocks.filter((b) => b.key?.toLowerCase() === q);
1288
+ const catOf = (b) => categoryOf(b.category);
1289
+ const rest = blocks.filter((b) => !exactKey.includes(b) && (b.title.toLowerCase().includes(q) || (b.key ?? "").toLowerCase().includes(q) || b.description.toLowerCase().includes(q) || catOf(b)?.label === text2.trim() || catOf(b)?.en === q));
1290
+ return [...exactKey, ...rest].slice(0, limit);
1291
+ }
1292
+ function sourceOfInput(p, portId, seen = /* @__PURE__ */ new Set()) {
1293
+ if (seen.has(portId)) return null;
1294
+ seen.add(portId);
1295
+ const port = p.ports[portId];
1296
+ if (!port || port.direction !== "in") return null;
1297
+ const e = incomingEdges(p, { portId, side: "outer" })[0];
1298
+ if (!e) return null;
1299
+ const from = p.ports[e.from.portId];
1300
+ if (!from) return null;
1301
+ if (from.direction === "out") return from;
1302
+ if (from.blockId === ROOT_ID) return from;
1303
+ return sourceOfInput(p, from.id, seen) ?? from;
1304
+ }
1305
+ function effectiveDescription(p, portId) {
1306
+ const port = p.ports[portId];
1307
+ if (!port) return "";
1308
+ if (port.direction === "out") return port.description;
1309
+ const src = sourceOfInput(p, portId);
1310
+ return src ? src.description : port.description;
1311
+ }
1312
+ function scopeChain(p, scope) {
1313
+ const out2 = [];
1314
+ let cur = scope;
1315
+ while (cur !== null) {
1316
+ out2.push(cur);
1317
+ cur = p.blocks[cur]?.parentId ?? null;
1318
+ }
1319
+ return out2;
1320
+ }
1321
+ function findOrAddPort(q, blockId, direction, name) {
1322
+ const existing = portsOf(q, blockId, direction).find((x) => x.name === name && !x.promotedFrom);
1323
+ if (existing) return existing;
1324
+ const r = addPort(q, { blockId, direction, name });
1325
+ Object.assign(q, r.project);
1326
+ return q.ports[r.portId] ?? null;
1327
+ }
1328
+ function routeConnect(q, from, to) {
1329
+ const fs = scopeOf(q, from);
1330
+ const ts = scopeOf(q, to);
1331
+ if (fs === null || ts === null) return;
1332
+ const fromPort = q.ports[from.portId];
1333
+ const toPort = q.ports[to.portId];
1334
+ if (!fromPort || !toPort) return;
1335
+ const fChain = scopeChain(q, fs);
1336
+ const tChain = scopeChain(q, ts);
1337
+ const common = fChain.find((s) => tChain.includes(s));
1338
+ if (!common) return;
1339
+ let f = from;
1340
+ for (const sc of fChain.slice(0, fChain.indexOf(common))) {
1341
+ if (sc === ROOT_ID) return;
1342
+ const via = findOrAddPort(q, sc, "out", fromPort.name);
1343
+ if (!via) return;
1344
+ const r = connect(q, f, { portId: via.id, side: "inner" });
1345
+ if (!r.error) Object.assign(q, r.project);
1346
+ f = { portId: via.id, side: "outer" };
1347
+ }
1348
+ for (const sc of tChain.slice(0, tChain.indexOf(common)).reverse()) {
1349
+ if (sc === ROOT_ID) return;
1350
+ const via = findOrAddPort(q, sc, "in", toPort.name);
1351
+ if (!via) return;
1352
+ const r = connect(q, f, { portId: via.id, side: "outer" });
1353
+ if (!r.error) Object.assign(q, r.project);
1354
+ f = { portId: via.id, side: "inner" };
1355
+ }
1356
+ const last = connect(q, f, to);
1357
+ if (!last.error) Object.assign(q, last.project);
1358
+ }
1359
+ function refSplits(ref) {
1360
+ const out2 = [];
1361
+ for (let i = ref.indexOf("."); i >= 0; i = ref.indexOf(".", i + 1)) {
1362
+ const title = ref.slice(0, i);
1363
+ const portName = ref.slice(i + 1);
1364
+ if (title && portName) out2.push({ title, portName });
1365
+ }
1366
+ return out2;
1367
+ }
1368
+ function moveBlockToParent(p, blockId, newParentId, position) {
1369
+ const b = p.blocks[blockId];
1370
+ const np = p.blocks[newParentId];
1371
+ if (!b || !np || blockId === ROOT_ID || newParentId === ROOT_ID || b.parentId === newParentId) return p;
1372
+ if (kindOf(b) === "project") return p;
1373
+ if (newParentId === blockId || descendantsOf(p, blockId).some((d) => d.id === newParentId)) return p;
1374
+ const q = touch(p);
1375
+ const oldParentId = b.parentId;
1376
+ const myPorts = new Set(portsOf(q, blockId).map((x) => x.id));
1377
+ const pending = [];
1378
+ for (const e of Object.values(q.edges)) {
1379
+ const mineIsFrom = myPorts.has(e.from.portId) && e.from.side === "outer";
1380
+ const mineIsTo = myPorts.has(e.to.portId) && e.to.side === "outer";
1381
+ if (!mineIsFrom && !mineIsTo) continue;
1382
+ delete q.edges[e.id];
1383
+ if (e.auto) continue;
1384
+ const myEp = mineIsFrom ? e.from : e.to;
1385
+ const otherEp = mineIsFrom ? e.to : e.from;
1386
+ const otherPort = q.ports[otherEp.portId];
1387
+ if (!otherPort) continue;
1388
+ if (otherEp.side === "outer" && otherPort.blockId === newParentId) continue;
1389
+ if (otherEp.side === "inner" && otherPort.blockId === oldParentId && oldParentId !== null) {
1390
+ if (mineIsTo) {
1391
+ const src = incomingEdges(q, { portId: otherPort.id, side: "outer" })[0];
1392
+ if (src) pending.push({ from: src.from, to: myEp });
1393
+ } else {
1394
+ for (const dst of outgoingEdges(q, { portId: otherPort.id, side: "outer" })) pending.push({ from: myEp, to: dst.to });
1395
+ }
1396
+ continue;
1397
+ }
1398
+ pending.push(mineIsFrom ? { from: myEp, to: otherEp } : { from: otherEp, to: myEp });
1399
+ }
1400
+ q.blocks[blockId].parentId = newParentId;
1401
+ q.blocks[blockId].position = { x: Math.max(CHILD_PADDING.left, position.x), y: Math.max(childTop(q, newParentId), position.y) };
1402
+ for (const pr of pending) routeConnect(q, pr.from, pr.to);
1403
+ if (q.blocks[newParentId].status === "black") q.blocks[newParentId].status = "gray";
1404
+ q.blocks[newParentId].collapsed = false;
1405
+ appendLog(q, { actor: "human", kind: "note", blockId, message: `\u300C${b.title}\u300D\u3092\u300C${np.title}\u300D\u306E\u4E2D\u3078\u79FB\u52D5` });
1406
+ return normalizePromotions(q);
1407
+ }
1408
+ function resolveOverlap(p, blockId, sizeOf) {
1409
+ const b = p.blocks[blockId];
1410
+ if (!b || b.parentId === null) return p;
1411
+ const q = clone(p);
1412
+ const GAP = 24;
1413
+ const me = sizeOf(q, blockId);
1414
+ for (let iter = 0; iter < 8; iter++) {
1415
+ const cur = q.blocks[blockId];
1416
+ const sib = childrenOf(q, cur.parentId).find((s) => {
1417
+ if (s.id === blockId) return false;
1418
+ const sz2 = sizeOf(q, s.id);
1419
+ return cur.position.x < s.position.x + sz2.width + GAP && cur.position.x + me.width + GAP > s.position.x && cur.position.y < s.position.y + sz2.height + GAP && cur.position.y + me.height + GAP > s.position.y;
1420
+ });
1421
+ if (!sib) break;
1422
+ const sz = sizeOf(q, sib.id);
1423
+ const moves = [
1424
+ { x: sib.position.x + sz.width + GAP, y: cur.position.y, d: sib.position.x + sz.width + GAP - cur.position.x },
1425
+ { x: cur.position.x, y: sib.position.y + sz.height + GAP, d: sib.position.y + sz.height + GAP - cur.position.y },
1426
+ { x: sib.position.x - me.width - GAP, y: cur.position.y, d: cur.position.x - (sib.position.x - me.width - GAP) },
1427
+ { x: cur.position.x, y: sib.position.y - me.height - GAP, d: cur.position.y - (sib.position.y - me.height - GAP) }
1428
+ ].filter((m) => m.d >= 0);
1429
+ const nested = cur.parentId !== ROOT_ID;
1430
+ const ok = moves.filter((m) => !nested || m.x >= CHILD_PADDING.left && m.y >= childTop(q, cur.parentId));
1431
+ const best = (ok.length > 0 ? ok : moves).sort((a, c) => a.d - c.d)[0];
1432
+ if (!best) break;
1433
+ q.blocks[blockId].position = { x: best.x, y: best.y };
1434
+ }
1435
+ return q;
1436
+ }
1437
+ var inputGroupsOf = (p) => p.inputGroups ?? [];
1438
+ function rootInputsOf(p, groupId) {
1439
+ return portsOf(p, ROOT_ID, "in").filter((x) => groupId === null ? !x.groupId : x.groupId === groupId);
1440
+ }
1441
+ function addInputGroup(p, name) {
1442
+ const q = touch(p);
1443
+ const list2 = q.inputGroups ?? (q.inputGroups = []);
1444
+ const id = newId();
1445
+ const y = list2.length === 0 ? q.terminals.in.y + 160 : Math.max(...list2.map((gp) => gp.position.y)) + 160;
1446
+ list2.push({ id, name, description: "", position: { x: q.terminals.in.x, y } });
1447
+ appendLog(q, { actor: "human", kind: "added", message: `\u5165\u529B\u30B0\u30EB\u30FC\u30D7\u300C${name}\u300D\u3092\u8FFD\u52A0` });
1448
+ return { project: q, groupId: id };
1449
+ }
1450
+ function updateInputGroup(p, groupId, patch) {
1451
+ const q = touch(p);
1452
+ const gp = (q.inputGroups ?? []).find((x) => x.id === groupId);
1453
+ if (!gp) return p;
1454
+ Object.assign(gp, patch);
1455
+ return q;
1456
+ }
1457
+ function setInputGroup(p, portId, groupId) {
1458
+ const port = p.ports[portId];
1459
+ if (!port || port.blockId !== ROOT_ID || port.direction !== "in") return p;
1460
+ const q = touch(p);
1461
+ if (groupId) q.ports[portId].groupId = groupId;
1462
+ else delete q.ports[portId].groupId;
1463
+ return q;
1464
+ }
1465
+ function exportInputGroup(p, groupId) {
1466
+ const gp = (p.inputGroups ?? []).find((x) => x.id === groupId);
1467
+ const inputs = rootInputsOf(p, groupId).map((x) => ({ name: x.name, description: x.description, artifacts: x.artifacts.map((a) => ({ title: a.title, url: a.url })) }));
1468
+ return JSON.stringify({ schema: "boxglow-input-group", name: gp?.name ?? "", description: gp?.description ?? "", inputs }, null, 2);
1469
+ }
1470
+ function importInputGroup(p, text2) {
1471
+ let d;
1472
+ try {
1473
+ d = JSON.parse(text2);
1474
+ } catch {
1475
+ throw new Error("JSON \u3068\u3057\u3066\u8AAD\u3081\u307E\u305B\u3093");
1476
+ }
1477
+ if (d.schema !== "boxglow-input-group" || !Array.isArray(d.inputs)) throw new Error("\u5165\u529B\u30B0\u30EB\u30FC\u30D7\u306E JSON (boxglow-input-group) \u3067\u306F\u3042\u308A\u307E\u305B\u3093");
1478
+ const r = addInputGroup(p, d.name ?? "Inputs");
1479
+ let q = updateInputGroup(r.project, r.groupId, { description: d.description ?? "" });
1480
+ for (const i of d.inputs) {
1481
+ const a = addPort(q, { blockId: ROOT_ID, direction: "in", name: i.name });
1482
+ q = updatePort(a.project, a.portId, { description: i.description ?? "", artifacts: (i.artifacts ?? []).map((x) => createArtifact(x.title, x.url ?? "")) });
1483
+ q = setInputGroup(q, a.portId, r.groupId);
1484
+ }
1485
+ return { project: q, groupId: r.groupId };
1486
+ }
1487
+ function resolveAllOverlaps(p, sizeOf) {
1488
+ let q = p;
1489
+ const parents = new Set(Object.values(p.blocks).filter((b) => b.id !== ROOT_ID).map((b) => b.parentId));
1490
+ for (const parentId of parents) {
1491
+ const kids = childrenOf(q, parentId).sort((a, b) => a.position.y - b.position.y || a.position.x - b.position.x);
1492
+ for (let i = 1; i < kids.length; i++) q = resolveOverlap(q, kids[i].id, sizeOf);
1493
+ }
1494
+ return q;
1495
+ }
1496
+ function issueKeyOf(url) {
1497
+ const u = url.trim();
1498
+ let m = u.match(/\/browse\/([A-Z][A-Z0-9_]+-\d+)/i);
1499
+ if (m) return m[1].toUpperCase();
1500
+ m = u.match(/\/issues\/(\d+)/);
1501
+ if (m) return `#${m[1]}`;
1502
+ m = u.match(/\/pull\/(\d+)/);
1503
+ if (m) return `PR #${m[1]}`;
1504
+ try {
1505
+ return new URL(u).hostname.replace(/^www\./, "");
1506
+ } catch {
1507
+ return u.slice(0, 20);
1508
+ }
1509
+ }
1510
+
1511
+ // src/model/export.ts
1512
+ var STATUS_LABEL = {
1513
+ black: "New (BlackBox: \u51FA\u529B\u3060\u3051\u6C7A\u3081\u3066\u3042\u308B)",
1514
+ gray: "In Progress (GrayBox: \u5206\u89E3\u4E2D\u30FB\u4F5C\u696D\u4E2D)",
1515
+ white: "Done (WhiteBox: \u51FA\u529B\u306B\u6210\u679C\u7269\u304C\u4ED8\u3044\u3066\u5B8C\u4E86)"
1516
+ };
1517
+ function portLine(p, port) {
1518
+ const parts = [`- **${port.name}**`];
1519
+ const desc = effectiveDescription(p, port.id);
1520
+ if (desc) parts.push(`: ${desc}`);
1521
+ if (port.direction === "in" && !port.required) parts.push(" (\u4EFB\u610F)");
1522
+ if (port.artifacts.length > 0) {
1523
+ parts.push(" / " + port.artifacts.map((a) => a.url ? `[${a.title}](${a.url})` : a.title).join(", "));
1524
+ }
1525
+ const links = port.direction === "in" ? incomingEdges(p, { portId: port.id, side: "outer" }).map((e) => describeEndpointSource(p, e.from.portId)) : outgoingEdges(p, { portId: port.id, side: "outer" }).map((e) => describeEndpointTarget(p, e.to.portId));
1526
+ if (links.length > 0) parts.push(` (${port.direction === "in" ? "\u4F9B\u7D66\u5143" : "\u884C\u304D\u5148"}: ${links.join(", ")})`);
1527
+ return parts.join("");
1528
+ }
1529
+ function describeEndpointSource(p, portId) {
1530
+ const port = p.ports[portId];
1531
+ if (!port) return "?";
1532
+ const block = p.blocks[port.blockId];
1533
+ const name = block?.id === ROOT_ID ? "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u5165\u529B" : block?.title ?? "?";
1534
+ return `${name}.${port.name}`;
1535
+ }
1536
+ function describeEndpointTarget(p, portId) {
1537
+ const port = p.ports[portId];
1538
+ if (!port) return "?";
1539
+ const block = p.blocks[port.blockId];
1540
+ const name = block?.id === ROOT_ID ? "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u51FA\u529B" : block?.title ?? "?";
1541
+ return `${name}.${port.name}`;
1542
+ }
1543
+ function blockSection(p, blockId, level, withChildren) {
1544
+ const b = p.blocks[blockId];
1545
+ if (!b) return "";
1546
+ const h = "#".repeat(Math.min(level, 6));
1547
+ const lines = [];
1548
+ lines.push(`${h} ${b.id === ROOT_ID ? `\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8: ${p.name}` : `${b.key ? b.key + " " : ""}${b.title}`}`);
1549
+ lines.push("");
1550
+ if (b.id !== ROOT_ID) {
1551
+ lines.push(`- \u72B6\u614B: ${STATUS_LABEL[b.status]}`);
1552
+ const cat = categoryOf(b.category);
1553
+ if (cat) lines.push(`- \u30AB\u30C6\u30B4\u30EA: ${cat.label}`);
1554
+ const names = b.assigneeIds.map((id) => p.members.find((m) => m.id === id)?.name).filter(Boolean);
1555
+ if (names.length > 0) lines.push(`- \u62C5\u5F53: ${names.join(", ")}`);
1556
+ const prog = computeProgress(p, b.id);
1557
+ if (childrenOf(p, b.id).length > 0) lines.push(`- \u9032\u6357: \u4E0B\u306E\u968E\u5C64 ${prog.white}/${prog.total} \u304C\u5B8C\u4E86`);
1558
+ }
1559
+ if (b.id !== ROOT_ID && b.decisions.length > 0) {
1560
+ lines.push("- \u5224\u65AD:");
1561
+ for (const d of b.decisions) {
1562
+ const c = candidatesOf(d);
1563
+ lines.push(` - ${d.question}${d.answer !== void 0 ? ` \u2192 ${d.answer}` : " (\u672A\u56DE\u7B54)"}${c.rejected.length ? `\u3002\u6B8B\u3057\u305F\u5019\u88DC: ${c.rejected.join(" / ")}` : ""}${(d.history ?? []).length ? `\u3002\u4EE5\u524D\u306E\u7B54\u3048: ${d.history.map((h2) => h2.answer).join(" \u2192 ")}` : ""}`);
1564
+ }
1565
+ }
1566
+ if (b.description) {
1567
+ lines.push("");
1568
+ lines.push(b.description);
1569
+ }
1570
+ const ins = portsOf(p, b.id, "in");
1571
+ const outs = portsOf(p, b.id, "out");
1572
+ lines.push("");
1573
+ lines.push(`**\u5165\u529B** (${ins.length === 0 ? "\u306A\u3057" : `${ins.length} \u4EF6`})`);
1574
+ for (const q of ins) lines.push(portLine(p, q));
1575
+ lines.push("");
1576
+ lines.push(`**\u51FA\u529B** (${outs.length} \u4EF6)`);
1577
+ for (const q of outs) lines.push(portLine(p, q));
1578
+ if (b.artifacts.length > 0) {
1579
+ lines.push("");
1580
+ lines.push("**\u95A2\u9023\u8CC7\u6599**");
1581
+ for (const a of b.artifacts) lines.push(`- ${a.url ? `[${a.title}](${a.url})` : a.title}${a.note ? `: ${a.note}` : ""}`);
1582
+ }
1583
+ lines.push("");
1584
+ if (withChildren) {
1585
+ for (const c of childrenOf(p, b.id)) lines.push(blockSection(p, c.id, level + 1, true));
1586
+ }
1587
+ return lines.join("\n");
1588
+ }
1589
+ function projectToMarkdown(p) {
1590
+ const prog = computeProgress(p, ROOT_ID);
1591
+ const head = [
1592
+ `# ${p.name}`,
1593
+ "",
1594
+ p.description,
1595
+ "",
1596
+ `\u5168\u4F53\u306E\u9032\u6357: ${prog.white}/${prog.total} \u30D6\u30ED\u30C3\u30AF\u304C\u5B8C\u4E86 (BlackBox ${prog.black} / GrayBox ${prog.gray} / WhiteBox ${prog.white})`,
1597
+ "",
1598
+ "\u30D6\u30ED\u30C3\u30AF\u306F\u300C\u5165\u529B\u304B\u3089\u51FA\u529B\u3092\u4F5C\u308B\u30BF\u30B9\u30AF\u300D\u3002BlackBox \u306F\u51FA\u529B\u3060\u3051\u6C7A\u307E\u3063\u3066\u3044\u3066\u4E2D\u8EAB\u304C\u672A\u5B9A\u3001GrayBox \u306F\u5206\u89E3\u4E2D\u30FB\u7740\u624B\u4E2D\u3001WhiteBox \u306F\u5B8C\u4E86\u3002",
1599
+ ""
1600
+ ].join("\n");
1601
+ return head + blockSection(p, ROOT_ID, 2, true);
1602
+ }
1603
+ function blockToPrompt(p, blockId, ask) {
1604
+ const b = p.blocks[blockId];
1605
+ if (!b) return "";
1606
+ const chain = [...ancestorsOf(p, blockId)].reverse().map((a) => a.id === ROOT_ID ? p.name : a.title);
1607
+ const lines = [];
1608
+ lines.push(`# \u4F9D\u983C: ${b.title}`);
1609
+ lines.push("");
1610
+ lines.push(`\u3053\u306E\u30BF\u30B9\u30AF\u306F\u300C${chain.join(" > ")}\u300D\u306E\u4E2D\u306E 1 \u30D6\u30ED\u30C3\u30AF\u3067\u3059\u3002\u30D6\u30ED\u30C3\u30AF\u306F\u300C\u5165\u529B\u304B\u3089\u51FA\u529B\u3092\u4F5C\u308B\u4F5C\u696D\u300D\u3067\u3001\u51FA\u529B\u306E\u6210\u679C\u7269\u304C\u78BA\u5B9A\u3057\u305F\u3089\u5B8C\u4E86 (WhiteBox) \u306B\u306A\u308A\u307E\u3059\u3002`);
1611
+ lines.push("");
1612
+ lines.push(blockSection(p, blockId, 2, false));
1613
+ const siblings = childrenOf(p, b.parentId ?? ROOT_ID).filter((s) => s.id !== blockId);
1614
+ if (siblings.length > 0) {
1615
+ lines.push("## \u540C\u3058\u968E\u5C64\u306E\u4ED6\u306E\u30D6\u30ED\u30C3\u30AF");
1616
+ lines.push("");
1617
+ for (const s of siblings) lines.push(`- ${s.title} (${STATUS_LABEL[s.status]})`);
1618
+ lines.push("");
1619
+ }
1620
+ const kids = childrenOf(p, blockId);
1621
+ if (kids.length > 0) {
1622
+ lines.push("## \u73FE\u5728\u306E\u4E0B\u306E\u968E\u5C64");
1623
+ lines.push("");
1624
+ for (const k of kids) lines.push(`- ${k.title} (${STATUS_LABEL[k.status]})`);
1625
+ lines.push("");
1626
+ }
1627
+ lines.push("## \u304A\u9858\u3044");
1628
+ lines.push("");
1629
+ if (ask === "plan") {
1630
+ lines.push("\u4E0A\u306E\u5165\u529B\u304B\u3089\u51FA\u529B\u3092\u5F97\u308B\u305F\u3081\u306E\u624B\u9806\u3092\u63D0\u6848\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u524D\u63D0\u304C\u8DB3\u308A\u306A\u3051\u308C\u3070\u3001\u8FFD\u52A0\u3067\u5FC5\u8981\u306A\u5165\u529B\u3092\u6319\u3052\u3066\u304F\u3060\u3055\u3044\u3002");
1631
+ } else if (ask === "decompose") {
1632
+ lines.push("\u3053\u306E\u30D6\u30ED\u30C3\u30AF\u3092\u3001\u5165\u529B\u3068\u51FA\u529B\u3092\u6301\u3064\u5C0F\u3055\u306A\u30D6\u30ED\u30C3\u30AF\u306B\u5206\u89E3\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u6B21\u306E JSON \u5F62\u5F0F\u3067\u7B54\u3048\u3066\u304F\u3060\u3055\u3044\u3002");
1633
+ lines.push("");
1634
+ lines.push("```json");
1635
+ lines.push(JSON.stringify({ blocks: [{ title: "\u30D6\u30ED\u30C3\u30AF\u540D", description: "\u3084\u308B\u3053\u3068", inputs: ["\u5165\u529B\u540D"], outputs: ["\u51FA\u529B\u540D"] }], connections: [{ from: "\u30D6\u30ED\u30C3\u30AF\u540D.\u51FA\u529B\u540D", to: "\u30D6\u30ED\u30C3\u30AF\u540D.\u5165\u529B\u540D" }] }, null, 2));
1636
+ lines.push("```");
1637
+ } else {
1638
+ lines.push("\u5165\u529B\u3068\u51FA\u529B\u306E\u5B9A\u7FA9\u306B\u629C\u3051\u3084\u66D6\u6627\u3055\u304C\u306A\u3044\u304B\u6307\u6458\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u51FA\u529B\u304C\u300C\u78BA\u5B9A\u3057\u305F\u300D\u3068\u8A00\u3048\u308B\u6761\u4EF6\u3082\u63D0\u6848\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
1639
+ }
1640
+ lines.push("");
1641
+ return lines.join("\n");
1642
+ }
1643
+
1644
+ // src/model/size.ts
1645
+ var BLOCK_W = 240;
1646
+ var HEADER_H = 44;
1647
+ var TITLE_H = 36;
1648
+ var TITLE_LINE_H = 18;
1649
+ var META_H = 24;
1650
+ var ROW_H = 26;
1651
+ var PAD_BOTTOM = 12;
1652
+ var EXPANDED_MIN_W = 320;
1653
+ var EXPANDED_PAD = 96;
1654
+ var EXPANDED_PAD_Y = 104;
1655
+ var TERMINAL_W = 200;
1656
+ var BLOCK_MAX_W = 460;
1657
+ function textWidth(s, fontPx) {
1658
+ let w = 0;
1659
+ for (const ch of s) {
1660
+ const code = ch.codePointAt(0) ?? 0;
1661
+ w += code > 11903 ? fontPx * 1.05 : fontPx * 0.6;
1662
+ }
1663
+ return w;
1664
+ }
1665
+ function metaRowWidth(opts) {
1666
+ const chip = (text2) => textWidth(text2, 10) + 14 + 6;
1667
+ let w = 12 + 10;
1668
+ if (!opts.isProject) w += chip("In Progress");
1669
+ if (opts.assigneeText) w += chip(opts.assigneeText);
1670
+ else if (opts.unassigned) w += chip("\u672A\u62C5\u5F53");
1671
+ if (opts.hasPercent) w += chip("100%");
1672
+ if (opts.activityText) w += chip(opts.activityText);
1673
+ if (opts.hasReady) w += chip("Ready");
1674
+ if (opts.hasDue) w += chip("\u671F\u65E5 10/15");
1675
+ w += opts.keyLen * 7 + 8;
1676
+ return w;
1677
+ }
1678
+ function isExpanded(p, blockId) {
1679
+ const b = p.blocks[blockId];
1680
+ return !!b && !b.collapsed && childrenOf(p, blockId).length > 0;
1681
+ }
1682
+ function blockSize(p, blockId) {
1683
+ const b = p.blocks[blockId];
1684
+ const insP = portsOf(p, blockId, "in");
1685
+ const outsP = portsOf(p, blockId, "out");
1686
+ const rows = Math.max(insP.length, outsP.length, 1);
1687
+ const kids = childrenOf(p, blockId);
1688
+ const titleExtras = 12 + 10 + (b?.kind === "project" ? 66 : 0) + (kids.length > 0 ? 30 : 0);
1689
+ const titleW = textWidth(b?.title ?? "", 14) + 4;
1690
+ const wantTitle = titleExtras + titleW;
1691
+ const titleLines = wantTitle > BLOCK_MAX_W ? Math.min(3, Math.ceil(titleW / (BLOCK_MAX_W - titleExtras))) : 1;
1692
+ const actorText = (actor) => {
1693
+ const a = actor.toLowerCase();
1694
+ return a.startsWith("claude") ? "Claude Code" : a.startsWith("codex") ? "Codex" : a.startsWith("human:") ? actor.slice(6) : actor;
1695
+ };
1696
+ const stateText = { working: "\u4F5C\u696D\u4E2D", blocked: "\u8A70\u307E\u308A", needs_decision: "\u5224\u65AD\u5F85\u3061", waiting_review: "\u78BA\u8A8D\u5F85\u3061" };
1697
+ const metaW = metaRowWidth({
1698
+ assigneeText: "",
1699
+ unassigned: false,
1700
+ hasPercent: !!b && b.status !== "white" && (kids.length > 0 || typeof b.progress === "number" && b.progress > 0),
1701
+ activityText: b?.activity ? `${stateText[b.activity.state] ?? ""} (${actorText(b.activity.actor)})` : null,
1702
+ hasDue: !!b?.dueDate && b.status !== "white",
1703
+ keyLen: (b?.key ?? "").length,
1704
+ isProject: b?.kind === "project",
1705
+ hasReady: !!b && b.status === "black" && b.kind !== "project" && insP.length > 0 && missingRequiredInputs(p, b.id).length === 0
1706
+ });
1707
+ const headWidth = Math.min(BLOCK_MAX_W, Math.max(BLOCK_W, wantTitle, metaW));
1708
+ const headerH = TITLE_H + (titleLines - 1) * TITLE_LINE_H + META_H;
1709
+ const inW = Math.max(0, ...insP.map((q) => textWidth(q.name, 11) + (q.name ? 14 : 0) + (q.required ? 0 : textWidth("(\u4EFB\u610F)", 9) + 4)));
1710
+ const outW = Math.max(0, ...outsP.map((q) => textWidth(q.name, 11)));
1711
+ const portsWidth = 12 + inW + 24 + outW + 10;
1712
+ const width = Math.max(BLOCK_W, headWidth, Math.min(BLOCK_MAX_W + 120, portsWidth));
1713
+ const baseH = headerH + rows * ROW_H + PAD_BOTTOM;
1714
+ if (!isExpanded(p, blockId)) return { width, height: baseH, headerH };
1715
+ let right = 0;
1716
+ let bottom = 0;
1717
+ for (const c of kids) {
1718
+ const s = blockSize(p, c.id);
1719
+ right = Math.max(right, c.position.x + s.width);
1720
+ bottom = Math.max(bottom, c.position.y + s.height);
1721
+ }
1722
+ return {
1723
+ width: Math.max(EXPANDED_MIN_W, width, right + EXPANDED_PAD),
1724
+ height: Math.max(baseH + 16, bottom + EXPANDED_PAD_Y + 12 + (headerH - HEADER_H)),
1725
+ headerH
1726
+ };
1727
+ }
1728
+ function terminalHeight(p, which) {
1729
+ const n = portsOf(p, ROOT_ID, which).length;
1730
+ return HEADER_H + Math.max(n, 1) * ROW_H + PAD_BOTTOM;
1731
+ }
1732
+
1733
+ // src/model/merge.ts
1734
+ var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
1735
+ function pick(path, base, ours, theirs, r) {
1736
+ if (same(ours, theirs)) return ours;
1737
+ if (same(ours, base)) {
1738
+ r.merged++;
1739
+ return theirs;
1740
+ }
1741
+ if (same(theirs, base)) return ours;
1742
+ r.conflicts.push({ path, ours, theirs });
1743
+ return ours;
1744
+ }
1745
+ function mergeObject(path, base, ours, theirs, r) {
1746
+ const out2 = {};
1747
+ const keys = /* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base ?? {})]);
1748
+ for (const k of keys) {
1749
+ const b = base?.[k];
1750
+ const o = ours[k];
1751
+ const t = theirs[k];
1752
+ const inO = k in ours;
1753
+ const inT = k in theirs;
1754
+ const inB = base ? k in base : false;
1755
+ if (!inO && inT) {
1756
+ if (inB && same(t, b)) continue;
1757
+ out2[k] = t;
1758
+ if (!inB) r.merged++;
1759
+ continue;
1760
+ }
1761
+ if (inO && !inT) {
1762
+ if (inB && same(o, b)) continue;
1763
+ out2[k] = o;
1764
+ continue;
1765
+ }
1766
+ if (!inO && !inT) continue;
1767
+ out2[k] = pick(`${path}.${k}`, b, o, t, r);
1768
+ }
1769
+ return out2;
1770
+ }
1771
+ function mergeMap(path, base, ours, theirs, r) {
1772
+ const out2 = {};
1773
+ const ids = /* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base ?? {})]);
1774
+ for (const id of ids) {
1775
+ const b = base?.[id];
1776
+ const o = ours[id];
1777
+ const t = theirs[id];
1778
+ if (o && t) {
1779
+ out2[id] = mergeObject(`${path}.${id}`, b, o, t, r);
1780
+ continue;
1781
+ }
1782
+ if (o && !t) {
1783
+ if (b && same(o, b)) continue;
1784
+ out2[id] = o;
1785
+ continue;
1786
+ }
1787
+ if (!o && t) {
1788
+ if (b && same(t, b)) continue;
1789
+ out2[id] = t;
1790
+ r.merged++;
1791
+ continue;
1792
+ }
1793
+ }
1794
+ return out2;
1795
+ }
1796
+ function mergeArrayById(path, base, ours, theirs, r) {
1797
+ const toMap = (xs) => Object.fromEntries((xs ?? []).map((x) => [x.id, x]));
1798
+ const merged = mergeMap(path, base ? toMap(base) : void 0, toMap(ours), toMap(theirs), r);
1799
+ const order = [...ours.map((x) => x.id), ...theirs.map((x) => x.id).filter((id) => !ours.some((x) => x.id === id))];
1800
+ return order.filter((id) => merged[id]).map((id) => merged[id]);
1801
+ }
1802
+ function mergeProjects(base, ours, theirs) {
1803
+ const r = { project: ours, merged: 0, conflicts: [] };
1804
+ const b = base ?? void 0;
1805
+ const o = ours;
1806
+ const t = theirs;
1807
+ const out2 = { ...o };
1808
+ for (const k of ["name", "description", "visibility", "terminals"]) out2[k] = pick(k, b?.[k], o[k], t[k], r);
1809
+ for (const k of ["blocks", "ports", "edges", "agents"]) {
1810
+ out2[k] = mergeMap(k, b?.[k], o[k] ?? {}, t[k] ?? {}, r);
1811
+ }
1812
+ out2.members = mergeArrayById("members", base?.members, ours.members ?? [], theirs.members ?? [], r);
1813
+ out2.inputGroups = mergeArrayById("inputGroups", base?.inputGroups, ours.inputGroups ?? [], theirs.inputGroups ?? [], r);
1814
+ const log = /* @__PURE__ */ new Map();
1815
+ for (const e of [...ours.log ?? [], ...theirs.log ?? []]) log.set(e.id, e);
1816
+ out2.log = [...log.values()].sort((p, q) => p.at < q.at ? -1 : p.at > q.at ? 1 : 0);
1817
+ const blocks = out2.blocks;
1818
+ const used = /* @__PURE__ */ new Map();
1819
+ let nextKey2 = Math.max(ours.nextKey ?? 1, theirs.nextKey ?? 1);
1820
+ for (const id of Object.keys(ours.blocks ?? {})) {
1821
+ const key = blocks[id]?.key;
1822
+ if (key) used.set(key, id);
1823
+ }
1824
+ for (const id of Object.keys(blocks)) {
1825
+ const key = blocks[id].key;
1826
+ if (!key) continue;
1827
+ const owner = used.get(key);
1828
+ if (owner && owner !== id) {
1829
+ blocks[id] = { ...blocks[id], key: `B${nextKey2}` };
1830
+ r.conflicts.push({ path: `blocks.${id}.key`, ours: key, theirs: `B${nextKey2} \u306B\u632F\u308A\u76F4\u3057` });
1831
+ nextKey2++;
1832
+ } else used.set(key, id);
1833
+ }
1834
+ out2.nextKey = nextKey2;
1835
+ r.project = out2;
1836
+ return r;
1837
+ }
1838
+
1839
+ // cli/main.ts
1840
+ import { execFileSync as execFileSync2 } from "node:child_process";
1841
+ import { dirname as dirname3, join as join2, resolve as resolve3 } from "node:path";
1842
+
1843
+ // src/model/report.ts
1844
+ var MARK = { black: "[ ]", gray: "[~]", white: "[x]" };
1845
+ function actorLabel(actor) {
1846
+ const a = actor.toLowerCase();
1847
+ if (a.startsWith("claude")) return "CC";
1848
+ if (a.startsWith("codex")) return "CX";
1849
+ if (a.startsWith("gemini")) return "GM";
1850
+ if (a.startsWith("copilot")) return "CP";
1851
+ if (a.startsWith("human:")) return actor.slice(6, 7).toUpperCase() || "H";
1852
+ return actor.slice(0, 2).toUpperCase();
1853
+ }
1854
+ var ACTIVITY_LABEL = { working: "\u4F5C\u696D\u4E2D", blocked: "\u8A70\u307E\u308A", needs_decision: "\u5224\u65AD\u5F85\u3061", waiting_review: "\u78BA\u8A8D\u5F85\u3061" };
1855
+ function ago(iso, now2 = Date.now()) {
1856
+ const sec = Math.max(0, (now2 - new Date(iso).getTime()) / 1e3);
1857
+ if (sec < 60) return "\u4ECA";
1858
+ if (sec < 3600) return `${Math.floor(sec / 60)} \u5206`;
1859
+ if (sec < 86400) return `${Math.floor(sec / 3600)} \u6642\u9593`;
1860
+ return `${Math.floor(sec / 86400)} \u65E5`;
1861
+ }
1862
+ function agoText(iso) {
1863
+ const a = ago(iso);
1864
+ return a === "\u4ECA" ? "\u305F\u3063\u305F\u4ECA" : `${a}\u524D\u304B\u3089`;
1865
+ }
1866
+ function shortTime(iso) {
1867
+ const d = new Date(iso);
1868
+ const pad = (n) => String(n).padStart(2, "0");
1869
+ return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
1870
+ }
1871
+ function treeLines(p, parentId, depth, lines) {
1872
+ const kids = childrenOf(p, parentId).sort((a, b) => a.position.y - b.position.y || a.position.x - b.position.x);
1873
+ for (const b of kids) {
1874
+ const outs = portsOf(p, b.id, "out").map((o) => o.name + (o.artifacts.length > 0 ? "*" : "")).join(", ");
1875
+ const act = b.activity ? ` <- ${actorLabel(b.activity.actor)} ${ACTIVITY_LABEL[b.activity.state]}${b.activity.note ? ": " + b.activity.note : ""}` : "";
1876
+ const cat = categoryOf(b.category);
1877
+ const tag = (cat ? ` [${cat.label}]` : "") + (b.kind === "project" ? ` [\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8${b.repo ? ": " + b.repo : ""}]` : b.template ? ` [\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8: ${b.template.name}]` : "") + (b.issue ? ` [${issueKeyOf(b.issue)}]` : "");
1878
+ const pct = b.status === "white" ? "" : (() => {
1879
+ const v = effectiveProgress(p, b.id);
1880
+ return v > 0 ? ` ${v}%` : "";
1881
+ })();
1882
+ const who = b.assigneeIds.length === 0 && b.kind !== "project" ? " (\u672A\u62C5\u5F53)" : "";
1883
+ const due = b.dueDate && b.status !== "white" ? ` \u671F\u65E5 ${b.dueDate}${isOverdue(b) ? " (\u8D85\u904E)" : ""}` : "";
1884
+ lines.push(`${" ".repeat(depth)}- ${MARK[b.status]}${pct} ${b.key ?? ""} ${b.title}${tag}${who}${due} -> ${outs}${act}`);
1885
+ treeLines(p, b.id, depth + 1, lines);
1886
+ }
1887
+ }
1888
+ function statusReport(p) {
1889
+ const s = summarize(p);
1890
+ const lines = [];
1891
+ lines.push(`# ${p.name}`);
1892
+ if (p.description) lines.push("", p.description);
1893
+ const prog = computeProgress(p, ROOT_ID);
1894
+ lines.push("", `\u5B8C\u4E86 ${s.white} / ${s.total} \u30FB \u9032\u6357 ${prog.percent}% (BlackBox ${s.black}, GrayBox ${s.gray}, WhiteBox ${s.white})`);
1895
+ const rootOuts = portsOf(p, ROOT_ID, "out").map((o) => o.name + (o.artifacts.length > 0 ? "*" : ""));
1896
+ const rootIns = portsOf(p, ROOT_ID, "in").filter((o) => !o.groupId).map((o) => o.name + (o.promotedFrom ? " (\u81EA\u52D5)" : ""));
1897
+ lines.push(`\u6700\u7D42\u6210\u679C\u7269: ${rootOuts.join(", ") || "(\u672A\u8A2D\u5B9A)"}`);
1898
+ lines.push(`\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u5165\u529B: ${rootIns.join(", ") || "(\u306A\u3057)"}`);
1899
+ for (const gp of p.inputGroups ?? []) {
1900
+ const names = portsOf(p, ROOT_ID, "in").filter((o) => o.groupId === gp.id).map((o) => o.name + (o.artifacts.length > 0 ? "*" : ""));
1901
+ lines.push(` \u5165\u529B\u30B0\u30EB\u30FC\u30D7\u300C${gp.name}\u300D: ${names.join(", ") || "(\u7A7A)"}`);
1902
+ }
1903
+ if (s.decisions.length > 0) {
1904
+ lines.push("", "## \u5224\u65AD\u5F85\u3061 (\u4EBA\u9593\u306E\u56DE\u7B54\u304C\u5FC5\u8981)");
1905
+ for (const { block, decision } of s.decisions) {
1906
+ lines.push(`- ${block.key ?? ""} \u300C${block.title}\u300D ${decision.question}${decision.options.length > 0 ? " \u9078\u629E\u80A2: " + decision.options.join(" / ") : ""} (decision: ${decision.id})`);
1907
+ if (decision.context) lines.push(` \u5224\u65AD\u6750\u6599: ${decision.context.replace(/\n/g, "\n ")}`);
1908
+ }
1909
+ }
1910
+ if (s.overdue.length > 0) {
1911
+ lines.push("", "## \u671F\u65E5\u8D85\u904E");
1912
+ for (const b of s.overdue) lines.push(`- ${b.key ?? ""} \u300C${b.title}\u300D \u671F\u65E5 ${b.dueDate}`);
1913
+ }
1914
+ if (s.working.length > 0) {
1915
+ lines.push("", "## \u4F5C\u696D\u4E2D");
1916
+ for (const w of s.working) lines.push(`- ${actorLabel(w.actor)} ${w.block.key ?? ""} \u300C${w.block.title}\u300D ${w.note} (${agoText(w.since)})`);
1917
+ }
1918
+ if (s.blocked.length > 0) {
1919
+ lines.push("", "## \u8A70\u307E\u308A\u30FB\u78BA\u8A8D\u5F85\u3061");
1920
+ for (const w of s.blocked) lines.push(`- ${actorLabel(w.actor)} ${w.block.key ?? ""} \u300C${w.block.title}\u300D ${w.note}`);
1921
+ }
1922
+ lines.push("", "## \u968E\u5C64 ([ ] New / [~] In Progress / [x] Done\u3002B \u756A\u53F7\u306F\u7BB1\u306E ID\u3002\u51FA\u529B\u540D\u306E * \u306F\u6210\u679C\u7269\u3042\u308A)");
1923
+ treeLines(p, ROOT_ID, 0, lines);
1924
+ if (s.next.length > 0) {
1925
+ lines.push("", "## \u6B21\u306E\u5019\u88DC (\u672A\u7740\u624B\u306E New\u3002\u5FC5\u9808\u306E\u5165\u529B\u304C\u305D\u308D\u3063\u3066\u3044\u308B\u3082\u306E\u304B\u3089)");
1926
+ for (const b of s.next.slice(0, 10)) {
1927
+ const missing = missingRequiredInputs(p, b.id);
1928
+ lines.push(`- ${b.key ?? ""} ${b.title}${missing.length > 0 ? ` (\u5FC5\u9808\u306E\u5165\u529B\u5F85\u3061: ${missing.map((q) => q.name).join(", ")})` : " (\u7740\u624B\u3067\u304D\u308B)"}`);
1929
+ }
1930
+ }
1931
+ lines.push("");
1932
+ return lines.join("\n");
1933
+ }
1934
+ function blockReport(p, blockId) {
1935
+ const b = p.blocks[blockId];
1936
+ if (!b) return "";
1937
+ const name = (id) => id === ROOT_ID ? "project" : p.blocks[id]?.title ?? "?";
1938
+ const lines = [`# ${b.key ?? ""} ${b.title}`, "", `- \u72B6\u614B: ${b.status}${b.status !== "white" ? ` (\u9032\u6357 ${effectiveProgress(p, blockId)}%)` : ""}`];
1939
+ const cat = categoryOf(b.category);
1940
+ if (cat) lines.push(`- \u30AB\u30C6\u30B4\u30EA: ${cat.label} (${cat.en})`);
1941
+ if (b.issue) lines.push(`- \u8AB2\u984C: ${issueKeyOf(b.issue)} <${b.issue}>`);
1942
+ if (b.startDate || b.dueDate) lines.push(`- \u65E5\u7A0B: ${b.startDate ? "\u958B\u59CB " + b.startDate : ""}${b.startDate && b.dueDate ? " / " : ""}${b.dueDate ? "\u671F\u65E5 " + b.dueDate + (isOverdue(b) ? " (\u8D85\u904E)" : "") : ""}`);
1943
+ if (b.estimateHours !== void 0 || b.actualHours !== void 0) lines.push(`- \u6642\u9593: ${b.estimateHours !== void 0 ? "\u898B\u7A4D " + b.estimateHours + "h" : ""}${b.estimateHours !== void 0 && b.actualHours !== void 0 ? " / " : ""}${b.actualHours !== void 0 ? "\u5B9F\u7E3E " + b.actualHours + "h" : ""}`);
1944
+ if (b.activity) lines.push(`- \u6D3B\u52D5: ${b.activity.actor} ${ACTIVITY_LABEL[b.activity.state]} ${b.activity.note} (${b.activity.since})`);
1945
+ if (b.description) lines.push("", b.description);
1946
+ lines.push("", "## \u5165\u529B");
1947
+ for (const q of portsOf(p, blockId, "in")) {
1948
+ const src = incomingEdges(p, { portId: q.id, side: "outer" }).map((e) => `${name(p.ports[e.from.portId].blockId)}.${p.ports[e.from.portId].name}${e.auto ? " (\u81EA\u52D5)" : ""}`);
1949
+ const desc = effectiveDescription(p, q.id);
1950
+ lines.push(`- ${q.name}${q.required ? "" : " (\u4EFB\u610F)"}${desc ? ": " + desc : ""}${src.length ? " <- " + src.join(", ") : " <- (\u672A\u63A5\u7D9A)"}`);
1951
+ }
1952
+ lines.push("", "## \u51FA\u529B");
1953
+ for (const q of portsOf(p, blockId, "out")) {
1954
+ const dst = outgoingEdges(p, { portId: q.id, side: "outer" }).map((e) => `${name(p.ports[e.to.portId].blockId)}.${p.ports[e.to.portId].name}`);
1955
+ const arts = q.artifacts.map((a) => a.url ? `${a.title} <${a.url}>` : a.title);
1956
+ lines.push(`- ${q.name}${q.description ? ": " + q.description : ""}${dst.length ? " -> " + dst.join(", ") : ""}${arts.length ? " \u6210\u679C\u7269: " + arts.join(", ") : ""}`);
1957
+ }
1958
+ const kids = childrenOf(p, blockId);
1959
+ if (kids.length > 0) {
1960
+ lines.push("", "## \u4E0B\u306E\u968E\u5C64");
1961
+ for (const k of kids) lines.push(`- ${MARK[k.status]} ${k.title} (id: ${k.id})`);
1962
+ }
1963
+ if (b.decisions.length > 0) {
1964
+ lines.push("", "## \u5224\u65AD");
1965
+ for (const d of b.decisions) {
1966
+ const c = candidatesOf(d);
1967
+ lines.push(`- ${d.answer === void 0 ? "[\u672A\u56DE\u7B54]" : "[\u56DE\u7B54\u6E08]"} ${d.question}${d.answer !== void 0 ? " -> " + d.answer : ""} (decision: ${d.id})`);
1968
+ if (d.context) lines.push(` \u5224\u65AD\u6750\u6599: ${d.context.replace(/\n/g, "\n ")}`);
1969
+ if (c.rejected.length > 0) lines.push(` ${d.answer === void 0 ? "\u5019\u88DC" : "\u6B8B\u3057\u305F\u5019\u88DC"}: ${c.rejected.join(" / ")}`);
1970
+ for (const h of d.history ?? []) lines.push(` \u4EE5\u524D\u306E\u7B54\u3048: ${h.answer} (${h.by}${h.note ? "\u3001" + h.note : ""}) ${h.at}`);
1971
+ }
1972
+ }
1973
+ if (b.artifacts.length > 0) {
1974
+ lines.push("", "## \u8CC7\u6599");
1975
+ for (const a of b.artifacts) lines.push(`- ${a.url ? `${a.title} <${a.url}>` : a.title}`);
1976
+ }
1977
+ lines.push("");
1978
+ return lines.join("\n");
1979
+ }
1980
+ function logReport(p, n = 20) {
1981
+ const items = p.log.slice(-n).reverse();
1982
+ if (items.length === 0) return "(\u30ED\u30B0\u306F\u307E\u3060\u3042\u308A\u307E\u305B\u3093)\n";
1983
+ return items.map((e) => `- ${shortTime(e.at)} ${actorLabel(e.actor)} ${e.message}`).join("\n") + "\n";
1984
+ }
1985
+
1986
+ // cli/git.ts
1987
+ import { execFileSync } from "node:child_process";
1988
+ import { existsSync as existsSync2, statSync as statSync2 } from "node:fs";
1989
+ import { dirname as dirname2, relative, resolve as resolve2 } from "node:path";
1990
+ function git(args, cwd) {
1991
+ try {
1992
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
1993
+ } catch {
1994
+ return null;
1995
+ }
1996
+ }
1997
+ function remoteToWeb(remote) {
1998
+ let m = remote.match(/^git@([^:]+):(.+?)(\.git)?$/);
1999
+ if (m) return `https://${m[1]}/${m[2]}`;
2000
+ m = remote.match(/^(https?:\/\/[^/]+\/.+?)(\.git)?$/);
2001
+ if (m) return m[1];
2002
+ m = remote.match(/^ssh:\/\/git@([^/]+)\/(.+?)(\.git)?$/);
2003
+ if (m) return `https://${m[1]}/${m[2]}`;
2004
+ return null;
2005
+ }
2006
+ function gitRefFor(filePath) {
2007
+ const abs = resolve2(filePath);
2008
+ if (!existsSync2(abs) || !statSync2(abs).isFile()) return null;
2009
+ const root = git(["rev-parse", "--show-toplevel"], dirname2(abs));
2010
+ if (!root) return null;
2011
+ const rel = relative(root, abs).replace(/\\/g, "/");
2012
+ const commit = git(["rev-parse", "HEAD"], root) ?? "";
2013
+ const blob = git(["hash-object", abs], root) ?? "";
2014
+ const repo = git(["config", "--get", "remote.origin.url"], root) ?? "";
2015
+ const web = repo ? remoteToWeb(repo) : null;
2016
+ const url = web && commit ? `${web}/blob/${commit}/${rel}` : "";
2017
+ return { root, repo, path: rel, commit, blob, url };
2018
+ }
2019
+ function currentRef(root, rel) {
2020
+ const commit = git(["rev-parse", "HEAD"], root) ?? "";
2021
+ const blob = git(["hash-object", resolve2(root, rel)], root) ?? "";
2022
+ const repo = git(["config", "--get", "remote.origin.url"], root) ?? "";
2023
+ const web = repo ? remoteToWeb(repo) : null;
2024
+ return { path: rel, commit, blob, url: web && commit ? `${web}/blob/${commit}/${rel}` : "" };
2025
+ }
2026
+ function checkGitRef(ref, cwd) {
2027
+ const root = git(["rev-parse", "--show-toplevel"], cwd);
2028
+ if (!root || !ref.path) return { state: "missing" };
2029
+ if (existsSync2(resolve2(root, ref.path))) return { state: "ok", path: ref.path };
2030
+ if (ref.blob) {
2031
+ const tree = git(["ls-tree", "-r", "HEAD"], root) ?? "";
2032
+ for (const line of tree.split("\n")) {
2033
+ const m = line.match(/^\d+ blob ([0-9a-f]+)\t(.+)$/);
2034
+ if (m && m[1] === ref.blob) return { state: "moved", ...currentRef(root, m[2]) };
2035
+ }
2036
+ }
2037
+ if (ref.commit) {
2038
+ const commits = (git(["rev-list", "--reverse", `${ref.commit}..HEAD`], root) ?? "").split("\n").filter(Boolean).slice(0, 2e3);
2039
+ let cur = ref.path;
2040
+ for (const c of commits) {
2041
+ const diff = git(["diff-tree", "-M", "--name-status", "-r", `${c}^`, c], root) ?? "";
2042
+ for (const line of diff.split("\n")) {
2043
+ const m = line.match(/^R\d+\t(.+)\t(.+)$/);
2044
+ if (m && m[1] === cur) cur = m[2];
2045
+ }
2046
+ }
2047
+ if (cur !== ref.path && existsSync2(resolve2(root, cur))) return { state: "moved", ...currentRef(root, cur) };
2048
+ }
2049
+ return { state: "missing" };
2050
+ }
2051
+
2052
+ // src/model/autolayout.ts
2053
+ var GAP_X = 144;
2054
+ var GAP_Y = 96;
2055
+ var MAX_ROW_W = 3e3;
2056
+ function layoutScope(p, scopeId) {
2057
+ let q = structuredClone(p);
2058
+ const kids = childrenOf(q, scopeId);
2059
+ if (kids.length === 0) return q;
2060
+ for (const k of kids) if (childrenOf(q, k.id).length > 0) q = layoutScope(q, k.id);
2061
+ const ids = kids.map((k) => k.id);
2062
+ const succ = /* @__PURE__ */ new Map();
2063
+ for (const id of ids) {
2064
+ const next = /* @__PURE__ */ new Set();
2065
+ for (const port of portsOf(q, id, "out")) {
2066
+ for (const e of outgoingEdges(q, { portId: port.id, side: "outer" })) {
2067
+ if (e.kind !== "sibling") continue;
2068
+ const to = q.ports[e.to.portId]?.blockId;
2069
+ if (to && ids.includes(to)) next.add(to);
2070
+ }
2071
+ }
2072
+ succ.set(id, [...next]);
2073
+ }
2074
+ const layer = /* @__PURE__ */ new Map();
2075
+ const visiting = /* @__PURE__ */ new Set();
2076
+ const depth = (id) => {
2077
+ if (layer.has(id)) return layer.get(id);
2078
+ if (visiting.has(id)) return 0;
2079
+ visiting.add(id);
2080
+ let d = 0;
2081
+ for (const prev of ids) if (succ.get(prev).includes(id)) d = Math.max(d, depth(prev) + 1);
2082
+ visiting.delete(id);
2083
+ layer.set(id, d);
2084
+ return d;
2085
+ };
2086
+ for (const id of ids) depth(id);
2087
+ const maxLayer = Math.max(...ids.map((id) => layer.get(id)));
2088
+ const order = /* @__PURE__ */ new Map();
2089
+ const initial = [...kids].sort((a, b) => a.position.y - b.position.y || a.position.x - b.position.x);
2090
+ initial.forEach((k, i) => order.set(k.id, i));
2091
+ const pred = /* @__PURE__ */ new Map();
2092
+ for (const id of ids) pred.set(id, ids.filter((prev) => succ.get(prev).includes(id)));
2093
+ for (let pass = 0; pass < 2; pass++) {
2094
+ for (let L = 1; L <= maxLayer; L++) {
2095
+ const members = ids.filter((id) => layer.get(id) === L);
2096
+ const bary = /* @__PURE__ */ new Map();
2097
+ for (const id of members) {
2098
+ const ps = pred.get(id);
2099
+ bary.set(id, ps.length === 0 ? order.get(id) : ps.reduce((acc, q2) => acc + order.get(q2), 0) / ps.length);
2100
+ }
2101
+ members.sort((a, b) => bary.get(a) - bary.get(b) || order.get(a) - order.get(b));
2102
+ members.forEach((id, i) => order.set(id, i));
2103
+ }
2104
+ for (let L = maxLayer - 1; L >= 0; L--) {
2105
+ const members = ids.filter((id) => layer.get(id) === L);
2106
+ const bary = /* @__PURE__ */ new Map();
2107
+ for (const id of members) {
2108
+ const ss = succ.get(id);
2109
+ bary.set(id, ss.length === 0 ? order.get(id) : ss.reduce((acc, q2) => acc + order.get(q2), 0) / ss.length);
2110
+ }
2111
+ members.sort((a, b) => bary.get(a) - bary.get(b) || order.get(a) - order.get(b));
2112
+ members.forEach((id, i) => order.set(id, i));
2113
+ }
2114
+ }
2115
+ const crossing = new Array(Math.max(1, maxLayer)).fill(0);
2116
+ for (const id of ids) {
2117
+ for (const nxt of succ.get(id)) {
2118
+ const a = layer.get(id);
2119
+ const b = layer.get(nxt);
2120
+ for (let g = Math.min(a, b); g < Math.max(a, b); g++) crossing[g]++;
2121
+ }
2122
+ }
2123
+ const gapAfter = (L) => Math.max(GAP_X, 72 + 16 * Math.max(0, (crossing[L] ?? 0) - 1) + 16);
2124
+ const startX = scopeId === ROOT_ID ? TERMINAL_W + GAP_X : CHILD_PADDING.left;
2125
+ const startY = scopeId === ROOT_ID ? 40 : childTop(q, scopeId);
2126
+ let x = startX;
2127
+ let rowTop = startY;
2128
+ let maxBottom = startY;
2129
+ let rowBottom = startY;
2130
+ for (let L = 0; L <= maxLayer; L++) {
2131
+ const members = kids.filter((k) => layer.get(k.id) === L).sort((a, b) => order.get(a.id) - order.get(b.id));
2132
+ const colW = Math.max(0, ...members.map((m) => blockSize(q, m.id).width));
2133
+ if (L > 0 && x + colW > startX + MAX_ROW_W) {
2134
+ x = startX;
2135
+ rowTop = rowBottom + GAP_Y * 2;
2136
+ }
2137
+ let y = rowTop;
2138
+ for (const m of members) {
2139
+ const s = blockSize(q, m.id);
2140
+ q.blocks[m.id].position = { x, y };
2141
+ y += s.height + GAP_Y;
2142
+ }
2143
+ rowBottom = Math.max(rowBottom, y - GAP_Y);
2144
+ maxBottom = Math.max(maxBottom, rowBottom);
2145
+ x += colW + gapAfter(L);
2146
+ }
2147
+ if (scopeId === ROOT_ID) {
2148
+ const midY = Math.max(startY, (startY + maxBottom) / 2 - terminalHeight(q, "in") / 2);
2149
+ q.terminals.in = { x: 0, y: midY };
2150
+ let gy = midY + terminalHeight(q, "in") + GAP_Y;
2151
+ for (const gp of q.inputGroups ?? []) {
2152
+ gp.position = { x: 0, y: gy };
2153
+ gy += 44 + Math.max(1, Object.values(q.ports).filter((x2) => x2.blockId === ROOT_ID && x2.groupId === gp.id).length) * 26 + 12 + GAP_Y;
2154
+ }
2155
+ q.terminals.out = { x, y: Math.max(startY, (startY + maxBottom) / 2 - terminalHeight(q, "out") / 2) };
2156
+ }
2157
+ return q;
2158
+ }
2159
+ function layoutAll(p) {
2160
+ return layoutScope(p, ROOT_ID);
2161
+ }
2162
+ function nextFreePosition(p, parentId) {
2163
+ const kids = childrenOf(p, parentId);
2164
+ const startX = parentId === ROOT_ID ? TERMINAL_W + GAP_X : CHILD_PADDING.left;
2165
+ const startY = parentId === ROOT_ID ? 40 : childTop(p, parentId);
2166
+ if (kids.length === 0) return { x: startX, y: startY };
2167
+ let bottom = startY;
2168
+ let leftmost = Infinity;
2169
+ for (const k of kids) {
2170
+ const s = blockSize(p, k.id);
2171
+ bottom = Math.max(bottom, k.position.y + s.height);
2172
+ leftmost = Math.min(leftmost, k.position.x);
2173
+ }
2174
+ return { x: Math.max(startX, leftmost), y: bottom + GAP_Y };
2175
+ }
2176
+
2177
+ // cli/main.ts
2178
+ function categoryKeyOf(text2) {
2179
+ const c = findCategory(text2);
2180
+ if (!c) throw new Error(`\u77E5\u3089\u306A\u3044\u30AB\u30C6\u30B4\u30EA\u3067\u3059: ${text2} (\u4F7F\u3048\u308B\u3082\u306E: ${CATEGORIES.map((x) => `${x.en} ${x.label}`).join(", ")})`);
2181
+ return c.key;
2182
+ }
2183
+ function parseArgs(argv2) {
2184
+ const positional = [];
2185
+ const options = {};
2186
+ for (let i = 0; i < argv2.length; i++) {
2187
+ const a = argv2[i];
2188
+ if (a.startsWith("--")) {
2189
+ let key = a.slice(2);
2190
+ let value = true;
2191
+ if (key.includes("=")) {
2192
+ value = key.slice(key.indexOf("=") + 1);
2193
+ key = key.slice(0, key.indexOf("="));
2194
+ } else if (i + 1 < argv2.length && !argv2[i + 1].startsWith("--")) {
2195
+ value = argv2[++i];
2196
+ }
2197
+ const prev = options[key];
2198
+ if (prev === void 0) options[key] = value;
2199
+ else if (Array.isArray(prev)) prev.push(String(value));
2200
+ else options[key] = [String(prev), String(value)];
2201
+ } else {
2202
+ positional.push(a);
2203
+ }
2204
+ }
2205
+ return { positional, options };
2206
+ }
2207
+ var str = (v) => typeof v === "string" ? v : Array.isArray(v) ? v[v.length - 1] : void 0;
2208
+ var list = (v) => typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
2209
+ function locateFile(opt2, forInit = false) {
2210
+ if (opt2) return resolve3(opt2);
2211
+ if (process.env.BOXGLOW_FILE) return resolve3(process.env.BOXGLOW_FILE);
2212
+ let dir = process.cwd();
2213
+ for (let i = 0; i < 12; i++) {
2214
+ const candidate = join2(dir, "boxglow.json");
2215
+ if (existsSync3(candidate)) return candidate;
2216
+ const parent = dirname3(dir);
2217
+ if (parent === dir) break;
2218
+ dir = parent;
2219
+ }
2220
+ if (forInit) return join2(process.cwd(), "boxglow.json");
2221
+ throw new Error("boxglow.json \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (--file \u3067\u6307\u5B9A\u3059\u308B\u304B\u3001`boxglow init` \u3067\u4F5C\u3063\u3066\u304F\u3060\u3055\u3044)");
2222
+ }
2223
+ function actorOf(opt2) {
2224
+ if (opt2) return opt2;
2225
+ if (process.env.BOXGLOW_ACTOR) return process.env.BOXGLOW_ACTOR;
2226
+ if (process.env.CLAUDECODE || process.env.CLAUDE_CODE) return "claude-code";
2227
+ if (process.env.CODEX_SANDBOX || process.env.CODEX_HOME) return "codex";
2228
+ return "agent";
2229
+ }
2230
+ function artifactFrom(spec, titleOpt) {
2231
+ const eq = spec.indexOf("=");
2232
+ const isUrlLike = (s) => /^[a-z]+:\/\//i.test(s);
2233
+ let title;
2234
+ let value;
2235
+ if (eq > 0 && !isUrlLike(spec.slice(0, eq))) {
2236
+ title = spec.slice(0, eq);
2237
+ value = spec.slice(eq + 1);
2238
+ } else {
2239
+ value = spec;
2240
+ title = titleOpt ?? spec.split(/[\\/]/).pop() ?? spec;
2241
+ }
2242
+ if (titleOpt) title = titleOpt;
2243
+ if (isUrlLike(value)) return createArtifact(title, value);
2244
+ const ref = gitRefFor(value);
2245
+ if (ref) return createGitArtifact(title, { repo: ref.repo, path: ref.path, commit: ref.commit, blob: ref.blob, url: ref.url });
2246
+ const a = createArtifact(title, "");
2247
+ a.kind = "file";
2248
+ a.path = value;
2249
+ return a;
2250
+ }
2251
+ function load(path) {
2252
+ return fromJSON(readFileSync2(path, "utf8"));
2253
+ }
2254
+ function save(path, p) {
2255
+ writeFileSync2(path, toJSON(resolveAllOverlaps(normalizeCollapsed(p), (q, id) => blockSize(q, id))) + "\n", "utf8");
2256
+ }
2257
+ function mustFind(p, ref, what = "block") {
2258
+ if (!ref) throw new Error(`<${what}> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044 (id \u307E\u305F\u306F\u984C\u540D)`);
2259
+ const r = findBlock(p, ref);
2260
+ if (!r.block) {
2261
+ const hint = r.candidates.length > 0 ? ` \u5019\u88DC: ${r.candidates.map((b) => `${b.title} (id: ${b.id})`).join(", ")}` : "";
2262
+ throw new Error(`\u30D6\u30ED\u30C3\u30AF\u300C${ref}\u300D\u304C${r.candidates.length > 0 ? "1 \u3064\u306B\u6C7A\u307E\u308A\u307E\u305B\u3093\u3002" : "\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002"}${hint}`);
2263
+ }
2264
+ return r.block;
2265
+ }
2266
+ function resolveRef(p, ref) {
2267
+ if (!ref.includes(".")) throw new Error(`\u300C${ref}\u300D\u306F <\u984C\u540D>.<\u30DD\u30FC\u30C8\u540D> \u306E\u5F62\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044`);
2268
+ const candidates = [];
2269
+ for (let i = ref.indexOf("."); i >= 0; i = ref.indexOf(".", i + 1)) {
2270
+ const title = ref.slice(0, i);
2271
+ const portName = ref.slice(i + 1);
2272
+ let blockId = null;
2273
+ if (title === "project") blockId = projectBlocks(p)[0]?.id ?? ROOT_ID;
2274
+ else if (title === ROOT_ID) blockId = ROOT_ID;
2275
+ else {
2276
+ const b = findBlock(p, title).block;
2277
+ if (b) blockId = b.id;
2278
+ }
2279
+ if (!blockId || !portName) continue;
2280
+ candidates.push({ blockId, portName, hasPort: portsOf(p, blockId).some((x) => x.name === portName) });
2281
+ }
2282
+ const best = candidates.find((c) => c.hasPort) ?? candidates[0];
2283
+ if (!best) throw new Error(`\u300C${ref}\u300D\u306E\u7BB1\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (<\u984C\u540D>.<\u30DD\u30FC\u30C8\u540D> \u306E\u5F62\u3002\u984C\u540D\u306F status \u306B\u51FA\u308B\u984C\u540D\u304B B \u756A\u53F7)`);
2284
+ return { blockId: best.blockId, portName: best.portName };
2285
+ }
2286
+ var sink = (text2) => process.stdout.write(text2);
2287
+ var out = (text2) => sink(text2.endsWith("\n") ? text2 : text2 + "\n");
2288
+ function runCli(argv2) {
2289
+ const prev = sink;
2290
+ let buf = "";
2291
+ sink = (text2) => {
2292
+ buf += text2;
2293
+ };
2294
+ try {
2295
+ main(argv2);
2296
+ return buf;
2297
+ } finally {
2298
+ sink = prev;
2299
+ }
2300
+ }
2301
+ function main(argv2) {
2302
+ const { positional, options } = parseArgs(argv2);
2303
+ const [cmd, ...rest] = positional;
2304
+ if (!cmd || cmd === "help" || options.help) {
2305
+ out(readFileSync2(new URL(import.meta.url)).toString().match(/\/\*\*[\s\S]*?\*\//)?.[0].replace(/^\/\*\*|\*\/$/g, "").replace(/^ \* ?/gm, "") ?? "boxglow");
2306
+ return;
2307
+ }
2308
+ const actor = actorOf(str(options.actor));
2309
+ if (cmd === "init") {
2310
+ const path2 = locateFile(str(options.file), true);
2311
+ if (existsSync3(path2) && !options.force) throw new Error(`${path2} \u306F\u65E2\u306B\u3042\u308A\u307E\u3059 (--force \u3067\u4E0A\u66F8\u304D)`);
2312
+ const p2 = createProject(str(options.name) ?? "\u65B0\u3057\u3044\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8");
2313
+ save(path2, p2);
2314
+ out(`\u4F5C\u6210: ${path2}`);
2315
+ return;
2316
+ }
2317
+ const path = locateFile(str(options.file));
2318
+ let p = load(path);
2319
+ switch (cmd) {
2320
+ case "status": {
2321
+ if (options.json) out(JSON.stringify(p, null, 2));
2322
+ else out(statusReport(p));
2323
+ return;
2324
+ }
2325
+ case "export": {
2326
+ const fmt = str(options.format) ?? "md";
2327
+ const text2 = fmt === "json" ? toJSON(p) + "\n" : projectToMarkdown(p);
2328
+ const target = str(options.out);
2329
+ if (target) {
2330
+ writeFileSync2(target, text2, "utf8");
2331
+ out(`\u66F8\u304D\u51FA\u3057: ${target}`);
2332
+ } else out(text2);
2333
+ return;
2334
+ }
2335
+ case "show": {
2336
+ out(blockReport(p, mustFind(p, rest[0]).id));
2337
+ return;
2338
+ }
2339
+ case "setup-agent": {
2340
+ const root = str(options.dir) ? resolve3(str(options.dir)) : dirname3(path);
2341
+ const BEGIN = "<!-- boxglow:begin -->";
2342
+ const END = "<!-- boxglow:end -->";
2343
+ const parts = AGENTS_SNIPPET_default.split(/^---$/m);
2344
+ const body = (parts.length >= 3 ? parts.slice(1, -1).join("---") : AGENTS_SNIPPET_default).trim();
2345
+ const block = `${BEGIN}
2346
+ ${body}
2347
+ ${END}
2348
+ `;
2349
+ const done = [];
2350
+ for (const name of ["AGENTS.md", "CLAUDE.md"]) {
2351
+ const f = join2(root, name);
2352
+ const cur = existsSync3(f) ? readFileSync2(f, "utf8") : "";
2353
+ let next;
2354
+ if (cur.includes(BEGIN) && cur.includes(END)) next = cur.slice(0, cur.indexOf(BEGIN)) + block + cur.slice(cur.indexOf(END) + END.length + 1);
2355
+ else next = (cur ? cur.replace(/\s*$/, "\n\n") : "") + block;
2356
+ if (next !== cur) {
2357
+ writeFileSync2(f, next, "utf8");
2358
+ done.push(name);
2359
+ }
2360
+ }
2361
+ const skillDir = join2(root, ".claude", "skills", "boxglow");
2362
+ mkdirSync(skillDir, { recursive: true });
2363
+ writeFileSync2(join2(skillDir, "SKILL.md"), SKILL_default, "utf8");
2364
+ done.push(".claude/skills/boxglow/SKILL.md");
2365
+ const settingsPath = join2(root, ".claude", "settings.json");
2366
+ let settings = {};
2367
+ if (existsSync3(settingsPath)) {
2368
+ try {
2369
+ settings = JSON.parse(readFileSync2(settingsPath, "utf8"));
2370
+ } catch {
2371
+ settings = {};
2372
+ }
2373
+ }
2374
+ const hooks = settings.hooks ?? {};
2375
+ const start = hooks.SessionStart ?? [];
2376
+ const cmd2 = "npx boxglow status";
2377
+ if (!start.some((h) => (h.hooks ?? []).some((x) => x.command === cmd2))) {
2378
+ start.push({ hooks: [{ type: "command", command: cmd2 }] });
2379
+ hooks.SessionStart = start;
2380
+ settings.hooks = hooks;
2381
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
2382
+ done.push(".claude/settings.json (SessionStart: npx boxglow status)");
2383
+ }
2384
+ const mcpPath = join2(root, ".mcp.json");
2385
+ let mcp = {};
2386
+ if (existsSync3(mcpPath)) {
2387
+ try {
2388
+ mcp = JSON.parse(readFileSync2(mcpPath, "utf8"));
2389
+ } catch {
2390
+ mcp = {};
2391
+ }
2392
+ }
2393
+ mcp.mcpServers = mcp.mcpServers ?? {};
2394
+ if (!mcp.mcpServers.boxglow) {
2395
+ mcp.mcpServers.boxglow = { command: "npx", args: ["boxglow", "mcp"] };
2396
+ writeFileSync2(mcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf8");
2397
+ done.push(".mcp.json (MCP \u30B5\u30FC\u30D0 boxglow)");
2398
+ }
2399
+ out(`\u8A2D\u5B9A\u3057\u307E\u3057\u305F: ${done.join(", ")}
2400
+ AI \u306F\u4F5C\u696D\u306E\u59CB\u307E\u308A\u30FB\u7D42\u308F\u308A\u30FB\u5224\u65AD\u5F85\u3061\u3092 boxglow.json \u306B\u8A18\u9332\u3057\u3001\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6700\u521D\u306B\u8A08\u753B\u3092\u8AAD\u307F\u307E\u3059\u3002\u4EBA\u306F\u753B\u9762 (boxglow.json \u3092\u958B\u304F\u3001\u307E\u305F\u306F npx boxglow serve) \u3067\u898B\u3066\u5224\u65AD\u3057\u3066\u304F\u3060\u3055\u3044`);
2401
+ return;
2402
+ }
2403
+ case "project": {
2404
+ const name = rest.join(" ");
2405
+ if (!name) throw new Error("<\u540D\u524D> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2406
+ const r = addProjectBlock(p, name);
2407
+ if (str(options.repo)) r.project = updateBlock(r.project, r.blockId, { repo: str(options.repo) });
2408
+ save(path, r.project);
2409
+ out(`\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1\u3092\u8FFD\u52A0: \u300C${name}\u300D(id: ${r.blockId})\u3002\u5168\u90E8\u3067 ${projectBlocks(r.project).length} \u4EF6`);
2410
+ return;
2411
+ }
2412
+ case "export-block": {
2413
+ const b = mustFind(p, rest[0]);
2414
+ const tags = str(options.tags) ? str(options.tags).split(/[,、]/).map((s) => s.trim()).filter(Boolean) : [];
2415
+ const tpl = extractTemplate(p, b.id, { tags });
2416
+ const target = str(options.out) ?? `${b.title.replace(/[\\/:*?"<>|\s]/g, "_")}.boxglow-block.json`;
2417
+ writeFileSync2(target, JSON.stringify(tpl, null, 2) + "\n", "utf8");
2418
+ out(`\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u66F8\u304D\u51FA\u3057: ${target} (${tpl.root.children.length} \u500B\u306E\u5B50)`);
2419
+ return;
2420
+ }
2421
+ case "import-block": {
2422
+ if (!rest[0]) throw new Error("<path> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2423
+ const tpl = parseTemplate(readFileSync2(rest[0], "utf8"));
2424
+ const parentId = str(options.parent) ? mustFind(p, str(options.parent)).id : defaultTaskParent(p);
2425
+ const r = instantiateTemplate(p, parentId, tpl, actor, nextFreePosition(p, parentId));
2426
+ save(path, r.project);
2427
+ out(`\u633F\u5165: \u300C${tpl.root.title}\u300D(id: ${r.blockId}) \u30C6\u30F3\u30D7\u30EC\u30FC\u30C8 ${tpl.name} v${tpl.version}`);
2428
+ return;
2429
+ }
2430
+ case "group": {
2431
+ const name = rest.join(" ");
2432
+ if (!name) throw new Error("<\u540D\u524D> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2433
+ const r = addInputGroup(p, name);
2434
+ save(path, r.project);
2435
+ out(`\u5165\u529B\u30B0\u30EB\u30FC\u30D7\u3092\u8FFD\u52A0: \u300C${name}\u300D`);
2436
+ return;
2437
+ }
2438
+ case "group-set": {
2439
+ const port = portsOf(p, ROOT_ID, "in").find((x) => x.name === rest[0]);
2440
+ if (!port) throw new Error(`\u6700\u4E0A\u4F4D\u306E\u5165\u529B\u300C${rest[0] ?? ""}\u300D\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${portsOf(p, ROOT_ID, "in").map((x) => x.name).join(", ")})`);
2441
+ const gname = rest.slice(1).join(" ");
2442
+ const gp = gname === "none" ? null : inputGroupsOf(p).find((g) => g.name === gname);
2443
+ if (gname !== "none" && !gp) throw new Error(`\u30B0\u30EB\u30FC\u30D7\u300C${gname}\u300D\u304C\u3042\u308A\u307E\u305B\u3093 (${inputGroupsOf(p).map((g) => g.name).join(", ")})`);
2444
+ save(path, setInputGroup(p, port.id, gp ? gp.id : null));
2445
+ out(`\u300C${port.name}\u300D\u3092 ${gp ? "\u30B0\u30EB\u30FC\u30D7\u300C" + gp.name + "\u300D" : "Inputs"} \u3078`);
2446
+ return;
2447
+ }
2448
+ case "group-export": {
2449
+ const gp = inputGroupsOf(p).find((g) => g.name === rest.join(" "));
2450
+ if (!gp) throw new Error("\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
2451
+ const target = str(options.out) ?? `${gp.name.replace(/[\\/:*?"<>|\s]/g, "_")}.boxglow-inputs.json`;
2452
+ writeFileSync2(target, exportInputGroup(p, gp.id) + "\n", "utf8");
2453
+ out(`\u66F8\u304D\u51FA\u3057: ${target}`);
2454
+ return;
2455
+ }
2456
+ case "group-import": {
2457
+ if (!rest[0]) throw new Error("<path> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2458
+ const r = importInputGroup(p, readFileSync2(rest[0], "utf8"));
2459
+ save(path, r.project);
2460
+ out(`\u8AAD\u307F\u8FBC\u307F: \u30B0\u30EB\u30FC\u30D7\u300C${inputGroupsOf(r.project).find((g) => g.id === r.groupId)?.name}\u300D`);
2461
+ return;
2462
+ }
2463
+ case "find": {
2464
+ const hits = searchBlocks(p, rest.join(" "), 30);
2465
+ out(hits.length === 0 ? "\u898B\u3064\u304B\u308A\u307E\u305B\u3093" : hits.map((b) => `- ${b.key ?? ""} ${b.title} (id: ${b.id})`).join("\n"));
2466
+ return;
2467
+ }
2468
+ case "layout": {
2469
+ const q = rest[0] ? layoutScope(normalizeCollapsed(p), mustFind(p, rest[0]).id) : layoutAll(normalizeCollapsed(p));
2470
+ save(path, q);
2471
+ out(rest[0] ? `\u6574\u5217: \u300C${mustFind(p, rest[0]).title}\u300D\u306E\u4E2D` : "\u6574\u5217: \u5168\u4F53");
2472
+ return;
2473
+ }
2474
+ case "log": {
2475
+ out(logReport(p, Number(str(options.n) ?? 20)));
2476
+ return;
2477
+ }
2478
+ case "prompt": {
2479
+ const ask = str(options.ask) ?? "plan";
2480
+ out(blockToPrompt(p, mustFind(p, rest[0]).id, ask));
2481
+ return;
2482
+ }
2483
+ case "merge": {
2484
+ const [basePath, oursPath, theirsPath] = rest;
2485
+ if (!basePath || !oursPath || !theirsPath) throw new Error("merge <base> <ours> <theirs> \u306E 3 \u3064\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2486
+ const read = (f) => {
2487
+ const text2 = readFileSync2(f, "utf8");
2488
+ return text2.trim() ? fromJSON(text2) : null;
2489
+ };
2490
+ const base = read(basePath);
2491
+ const ours = read(oursPath);
2492
+ const theirs = read(theirsPath);
2493
+ if (!ours || !theirs) throw new Error("ours / theirs \u304C\u8AAD\u3081\u307E\u305B\u3093");
2494
+ const r = mergeProjects(base, ours, theirs);
2495
+ let q = r.project;
2496
+ for (const c of r.conflicts) {
2497
+ q = { ...q, log: [...q.log, { id: `m${Math.random().toString(36).slice(2, 10)}`, at: (/* @__PURE__ */ new Date()).toISOString(), actor: "merge", kind: "note", message: `\u30DE\u30FC\u30B8\u3067\u4E21\u5074\u304C\u5909\u66F4: ${c.path} (\u63A1\u7528: ${JSON.stringify(c.ours)} / \u76F8\u624B: ${JSON.stringify(c.theirs)})` }] };
2498
+ }
2499
+ writeFileSync2(oursPath, toJSON(q) + "\n", "utf8");
2500
+ out(`\u30DE\u30FC\u30B8: \u76F8\u624B\u306E\u5909\u66F4 ${r.merged} \u4EF6\u3092\u53D6\u308A\u8FBC\u307F` + (r.conflicts.length ? `\u3001\u4E21\u5074\u3067\u5909\u66F4\u304C ${r.conflicts.length} \u4EF6 (\u81EA\u5206\u306E\u5024\u3092\u63A1\u7528\u3057\u3001\u76F8\u624B\u306E\u5024\u306F\u30ED\u30B0\u306B\u8A18\u9332)` : ""));
2501
+ if (r.conflicts.length && options.strict) process.exitCode = 1;
2502
+ return;
2503
+ }
2504
+ case "git-setup": {
2505
+ const root = dirname3(path);
2506
+ const attrs = join2(root, ".gitattributes");
2507
+ const line = "boxglow.json merge=boxglow";
2508
+ const cur = existsSync3(attrs) ? readFileSync2(attrs, "utf8") : "";
2509
+ if (!cur.split(/\r?\n/).includes(line)) writeFileSync2(attrs, (cur && !cur.endsWith("\n") ? cur + "\n" : cur) + line + "\n", "utf8");
2510
+ execFileSync2("git", ["config", "merge.boxglow.name", "Boxglow plan merge (box level)"], { cwd: root });
2511
+ execFileSync2("git", ["config", "merge.boxglow.driver", "npx boxglow merge %O %A %B"], { cwd: root });
2512
+ out(`\u767B\u9332\u3057\u307E\u3057\u305F: ${attrs} \u306B\u300C${line}\u300D\u3001git config merge.boxglow.driver = "npx boxglow merge %O %A %B"
2513
+ \u4EE5\u5F8C git merge / pull / rebase \u3067 boxglow.json \u306F\u7BB1\u306E\u5358\u4F4D\u3067\u81EA\u52D5\u30DE\u30FC\u30B8\u3055\u308C\u307E\u3059 (\u30C1\u30FC\u30E0\u5168\u54E1\u304C\u3053\u306E\u30B3\u30DE\u30F3\u30C9\u3092 1 \u56DE\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044)`);
2514
+ return;
2515
+ }
2516
+ case "validate": {
2517
+ const problems = [];
2518
+ for (const b of Object.values(p.blocks)) {
2519
+ if (b.id !== ROOT_ID && portsOf(p, b.id, "out").length === 0) problems.push(`\u300C${b.title}\u300D\u306B\u51FA\u529B\u304C\u3042\u308A\u307E\u305B\u3093`);
2520
+ }
2521
+ for (const e of Object.values(p.edges)) {
2522
+ const c = validateConnection(p, e.from, e.to);
2523
+ if (!c.ok) problems.push(`\u7DDA ${e.id}: ${c.reason}`);
2524
+ }
2525
+ const warnings = [];
2526
+ const used = /* @__PURE__ */ new Set();
2527
+ for (const e of Object.values(p.edges)) {
2528
+ used.add(`${e.from.portId}:${e.from.side}`);
2529
+ used.add(`${e.to.portId}:${e.to.side}`);
2530
+ }
2531
+ for (const b of Object.values(p.blocks)) {
2532
+ if (b.id === ROOT_ID) continue;
2533
+ const hasKids = Object.values(p.blocks).some((x) => x.parentId === b.id);
2534
+ for (const q of portsOf(p, b.id)) {
2535
+ if (!used.has(`${q.id}:outer`) && !q.promotedFrom) warnings.push(`${b.key ?? ""} \u300C${b.title}\u300D\u306E${q.direction === "in" ? "\u5165\u529B" : "\u51FA\u529B"}\u300C${q.name}\u300D\u304C\u3069\u3053\u306B\u3082\u3064\u306A\u304C\u3063\u3066\u3044\u307E\u305B\u3093`);
2536
+ if (hasKids && !used.has(`${q.id}:inner`)) warnings.push(`${b.key ?? ""} \u300C${b.title}\u300D\u306E${q.direction === "in" ? "\u5165\u529B" : "\u51FA\u529B"}\u300C${q.name}\u300D\u304C\u4E2D\u306E\u7BB1\u3068\u3064\u306A\u304C\u3063\u3066\u3044\u307E\u305B\u3093`);
2537
+ }
2538
+ if (b.status === "white" && !hasKids && portsOf(p, b.id, "out").every((q) => q.artifacts.length === 0)) warnings.push(`${b.key ?? ""} \u300C${b.title}\u300D\u306F Done \u3067\u3059\u304C\u6210\u679C\u7269\u304C\u3042\u308A\u307E\u305B\u3093 (--artifact \u3067\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044)`);
2539
+ }
2540
+ const lines = [problems.length === 0 ? "\u554F\u984C\u3042\u308A\u307E\u305B\u3093" : problems.map((x) => "- " + x).join("\n")];
2541
+ if (warnings.length > 0) lines.push("", `\u6CE8\u610F (${warnings.length} \u4EF6\u3002\u8A08\u753B\u306E\u7A74):`, ...warnings.map((x) => "- " + x));
2542
+ out(lines.join("\n"));
2543
+ if (problems.length > 0) process.exitCode = 1;
2544
+ return;
2545
+ }
2546
+ case "add": {
2547
+ const title = rest[0];
2548
+ if (!title) throw new Error("<\u984C\u540D> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2549
+ const parentId = str(options.parent) ? mustFind(p, str(options.parent)).id : defaultTaskParent(p);
2550
+ const r = addBlock(p, { parentId, title, outputName: str(options.out), actor, position: nextFreePosition(p, parentId) });
2551
+ p = r.project;
2552
+ for (const name of list(options.in)) p = addPort(p, { blockId: r.blockId, direction: "in", name }).project;
2553
+ if (str(options.note)) p = updateBlock(p, r.blockId, { description: str(options.note) });
2554
+ if (str(options.category)) p = setCategory(p, r.blockId, categoryKeyOf(str(options.category)));
2555
+ save(path, p);
2556
+ out(`\u8FFD\u52A0: \u300C${title}\u300D(id: ${r.blockId})`);
2557
+ return;
2558
+ }
2559
+ case "split": {
2560
+ const b = mustFind(p, rest[0]);
2561
+ const specText = str(options["spec-file"]) ? readFileSync2(str(options["spec-file"]), "utf8") : str(options.spec);
2562
+ if (!specText) throw new Error("--spec '<JSON>' \u307E\u305F\u306F --spec-file <path> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2563
+ const spec = JSON.parse(specText);
2564
+ const r = splitBlock(p, b.id, spec, actor);
2565
+ save(path, layoutScope(r.project, b.id));
2566
+ out(`\u5206\u89E3: \u300C${b.title}\u300D\u306B ${spec.blocks.length} \u500B\u3092\u8FFD\u52A0` + (r.errors.length ? "\n" + r.errors.map((x) => "- " + x).join("\n") : ""));
2567
+ return;
2568
+ }
2569
+ case "move": {
2570
+ const b = mustFind(p, rest[0]);
2571
+ const target = str(options.parent);
2572
+ if (!target) throw new Error("--parent <block|project> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2573
+ const parentId = target === "project" ? defaultTaskParent(p) : mustFind(p, target).id;
2574
+ const q = moveBlockToParent(p, b.id, parentId, nextFreePosition(p, parentId));
2575
+ if (q === p) throw new Error("\u79FB\u305B\u307E\u305B\u3093 (\u81EA\u5206\u306E\u5B50\u5B6B\u306E\u4E2D\u3001\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1\u3001\u540C\u3058\u89AA\u306A\u3069\u306F\u4E0D\u53EF)");
2576
+ save(path, q);
2577
+ out(`\u79FB\u52D5: \u300C${b.title}\u300D\u3092\u300C${q.blocks[parentId].title}\u300D\u306E\u4E2D\u3078 (\u7DDA\u306F\u3064\u306A\u304E\u76F4\u3057\u307E\u3057\u305F)`);
2578
+ return;
2579
+ }
2580
+ case "port": {
2581
+ const target = rest[0] ?? "";
2582
+ const blockId = target === "project" ? projectBlocks(p)[0]?.id ?? ROOT_ID : target === ROOT_ID ? ROOT_ID : mustFind(p, target).id;
2583
+ const added = [];
2584
+ for (const name of list(options.in)) {
2585
+ p = addPort(p, { blockId, direction: "in", name }).project;
2586
+ added.push(`\u5165\u529B ${name}`);
2587
+ }
2588
+ for (const name of list(options.out)) {
2589
+ p = addPort(p, { blockId, direction: "out", name }).project;
2590
+ added.push(`\u51FA\u529B ${name}`);
2591
+ }
2592
+ for (const spec of list(options.rename)) {
2593
+ const eq = spec.indexOf("=");
2594
+ if (eq < 0) throw new Error(`--rename \u306F <\u65E7\u540D>=<\u65B0\u540D> \u306E\u5F62\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044: ${spec}`);
2595
+ const from = spec.slice(0, eq);
2596
+ const to = spec.slice(eq + 1);
2597
+ const port = portsOf(p, blockId).find((x) => x.name === from);
2598
+ if (!port) throw new Error(`\u30DD\u30FC\u30C8\u300C${from}\u300D\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093`);
2599
+ p = updatePort(p, port.id, { name: to });
2600
+ added.push(`${from} -> ${to}`);
2601
+ }
2602
+ if (added.length === 0) throw new Error("--in <\u540D\u524D> / --out <\u540D\u524D> / --rename <\u65E7\u540D>=<\u65B0\u540D> \u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2603
+ save(path, p);
2604
+ out(`\u30DD\u30FC\u30C8: ${p.blocks[blockId]?.title ?? "project"}: ${added.join(", ")}`);
2605
+ return;
2606
+ }
2607
+ case "remove": {
2608
+ const b = mustFind(p, rest[0]);
2609
+ if (b.kind === "project") throw new Error("\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u7BB1\u306F\u6D88\u305B\u307E\u305B\u3093 (\u4E2D\u306E\u7BB1\u3092\u5168\u90E8\u6D88\u3059\u304B\u3001\u30D5\u30A1\u30A4\u30EB\u3054\u3068\u4F5C\u308A\u76F4\u3057\u3066\u304F\u3060\u3055\u3044)");
2610
+ const kids = Object.values(p.blocks).filter((x) => x.parentId === b.id).length;
2611
+ if (kids > 0 && !options.force) throw new Error(`\u300C${b.title}\u300D\u306E\u4E2D\u306B ${kids} \u500B\u306E\u7BB1\u304C\u3042\u308A\u307E\u3059\u3002\u307E\u3068\u3081\u3066\u6D88\u3059\u306A\u3089 --force \u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044`);
2612
+ const q = removeBlock(p, b.id);
2613
+ save(path, q);
2614
+ out(`\u524A\u9664: \u300C${b.title}\u300D` + (kids > 0 ? ` \u3068\u4E2D\u306E ${kids} \u500B\u306E\u7BB1` : "") + " (\u3064\u306A\u304C\u3063\u3066\u3044\u305F\u7DDA\u3082\u5916\u3057\u307E\u3057\u305F)");
2615
+ return;
2616
+ }
2617
+ case "disconnect": {
2618
+ const a = resolveRef(p, rest[0] ?? "");
2619
+ const b = resolveRef(p, rest[1] ?? "");
2620
+ const aPorts = portsOf(p, a.blockId).filter((x) => x.name === a.portName).map((x) => x.id);
2621
+ const bPorts = portsOf(p, b.blockId).filter((x) => x.name === b.portName).map((x) => x.id);
2622
+ const hit = Object.values(p.edges).find((e) => aPorts.includes(e.from.portId) && bPorts.includes(e.to.portId));
2623
+ if (!hit) throw new Error(`\u7DDA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${rest[0]} -> ${rest[1]}`);
2624
+ save(path, disconnect(p, hit.id));
2625
+ out(`\u7DDA\u3092\u5916\u3057\u307E\u3057\u305F: ${rest[0]} -> ${rest[1]}`);
2626
+ return;
2627
+ }
2628
+ case "connect": {
2629
+ const a = resolveRef(p, rest[0] ?? "");
2630
+ const b = resolveRef(p, rest[1] ?? "");
2631
+ const fromBlock = p.blocks[a.blockId];
2632
+ const toBlock = p.blocks[b.blockId];
2633
+ const fromIsParent = toBlock.parentId === a.blockId;
2634
+ const toIsParent = fromBlock.parentId === b.blockId;
2635
+ const fromPort = portsOf(p, a.blockId, fromIsParent ? "in" : "out").find((x) => x.name === a.portName);
2636
+ const toPort = portsOf(p, b.blockId, toIsParent ? "out" : "in").find((x) => x.name === b.portName);
2637
+ if (!fromPort) throw new Error(`\u300C${rest[0]}\u300D\u306E\u30DD\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093`);
2638
+ if (!toPort) throw new Error(`\u300C${rest[1]}\u300D\u306E\u30DD\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093`);
2639
+ const from = { portId: fromPort.id, side: fromIsParent ? "inner" : "outer" };
2640
+ const to = { portId: toPort.id, side: toIsParent ? "inner" : "outer" };
2641
+ const r = connect(p, from, to);
2642
+ if (r.error) throw new Error(r.error);
2643
+ save(path, r.project);
2644
+ out(`\u7D50\u7DDA: ${rest[0]} -> ${rest[1]}`);
2645
+ return;
2646
+ }
2647
+ case "start": {
2648
+ const b = mustFind(p, rest[0]);
2649
+ save(path, setActivity(p, b.id, actor, "working", str(options.note) ?? ""));
2650
+ out(`\u958B\u59CB: \u300C${b.title}\u300D(${actor})`);
2651
+ return;
2652
+ }
2653
+ case "blocked": {
2654
+ const b = mustFind(p, rest[0]);
2655
+ save(path, setActivity(p, b.id, actor, "blocked", str(options.note) ?? ""));
2656
+ out(`\u8A70\u307E\u308A: \u300C${b.title}\u300D`);
2657
+ return;
2658
+ }
2659
+ case "review": {
2660
+ const b = mustFind(p, rest[0]);
2661
+ save(path, setActivity(p, b.id, actor, "waiting_review", str(options.note) ?? ""));
2662
+ out(`\u78BA\u8A8D\u5F85\u3061: \u300C${b.title}\u300D`);
2663
+ return;
2664
+ }
2665
+ case "leave": {
2666
+ const b = mustFind(p, rest[0]);
2667
+ save(path, clearActivity(p, b.id));
2668
+ out(`\u6D3B\u52D5\u3092\u6D88\u3057\u307E\u3057\u305F: \u300C${b.title}\u300D`);
2669
+ return;
2670
+ }
2671
+ case "done": {
2672
+ const b = mustFind(p, rest[0]);
2673
+ const artifacts = list(options.artifact).map((s) => artifactFrom(s));
2674
+ const r = finishBlock(p, b.id, actor, { artifacts, outputName: str(options.output), note: str(options.note) });
2675
+ if (r.error) throw new Error(r.error);
2676
+ save(path, r.project);
2677
+ out(`\u5B8C\u4E86: \u300C${b.title}\u300D${artifacts.length ? " \u6210\u679C\u7269: " + artifacts.map((a) => a.title + (a.kind === "git" ? ` (git ${a.path} @ ${(a.commit ?? "").slice(0, 7)})` : "")).join(", ") : ""}`);
2678
+ if (artifacts.length === 0) out('\u6CE8\u610F: \u6210\u679C\u7269\u304C\u4ED8\u3044\u3066\u3044\u307E\u305B\u3093\u3002--artifact "<\u540D\u524D>=<\u30D1\u30B9\u307E\u305F\u306F URL>" \u3067\u3001\u4EBA\u304C\u5F8C\u304B\u3089\u958B\u3051\u308B\u5177\u4F53\u7684\u306A\u7269\u3092\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044');
2679
+ return;
2680
+ }
2681
+ case "artifact": {
2682
+ const b = mustFind(p, rest[0]);
2683
+ if (!rest[1]) throw new Error("<URL \u307E\u305F\u306F\u30D1\u30B9> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2684
+ const a = artifactFrom(rest[1], str(options.title));
2685
+ const outs = portsOf(p, b.id, "out");
2686
+ const target = str(options.output) ? outs.find((o) => o.name === str(options.output)) : outs[0];
2687
+ if (!target) throw new Error("\u51FA\u529B\u30DD\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093");
2688
+ save(path, updatePort(p, target.id, { artifacts: [...target.artifacts, a] }));
2689
+ out(`\u6210\u679C\u7269\u3092\u4ED8\u3051\u307E\u3057\u305F: \u300C${b.title}\u300D.${target.name} <- ${a.title}${a.kind === "git" ? ` (git ${a.path} @ ${(a.commit ?? "").slice(0, 7)})` : ""}`);
2690
+ return;
2691
+ }
2692
+ case "check": {
2693
+ let changed = 0;
2694
+ const lines = [];
2695
+ for (const port of Object.values(p.ports)) {
2696
+ for (const a of port.artifacts) {
2697
+ if (a.kind !== "git") continue;
2698
+ const owner = p.blocks[port.blockId]?.title ?? "?";
2699
+ if (!a.commit && a.path) {
2700
+ const ref = gitRefFor(resolve3(dirname3(path), a.path));
2701
+ if (ref?.commit) {
2702
+ a.commit = ref.commit;
2703
+ a.blob = ref.blob;
2704
+ if (ref.url) a.url = ref.url;
2705
+ a.state = "ok";
2706
+ a.checkedAt = (/* @__PURE__ */ new Date()).toISOString();
2707
+ lines.push(`- [\u88DC\u5B8C] ${owner}.${port.name} \u300C${a.title}\u300D ${a.path} @ ${ref.commit.slice(0, 7)}`);
2708
+ changed++;
2709
+ continue;
2710
+ }
2711
+ }
2712
+ const r = checkGitRef(a, dirname3(path));
2713
+ if (r.state === "moved" && r.path) {
2714
+ lines.push(`- [\u79FB\u52D5] ${owner}.${port.name} \u300C${a.title}\u300D ${a.path} -> ${r.path}`);
2715
+ a.path = r.path;
2716
+ if (r.commit) a.commit = r.commit;
2717
+ if (r.blob) a.blob = r.blob;
2718
+ if (r.url) a.url = r.url;
2719
+ a.state = "moved";
2720
+ changed++;
2721
+ } else if (r.state === "missing") {
2722
+ lines.push(`- [\u898B\u3064\u304B\u308A\u307E\u305B\u3093] ${owner}.${port.name} \u300C${a.title}\u300D ${a.path} (\u30B3\u30DF\u30C3\u30C8 ${(a.commit ?? "").slice(0, 7)} \u304B\u3089\u306F\u53D6\u308A\u51FA\u305B\u307E\u3059: git show ${(a.commit ?? "").slice(0, 7)}:${a.path})`);
2723
+ if (a.state !== "missing") changed++;
2724
+ a.state = "missing";
2725
+ } else {
2726
+ if (a.state !== "ok") changed++;
2727
+ a.state = "ok";
2728
+ }
2729
+ a.checkedAt = (/* @__PURE__ */ new Date()).toISOString();
2730
+ }
2731
+ }
2732
+ save(path, p);
2733
+ out(lines.length === 0 ? "Git \u306E\u6210\u679C\u7269\u306F\u3059\u3079\u3066\u898B\u3064\u304B\u308A\u307E\u3057\u305F" : lines.join("\n"));
2734
+ if (changed > 0) out(`${changed} \u4EF6\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F`);
2735
+ return;
2736
+ }
2737
+ case "ask": {
2738
+ const b = mustFind(p, rest[0]);
2739
+ const question = rest.slice(1).join(" ");
2740
+ if (!question) throw new Error("<\u8CEA\u554F> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2741
+ const opts = str(options.options) ? str(options.options).split("|").map((s) => s.trim()).filter(Boolean) : [];
2742
+ const r = askDecision(p, b.id, actor, question, opts, str(options.context) ?? "");
2743
+ save(path, r.project);
2744
+ out(`\u5224\u65AD\u5F85\u3061: \u300C${b.title}\u300D ${question} (decision: ${r.decisionId})`);
2745
+ return;
2746
+ }
2747
+ case "decision": {
2748
+ const b = mustFind(p, rest[0]);
2749
+ const id = str(options.id) ?? [...b.decisions].reverse().find((d) => d.answer === void 0)?.id;
2750
+ if (!id) throw new Error("\u672A\u56DE\u7B54\u306E\u5224\u65AD\u304C\u3042\u308A\u307E\u305B\u3093 (--id \u3067\u6307\u5B9A)");
2751
+ const patch = {};
2752
+ if (str(options.question) !== void 0) patch.question = str(options.question);
2753
+ if (str(options.options) !== void 0) patch.options = str(options.options).split("|").map((s) => s.trim()).filter(Boolean);
2754
+ if (str(options.context) !== void 0) patch.context = str(options.context);
2755
+ const q = updateDecision(p, b.id, id, patch);
2756
+ if (q === p) throw new Error("\u66F8\u304D\u76F4\u305B\u307E\u305B\u3093 (\u56DE\u7B54\u6E08\u307F\u3001\u307E\u305F\u306F\u898B\u3064\u304B\u308A\u307E\u305B\u3093)");
2757
+ save(path, q);
2758
+ out(`\u5224\u65AD\u3092\u66F8\u304D\u76F4\u3057\u307E\u3057\u305F: \u300C${b.title}\u300D (decision: ${id})`);
2759
+ return;
2760
+ }
2761
+ case "answer": {
2762
+ const b = mustFind(p, rest[0]);
2763
+ const answer = rest.slice(1).join(" ");
2764
+ if (!answer) throw new Error("<\u56DE\u7B54> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
2765
+ const id = str(options.id) ?? [...b.decisions].reverse().find((d) => d.answer === void 0)?.id;
2766
+ if (!id) throw new Error("\u672A\u56DE\u7B54\u306E\u5224\u65AD\u304C\u3042\u308A\u307E\u305B\u3093");
2767
+ save(path, answerDecision(p, b.id, id, answer, str(options.by) ?? (actor === "agent" ? "human" : actor)));
2768
+ out(`\u56DE\u7B54: \u300C${b.title}\u300D ${answer}`);
2769
+ return;
2770
+ }
2771
+ case "reopen": {
2772
+ const b = mustFind(p, rest[0]);
2773
+ const id = str(options.id) ?? [...b.decisions].reverse().find((d) => d.answer !== void 0)?.id;
2774
+ if (!id) throw new Error("\u3084\u308A\u76F4\u305B\u308B\u5224\u65AD (\u56DE\u7B54\u6E08\u307F) \u304C\u3042\u308A\u307E\u305B\u3093");
2775
+ save(path, reopenDecision(p, b.id, id, actor, str(options.note) ?? ""));
2776
+ out(`\u3084\u308A\u76F4\u3057: \u300C${b.title}\u300D\u306E\u5224\u65AD\u3092\u672A\u56DE\u7B54\u306B\u623B\u3057\u307E\u3057\u305F (\u524D\u306E\u7B54\u3048\u306F\u5C65\u6B74\u306B\u6B8B\u308A\u307E\u3059)`);
2777
+ return;
2778
+ }
2779
+ case "set": {
2780
+ const b = mustFind(p, rest[0]);
2781
+ if (str(options.status)) p = setStatus(p, b.id, str(options.status), actor);
2782
+ if (str(options.progress) !== void 0) p = setProgress(p, b.id, str(options.progress) === "auto" ? null : Number(str(options.progress)), actor);
2783
+ const sched = {};
2784
+ const dateOf = (v) => v === void 0 ? void 0 : v === "none" ? null : v;
2785
+ const numOf = (v) => v === void 0 ? void 0 : v === "none" ? null : Number(v);
2786
+ if (str(options.start) !== void 0) sched.startDate = dateOf(str(options.start));
2787
+ if (str(options.due) !== void 0) sched.dueDate = dateOf(str(options.due));
2788
+ if (str(options.estimate) !== void 0) sched.estimateHours = numOf(str(options.estimate));
2789
+ if (str(options.hours) !== void 0) sched.actualHours = numOf(str(options.hours));
2790
+ for (const v of [sched.startDate, sched.dueDate]) if (typeof v === "string" && !/^\d{4}-\d{2}-\d{2}$/.test(v)) throw new Error(`\u65E5\u4ED8\u306F YYYY-MM-DD \u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044: ${v}`);
2791
+ if (Object.keys(sched).length > 0) p = setSchedule(p, b.id, sched, actor);
2792
+ const patch = {};
2793
+ if (str(options.title)) patch.title = str(options.title);
2794
+ if (str(options.note)) patch.description = str(options.note);
2795
+ if (Object.keys(patch).length > 0) p = updateBlock(p, b.id, patch);
2796
+ if (str(options.category) !== void 0) p = setCategory(p, b.id, str(options.category) === "none" ? null : categoryKeyOf(str(options.category)));
2797
+ if (str(options.repo) !== void 0) p = updateBlock(p, b.id, { repo: str(options.repo) === "none" ? void 0 : str(options.repo) });
2798
+ if (str(options.issue) !== void 0) p = updateBlock(p, b.id, { issue: str(options.issue) === "none" ? void 0 : str(options.issue) });
2799
+ save(path, p);
2800
+ out(`\u66F4\u65B0: \u300C${p.blocks[b.id].title}\u300D`);
2801
+ return;
2802
+ }
2803
+ default:
2804
+ throw new Error(`\u77E5\u3089\u306A\u3044\u30B3\u30DE\u30F3\u30C9\u3067\u3059: ${cmd} (boxglow help \u3067\u4E00\u89A7)`);
2805
+ }
2806
+ }
2807
+ var argv = process.argv.slice(2);
2808
+ if (argv[0] === "mcp") {
2809
+ const { options } = parseArgs(argv.slice(1));
2810
+ if (str(options.file)) process.env.BOXGLOW_FILE = resolve3(str(options.file));
2811
+ startMcp(runCli).catch((e) => {
2812
+ console.error(`[boxglow mcp] ${e instanceof Error ? e.message : String(e)}`);
2813
+ process.exitCode = 1;
2814
+ });
2815
+ } else if (argv[0] === "serve") {
2816
+ const { options } = parseArgs(argv.slice(1));
2817
+ try {
2818
+ const file = locateFile(str(options.file));
2819
+ startServe({ file, port: Number(str(options.port) ?? 4174), dist: fileURLToPath(new URL("../dist/", import.meta.url)), open: !!options.open, log: out });
2820
+ } catch (e) {
2821
+ console.error(`[boxglow serve] ${e instanceof Error ? e.message : String(e)}`);
2822
+ process.exitCode = 1;
2823
+ }
2824
+ } else {
2825
+ try {
2826
+ main(argv);
2827
+ } catch (e) {
2828
+ console.error(`[boxglow] ${e instanceof Error ? e.message : String(e)}`);
2829
+ process.exitCode = 1;
2830
+ }
2831
+ }
2832
+ export {
2833
+ runCli
2834
+ };