@kw-yeh/vibeflow 4.1.0 → 4.2.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/dist/vibeflow.mjs CHANGED
@@ -598,9 +598,9 @@ var init_attachments = __esm({
598
598
 
599
599
  // packages/tui/src/task-form.ts
600
600
  import { spawnSync } from "child_process";
601
- import fs19 from "fs";
601
+ import fs20 from "fs";
602
602
  import os6 from "os";
603
- import path23 from "path";
603
+ import path24 from "path";
604
604
  import React, { useEffect, useRef, useState } from "react";
605
605
  import { Box, Text, render, useApp, useInput } from "ink";
606
606
  function newFormState(defaultAutoMode, agents) {
@@ -719,17 +719,17 @@ function editorCommand(env, platform) {
719
719
  return env.VISUAL?.trim() || env.EDITOR?.trim() || (platform === "win32" ? "notepad" : "vi");
720
720
  }
721
721
  function editInEditor(text) {
722
- const dir = fs19.mkdtempSync(path23.join(os6.tmpdir(), "vibeflow-"));
723
- const file = path23.join(dir, "description.md");
722
+ const dir = fs20.mkdtempSync(path24.join(os6.tmpdir(), "vibeflow-"));
723
+ const file = path24.join(dir, "description.md");
724
724
  try {
725
- fs19.writeFileSync(file, text);
725
+ fs20.writeFileSync(file, text);
726
726
  const cmd = editorCommand(process.env, process.platform);
727
727
  const r = spawnSync(`${cmd} "${file}"`, { stdio: "inherit", shell: true });
728
728
  if (r.error) return { error: `\u7121\u6CD5\u555F\u52D5\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\uFF1A${r.error.message}` };
729
729
  if (r.status !== 0) return { error: `\u7DE8\u8F2F\u5668\uFF08${cmd}\uFF09\u7D50\u675F\u78BC ${r.status}\uFF0C\u63CF\u8FF0\u672A\u8B8A\u66F4` };
730
- return { text: fs19.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
730
+ return { text: fs20.readFileSync(file, "utf8").replace(/\r?\n$/, "") };
731
731
  } finally {
732
- fs19.rmSync(dir, { recursive: true, force: true });
732
+ fs20.rmSync(dir, { recursive: true, force: true });
733
733
  }
734
734
  }
735
735
  function cycle(list, current, step) {
@@ -797,10 +797,10 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
797
797
  if (editing === "title") setS((prev) => ({ ...prev, title: draft }));
798
798
  else if (editing === "branch") setS((prev) => ({ ...prev, branch: value2 }));
799
799
  else if (editing === "project") {
800
- if (value2) setProject(path23.resolve(value2.replace(/^~(?=$|\/)/, os6.homedir())));
800
+ if (value2) setProject(path24.resolve(value2.replace(/^~(?=$|\/)/, os6.homedir())));
801
801
  } else if (editing === "attachments" && value2) {
802
- const file = path23.resolve(value2.replace(/^~(?=$|\/)/, os6.homedir()));
803
- if (!fs19.existsSync(file) || !fs19.statSync(file).isFile()) {
802
+ const file = path24.resolve(value2.replace(/^~(?=$|\/)/, os6.homedir()));
803
+ if (!fs20.existsSync(file) || !fs20.statSync(file).isFile()) {
804
804
  setMessage(`\u627E\u4E0D\u5230\u6A94\u6848\uFF1A${file}`);
805
805
  return;
806
806
  }
@@ -870,7 +870,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
870
870
  case "title":
871
871
  return s.title || "\uFF08\u4F8B\u5982\uFF1A\u5BE6\u4F5C\u767B\u5165\u9801\u9762\uFF09";
872
872
  case "project": {
873
- if (projectLocked) return `${s.projectPath ? path23.basename(s.projectPath) : "\u2014"}\uFF08\u4EFB\u52D9\u5DF2\u958B\u59CB\uFF0C\u9396\u5B9A\uFF09`;
873
+ if (projectLocked) return `${s.projectPath ? path24.basename(s.projectPath) : "\u2014"}\uFF08\u4EFB\u52D9\u5DF2\u958B\u59CB\uFF0C\u9396\u5B9A\uFF09`;
874
874
  if (!s.projectPath) return ctx.recent.length ? "\u2190/\u2192 \u9078\u64C7\u4F7F\u7528\u904E\u7684\u5C08\u6848\uFF0C\u6216 Enter \u8F38\u5165\u8DEF\u5F91" : "Enter \u8F38\u5165\u5C08\u6848\u8CC7\u6599\u593E\u7684\u7D55\u5C0D\u8DEF\u5F91";
875
875
  return `${s.projectPath}${missing ? "\uFF08\u8DEF\u5F91\u907A\u5931\uFF09" : ""}`;
876
876
  }
@@ -881,7 +881,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
881
881
  case "branch":
882
882
  return s.branch || "\uFF08\u7559\u7A7A\u5247\u81EA\u52D5\u547D\u540D\uFF0C\u4F8B\u5982 feature/login-fix\uFF09";
883
883
  case "attachments":
884
- return s.attachments.length ? s.attachments.map((a) => path23.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
884
+ return s.attachments.length ? s.attachments.map((a) => path24.basename(a)).join("\u3001") : "\uFF08Enter \u8F38\u5165\u6A94\u6848\u8DEF\u5F91\u52A0\u5165\uFF09";
885
885
  case "description": {
886
886
  if (!s.description) return "\uFF08Enter \u7528\u7DE8\u8F2F\u5668\u64B0\u5BEB\uFF09";
887
887
  const lines = s.description.split("\n");
@@ -912,7 +912,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
912
912
  if (info && !info.isRepo) return "\u9019\u500B\u8CC7\u6599\u593E\u4E0D\u662F Git repository\u3002\u8ACB\u6539\u9078\u4E00\u500B Git \u5C08\u6848\uFF0C\u6216\u5728\u4E0B\u65B9\u521D\u59CB\u5316\u3002";
913
913
  if (s.mode === "edit" && projectChanged(s)) return "\u66F4\u63DB\u5C08\u6848\u6703\u5728\u65B0\u5C08\u6848\u91CD\u5EFA worktree\uFF08\u6B64\u4EFB\u52D9\u5C1A\u672A\u958B\u59CB\uFF0C\u7121\u8B8A\u66F4\u6703\u907A\u5931\uFF09\u3002";
914
914
  if (info?.isRepo && !info.hasRemote) return `\u6B64 repository \u6C92\u6709 remote\uFF0C\u5C07\u4EE5\u76EE\u524D\u5206\u652F (${info.currentBranch ?? "HEAD"}) \u70BA\u57FA\u6E96\u5EFA\u7ACB\u672C\u5730 worktree\u3002`;
915
- if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path23.basename(s.projectPath)}`;
915
+ if (info?.isRepo && s.projectPath) return `worktree \u6703\u5EFA\u5728 ${ctx.workstationPath || "~/Desktop"}/${path24.basename(s.projectPath)}`;
916
916
  }
917
917
  if (field === "branch") return info?.hasRemote ? "\u586B\u5BEB\u5F8C\u6703\u5EFA\u7ACB\u540C\u540D\u5206\u652F\uFF1B\u82E5 remote \u5DF2\u6709\u9019\u500B\u5206\u652F\uFF0C\u5247\u76F4\u63A5\u53D6\u56DE\u4E26\u63A5\u7E8C\u4E0A\u9762\u7684\u5DE5\u4F5C\u3002" : "\u586B\u5BEB\u5F8C\u6703\u5EFA\u7ACB\u540C\u540D\u5206\u652F\u3002";
918
918
  if (field === "effort") return `${EFFORTS.find((x) => x.value === s.effort).description} Claude Code \u8207 Codex \u6703\u5728\u555F\u52D5\u6B64\u4EFB\u52D9\u6642\u5957\u7528\uFF1B\u5BE6\u969B\u53EF\u7528\u7D1A\u8DDD\u4F9D model \u800C\u5B9A\u3002`;
@@ -1029,7 +1029,7 @@ __export(src_exports, {
1029
1029
  showDiff: () => showDiff
1030
1030
  });
1031
1031
  import { spawnSync as spawnSync2 } from "child_process";
1032
- import path24 from "path";
1032
+ import path25 from "path";
1033
1033
  import React2, { useEffect as useEffect2, useState as useState2 } from "react";
1034
1034
  import { Box as Box2, Text as Text2, render as render2, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1035
1035
  function findDetachKey(text) {
@@ -1291,7 +1291,7 @@ function showDiff(task) {
1291
1291
  });
1292
1292
  }
1293
1293
  function tmuxAttach(opts, sessionName) {
1294
- const conf = path24.join(opts.userDataDir, "tmux.conf");
1294
+ const conf = path25.join(opts.userDataDir, "tmux.conf");
1295
1295
  const inOurServer = (process.env.TMUX ?? "").split(",")[0].endsWith(`/${TMUX_SOCKET}`);
1296
1296
  const args = inOurServer ? ["-L", TMUX_SOCKET, "-f", conf, "switch-client", "-t", `=${sessionName}`] : ["-L", TMUX_SOCKET, "-f", conf, "attach-session", "-t", `=${sessionName}`];
1297
1297
  spawnSync2("tmux", args, { stdio: "inherit", env: { ...process.env, TMUX: inOurServer ? process.env.TMUX : "" } });
@@ -1421,8 +1421,8 @@ var init_src = __esm({
1421
1421
  });
1422
1422
 
1423
1423
  // packages/cli/src/main.ts
1424
- import fs20 from "fs";
1425
- import path25 from "path";
1424
+ import fs21 from "fs";
1425
+ import path26 from "path";
1426
1426
  import { fileURLToPath } from "url";
1427
1427
  import { parseArgs as parseArgs2 } from "node:util";
1428
1428
 
@@ -1814,8 +1814,8 @@ function createBridge(t) {
1814
1814
  * in-app preview will not inline. Resolves to '' on success, else a reason.
1815
1815
  */
1816
1816
  openArtifactsDir: (taskId) => t.invoke("task:openArtifactsDir", taskId),
1817
- /** The task's decision record; null when it has no workspace folder. */
1818
- getDecisions: (taskId) => t.invoke("task:getDecisions", taskId),
1817
+ /** Every spec.md the task's branch added or changed; empty when there is none. */
1818
+ getSpecs: (taskId) => t.invoke("task:getSpecs", taskId),
1819
1819
  /** Store dir + CLI paths for launch injection, so an agent can write cards. */
1820
1820
  getBoardCliLaunchInfo: () => t.invoke("board:getCliLaunchInfo"),
1821
1821
  /** VibeFlow's own skill / prompt / script store. */
@@ -3574,8 +3574,8 @@ async function taskCommand(argv) {
3574
3574
  }
3575
3575
 
3576
3576
  // packages/cli/src/host.ts
3577
- import fs17 from "fs";
3578
- import path21 from "path";
3577
+ import fs18 from "fs";
3578
+ import path22 from "path";
3579
3579
  import { randomBytes as randomBytes2 } from "crypto";
3580
3580
 
3581
3581
  // packages/core/src/events.ts
@@ -4113,9 +4113,9 @@ async function syncBuiltinsAtStartup() {
4113
4113
  }
4114
4114
 
4115
4115
  // packages/core/src/service.ts
4116
- import fs15 from "fs";
4116
+ import fs16 from "fs";
4117
4117
  import os5 from "os";
4118
- import path19 from "path";
4118
+ import path20 from "path";
4119
4119
  import { randomUUID as randomUUID4 } from "crypto";
4120
4120
 
4121
4121
  // packages/core/src/agent-models.ts
@@ -4364,34 +4364,81 @@ async function boardCliLaunchInfo() {
4364
4364
  import fs14 from "fs";
4365
4365
  import path18 from "path";
4366
4366
  var DECISIONS_FILE_SUFFIX = ".DECISIONS.md";
4367
- var MAX_DECISION_BYTES = 256 * 1024;
4368
4367
  function decisionsKey(worktreePath, branch) {
4369
4368
  return worktreePath ? path18.basename(worktreePath) : branch.replace(/\//g, "-");
4370
4369
  }
4371
4370
  function decisionsPath(workspacePath, key) {
4372
4371
  return path18.join(workspacePath, `${key}${DECISIONS_FILE_SUFFIX}`);
4373
4372
  }
4374
- function readDecisions(workspacePath, key) {
4375
- const target = decisionsPath(workspacePath, key);
4373
+ function deleteDecisions(workspacePath, key) {
4376
4374
  try {
4377
- const stat = fs14.statSync(target);
4378
- if (!stat.isFile()) return { path: target, markdown: null };
4379
- const buf = fs14.readFileSync(target);
4380
- return {
4381
- path: target,
4382
- markdown: buf.subarray(0, MAX_DECISION_BYTES).toString("utf8"),
4383
- updatedAt: stat.mtimeMs,
4384
- ...buf.byteLength > MAX_DECISION_BYTES ? { truncated: true } : {}
4385
- };
4375
+ fs14.rmSync(decisionsPath(workspacePath, key), { force: true });
4386
4376
  } catch {
4387
- return { path: target, markdown: null };
4388
4377
  }
4389
4378
  }
4390
- function deleteDecisions(workspacePath, key) {
4379
+
4380
+ // packages/core/src/specs.ts
4381
+ import fs15 from "fs";
4382
+ import path19 from "path";
4383
+ var SPEC_FILE_NAME = "spec.md";
4384
+ var MAX_SPEC_BYTES = 256 * 1024;
4385
+ function isSpecPath(p) {
4386
+ return path19.posix.basename(p) === SPEC_FILE_NAME;
4387
+ }
4388
+ function clip2(buf) {
4389
+ return {
4390
+ markdown: buf.subarray(0, MAX_SPEC_BYTES).toString("utf8"),
4391
+ ...buf.byteLength > MAX_SPEC_BYTES ? { truncated: true } : {}
4392
+ };
4393
+ }
4394
+ async function readWorktreeSpecs(worktreePath, baseBranch) {
4395
+ const baseRef = await resolveBaseRef(worktreePath, baseBranch);
4396
+ const entries = await collectDiffEntries(worktreePath, baseRef);
4397
+ const specs = [];
4398
+ for (const entry of entries) {
4399
+ if (entry.status === "D" || !isSpecPath(entry.path)) continue;
4400
+ try {
4401
+ const full = path19.join(worktreePath, entry.path);
4402
+ const stat = fs15.statSync(full);
4403
+ if (!stat.isFile()) continue;
4404
+ specs.push({ path: entry.path, ...clip2(fs15.readFileSync(full)), updatedAt: stat.mtimeMs });
4405
+ } catch {
4406
+ }
4407
+ }
4408
+ return specs.sort((a, b) => a.path.localeCompare(b.path));
4409
+ }
4410
+ async function readBranchSpecs(projectPath, branch, baseBranch, changedPaths) {
4411
+ const ref = `refs/heads/${branch}`;
4391
4412
  try {
4392
- fs14.rmSync(decisionsPath(workspacePath, key), { force: true });
4413
+ await git(projectPath, ["rev-parse", "--verify", ref]);
4393
4414
  } catch {
4415
+ return [];
4416
+ }
4417
+ let paths = changedPaths;
4418
+ if (!paths) {
4419
+ const baseRef = await resolveBaseRef(projectPath, baseBranch);
4420
+ const names = await git(projectPath, [
4421
+ "diff",
4422
+ "--name-only",
4423
+ "--diff-filter=d",
4424
+ `${baseRef}...${ref}`
4425
+ ]).catch(() => "");
4426
+ paths = names.split("\n").map((l) => l.trim());
4427
+ }
4428
+ const specs = [];
4429
+ for (const p of paths.filter(isSpecPath)) {
4430
+ try {
4431
+ const content = await git(projectPath, ["show", `${ref}:${p}`]);
4432
+ const ct = await git(projectPath, ["log", "-1", "--format=%ct", ref, "--", p]);
4433
+ specs.push({
4434
+ path: p,
4435
+ ...clip2(Buffer.from(content, "utf8")),
4436
+ ...ct ? { updatedAt: Number(ct) * 1e3 } : {}
4437
+ });
4438
+ } catch {
4439
+ }
4394
4440
  }
4441
+ return specs.sort((a, b) => a.path.localeCompare(b.path));
4395
4442
  }
4396
4443
 
4397
4444
  // packages/core/src/chat-session.ts
@@ -4705,22 +4752,6 @@ function buildArtifactPrompt(artifactsDir = ARTIFACTS_FALLBACK_DIR2) {
4705
4752
  "5. Artifact \u8CC7\u6599\u593E\u4F4D\u65BC worktree \u4E4B\u5916\uFF1B\u4E0D\u8981\u628A\u5176\u4E2D\u5167\u5BB9\u52A0\u5165 git commit\uFF0C\u4E5F\u4E0D\u8981\u628A\u6700\u7D42\u4EA4\u4ED8\u7269\u53EA\u7559\u5728\u9019\u88E1\u3002"
4706
4753
  ].join("\n");
4707
4754
  }
4708
- var DECISIONS_FILE_SUFFIX2 = ".DECISIONS.md";
4709
- function taskDecisionsPath(worktreePath, workspacePath) {
4710
- if (!worktreePath || !workspacePath) return null;
4711
- return `${toShellPath(workspacePath)}/${pathBasename(worktreePath)}${DECISIONS_FILE_SUFFIX2}`;
4712
- }
4713
- function buildDecisionPrompt(decisionsPath2) {
4714
- return [
4715
- "\u6C7A\u7B56\u66F8\uFF08\u672C\u6B21 session \u5168\u7A0B\u9069\u7528\uFF09\uFF1A",
4716
- `1. \u672C\u4EFB\u52D9\u7684\u6C7A\u7B56\u66F8\u662F ${decisionsPath2}\uFF1B\u505A\u51FA\u65B0\u6C7A\u7B56\u3001\u6216\u8ABF\u6574\u65E2\u6709\u6C7A\u7B56\u6642\uFF0C\u7576\u4E0B\u5C31\u66F4\u65B0\u5B83\uFF08\u4E0D\u5B58\u5728\u8ACB\u5EFA\u7ACB\uFF09\u3002`,
4717
- "2. \u53EA\u5BEB\u300C\u9019\u6B21\u65B0\u589E\u4E86\u4EC0\u9EBC\u6C7A\u7B56\u3001\u8ABF\u6574\u4E86\u4EC0\u9EBC\u65E2\u6709\u6C7A\u7B56\u300D\u8207\u7406\u7531\u3002\u9700\u6C42\u8986\u8FF0\u3001\u9032\u5EA6\u56DE\u5831\u3001\u5BE6\u4F5C\u6D41\u6C34\u5E33\u3001\u672C\u4F86\u5C31\u6210\u7ACB\u7684\u6163\u4F8B\u90FD\u4E0D\u8981\u5BEB\u3002",
4718
- "3. \u6BCF\u5247\u6C7A\u7B56\u4E00\u500B `## <\u4E00\u53E5\u8A71\u8B1B\u5B8C\u7684\u6C7A\u7B56>` \u6BB5\u843D\uFF0C\u5E95\u4E0B 2-4 \u884C\u4EA4\u4EE3\uFF1A\u70BA\u4EC0\u9EBC\u9019\u6A23\u9078\u3001\u5426\u6C7A\u4E86\u4EC0\u9EBC\u66FF\u4EE3\u65B9\u6848\u3001\u8AB0\u4E4B\u5F8C\u6703\u88AB\u9019\u500B\u6C7A\u7B56\u7D81\u4F4F\u3002",
4719
- "4. \u8981\u8AAA\u660E\u6D41\u7A0B\u3001\u72C0\u614B\u8F49\u63DB\u6216\u6A21\u7D44\u95DC\u4FC2\u6642\uFF0C\u7528 mermaid fence \u756B\u5716\uFF0CVibeFlow \u6703\u76F4\u63A5\u6E32\u67D3\u3002",
4720
- "5. \u9019\u6B21\u6C92\u6709\u4EFB\u4F55\u6C7A\u7B56\u5C31\u4E0D\u8981\u5EFA\u7ACB\u9019\u500B\u6A94\u6848\uFF1B\u5BE7\u53EF\u7559\u767D\uFF0C\u4E0D\u8981\u6E4A\u5167\u5BB9\u3002",
4721
- "6. \u7528\u7E41\u9AD4\u4E2D\u6587\u66F8\u5BEB\uFF0C\u7A0B\u5F0F\u8B58\u5225\u5B57\u4FDD\u7559\u82F1\u6587\u3002"
4722
- ].join("\n");
4723
- }
4724
4755
  var AUTO_PERMISSION_MODE = "auto";
4725
4756
  function claudePermissionFlag(autoMode) {
4726
4757
  return autoMode ? ` --permission-mode ${AUTO_PERMISSION_MODE}` : "";
@@ -4849,11 +4880,9 @@ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
4849
4880
  const model = taskModel(task);
4850
4881
  const artifactsDir = agentArtifactsDir(task.worktreePath, workspacePath) ?? ARTIFACTS_FALLBACK_DIR2;
4851
4882
  const builtInPrompt = buildArtifactPrompt(artifactsDir);
4852
- const decisionsPath2 = taskDecisionsPath(task.worktreePath, workspacePath);
4853
- const decisionPrompt = decisionsPath2 ? buildDecisionPrompt(decisionsPath2) : "";
4854
4883
  const libraryPrompt = opts?.library?.promptText?.trim();
4855
4884
  const customPrompt = resolveSystemPrompt(systemPrompt);
4856
- const sys = [builtInPrompt, decisionPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
4885
+ const sys = [builtInPrompt, libraryPrompt, customPrompt].filter(Boolean).join("\n\n");
4857
4886
  const includeTaskPrompt = opts?.includeTaskPrompt !== false;
4858
4887
  const prompt = includeTaskPrompt && !opts?.resume ? buildPrompt(task) : "";
4859
4888
  const sessionId = agent === "claude" && includeTaskPrompt ? executorSessionId(task.id, task.runId) : void 0;
@@ -4962,13 +4991,13 @@ function createCore({ sessions, bus, version }) {
4962
4991
  }
4963
4992
  function claudeSessionFile(cwd, sessionId) {
4964
4993
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
4965
- return path19.join(os5.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
4994
+ return path20.join(os5.homedir(), ".claude", "projects", munged, `${sessionId}.jsonl`);
4966
4995
  }
4967
4996
  async function removeTaskWorktree(task) {
4968
4997
  if (!task.projectPath || !task.worktreePath) return;
4969
4998
  const branch = task.branch || fallbackBranchName(task.id);
4970
4999
  await removeWorktree(task.projectPath, task.worktreePath);
4971
- deleteArtifacts(task.workspacePath ?? path19.dirname(task.worktreePath), task.worktreePath);
5000
+ deleteArtifacts(task.workspacePath ?? path20.dirname(task.worktreePath), task.worktreePath);
4972
5001
  await deleteBranch(task.projectPath, branch);
4973
5002
  }
4974
5003
  const handlers = {
@@ -5007,8 +5036,8 @@ function createCore({ sessions, bus, version }) {
5007
5036
  "projects:listRecent": () => listRecentProjects(getStore()),
5008
5037
  // `vibeflow open <path>` while a host runs: only the host writes the store.
5009
5038
  "projects:record": (projectPath) => {
5010
- const p = path19.resolve(str(projectPath, "projectPath"));
5011
- if (!fs15.existsSync(p) || !fs15.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
5039
+ const p = path20.resolve(str(projectPath, "projectPath"));
5040
+ if (!fs16.existsSync(p) || !fs16.statSync(p).isDirectory()) invalid(`not a directory: ${p}`);
5012
5041
  recordRecentProject(getStore(), p);
5013
5042
  return listRecentProjects(getStore());
5014
5043
  },
@@ -5040,7 +5069,7 @@ function createCore({ sessions, bus, version }) {
5040
5069
  },
5041
5070
  // Discard everything the last run left behind — the PTY session, the
5042
5071
  // sub-agent timeline, the conversation id, the code it changed, its artifacts
5043
- // and its decision record — so the next launch starts over as if the card
5072
+ // and its legacy decision record — so the next launch starts over as if the card
5044
5073
  // were new. Launching again is the caller's decision.
5045
5074
  "vibeflow:resetTaskRun": async (taskId) => {
5046
5075
  const task = requireTask(taskId);
@@ -5048,7 +5077,7 @@ function createCore({ sessions, bus, version }) {
5048
5077
  if (!task.baseBranch) throw new Error("\u4EFB\u52D9\u6C92\u6709\u8A18\u9304 base branch\uFF0C\u7121\u6CD5\u9084\u539F worktree");
5049
5078
  await teardownTask(task.id);
5050
5079
  await resetWorktreeToBase(task.worktreePath, task.baseBranch);
5051
- const workspacePath = task.workspacePath ?? path19.dirname(task.worktreePath);
5080
+ const workspacePath = task.workspacePath ?? path20.dirname(task.worktreePath);
5052
5081
  deleteArtifacts(workspacePath, task.worktreePath);
5053
5082
  deleteDecisions(workspacePath, decisionsKey(task.worktreePath, task.branch));
5054
5083
  resetSubAgents(task.worktreePath);
@@ -5087,9 +5116,9 @@ function createCore({ sessions, bus, version }) {
5087
5116
  if (!info.isRepo) throw new Error("\u76EE\u6A19\u8CC7\u6599\u593E\u4E0D\u662F git repository");
5088
5117
  await teardownTask(existing.id);
5089
5118
  await removeTaskWorktree(existing);
5090
- const projectName = path19.basename(nextProject);
5119
+ const projectName = path20.basename(nextProject);
5091
5120
  const workspacePath = projectWorkstationPath(resolveWorkstationPath(getSettings()), projectName);
5092
- await fs15.promises.mkdir(workspacePath, { recursive: true });
5121
+ await fs16.promises.mkdir(workspacePath, { recursive: true });
5093
5122
  const result = await provisionWorktree(
5094
5123
  nextProject,
5095
5124
  workspacePath,
@@ -5177,7 +5206,7 @@ function createCore({ sessions, bus, version }) {
5177
5206
  "claude:sessionExists": (taskId) => {
5178
5207
  const task = requireTask(taskId);
5179
5208
  if (!task.worktreePath) return false;
5180
- return fs15.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
5209
+ return fs16.existsSync(claudeSessionFile(task.worktreePath, executorSessionId(task.id, task.runId)));
5181
5210
  },
5182
5211
  "sessions:list": async () => {
5183
5212
  const keys = await sessions.list();
@@ -5224,13 +5253,16 @@ function createCore({ sessions, bus, version }) {
5224
5253
  const task = requireTask(taskId);
5225
5254
  if (!task.worktreePath || !task.workspacePath) return "task has no worktree";
5226
5255
  const dir = agentArtifactsPath(task.workspacePath, task.worktreePath);
5227
- if (!fs15.existsSync(dir)) return "artifacts directory does not exist yet";
5256
+ if (!fs16.existsSync(dir)) return "artifacts directory does not exist yet";
5228
5257
  return getPlatform().openPath(dir);
5229
5258
  },
5230
- "task:getDecisions": (taskId) => {
5259
+ "task:getSpecs": async (taskId) => {
5231
5260
  const task = requireTask(taskId);
5232
- if (!task.workspacePath) return null;
5233
- return readDecisions(task.workspacePath, decisionsKey(task.worktreePath, task.branch));
5261
+ const base = task.baseBranch ?? "main";
5262
+ if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base);
5263
+ if (!task.projectPath) return [];
5264
+ const changed = task.outcome?.files.filter((f) => f.status !== "D").map((f) => f.path);
5265
+ return readBranchSpecs(task.projectPath, task.branch, base, changed);
5234
5266
  },
5235
5267
  "board:getCliLaunchInfo": () => boardCliLaunchInfo(),
5236
5268
  "library:list": async () => listLibrary(await libraryRoot(), builtinSkillsDir()),
@@ -5382,9 +5414,9 @@ function createCore({ sessions, bus, version }) {
5382
5414
  // Watch the containing directory, not the file: JsonStore renames a temp
5383
5415
  // file over the target and so replaces the inode on every save.
5384
5416
  watchStore() {
5385
- const file = path19.basename(getStorePath());
5417
+ const file = path20.basename(getStorePath());
5386
5418
  let debounce = null;
5387
- storeWatcher = fs15.watch(path19.dirname(getStorePath()), (_event, filename) => {
5419
+ storeWatcher = fs16.watch(path20.dirname(getStorePath()), (_event, filename) => {
5388
5420
  if (filename !== file) return;
5389
5421
  if (debounce) clearTimeout(debounce);
5390
5422
  debounce = setTimeout(() => {
@@ -5416,9 +5448,9 @@ function createSessionBackend(sink, choice) {
5416
5448
  }
5417
5449
 
5418
5450
  // packages/core/src/web-server.ts
5419
- import fs16 from "fs";
5451
+ import fs17 from "fs";
5420
5452
  import http from "http";
5421
- import path20 from "path";
5453
+ import path21 from "path";
5422
5454
  import { timingSafeEqual } from "crypto";
5423
5455
  import { WebSocketServer } from "ws";
5424
5456
  var TOKEN_COOKIE = "vf_token";
@@ -5471,14 +5503,14 @@ function resolveStatic(root, urlPath) {
5471
5503
  return null;
5472
5504
  }
5473
5505
  if (decoded.includes("\0")) return null;
5474
- const rel = path20.posix.normalize(decoded).replace(/^\/+/, "");
5475
- const base = path20.resolve(root);
5476
- const candidates = [rel, path20.posix.join(rel, "index.html"), `${rel}.html`];
5506
+ const rel = path21.posix.normalize(decoded).replace(/^\/+/, "");
5507
+ const base = path21.resolve(root);
5508
+ const candidates = [rel, path21.posix.join(rel, "index.html"), `${rel}.html`];
5477
5509
  for (const c of candidates) {
5478
- const full = path20.resolve(base, c);
5479
- if (full !== base && !full.startsWith(base + path20.sep)) return null;
5510
+ const full = path21.resolve(base, c);
5511
+ if (full !== base && !full.startsWith(base + path21.sep)) return null;
5480
5512
  try {
5481
- if (fs16.statSync(full).isFile()) return full;
5513
+ if (fs17.statSync(full).isFile()) return full;
5482
5514
  } catch {
5483
5515
  }
5484
5516
  }
@@ -5539,7 +5571,7 @@ async function startWebServer(options) {
5539
5571
  send(res, 404, "Not found");
5540
5572
  return;
5541
5573
  }
5542
- const ext = path20.extname(file).toLowerCase();
5574
+ const ext = path21.extname(file).toLowerCase();
5543
5575
  const immutable = url.pathname.startsWith("/_next/static/");
5544
5576
  res.writeHead(200, {
5545
5577
  "Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
@@ -5552,7 +5584,7 @@ async function startWebServer(options) {
5552
5584
  res.end();
5553
5585
  return;
5554
5586
  }
5555
- fs16.createReadStream(file).pipe(res);
5587
+ fs17.createReadStream(file).pipe(res);
5556
5588
  }
5557
5589
  const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
5558
5590
  function reject(socket, status, reason) {
@@ -5724,7 +5756,7 @@ function loginUrl(lock, pathname = "/home/") {
5724
5756
  return `http://127.0.0.1:${lock.port}${pathname}?token=${encodeURIComponent(lock.token)}`;
5725
5757
  }
5726
5758
  function isWebDir(dir) {
5727
- return fs17.existsSync(path21.join(dir, "home", "index.html"));
5759
+ return fs18.existsSync(path22.join(dir, "home", "index.html"));
5728
5760
  }
5729
5761
 
5730
5762
  // packages/cli/src/remote.ts
@@ -5841,8 +5873,8 @@ function connectToHost(lock) {
5841
5873
  }
5842
5874
 
5843
5875
  // packages/cli/src/doctor.ts
5844
- import fs18 from "fs";
5845
- import path22 from "path";
5876
+ import fs19 from "fs";
5877
+ import path23 from "path";
5846
5878
  import { execFile as execFile7 } from "child_process";
5847
5879
  import { promisify as promisify5 } from "util";
5848
5880
  init_env();
@@ -5982,10 +6014,10 @@ async function runDoctor(options) {
5982
6014
  });
5983
6015
  }
5984
6016
  try {
5985
- fs18.mkdirSync(options.userDataDir, { recursive: true });
5986
- const probe = path22.join(options.userDataDir, `.doctor-${process.pid}`);
5987
- fs18.writeFileSync(probe, "");
5988
- fs18.rmSync(probe);
6017
+ fs19.mkdirSync(options.userDataDir, { recursive: true });
6018
+ const probe = path23.join(options.userDataDir, `.doctor-${process.pid}`);
6019
+ fs19.writeFileSync(probe, "");
6020
+ fs19.rmSync(probe);
5989
6021
  checks.push({ id: "data", name: "\u8CC7\u6599\u76EE\u9304", level: "ok", detail: options.userDataDir });
5990
6022
  } catch (err) {
5991
6023
  checks.push({
@@ -6039,24 +6071,24 @@ Options:
6039
6071
  `.trim();
6040
6072
  function detectLayout() {
6041
6073
  const file = fileURLToPath(import.meta.url);
6042
- const here = path25.dirname(file);
6043
- const bundledWeb = path25.join(here, "web");
6044
- if (isWebDir(bundledWeb) || path25.basename(here) === "dist") {
6074
+ const here = path26.dirname(file);
6075
+ const bundledWeb = path26.join(here, "web");
6076
+ if (isWebDir(bundledWeb) || path26.basename(here) === "dist") {
6045
6077
  return {
6046
- version: readVersion(path25.join(here, "..", "package.json")),
6078
+ version: readVersion(path26.join(here, "..", "package.json")),
6047
6079
  webDir: isWebDir(bundledWeb) ? bundledWeb : null,
6048
6080
  sourceRoot: null,
6049
6081
  cliEntry: file
6050
6082
  };
6051
6083
  }
6052
- const root = path25.resolve(here, "..", "..", "..");
6084
+ const root = path26.resolve(here, "..", "..", "..");
6053
6085
  const override = process.env.VIBEFLOW_WEB_DIR;
6054
- const webDir = override && isWebDir(override) ? override : isWebDir(path25.join(root, "app")) ? path25.join(root, "app") : null;
6055
- return { version: readVersion(path25.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
6086
+ const webDir = override && isWebDir(override) ? override : isWebDir(path26.join(root, "app")) ? path26.join(root, "app") : null;
6087
+ return { version: readVersion(path26.join(root, "package.json")), webDir, sourceRoot: root, cliEntry: null };
6056
6088
  }
6057
6089
  function readVersion(pkgPath) {
6058
6090
  try {
6059
- return JSON.parse(fs20.readFileSync(pkgPath, "utf8")).version;
6091
+ return JSON.parse(fs21.readFileSync(pkgPath, "utf8")).version;
6060
6092
  } catch {
6061
6093
  return "0.0.0";
6062
6094
  }
@@ -6262,8 +6294,8 @@ async function cmdOpen(values, target, layout, userDataDir) {
6262
6294
  console.error("\u7528\u6CD5\uFF1Avibeflow open <path>");
6263
6295
  return 1;
6264
6296
  }
6265
- const dir = path25.resolve(target);
6266
- if (!fs20.existsSync(dir) || !fs20.statSync(dir).isDirectory()) {
6297
+ const dir = path26.resolve(target);
6298
+ if (!fs21.existsSync(dir) || !fs21.statSync(dir).isDirectory()) {
6267
6299
  console.error(`\u4E0D\u662F\u8CC7\u6599\u593E\uFF1A${dir}`);
6268
6300
  return 1;
6269
6301
  }
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/c380333c2873a2b8.css" as="style"/><link rel="stylesheet" href="/_next/static/css/c380333c2873a2b8.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-66261c34aef6a4cb.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/E9h3ANqgTHA4it6NLyDv5/_buildManifest.js" defer=""></script><script src="/_next/static/E9h3ANqgTHA4it6NLyDv5/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"E9h3ANqgTHA4it6NLyDv5","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/c380333c2873a2b8.css" as="style"/><link rel="stylesheet" href="/_next/static/css/c380333c2873a2b8.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-014e95fe86cd1d39.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/lyOZoF78HzRwmQ3DYf88f/_buildManifest.js" defer=""></script><script src="/_next/static/lyOZoF78HzRwmQ3DYf88f/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"lyOZoF78HzRwmQ3DYf88f","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
package/dist/web/404.html CHANGED
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/c380333c2873a2b8.css" as="style"/><link rel="stylesheet" href="/_next/static/css/c380333c2873a2b8.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-66261c34aef6a4cb.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/E9h3ANqgTHA4it6NLyDv5/_buildManifest.js" defer=""></script><script src="/_next/static/E9h3ANqgTHA4it6NLyDv5/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"E9h3ANqgTHA4it6NLyDv5","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charSet="utf-8" data-next-head=""/><meta name="viewport" content="width=device-width" data-next-head=""/><link rel="icon" href="/favicon.ico" sizes="any" data-next-head=""/><link rel="icon" href="/logo.svg" type="image/svg+xml" data-next-head=""/><link rel="apple-touch-icon" href="/apple-touch-icon.png" data-next-head=""/><title data-next-head="">404: This page could not be found</title><link rel="preload" href="/_next/static/css/c380333c2873a2b8.css" as="style"/><link rel="stylesheet" href="/_next/static/css/c380333c2873a2b8.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" noModule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-c8aa5df0425f6b7b.js" defer=""></script><script src="/_next/static/chunks/framework-b92a367cf1ad4bf7.js" defer=""></script><script src="/_next/static/chunks/main-ec7fd4c371d06729.js" defer=""></script><script src="/_next/static/chunks/pages/_app-014e95fe86cd1d39.js" defer=""></script><script src="/_next/static/chunks/pages/_error-8f65b0f8749f42d2.js" defer=""></script><script src="/_next/static/lyOZoF78HzRwmQ3DYf88f/_buildManifest.js" defer=""></script><script src="/_next/static/lyOZoF78HzRwmQ3DYf88f/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"lyOZoF78HzRwmQ3DYf88f","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>