mainflow 0.10.0 → 0.11.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/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { $ as MAINLINE_FILE, A as setSessionStatus, B as appendOps, C as updateTask, D as readSession, F as sessionBriefing, G as reopenIssue, H as createFinding, J as setIssueArchived, K as setFindingArchived, M as startSession, O as setSessionArchived, P as updateAction, S as supersedeDecision, U as createIssue, V as closeIssue, W as planIssue, X as loadHub, Y as HubError, _ as setArchived, at as findHubRoot, b as setMainlineStatus, c as migrateHub, d as addRepo, et as TASK_FILE, f as createDecision, g as initHub, h as dropChecklistItem, l as acceptDecision, m as createTask, nt as WriteError, p as createMainline, q as setFindingStatus, u as addHub, v as setChecklistItem, w as addAction, y as setCriterion } from "./host-BFGfpTDq.js";
2
+ import { A as setSessionStatus, C as updateTask, D as readSession, F as sessionBriefing, G as planIssue, H as closeIssue, J as setFindingStatus, K as reopenIssue, M as startSession, O as setSessionArchived, P as updateAction, S as supersedeDecision, U as createFinding, V as appendOps, W as createIssue, X as HubError, Y as setIssueArchived, Z as loadHub, _ as setArchived, b as setMainlineStatus, c as migrateHub, d as addRepo, et as MAINLINE_FILE, f as createDecision, g as initHub, h as dropChecklistItem, l as acceptDecision, m as createTask, ot as findHubRoot, p as createMainline, q as setFindingArchived, rt as WriteError, tt as TASK_FILE, u as addHub, v as setChecklistItem, w as addAction, y as setCriterion } from "./host-iTpj7MPD.js";
3
3
  import { t as listProgress } from "./progress-1BfPg2zN.js";
4
4
  import { COMMAND_SPECS, OPTIONS } from "./facts.js";
5
5
  import { runHook } from "./hooks.js";
6
- import { c as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-D5uHpqkh.js";
6
+ import { c as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-BMuk7UvN.js";
7
7
  import { readFileSync, realpathSync } from "node:fs";
8
8
  import { join, relative } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
package/dist/hooks.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as setSessionStatus, D as readSession, F as sessionBriefing, I as turnReminder, L as decorateReply, R as endsWithMarker, T as ensureSession, X as loadHub, Y as HubError, at as findHubRoot, i as hostName, it as currentBranch, j as settleSession, k as setSessionInfo, ot as worktreesOf, rt as DATA_DIR, z as mayDecorate } from "./host-BFGfpTDq.js";
1
+ import { A as setSessionStatus, B as mayDecorate, D as readSession, F as sessionBriefing, I as turnReminder, L as decorateReply, R as endsWithMarker, T as ensureSession, X as HubError, Z as loadHub, at as currentBranch, i as hostName, it as DATA_DIR, j as settleSession, k as setSessionInfo, ot as findHubRoot, st as worktreesOf, z as headerLine } from "./host-iTpj7MPD.js";
2
2
  import { appendFileSync, existsSync, readFileSync } from "node:fs";
3
3
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
4
4
  //#region src/hooks.ts
@@ -75,18 +75,19 @@ const displayReply = (root, { payload }) => {
75
75
  } });
76
76
  };
77
77
  /**
78
- * Stop: settle the session, then ask for the end marker when a session with an action in progress ended its
79
- * turn without one. Only the model knows which message closes a turn, so the marker is its to write; this makes
78
+ * Stop: settle the session, then ask for the header line when a session with an action in progress ended its
79
+ * turn without one. Only the model knows which message closes a turn, so the header is its to write; this makes
80
80
  * a forgotten one cost a single extra message. stop_hook_active stops it from asking twice in a row.
81
81
  */
