mainflow 0.23.0 → 0.25.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 +119 -13
- package/dist/{dist-Dy45myuN.js → dist-DlmecRLf.js} +110 -4
- package/dist/facts.d.ts +30 -4
- package/dist/facts.js +30 -5
- package/dist/grant-commands.d.ts +1 -1
- package/dist/grant-commands.js +2 -2
- package/dist/hooks.js +3 -3
- package/dist/{host-DkaCYnnc.js → host-Dr0bCHd8.js} +252 -9
- package/dist/host.js +1 -1
- package/dist/{index-CV30LkNZ.d.ts → index-D4IIwDGE.d.ts} +18 -1
- package/dist/{launch-DOX1TTGx.js → launch-lnD-Z3hb.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/{ui-Do7fLlUC.js → ui-Cz8suWal.js} +2 -2
- package/dist/web/assets/{index-BvKRkaWK.js → index-Cx_Rtc3B.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as grantRepo, A as configSchema, B as parseUntil, C as SLUG, D as TASK_SECTIONS, E as TASK_REQUIRED_SECTIONS, F as mainlineSchema, G as waitingForAction, H as refMainline, I as opsEntrySchema, K as waitingForRef, L as parseActionRef, M as findingSchema, N as formatOpsEntry, P as issueSchema, Q as grantForm, R as parseOpsLine, S as SESSION_STATUS, T as SURVEY_SECTION, U as sessionSchema, V as refExample, W as taskSchema, X as describeRequest, Z as grantCovers, _ as MERGE_STATES, a as DECISION_SECTIONS, b as RESERVED_HUB_NAMES, c as FINDING_SECTIONS, et as parseGrantTarget, f as ISSUE_SECTIONS, g as MAINLINE_STATUS, h as MAINLINE_SECTIONS, i as DECISION_BASES, j as decisionSchema, k as WORK_STATUS, l as FINDING_STATUS, n as CLOSED_STATUS, r as DECIDERS, s as DEFAULT_CATEGORIES, t as CHECKLIST_REF, w as SURVEY_LINES, x as SESSION_ID, y as REF_EXAMPLES, z as parseRef } from "./dist-DlmecRLf.js";
|
|
2
2
|
import { appendFileSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
3
3
|
import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
4
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
5
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
6
6
|
import { parse as parse$1, stringify } from "yaml";
|
|
7
7
|
//#region ../core/dist/paths.js
|
|
@@ -637,6 +637,7 @@ function loadHub(root, options = {}) {
|
|
|
637
637
|
mainlines.push({
|
|
638
638
|
...mainline,
|
|
639
639
|
dir: rel(mainlineDir),
|
|
640
|
+
artifacts: listFiles(join(mainlineDir, "artifacts")).map(rel),
|
|
640
641
|
views: listFiles(join(mainlineDir, "views")).map(rel),
|
|
641
642
|
decisions,
|
|
642
643
|
tasks
|
|
@@ -1387,6 +1388,50 @@ function splitActionRef(ref) {
|
|
|
1387
1388
|
action
|
|
1388
1389
|
};
|
|
1389
1390
|
}
|
|
1391
|
+
/** The note a handoff leaves on the dropped action, naming where the work went. */
|
|
1392
|
+
const handedOffNote = (to) => `handed off to ${to}`;
|
|
1393
|
+
const HANDED_OFF = /(?:^|; )handed off to ([a-z0-9][a-z0-9-]*#a\d{3})$/;
|
|
1394
|
+
/**
|
|
1395
|
+
* Find the action a reference names. A handoff drops the action and names its new place in the note, so the
|
|
1396
|
+
* reference is followed there: a wait on the old reference keeps working after the work moved to another session.
|
|
1397
|
+
*/
|
|
1398
|
+
function resolveAction(root, ref) {
|
|
1399
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1400
|
+
let current = ref;
|
|
1401
|
+
for (;;) {
|
|
1402
|
+
const parsed = parseActionRef(current);
|
|
1403
|
+
if (!parsed) return {
|
|
1404
|
+
ref: current,
|
|
1405
|
+
missing: `"${current}" is not an action reference like claude-code-1a2b3c4d#a003`
|
|
1406
|
+
};
|
|
1407
|
+
let session;
|
|
1408
|
+
try {
|
|
1409
|
+
session = readSession(root, parsed.session);
|
|
1410
|
+
} catch (error) {
|
|
1411
|
+
return {
|
|
1412
|
+
ref: current,
|
|
1413
|
+
missing: error.message
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
if (!session) return {
|
|
1417
|
+
ref: current,
|
|
1418
|
+
missing: `session ${parsed.session} not found`
|
|
1419
|
+
};
|
|
1420
|
+
const action = session.actions.find((a) => a.id === parsed.action);
|
|
1421
|
+
if (!action) return {
|
|
1422
|
+
ref: current,
|
|
1423
|
+
missing: `action ${current} not found`
|
|
1424
|
+
};
|
|
1425
|
+
const next = action.status === "dropped" ? HANDED_OFF.exec(action.note ?? "")?.[1] : void 0;
|
|
1426
|
+
if (!next || seen.has(next)) return {
|
|
1427
|
+
ref: current,
|
|
1428
|
+
session,
|
|
1429
|
+
action
|
|
1430
|
+
};
|
|
1431
|
+
seen.add(current);
|
|
1432
|
+
current = next;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1390
1435
|
/**
|
|
1391
1436
|
* Every served checklist item must exist, so a session never points at work that is not in a mainline. Its
|
|
1392
1437
|
* mainline must still be open: follow-up work on an ended one either reopens it with a criterion or task for the
|
|
@@ -1422,7 +1467,7 @@ function takeUnfinished(root, from, to, existing) {
|
|
|
1422
1467
|
...a,
|
|
1423
1468
|
id
|
|
1424
1469
|
});
|
|
1425
|
-
const handedOff =
|
|
1470
|
+
const handedOff = handedOffNote(`${to}#${id}`);
|
|
1426
1471
|
return {
|
|
1427
1472
|
...a,
|
|
1428
1473
|
status: "dropped",
|
|
@@ -1443,7 +1488,7 @@ function takeUnfinished(root, from, to, existing) {
|
|
|
1443
1488
|
*/
|
|
1444
1489
|
function startSession(root, input) {
|
|
1445
1490
|
const id = input.id ?? `${input.agent}-${randomBytes(4).toString("hex")}`;
|
|
1446
|
-
if (
|
|
1491
|
+
if (!SESSION_ID.test(id)) throw new WriteError(`session id "${id}" must be lowercase letters, digits and hyphens`);
|
|
1447
1492
|
return withLock(root, () => {
|
|
1448
1493
|
const file = sessionFile(root, id);
|
|
1449
1494
|
if (existsSync(file)) throw new WriteError(`session ${id} already exists`);
|
|
@@ -1585,16 +1630,40 @@ function addAction(root, sessionId, input) {
|
|
|
1585
1630
|
});
|
|
1586
1631
|
return `${sessionId}#${created}`;
|
|
1587
1632
|
}
|
|
1633
|
+
/**
|
|
1634
|
+
* What a blocked action waits for must be checkable: an action it names has to exist and, followed through
|
|
1635
|
+
* handoffs, cannot be the action itself, and an `until` has to be one of the condition forms. An old reference
|
|
1636
|
+
* to work that has since been handed off to this very action would otherwise wait forever.
|
|
1637
|
+
*/
|
|
1638
|
+
function requireBlock(root, ref, block) {
|
|
1639
|
+
const targets = [waitingForAction(block.waitingFor), block.until === void 0 ? void 0 : untilTarget(block.until)];
|
|
1640
|
+
for (const target of targets) {
|
|
1641
|
+
if (!target) continue;
|
|
1642
|
+
const found = resolveAction(root, target);
|
|
1643
|
+
if ("missing" in found) throw new WriteError(`${found.missing}; --waiting-for and --until action-done take an existing action like claude-code-1a2b3c4d#a003`);
|
|
1644
|
+
if (found.ref === ref) throw new WriteError(`action ${ref} cannot wait for itself${target === ref ? "" : ` (${target} was handed off to it)`}`);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
/** The action an `until` waits for; throws when the condition matches none of the forms. */
|
|
1648
|
+
function untilTarget(until) {
|
|
1649
|
+
const parsed = parseUntil(until);
|
|
1650
|
+
if (!parsed) throw new WriteError(`--until takes a condition like commit:{branch}:{text}, action-done:{sessionId}#a{NNN} or file:{relative path}, got "${until}"`);
|
|
1651
|
+
return parsed.kind === "action-done" ? `${parsed.session}#${parsed.action}` : void 0;
|
|
1652
|
+
}
|
|
1588
1653
|
function updateAction(root, ref, patch) {
|
|
1589
1654
|
const { session, action } = splitActionRef(ref);
|
|
1590
1655
|
const status = patch.status === void 0 ? void 0 : requireOneOf(patch.status, WORK_STATUS, "action status");
|
|
1591
1656
|
if (patch.serves) requireServes(root, patch.serves);
|
|
1592
1657
|
if (patch.before === action) throw new WriteError(`action ${action} cannot be placed before itself`);
|
|
1658
|
+
if (status === "blocked" && !patch.blockedBy?.reason) throw new WriteError(`blocking ${ref} needs a reason, e.g. --reason "等 schema 定案" --waiting-for claude-code-1a2b3c4d#a003 --until action-done:claude-code-1a2b3c4d#a003`);
|
|
1659
|
+
if (patch.blockedBy) requireBlock(root, `${session}#${action}`, patch.blockedBy);
|
|
1593
1660
|
let updated;
|
|
1594
1661
|
mutateYaml(root, requireSession(root, session), sessionSchema, (s) => {
|
|
1595
1662
|
const index = s.actions.findIndex((a) => a.id === action);
|
|
1596
1663
|
if (index < 0) throw new WriteError(`action ${ref} not found`);
|
|
1597
1664
|
const [current] = s.actions.splice(index, 1);
|
|
1665
|
+
const handedTo = patch.note ? HANDED_OFF.exec(current.note ?? "")?.[1] : void 0;
|
|
1666
|
+
if (handedTo) throw new WriteError(`${ref} was handed off to ${handedTo}, and references to ${ref} follow its note there, so the note stays as it is; add the note to ${handedTo} instead`);
|
|
1598
1667
|
const next = {
|
|
1599
1668
|
...current,
|
|
1600
1669
|
...status && { status },
|
|
@@ -1602,6 +1671,11 @@ function updateAction(root, ref, patch) {
|
|
|
1602
1671
|
...patch.serves && { serves: patch.serves },
|
|
1603
1672
|
...patch.note && { note: patch.note }
|
|
1604
1673
|
};
|
|
1674
|
+
if (patch.blockedBy) {
|
|
1675
|
+
if (next.status !== "blocked") throw new WriteError(`--reason, --waiting-for and --until go with --status blocked; ${ref} is ${next.status}`);
|
|
1676
|
+
next.blockedBy = patch.blockedBy;
|
|
1677
|
+
}
|
|
1678
|
+
if (next.status !== "blocked") delete next.blockedBy;
|
|
1605
1679
|
if (status === "doing" && current.status !== "doing") {
|
|
1606
1680
|
next.startedAt = nowMinute();
|
|
1607
1681
|
delete next.doneAt;
|
|
@@ -1653,8 +1727,16 @@ function queueWarnings(root, session, cli) {
|
|
|
1653
1727
|
}
|
|
1654
1728
|
const current = (session) => session.actions.find((a) => a.status === "doing");
|
|
1655
1729
|
const isOpen = (a) => a.status === "todo" || a.status === "doing";
|
|
1656
|
-
/** A blocked action and what it waits for
|
|
1657
|
-
|
|
1730
|
+
/** A blocked action and what it waits for: the reason, the other party and the condition, as `action set --status blocked` recorded them. */
|
|
1731
|
+
function waitingLine(a) {
|
|
1732
|
+
const block = a.blockedBy;
|
|
1733
|
+
const parts = block ? [
|
|
1734
|
+
block.reason,
|
|
1735
|
+
...block.waitingFor ? [`等 ${block.waitingFor}`] : [],
|
|
1736
|
+
...block.until ? [`條件 ${block.until}`] : []
|
|
1737
|
+
] : [];
|
|
1738
|
+
return `${a.id} ${a.do}${parts.length > 0 ? `(${parts.join(";")})` : ""}`;
|
|
1739
|
+
}
|
|
1658
1740
|
/** Mainlines a session could be pushing forward. */
|
|
1659
1741
|
const openMainlines = (root) => loadHub(root).mainlines.filter((m) => !m.archivedAt && (m.status === "active" || m.status === "planning"));
|
|
1660
1742
|
/** How recently a session's transcript must have been written for the session to count as possibly still running. */
|
|
@@ -1668,6 +1750,69 @@ function mayBeRunning(root, session) {
|
|
|
1668
1750
|
return false;
|
|
1669
1751
|
}
|
|
1670
1752
|
}
|
|
1753
|
+
/** The session and every session in its delegation tree: the sessions that delegated to it, and theirs, and all their subagents. */
|
|
1754
|
+
function family(sessions, id) {
|
|
1755
|
+
const byId = new Map(sessions.map((s) => [s.id, s]));
|
|
1756
|
+
const chain = /* @__PURE__ */ new Set([id]);
|
|
1757
|
+
let top = id;
|
|
1758
|
+
for (let up = byId.get(id)?.delegatedFrom; up && byId.has(up) && !chain.has(up); up = byId.get(up)?.delegatedFrom) {
|
|
1759
|
+
chain.add(up);
|
|
1760
|
+
top = up;
|
|
1761
|
+
}
|
|
1762
|
+
const members = /* @__PURE__ */ new Set([top]);
|
|
1763
|
+
for (let grew = true; grew;) {
|
|
1764
|
+
grew = false;
|
|
1765
|
+
for (const s of sessions) if (s.delegatedFrom && members.has(s.delegatedFrom) && !members.has(s.id)) {
|
|
1766
|
+
members.add(s.id);
|
|
1767
|
+
grew = true;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
return members.add(id);
|
|
1771
|
+
}
|
|
1772
|
+
/** The actions in this session that a blocked action elsewhere waits for, named by `waitingFor` or by an `until` of kind action-done. */
|
|
1773
|
+
function waitedFor(root, blocked) {
|
|
1774
|
+
const until = blocked.blockedBy?.until ? parseUntil(blocked.blockedBy.until) : null;
|
|
1775
|
+
const refs = [waitingForAction(blocked.blockedBy?.waitingFor), until?.kind === "action-done" ? `${until.session}#${until.action}` : void 0];
|
|
1776
|
+
return [...new Set(refs.filter((r) => r !== void 0).map((r) => resolveAction(root, r).ref))];
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* One line for each blocked action in another session that waits for an action of this one, followed through
|
|
1780
|
+
* handoffs, so the session being waited for sees it without the other side having to send a message. An action
|
|
1781
|
+
* already done or dropped has nothing left to deliver, so a wait on it is not shown.
|
|
1782
|
+
*/
|
|
1783
|
+
function waiterLines(root, session) {
|
|
1784
|
+
const lines = [];
|
|
1785
|
+
for (const other of loadSessions(root).sessions) {
|
|
1786
|
+
if (other.id === session.id || other.archivedAt || other.status === "handed-off") continue;
|
|
1787
|
+
for (const blocked of other.actions.filter((a) => a.status === "blocked")) for (const ref of waitedFor(root, blocked)) {
|
|
1788
|
+
const mine = ref.startsWith(`${session.id}#`) ? session.actions.find((a) => `${session.id}#${a.id}` === ref) : void 0;
|
|
1789
|
+
if (!mine || mine.status === "done" || mine.status === "dropped") continue;
|
|
1790
|
+
const until = blocked.blockedBy?.until ? `。條件:${blocked.blockedBy.until}` : "";
|
|
1791
|
+
lines.push(`有會話在等你:${other.id}#${blocked.id} 等 ${mine.id} ${mine.do}(${blocked.blockedBy.reason})${until}`);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
return lines;
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* One reminder for each action in progress in another session that may still be running and serves an item
|
|
1798
|
+
* this action also serves. Sessions in the same delegation tree are left out: a subagent working on the item
|
|
1799
|
+
* its parent handed it is not a second claim.
|
|
1800
|
+
*/
|
|
1801
|
+
function duplicateClaimLines(root, sessionId, actionId) {
|
|
1802
|
+
const { sessions } = loadSessions(root);
|
|
1803
|
+
const action = sessions.find((s) => s.id === sessionId)?.actions.find((a) => a.id === actionId);
|
|
1804
|
+
if (!action || action.serves.length === 0) return [];
|
|
1805
|
+
const related = family(sessions, sessionId);
|
|
1806
|
+
const lines = [];
|
|
1807
|
+
for (const other of sessions) {
|
|
1808
|
+
if (related.has(other.id) || !mayBeRunning(root, other)) continue;
|
|
1809
|
+
for (const theirs of other.actions.filter((a) => a.status === "doing")) {
|
|
1810
|
+
const shared = theirs.serves.filter((ref) => action.serves.includes(ref));
|
|
1811
|
+
if (shared.length > 0) lines.push(`⚠ ${other.id}#${theirs.id}(15 分鐘內有活動)也在做 ${shared.join("、")},先協調再開始`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
return lines;
|
|
1815
|
+
}
|
|
1671
1816
|
/**
|
|
1672
1817
|
* Lines naming the other top-level sessions that still hold unfinished actions, with the command that takes them
|
|
1673
1818
|
* over into this one. Subagent sessions are left out: their work returns to the session that delegated it.
|
|
@@ -1704,6 +1849,7 @@ function sessionBriefing(root, session, options) {
|
|
|
1704
1849
|
const blocked = session.actions.filter((a) => a.status === "blocked");
|
|
1705
1850
|
if (blocked.length > 0) lines.push(`等外部條件:${blocked.map(waitingLine).join(";")}`);
|
|
1706
1851
|
}
|
|
1852
|
+
lines.push(...waiterLines(root, session));
|
|
1707
1853
|
if (!session.actions.some(isOpen)) lines.push(...handoffLines(root, session, cli));
|
|
1708
1854
|
const warnings = queueWarnings(root, session, cli);
|
|
1709
1855
|
if (warnings.length > 0) lines.push("需要處理:", ...warnings.map((w) => `- ${w}`));
|
|
@@ -1716,18 +1862,115 @@ function turnReminder(root, session, options) {
|
|
|
1716
1862
|
const doing = current(session);
|
|
1717
1863
|
const open = session.actions.filter(isOpen);
|
|
1718
1864
|
const blocked = session.actions.filter((a) => a.status === "blocked");
|
|
1719
|
-
const head = doing ? `[mainflow] 目前行動 ${doing.id} ${doing.do},服務 ${doing.serves.join("、") || "(未指定)"}。這一輪的工作若不屬於它,先調整佇列再做。` : open.length > 0 ? `[mainflow] ${session.id} 目前沒有進行中的行動。` : blocked.length > 0 ? `[mainflow] ${session.id} 的行動在等外部條件:${blocked.map(waitingLine).join(";")}
|
|
1865
|
+
const head = doing ? `[mainflow] 目前行動 ${doing.id} ${doing.do},服務 ${doing.serves.join("、") || "(未指定)"}。這一輪的工作若不屬於它,先調整佇列再做。` : open.length > 0 ? `[mainflow] ${session.id} 目前沒有進行中的行動。` : blocked.length > 0 ? `[mainflow] ${session.id} 的行動在等外部條件:${blocked.map(waitingLine).join(";")}。有條件(until)的可以在背景執行 ${options.cli} wait <a編號> 等它成立。條件出現後用 ${options.cli} action set <a編號> --status doing 繼續;要先做別的事,用 ${options.cli} action add 列出行動。` : `[mainflow] ${session.id} 的行動都已結束。繼續工作前先用 ${options.cli} action add 列出下一個行動。`;
|
|
1720
1866
|
const header = headerLine(session);
|
|
1721
1867
|
const marker = header ? [`- 這一輪最後一則回覆的最後一行寫「${header}」,回覆範例等其他內容都放在它前面。有顯示 hook 時,它會換成標題與連結。`] : [];
|
|
1722
1868
|
const grants = grantLines(root, session);
|
|
1869
|
+
const granted = grants.length > 0 ? ["- 目前行動服務的主脈絡預先授權了這些動作:", ...grants.map((g) => ` ${g}`)] : [];
|
|
1723
1870
|
return [
|
|
1724
1871
|
head,
|
|
1725
|
-
...
|
|
1872
|
+
...waiterLines(root, session).map((w) => `- ${w}`),
|
|
1873
|
+
...granted,
|
|
1726
1874
|
...marker,
|
|
1727
1875
|
...queueWarnings(root, session, options.cli).map((w) => `- ${w}`)
|
|
1728
1876
|
].join("\n");
|
|
1729
1877
|
}
|
|
1730
1878
|
//#endregion
|
|
1879
|
+
//#region ../core/dist/waiting.js
|
|
1880
|
+
/** Separates commits in the git log output (%x1e); commit messages do not use this control character. */
|
|
1881
|
+
const RECORD = "";
|
|
1882
|
+
/** Whether git finds a repository at the hub root; false as well when git itself cannot run. */
|
|
1883
|
+
function inGitRepository(root) {
|
|
1884
|
+
try {
|
|
1885
|
+
execFileSync("git", ["rev-parse", "--git-dir"], {
|
|
1886
|
+
cwd: root,
|
|
1887
|
+
stdio: "ignore"
|
|
1888
|
+
});
|
|
1889
|
+
return true;
|
|
1890
|
+
} catch {
|
|
1891
|
+
return false;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
/** Whether a commit on the branch has the text in its message; git runs at the hub root, so the branch is the hub repository's. */
|
|
1895
|
+
function commitState(root, branch, text) {
|
|
1896
|
+
let log;
|
|
1897
|
+
try {
|
|
1898
|
+
log = execFileSync("git", [
|
|
1899
|
+
"log",
|
|
1900
|
+
"--format=%h %s%n%b%x1e",
|
|
1901
|
+
branch,
|
|
1902
|
+
"--"
|
|
1903
|
+
], {
|
|
1904
|
+
cwd: root,
|
|
1905
|
+
encoding: "utf8",
|
|
1906
|
+
stdio: [
|
|
1907
|
+
"ignore",
|
|
1908
|
+
"pipe",
|
|
1909
|
+
"pipe"
|
|
1910
|
+
],
|
|
1911
|
+
maxBuffer: 268435456
|
|
1912
|
+
});
|
|
1913
|
+
} catch {
|
|
1914
|
+
if (!inGitRepository(root)) return {
|
|
1915
|
+
met: false,
|
|
1916
|
+
unreachable: true,
|
|
1917
|
+
detail: `the hub root ${root} is not in a git repository, so no commit can appear there`
|
|
1918
|
+
};
|
|
1919
|
+
return {
|
|
1920
|
+
met: false,
|
|
1921
|
+
detail: `branch ${branch} is not in the repository at the hub root yet`
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
for (const record of log.split(RECORD)) {
|
|
1925
|
+
const entry = record.replace(/^\n/, "");
|
|
1926
|
+
const space = entry.indexOf(" ");
|
|
1927
|
+
const message = entry.slice(space + 1);
|
|
1928
|
+
if (space > 0 && message.includes(text)) return {
|
|
1929
|
+
met: true,
|
|
1930
|
+
detail: `commit ${entry.slice(0, space)} on ${branch} contains "${text}": ${message.split("\n")[0]}`
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
return {
|
|
1934
|
+
met: false,
|
|
1935
|
+
detail: `no commit on ${branch} contains "${text}"`
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
function actionState(root, ref) {
|
|
1939
|
+
const found = resolveAction(root, ref);
|
|
1940
|
+
if ("missing" in found) return {
|
|
1941
|
+
met: false,
|
|
1942
|
+
detail: found.missing
|
|
1943
|
+
};
|
|
1944
|
+
const via = found.ref === ref ? "" : ` (handed off to ${found.ref})`;
|
|
1945
|
+
if (found.action.status === "done") return {
|
|
1946
|
+
met: true,
|
|
1947
|
+
detail: `${ref}${via} is done`
|
|
1948
|
+
};
|
|
1949
|
+
if (found.action.status === "dropped") return {
|
|
1950
|
+
met: false,
|
|
1951
|
+
unreachable: true,
|
|
1952
|
+
detail: `${ref}${via} was dropped without a handoff, so it will never be done`
|
|
1953
|
+
};
|
|
1954
|
+
return {
|
|
1955
|
+
met: false,
|
|
1956
|
+
detail: `${ref}${via} is ${found.action.status}`
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
/** Check a wait condition once. */
|
|
1960
|
+
function untilState(root, until) {
|
|
1961
|
+
switch (until.kind) {
|
|
1962
|
+
case "commit": return commitState(root, until.branch, until.text);
|
|
1963
|
+
case "action-done": return actionState(root, `${until.session}#${until.action}`);
|
|
1964
|
+
case "file": return existsSync(resolve(root, until.path)) ? {
|
|
1965
|
+
met: true,
|
|
1966
|
+
detail: `${until.path} exists`
|
|
1967
|
+
} : {
|
|
1968
|
+
met: false,
|
|
1969
|
+
detail: `${until.path} does not exist yet`
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
//#endregion
|
|
1731
1974
|
//#region ../core/dist/write.js
|
|
1732
1975
|
function checkTaskLinks(root, mainline, task) {
|
|
1733
1976
|
if (task.repo && !(task.repo in readConfig(root).repos)) throw new WriteError(`repo "${task.repo}" is not listed in .mainflow/config.yaml; add it under repos first`);
|
|
@@ -2354,4 +2597,4 @@ function openInApp(hub, target, closeAfterMs = 150) {
|
|
|
2354
2597
|
return true;
|
|
2355
2598
|
}
|
|
2356
2599
|
//#endregion
|
|
2357
|
-
export {
|
|
2600
|
+
export { createIssue as $, sessionBriefing as A, settleSession as B, setCriterionText as C, updateTask as D, supersedeDecision as E, loadSessions as F, matchGrants as G, transcriptPath as H, readSession as I, headerLine as J, decorateReply as K, setSessionArchived as L, addAction as M, ensureSession as N, untilState as O, handoffSession as P, createFinding as Q, setSessionInfo as R, setCriterion as S, setMainlineTitle as T, updateAction as U, startSession as V, grantScope as W, appendOps as X, mayDecorate as Y, closeIssue as Z, createTask as _, currentBranch as _t, openInApp as a, HubError as at, setArchived as b, syncHost as c, mainlineEdges as ct, setArchivedMany as d, MAINLINE_FILE as dt, planIssue as et, acceptDecision as f, TASK_FILE as ft, createMainline as g, DATA_DIR as gt, createDecision as h, readYaml as ht, hostName as i, setIssueArchived as it, turnReminder as j, duplicateClaimLines as k, migrateHub as l, linksInto as lt, addRepo as m, WriteError as mt, DEFAULT_UI_PORT as n, setFindingArchived as nt, removeHostLink as o, loadHub as ot, addHub as p, ConflictError as pt, endsWithMarker as q, caddySites as r, setFindingStatus as rt, siteBlock as s, dependencyGraph as st, APP_BUNDLE_ID as t, reopenIssue as tt, ARCHIVE_KINDS as u, refIndex as ut, dropChecklistItem as v, findHubRoot as vt, setMainlineStatus as w, setChecklistItem as x, initHub as y, worktreesOf as yt, setSessionStatus as z };
|
package/dist/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-
|
|
1
|
+
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-Dr0bCHd8.js";
|
|
2
2
|
export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, removeHostLink, siteBlock, syncHost };
|
|
@@ -23,5 +23,22 @@ interface SurveyLine {
|
|
|
23
23
|
/** Text a checked line must contain, e.g. the environment a spike ran in. */
|
|
24
24
|
requires?: string;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A condition a program can check, written `{kind}:{argument}`. `commit` holds once a commit message on the
|
|
28
|
+
* branch contains the text, `action-done` once the action is done, `file` once the path exists under the hub
|
|
29
|
+
* root. Git branch names cannot contain a colon, so the text after the branch may.
|
|
30
|
+
*/
|
|
31
|
+
type Until = {
|
|
32
|
+
kind: 'commit';
|
|
33
|
+
branch: string;
|
|
34
|
+
text: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'action-done';
|
|
37
|
+
session: string;
|
|
38
|
+
action: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'file';
|
|
41
|
+
path: string;
|
|
42
|
+
};
|
|
26
43
|
//#endregion
|
|
27
|
-
export { SurveyLine as a, SLUG as i, FORMAT_VERSION as n, ID_PATTERNS as r, CHECKLIST_REF as t };
|
|
44
|
+
export { SurveyLine as a, SLUG as i, FORMAT_VERSION as n, Until as o, ID_PATTERNS as r, CHECKLIST_REF as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as syncHost,
|
|
1
|
+
import { c as syncHost, gt as DATA_DIR, i as hostName, mt as WriteError, n as DEFAULT_UI_PORT, o as removeHostLink } from "./host-Dr0bCHd8.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -176,7 +176,7 @@ async function launchUi(root, hub, configured, options) {
|
|
|
176
176
|
}
|
|
177
177
|
let server;
|
|
178
178
|
if (!reuse) {
|
|
179
|
-
const { serveUi } = await import("./ui-
|
|
179
|
+
const { serveUi } = await import("./ui-Cz8suWal.js");
|
|
180
180
|
server = await serveUi(root, choice.port);
|
|
181
181
|
writeUiRecord(root, {
|
|
182
182
|
port: choice.port,
|
package/dist/launch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-
|
|
1
|
+
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-lnD-Z3hb.js";
|
|
2
2
|
export { PORT_SPAN, choosePort, launchUi, networkProbe, portCandidates, readUiRecord, startUiInBackground, watchHubGone };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as updateTask, F as loadSessions, H as transcriptPath, I as readSession, L as setSessionArchived, S as setCriterion, T as setMainlineTitle, a as openInApp, b as setArchived, ct as mainlineEdges, d as setArchivedMany, gt as DATA_DIR, it as setIssueArchived, lt as linksInto, mt as WriteError, nt as setFindingArchived, ot as loadHub, pt as ConflictError, st as dependencyGraph, u as ARCHIVE_KINDS, ut as refIndex, w as setMainlineStatus, x as setChecklistItem } from "./host-Dr0bCHd8.js";
|
|
2
2
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
3
|
-
import { l as CLI_VERSION } from "./launch-
|
|
3
|
+
import { l as CLI_VERSION } from "./launch-lnD-Z3hb.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";
|