82
82
  const settleAndCheckMarker = (root, context) => {
83
83
  settleOnEnd(root, context);
84
84
  const { payload } = context;
85
85
  if (!payload.session_id || payload.stop_hook_active || endsWithMarker(payload.last_assistant_message ?? "")) return null;
86
- if (!readSession(root, claudeCodeSessionId(payload.session_id))?.actions.some((a) => a.status === "doing")) return null;
86
+ const header = headerLine(readSession(root, claudeCodeSessionId(payload.session_id)));
87
+ if (!header) return null;
87
88
  return JSON.stringify({ hookSpecificOutput: {
88
89
  hookEventName: "Stop",
89
- additionalContext: `[mainflow] 這一輪最後一則回覆沒有以單獨一行的 ▸ 結尾。請只回一則內容為 ▸ 的訊息,不加其他文字。`
90
+ additionalContext: `[mainflow] 這一輪最後一則回覆的最後一行不是標頭。請只回一則內容為「${header}」的訊息,不加其他文字。`
90
91
  } });
91
92
  };
92
93
  const CLAUDE_CODE_EVENTS = {
@@ -954,20 +954,34 @@ function currentActionLine(context) {
954
954
  parts.push(`[${action.id}](${context.link(sessionPath(context.session.id))}) ${action.do}`);
955
955
  return `▸ ${parts.join(" › ")}`;
956
956
  }
957
- /** Whether a message ends with the marker line, as the turn's last message should. */
958
- const endsWithMarker = (text) => text.trimEnd().split("\n").at(-1)?.trim() === "▸";
957
+ /** The header line the model writes at the end of a turn, e.g. `▸ m001/t001 · a001`; null without an action in progress. */
958
+ function headerLine(session) {
959
+ const action = session?.actions.find((a) => a.status === "doing");
960
+ if (!action) return null;
961
+ const served = action.serves.map((s) => parseRef(s)).find((r) => r !== null);
962
+ const where = served && "mainline" in served ? "task" in served ? `${served.mainline}/${served.task}` : served.mainline : null;
963
+ return `▸ ${where ? `${where} · ` : ""}${action.id}`;
964
+ }
965
+ /** A line that is the header: the marker alone, or the marker followed by the ids the model copied. */
966
+ const HEADER = new RegExp(`^[ \\t]*▸(?:[ \\t].*)?$`);
967
+ /** The last non-empty line, if it is the header line. */
968
+ const lastHeader = (text) => {
969
+ const last = text.trimEnd().split("\n").at(-1) ?? "";
970
+ return HEADER.test(last) ? last : null;
971
+ };
972
+ /** Whether a message ends with the header line, as the turn's last message should. */
973
+ const endsWithMarker = (text) => lastHeader(text) !== null;
959
974
  /**
960
- * The text to display for a piece of a reply: mentions linked, and a trailing marker line replaced with the
961
- * current action line (or removed when there is none). Returns the input unchanged when there is nothing to do.
975
+ * The text to display for a piece of a reply: mentions linked, and a trailing header line replaced with the
976
+ * session's current action line, titles and links included. Without an action in progress the line the model
977
+ * wrote stays, its ids linked; a bare marker is dropped. Returns the input unchanged when there is nothing to do.
962
978
  */
963
979
  function decorateReply(text, context) {
964
- let shown = linkMentions(text, context);
965
- if (endsWithMarker(shown)) {
966
- const body = shown.trimEnd().slice(0, -1).trimEnd();
967
- const line = currentActionLine(context);
968
- shown = line ? `${body}\n\n${line}\n` : `${body}\n`;
969
- }
970
- return shown;
980
+ const header = lastHeader(text);
981
+ if (header === null) return linkMentions(text, context);
982
+ const body = linkMentions(text.trimEnd().slice(0, -header.length).trimEnd(), context);
983
+ const line = currentActionLine(context) ?? (header.trim() === "▸" ? null : linkMentions(header.trim(), context));
984
+ return line ? `${body}\n\n${line}\n` : `${body}\n`;
971
985
  }
972
986
  /** Cheap test for text that decorateReply could change, so a display hook can skip loading the hub. */
973
987
  const mayDecorate = (text) => /m\d{3}|[if]\d{3}|\.mainflow\/mainlines/.test(text) || endsWithMarker(text);
@@ -1039,9 +1053,11 @@ function turnReminder(root, session, options) {
1039
1053
  if (session.actions.length === 0) return null;
1040
1054
  const doing = current(session);
1041
1055
  const open = session.actions.filter((a) => a.status === "todo" || a.status === "doing");
1056
+ const head = doing ? `[mainflow] 目前行動 ${doing.id} ${doing.do},服務 ${doing.serves.join("、") || "(未指定)"}。這一輪的工作若不屬於它,先調整佇列再做。` : open.length === 0 ? `[mainflow] ${session.id} 的行動都已結束。繼續工作前先用 ${options.cli} action add 列出下一個行動。` : `[mainflow] ${session.id} 目前沒有進行中的行動。`;
1057
+ const header = headerLine(session);
1042
1058
  return [
1043
- doing ? `[mainflow] 目前行動 ${doing.id} ${doing.do},服務 ${doing.serves.join("、") || "(未指定)"}。這一輪的工作若不屬於它,先調整佇列再做。` : open.length === 0 ? `[mainflow] ${session.id} 的行動都已結束。繼續工作前先用 ${options.cli} action add 列出下一個行動。` : `[mainflow] ${session.id} 目前沒有進行中的行動。`,
1044
- ...doing ? [`- 這一輪最後一則回覆的最後一行只寫 ▸,回覆範例等其他內容都放在它前面。畫面上它會換成目前行動與連結。`] : [],
1059
+ head,
1060
+ ...header ? [`- 這一輪最後一則回覆的最後一行寫「${header}」,回覆範例等其他內容都放在它前面。有顯示 hook 時,它會換成標題與連結。`] : [],
1045
1061
  ...queueWarnings(root, session, options.cli).map((w) => `- ${w}`)
1046
1062
  ].join("\n");
1047
1063
  }
@@ -1834,4 +1850,4 @@ function openInApp(hub, target, closeAfterMs = 150) {
1834
1850
  return true;
1835
1851
  }
1836
1852
  //#endregion
1837
- export { MAINLINE_FILE as $, setSessionStatus as A, appendOps as B, updateTask as C, readSession as D, loadSessions as E, sessionBriefing as F, reopenIssue as G, createFinding as H, turnReminder as I, setIssueArchived as J, setFindingArchived as K, decorateReply as L, startSession as M, transcriptPath as N, setSessionArchived as O, updateAction as P, refIndex as Q, endsWithMarker as R, supersedeDecision as S, ensureSession as T, createIssue as U, closeIssue as V, planIssue as W, loadHub as X, HubError as Y, linksInto as Z, setArchived as _, openInApp as a, findHubRoot as at, setMainlineStatus as b, migrateHub as c, addRepo as d, TASK_FILE as et, createDecision as f, initHub as g, dropChecklistItem as h, hostName as i, currentBranch as it, settleSession as j, setSessionInfo as k, acceptDecision as l, createTask as m, DEFAULT_UI_PORT as n, WriteError as nt, siteBlock as o, worktreesOf as ot, createMainline as p, setFindingStatus as q, caddySites as r, DATA_DIR as rt, syncHost as s, APP_BUNDLE_ID as t, ConflictError as tt, addHub as u, setChecklistItem as v, addAction as w, setMainlineTitle as x, setCriterion as y, mayDecorate as z };
1853
+ export { refIndex as $, setSessionStatus as A, mayDecorate as B, updateTask as C, readSession as D, loadSessions as E, sessionBriefing as F, planIssue as G, closeIssue as H, turnReminder as I, setFindingStatus as J, reopenIssue as K, decorateReply as L, startSession as M, transcriptPath as N, setSessionArchived as O, updateAction as P, linksInto as Q, endsWithMarker as R, supersedeDecision as S, ensureSession as T, createFinding as U, appendOps as V, createIssue as W, HubError as X, setIssueArchived as Y, loadHub as Z, setArchived as _, openInApp as a, currentBranch as at, setMainlineStatus as b, migrateHub as c, addRepo as d, MAINLINE_FILE as et, createDecision as f, initHub as g, dropChecklistItem as h, hostName as i, DATA_DIR as it, settleSession as j, setSessionInfo as k, acceptDecision as l, createTask as m, DEFAULT_UI_PORT as n, ConflictError as nt, siteBlock as o, findHubRoot as ot, createMainline as p, setFindingArchived as q, caddySites as r, WriteError as rt, syncHost as s, worktreesOf as st, APP_BUNDLE_ID as t, TASK_FILE as tt, addHub as u, setChecklistItem as v, addAction as w, setMainlineTitle as x, setCriterion as y, headerLine as z };
package/dist/host.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as openInApp, i as hostName, n as DEFAULT_UI_PORT, o as siteBlock, r as caddySites, s as syncHost, t as APP_BUNDLE_ID } from "./host-BFGfpTDq.js";
1
+ import { a as openInApp, i as hostName, n as DEFAULT_UI_PORT, o as siteBlock, r as caddySites, s as syncHost, t as APP_BUNDLE_ID } from "./host-iTpj7MPD.js";
2
2
  export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, siteBlock, syncHost };
@@ -1,4 +1,4 @@
1
- import { n as DEFAULT_UI_PORT, nt as WriteError, rt as DATA_DIR, s as syncHost } from "./host-BFGfpTDq.js";
1
+ import { it as DATA_DIR, n as DEFAULT_UI_PORT, rt as WriteError, s as syncHost } from "./host-iTpj7MPD.js";
2
2
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { spawn, spawnSync } from "node:child_process";
@@ -162,7 +162,7 @@ async function launchUi(root, hub, configured, options) {
162
162
  }
163
163
  let server;
164
164
  if (!reuse) {
165
- const { serveUi } = await import("./ui-DbEmJiwO.js");
165
+ const { serveUi } = await import("./ui-DNLdjWwd.js");
166
166
  server = await serveUi(root, choice.port);
167
167
  writeUiRecord(root, {
168
168
  port: choice.port,
package/dist/launch.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as portCandidates, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-D5uHpqkh.js";
1
+ import { a as portCandidates, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-BMuk7UvN.js";
2
2
  export { PORT_SPAN, choosePort, launchUi, networkProbe, portCandidates, readUiRecord, startUiInBackground };
@@ -1,6 +1,6 @@
1
- import { C as updateTask, D as readSession, E as loadSessions, J as setIssueArchived, K as setFindingArchived, N as transcriptPath, O as setSessionArchived, Q as refIndex, X as loadHub, Z as linksInto, _ as setArchived, a as openInApp, b as setMainlineStatus, nt as WriteError, rt as DATA_DIR, tt as ConflictError, v as setChecklistItem, x as setMainlineTitle, y as setCriterion } from "./host-BFGfpTDq.js";
1
+ import { $ as refIndex, C as updateTask, D as readSession, E as loadSessions, N as transcriptPath, O as setSessionArchived, Q as linksInto, Y as setIssueArchived, Z as loadHub, _ as setArchived, a as openInApp, b as setMainlineStatus, it as DATA_DIR, nt as ConflictError, q as setFindingArchived, rt as WriteError, v as setChecklistItem, x as setMainlineTitle, y as setCriterion } from "./host-iTpj7MPD.js";
2
2
  import { t as listProgress } from "./progress-1BfPg2zN.js";
3
- import { c as CLI_VERSION } from "./launch-D5uHpqkh.js";
3
+ import { c as CLI_VERSION } from "./launch-BMuk7UvN.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { closeSync, existsSync, openSync, readFileSync, readSync, statSync, watch } from "node:fs";
6
6
  import { basename, dirname, extname, join, normalize, relative, sep } from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mainflow",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "本機多 AI 協作的主脈絡任務管理:CLI 與介面",
5
5
  "license": "MIT",
6
6
  "type": "module",