mainflow 0.38.0 → 0.40.0-beta.1
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 +34 -4
- package/dist/{dist-CZYL5PJ4.js → dist-zSabglPq.js} +31 -8
- package/dist/facts.d.ts +25 -13
- package/dist/facts.js +31 -13
- package/dist/grant-commands.d.ts +1 -1
- package/dist/grant-commands.js +1 -1
- package/dist/hooks.d.ts +22 -4
- package/dist/hooks.js +92 -8
- package/dist/{host-aYx3DYrv.js → host-erQ_Sr8U.js} +249 -52
- package/dist/host.js +1 -1
- package/dist/{index-BH9gpiap.d.ts → index-Dk4fCI7q.d.ts} +29 -1
- package/dist/{launch-D38sNgXM.js → launch-I_JDTAtU.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/native/mainflow-swap-darwin-arm64 +0 -0
- package/dist/native/mainflow-swap-darwin-x64 +0 -0
- package/dist/{operation-1oQ8lA9C.js → operation-Bf6DtnfD.js} +1 -1
- package/dist/{ui-BwAp_sgf.js → ui-CFtibt8x.js} +9 -8
- package/dist/web/assets/{index-AEOSW4wb.js → index-BrA-csjk.js} +3 -3
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as stampMs, A as TASK_REQUIRED_SECTIONS, B as mainlineSchema, C as RESERVED_HUB_NAMES, D as SLUG, E as SESSION_STATUS, F as decisionSchema, G as parseUntil, H as parseActionRef, I as findingSchema, J as sessionSchema, K as refExample, L as formatOpsEntry, M as UNTIL_FORMS, N as WORK_STATUS, O as SURVEY_LINES, P as configSchema, Q as OFFSET, R as groupSchema, S as REF_EXAMPLES, T as SESSION_ID, U as parseOpsLine, V as opsEntrySchema, W as parseRef, X as waitingForAction, Y as taskSchema, Z as waitingForRef, a as DECISION_SECTIONS, at as grantForm, b as MERGE_STATES, c as FINDING_SECTIONS, d as GROUP_SECTIONS, f as GROUP_STATUS, h as ISSUE_SECTIONS, i as DECISION_BASES, it as grantCovers, j as TASK_SECTIONS, k as SURVEY_SECTION, l as FINDING_STATUS, n as CLOSED_STATUS, ot as grantRepo, q as refMainline, r as DECIDERS, rt as describeRequest, s as DEFAULT_CATEGORIES, st as parseGrantTarget, v as MAINLINE_SECTIONS, w as SERVES_REF, x as PRIORITIES, y as MAINLINE_STATUS, z as issueSchema } from "./dist-zSabglPq.js";
|
|
2
2
|
import { a as LABEL_ROLES, i as FINDING_SEVERITIES, n as FINDING_CLASSES, r as FINDING_FIELDS, t as FINDINGS_FENCE } from "./retro-formats-ECy0gGOL.js";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { appendFileSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, symlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
@@ -459,6 +459,11 @@ function decisionFileIn(hub, mainline, decision) {
|
|
|
459
459
|
return file;
|
|
460
460
|
}
|
|
461
461
|
const sessionFileIn = (id) => `${SESSIONS_DIR}/${id}.yaml`;
|
|
462
|
+
function groupFileIn(hub, id) {
|
|
463
|
+
const file = entryFileIn(hub, GROUPS_DIR, id);
|
|
464
|
+
if (!file) throw new WriteError(`group ${id} not found`);
|
|
465
|
+
return file;
|
|
466
|
+
}
|
|
462
467
|
//#endregion
|
|
463
468
|
//#region ../core/dist/ops/format.js
|
|
464
469
|
/** Why a hub whose data format is `found` cannot be written by this CLI; null when it can. */
|
|
@@ -751,6 +756,7 @@ const mainlineDir = (root, id) => onDisk(root, mainlineDirIn(nodeStorage(root),
|
|
|
751
756
|
const taskDir = (root, mainline, task) => onDisk(root, taskDirIn(nodeStorage(root), mainline, task));
|
|
752
757
|
const mainlineFile = (root, id) => join(mainlineDir(root, id), MAINLINE_FILE);
|
|
753
758
|
const taskFile = (root, mainline, task) => join(taskDir(root, mainline, task), TASK_FILE);
|
|
759
|
+
const groupFile = (root, id) => onDisk(root, groupFileIn(nodeStorage(root), id));
|
|
754
760
|
const readConfig = (root) => readConfigIn(nodeStorage(root));
|
|
755
761
|
//#endregion
|
|
756
762
|
//#region ../core/dist/refs.js
|
|
@@ -802,11 +808,18 @@ function refIndex(hub) {
|
|
|
802
808
|
title: f.title,
|
|
803
809
|
status: f.status
|
|
804
810
|
});
|
|
805
|
-
for (const g of hub.groups ?? [])
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
811
|
+
for (const g of hub.groups ?? []) {
|
|
812
|
+
index.set(g.id, {
|
|
813
|
+
kind: "group",
|
|
814
|
+
title: g.title,
|
|
815
|
+
status: g.status
|
|
816
|
+
});
|
|
817
|
+
for (const r of g.criteria ?? []) index.set(`${g.id}#${r.id}`, {
|
|
818
|
+
kind: "groupCriterion",
|
|
819
|
+
title: r.text,
|
|
820
|
+
status: itemStatus(r)
|
|
821
|
+
});
|
|
822
|
+
}
|
|
810
823
|
return index;
|
|
811
824
|
}
|
|
812
825
|
const isHubRoot = (dir) => existsSync(join(dir, DATA_DIR, "config.yaml"));
|
|
@@ -1358,6 +1371,7 @@ function loadHub(root, options = {}) {
|
|
|
1358
1371
|
const findings = hubDocs(join(dataDir, "findings"), findingSchema);
|
|
1359
1372
|
const groups = hubDocs(join(dataDir, "groups"), groupSchema);
|
|
1360
1373
|
problems.push(...groupProblems(config.hub, groups, mainlines, hubIssues, unreadable));
|
|
1374
|
+
for (const g of groups) problems.push(...grantProblems(g, config), ...listGaps(g.file, "criteria", g.criteria ?? [], g.status === "open"));
|
|
1361
1375
|
const ops = [];
|
|
1362
1376
|
const opsPath = join(dataDir, "ops");
|
|
1363
1377
|
for (const name of existsSync(opsPath) ? readdirSync(opsPath).filter((n) => n.endsWith(".log")).sort() : []) readFileSync(join(opsPath, name), "utf8").split("\n").forEach((line, index) => {
|
|
@@ -1600,7 +1614,10 @@ function checkReferences(input) {
|
|
|
1600
1614
|
}
|
|
1601
1615
|
for (const i of issues) add(i.file, i, i.id);
|
|
1602
1616
|
for (const f of findings) add(f.file, f, f.id);
|
|
1603
|
-
for (const g of groups)
|
|
1617
|
+
for (const g of groups) {
|
|
1618
|
+
add(g.file, g, g.id);
|
|
1619
|
+
items(g.file, "criteria", g.criteria ?? []);
|
|
1620
|
+
}
|
|
1604
1621
|
for (const p of pending) {
|
|
1605
1622
|
const parsed = parseRef(p.ref);
|
|
1606
1623
|
if (!parsed) continue;
|
|
@@ -1657,7 +1674,7 @@ function repoPathProblems(root, config) {
|
|
|
1657
1674
|
});
|
|
1658
1675
|
}
|
|
1659
1676
|
/**
|
|
1660
|
-
* What the schema cannot see about a mainline's grants: ids must be unique, since the file is edited directly,
|
|
1677
|
+
* What the schema cannot see about a mainline's or a group's grants: ids must be unique, since the file is edited directly,
|
|
1661
1678
|
* and a target that names a repository must name one listed in config.yaml.
|
|
1662
1679
|
*/
|
|
1663
1680
|
function grantProblems(mainline, config) {
|
|
@@ -1688,24 +1705,28 @@ const isOpen$2 = (kind, status) => !CLOSED_STATUS[kind].includes(status);
|
|
|
1688
1705
|
* entries are skipped, because their evidence already says what was done. Ids must also be unique within
|
|
1689
1706
|
* a list, since the files are edited directly and nothing else allocates them.
|
|
1690
1707
|
*/
|
|
1691
|
-
|
|
1708
|
+
/** Ids unique within a list, and every open item saying what has to be true to check it. */
|
|
1709
|
+
function listGaps(file, field, items, open) {
|
|
1692
1710
|
const gaps = [];
|
|
1693
|
-
const
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
message: `${item.id} is used twice; give it the next free id`
|
|
1700
|
-
});
|
|
1701
|
-
seen.add(item.id);
|
|
1702
|
-
if (open && !item.done && !item.dropped && !item.doneWhen?.trim()) gaps.push({
|
|
1703
|
-
file,
|
|
1704
|
-
field: `${field}.${i}.doneWhen`,
|
|
1705
|
-
message: `${item.id} is open and needs doneWhen: what has to be true to check it`
|
|
1706
|
-
});
|
|
1711
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1712
|
+
items.forEach((item, i) => {
|
|
1713
|
+
if (seen.has(item.id)) gaps.push({
|
|
1714
|
+
file,
|
|
1715
|
+
field: `${field}.${i}.id`,
|
|
1716
|
+
message: `${item.id} is used twice; give it the next free id`
|
|
1707
1717
|
});
|
|
1708
|
-
|
|
1718
|
+
seen.add(item.id);
|
|
1719
|
+
if (open && !item.done && !item.dropped && !item.doneWhen?.trim()) gaps.push({
|
|
1720
|
+
file,
|
|
1721
|
+
field: `${field}.${i}.doneWhen`,
|
|
1722
|
+
message: `${item.id} is open and needs doneWhen: what has to be true to check it`
|
|
1723
|
+
});
|
|
1724
|
+
});
|
|
1725
|
+
return gaps;
|
|
1726
|
+
}
|
|
1727
|
+
function contentGaps(mainline, tasks) {
|
|
1728
|
+
const gaps = [];
|
|
1729
|
+
const list = (file, field, items, open) => gaps.push(...listGaps(file, field, items, open));
|
|
1709
1730
|
const mainlineOpen = isOpen$2("mainline", mainline.status);
|
|
1710
1731
|
list(mainline.file, "criteria", mainline.criteria, mainlineOpen);
|
|
1711
1732
|
if (mainlineOpen && tasks.length > 0) for (const message of surveyGaps(mainline.body)) gaps.push({
|
|
@@ -1846,6 +1867,7 @@ function requireExisting(hub, ref) {
|
|
|
1846
1867
|
entryFile(hub, FINDINGS_DIR, parsed.finding, "finding");
|
|
1847
1868
|
return;
|
|
1848
1869
|
case "group":
|
|
1870
|
+
case "groupCriterion":
|
|
1849
1871
|
entryFile(hub, GROUPS_DIR, parsed.group, "group");
|
|
1850
1872
|
return;
|
|
1851
1873
|
default:
|
|
@@ -2029,6 +2051,26 @@ function createGroup(hub, input, ctx) {
|
|
|
2029
2051
|
file
|
|
2030
2052
|
});
|
|
2031
2053
|
}
|
|
2054
|
+
/** The mainlines and issues of this hub that are members of group `id` and not yet settled, by id. */
|
|
2055
|
+
function openMembers(hub, id) {
|
|
2056
|
+
const own = readConfigIn(hub).hub;
|
|
2057
|
+
const isMember = (value) => value === id || value === `${own}:${id}`;
|
|
2058
|
+
const read = (path) => splitFrontMatter(hub.read(path) ?? "").data ?? {};
|
|
2059
|
+
const mainlines = hub.list(MAINLINES_DIR).filter((e) => e.dir).map((e) => read(`${MAINLINES_DIR}/${e.name}/${MAINLINE_FILE}`));
|
|
2060
|
+
const issues = hub.list(ISSUES_DIR).filter((e) => !e.dir && e.name.endsWith(".md")).map((e) => read(`${ISSUES_DIR}/${e.name}`));
|
|
2061
|
+
return [...mainlines.filter((m) => isMember(m.group) && !closes("mainline", m.status ?? "")), ...issues.filter((i) => isMember(i.group) && i.status !== "closed")].map((d) => d.id ?? "?");
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Marking a group done is a claim that its goal is reached and its work is settled, so it is refused while a
|
|
2065
|
+
* criterion is still open or a mainline or issue of this hub in the group is not finished. The message names each one.
|
|
2066
|
+
* Members in other hubs are not read here, since the writer only sees this hub.
|
|
2067
|
+
*/
|
|
2068
|
+
function requireGroupDone(hub, id, g) {
|
|
2069
|
+
const open = (g.criteria ?? []).filter((c) => !c.done && !c.dropped).map((c) => `${id}#${c.id}`);
|
|
2070
|
+
const members = openMembers(hub, id);
|
|
2071
|
+
if (!open.length && !members.length) return;
|
|
2072
|
+
throw new WriteError(`${id} cannot be marked done: ${[open.length ? `unmet criteria ${open.join(", ")} (check each with evidence, e.g. criterion check ${open[0]} --evidence <commit>, or reword or drop it in the group file)` : "", members.length ? `unfinished members ${members.join(", ")} (finish them, or move them out with mainline set or issue set --group none)` : ""].filter(Boolean).join("; ")}`);
|
|
2073
|
+
}
|
|
2032
2074
|
function setGroup(hub, input, ctx) {
|
|
2033
2075
|
const { patch } = input;
|
|
2034
2076
|
requireRef(input.id, "group");
|
|
@@ -2041,6 +2083,7 @@ function setGroup(hub, input, ctx) {
|
|
|
2041
2083
|
const draft = new Draft(hub);
|
|
2042
2084
|
return draft.result(editFrontMatter(draft, entryFile(draft, GROUPS_DIR, input.id, "group"), groupSchema, (g) => {
|
|
2043
2085
|
if (status) {
|
|
2086
|
+
if (status === "done" && g.status !== "done") requireGroupDone(draft, input.id, g);
|
|
2044
2087
|
g.status = status;
|
|
2045
2088
|
stampClosed(g, closes("group", status), day);
|
|
2046
2089
|
}
|
|
@@ -2179,45 +2222,69 @@ function setMainlineArchived(hub, input, ctx) {
|
|
|
2179
2222
|
else delete m.archivedAt;
|
|
2180
2223
|
}, input.expectedVersion));
|
|
2181
2224
|
}
|
|
2182
|
-
/**
|
|
2225
|
+
/** The mainline or group a criterion list belongs to, from `m001` or `w001`. */
|
|
2226
|
+
function requireOwner(input) {
|
|
2227
|
+
const ref = parseRef(input);
|
|
2228
|
+
if (ref?.kind !== "mainline" && ref?.kind !== "group") throw new WriteError(`expected a mainline or group reference like ${refExample("mainline")} or ${refExample("group")}, got "${input}"`);
|
|
2229
|
+
return input;
|
|
2230
|
+
}
|
|
2231
|
+
/** The owner and criterion ids of `m001#r001` or `w001#r001`. */
|
|
2232
|
+
function requireCriterionRef(input) {
|
|
2233
|
+
const ref = parseRef(input);
|
|
2234
|
+
if (ref?.kind === "criterion") return {
|
|
2235
|
+
owner: ref.mainline,
|
|
2236
|
+
criterion: ref.criterion
|
|
2237
|
+
};
|
|
2238
|
+
if (ref?.kind === "groupCriterion") return {
|
|
2239
|
+
owner: ref.group,
|
|
2240
|
+
criterion: ref.criterion
|
|
2241
|
+
};
|
|
2242
|
+
throw new WriteError(`expected a criterion reference like ${refExample("criterion")} or ${refExample("groupCriterion")}, got "${input}"`);
|
|
2243
|
+
}
|
|
2244
|
+
/** Edit the criteria of the mainline or group `owner`; a group written before criteria existed gets an empty list first. */
|
|
2245
|
+
function editCriteria(draft, owner, mutate, expectedVersion) {
|
|
2246
|
+
if (parseRef(owner)?.kind === "group") return editFrontMatter(draft, groupFileIn(draft, owner), groupSchema, (g) => mutate(g.criteria ??= []), expectedVersion);
|
|
2247
|
+
return editFrontMatter(draft, mainlineFileIn(draft, owner), mainlineSchema, (m) => mutate(m.criteria), expectedVersion);
|
|
2248
|
+
}
|
|
2249
|
+
/** Add an open criterion at the end of the list of the mainline or group in `mainline`, numbered by the context's allocator. */
|
|
2183
2250
|
function addCriterion(hub, input, ctx) {
|
|
2184
|
-
|
|
2185
|
-
const fields = newListItem(input, `criterion add ${
|
|
2251
|
+
const owner = requireOwner(input.mainline);
|
|
2252
|
+
const fields = newListItem(input, `criterion add ${owner}`);
|
|
2186
2253
|
const draft = new Draft(hub);
|
|
2187
2254
|
let ref = "";
|
|
2188
|
-
|
|
2255
|
+
editCriteria(draft, owner, (criteria) => {
|
|
2189
2256
|
const id = draft.number(ctx, {
|
|
2190
2257
|
prefix: "r",
|
|
2191
|
-
taken:
|
|
2192
|
-
ref: (id) => `${
|
|
2258
|
+
taken: criteria.map((c) => c.id),
|
|
2259
|
+
ref: (id) => `${owner}#${id}`
|
|
2193
2260
|
});
|
|
2194
|
-
|
|
2261
|
+
criteria.push({
|
|
2195
2262
|
id,
|
|
2196
2263
|
...fields
|
|
2197
2264
|
});
|
|
2198
|
-
ref = `${
|
|
2265
|
+
ref = `${owner}#${id}`;
|
|
2199
2266
|
});
|
|
2200
2267
|
return draft.result({ ref });
|
|
2201
2268
|
}
|
|
2202
2269
|
function setCriterion(hub, input, ctx) {
|
|
2203
|
-
const
|
|
2270
|
+
const { owner, criterion: id } = requireCriterionRef(input.ref);
|
|
2204
2271
|
if (input.done && !input.evidence) throw new WriteError(`marking ${input.ref} as met needs evidence, e.g. --evidence mainflow@25e4165`);
|
|
2205
2272
|
const { day } = stamps(ctx);
|
|
2206
2273
|
const draft = new Draft(hub);
|
|
2207
|
-
return draft.result(
|
|
2208
|
-
const criterion =
|
|
2274
|
+
return draft.result(editCriteria(draft, owner, (criteria) => {
|
|
2275
|
+
const criterion = criteria.find((c) => c.id === id);
|
|
2209
2276
|
if (!criterion) throw new WriteError(`criterion ${input.ref} not found`);
|
|
2210
|
-
setListItem(criterion, input.done, input.evidence, `${input.ref} is dropped; remove dropped and supersededBy in ${MAINLINE_FILE} to bring it back before checking it`, day);
|
|
2277
|
+
setListItem(criterion, input.done, input.evidence, `${input.ref} is dropped; remove dropped and supersededBy in ${parseRef(owner)?.kind === "group" ? "the group file" : MAINLINE_FILE} to bring it back before checking it`, day);
|
|
2211
2278
|
}, input.expectedVersion));
|
|
2212
2279
|
}
|
|
2213
2280
|
/** Rewrite a criterion's text or doneWhen, leaving done and evidence as they are. */
|
|
2214
2281
|
function setCriterionText(hub, input) {
|
|
2215
|
-
const
|
|
2282
|
+
const { owner, criterion: id } = requireCriterionRef(input.ref);
|
|
2216
2283
|
const { patch } = input;
|
|
2217
2284
|
if (patch.text === void 0 && patch.doneWhen === void 0) throw new WriteError(`${input.ref} needs --text or --done-when, e.g. --text "CLI can create mainlines"`);
|
|
2218
2285
|
const draft = new Draft(hub);
|
|
2219
|
-
return draft.result(
|
|
2220
|
-
const criterion =
|
|
2286
|
+
return draft.result(editCriteria(draft, owner, (criteria) => {
|
|
2287
|
+
const criterion = criteria.find((c) => c.id === id);
|
|
2221
2288
|
if (!criterion) throw new WriteError(`criterion ${input.ref} not found`);
|
|
2222
2289
|
if (patch.text !== void 0) criterion.text = patch.text;
|
|
2223
2290
|
if (patch.doneWhen !== void 0) criterion.doneWhen = patch.doneWhen;
|
|
@@ -2668,14 +2735,21 @@ function resolveActionIn(hub, ref) {
|
|
|
2668
2735
|
}
|
|
2669
2736
|
/**
|
|
2670
2737
|
* Every served checklist item or criterion must exist, so a session never points at work that is not in a
|
|
2671
|
-
* mainline. A criterion stands in while the mainline has no task for it yet, as when it is still being
|
|
2672
|
-
* and planned
|
|
2738
|
+
* mainline or a group. A criterion stands in while the mainline has no task for it yet, as when it is still being
|
|
2739
|
+
* surveyed and planned; a group's criterion (w001#r001) stands for work that belongs to the group as a whole. Its
|
|
2740
|
+
* mainline or group must still be open: follow-up work on an ended one either reopens it with a criterion
|
|
2673
2741
|
* or task for the follow-up, or starts a new mainline, so the follow-up is tracked instead of hanging off finished work.
|
|
2674
2742
|
*/
|
|
2675
2743
|
function requireServes(hub, serves) {
|
|
2676
2744
|
for (const ref of serves) {
|
|
2677
2745
|
const parsed = parseRef(ref);
|
|
2678
|
-
if (!SERVES_REF.test(ref) || parsed?.kind !== "checklistItem" && parsed?.kind !== "criterion") throw new WriteError(`--serves takes checklist items like m001/t002#c003 or criteria like m001#r001, got "${ref}"`);
|
|
2746
|
+
if (!SERVES_REF.test(ref) || parsed?.kind !== "checklistItem" && parsed?.kind !== "criterion" && parsed?.kind !== "groupCriterion") throw new WriteError(`--serves takes checklist items like m001/t002#c003 or criteria like m001#r001 or w001#r001, got "${ref}"`);
|
|
2747
|
+
if (parsed.kind === "groupCriterion") {
|
|
2748
|
+
const group = readFrontMatterIn(hub, groupFileIn(hub, parsed.group), groupSchema);
|
|
2749
|
+
if (group.status !== "open") throw new WriteError(`${parsed.group} is ${group.status}; to continue it, run "group set ${parsed.group} --status open" and add a criterion for the follow-up`);
|
|
2750
|
+
if (!(group.criteria ?? []).some((r) => r.id === parsed.criterion)) throw new WriteError(`criterion ${ref} not found`);
|
|
2751
|
+
continue;
|
|
2752
|
+
}
|
|
2679
2753
|
const mainline = readFrontMatterIn(hub, mainlineFileIn(hub, parsed.mainline), mainlineSchema);
|
|
2680
2754
|
if (CLOSED_STATUS.mainline.includes(mainline.status)) throw new WriteError(`${parsed.mainline} is ${mainline.status}; to continue it, run "mainline set ${parsed.mainline} --status active" and add a criterion or task for the follow-up, or start a new mainline with "new mainline"`);
|
|
2681
2755
|
if (parsed.kind === "criterion") {
|
|
@@ -3231,6 +3305,7 @@ const rewriteOf = ({ from, to }) => {
|
|
|
3231
3305
|
case "task":
|
|
3232
3306
|
case "decision":
|
|
3233
3307
|
case "criterion": return short((s) => s.mainline === ref.mainline);
|
|
3308
|
+
case "groupCriterion": return short((s) => s.group === ref.group);
|
|
3234
3309
|
case "checklistItem": return short((s) => s.mainline === ref.mainline && s.task === ref.task);
|
|
3235
3310
|
default: return {
|
|
3236
3311
|
full: from,
|
|
@@ -3260,6 +3335,10 @@ const ENTRY = /^([mtdifw]\d{3})(-.+)$/;
|
|
|
3260
3335
|
function scopeOfPath(path) {
|
|
3261
3336
|
const [top, second, third, fourth] = path.split("/");
|
|
3262
3337
|
if (top === "local" && second === "sessions" && third?.endsWith(".yaml") && fourth === void 0) return { session: third.slice(0, -5) };
|
|
3338
|
+
if (top === "groups" && second && third === void 0) {
|
|
3339
|
+
const group = ENTRY.exec(second)?.[1];
|
|
3340
|
+
return group ? { group } : {};
|
|
3341
|
+
}
|
|
3263
3342
|
if (top !== "mainlines" || !second) return {};
|
|
3264
3343
|
const mainline = ENTRY.exec(second)?.[1];
|
|
3265
3344
|
const task = third === "tasks" && fourth ? ENTRY.exec(fourth)?.[1] : void 0;
|
|
@@ -3678,6 +3757,8 @@ const mayDecorate = (text) => /m\d{3}|[ifw]\d{3}|\.mainflow\/mainlines/.test(tex
|
|
|
3678
3757
|
//#region ../core/dist/grants.js
|
|
3679
3758
|
/** Mainline statuses whose grants apply. */
|
|
3680
3759
|
const GRANTING_STATUS = ["active", "planning"];
|
|
3760
|
+
/** Group statuses whose grants apply. */
|
|
3761
|
+
const GRANTING_GROUP_STATUS = ["open"];
|
|
3681
3762
|
/**
|
|
3682
3763
|
* The grants in effect for a session. Reading a mainline that is missing or invalid throws, so a caller that
|
|
3683
3764
|
* must fail closed catches it and asks.
|
|
@@ -3702,13 +3783,21 @@ function grantScope(root, session) {
|
|
|
3702
3783
|
return parsed ? refMainline(parsed) : void 0;
|
|
3703
3784
|
};
|
|
3704
3785
|
const mainlines = [...new Set(action.serves.flatMap((ref) => mainlineOf(ref) ?? []))];
|
|
3705
|
-
|
|
3786
|
+
const direct = /* @__PURE__ */ new Map();
|
|
3787
|
+
for (const ref of action.serves) {
|
|
3788
|
+
const parsed = parseRef(ref);
|
|
3789
|
+
if (parsed?.kind === "groupCriterion" && !direct.has(parsed.group)) direct.set(parsed.group, ref);
|
|
3790
|
+
}
|
|
3791
|
+
if (mainlines.length === 0 && direct.size === 0) return {
|
|
3706
3792
|
ok: false,
|
|
3707
3793
|
reason: `${action.id} 沒有服務任何 checklist 項目或解決條件`
|
|
3708
3794
|
};
|
|
3709
3795
|
const grants = [];
|
|
3710
3796
|
const served = {};
|
|
3711
3797
|
const closed = [];
|
|
3798
|
+
const hubName = readConfig(root).hub;
|
|
3799
|
+
const groups = new Map(direct);
|
|
3800
|
+
const reachedByMainline = /* @__PURE__ */ new Set();
|
|
3712
3801
|
for (const id of mainlines) {
|
|
3713
3802
|
const mainline = readFrontMatter(mainlineFile(root, id), mainlineSchema).data;
|
|
3714
3803
|
if (!GRANTING_STATUS.includes(mainline.status)) {
|
|
@@ -3717,15 +3806,42 @@ function grantScope(root, session) {
|
|
|
3717
3806
|
}
|
|
3718
3807
|
served[id] = action.serves.find((ref) => mainlineOf(ref) === id);
|
|
3719
3808
|
for (const grant of mainline.grants ?? []) grants.push({
|
|
3720
|
-
|
|
3809
|
+
owner: id,
|
|
3721
3810
|
grant,
|
|
3722
3811
|
ref: `${id}#${grant.id}`
|
|
3723
3812
|
});
|
|
3813
|
+
const own = mainline.group ? splitGroup(mainline.group, hubName) : void 0;
|
|
3814
|
+
if (own && own.hub === void 0 && !groups.has(own.id)) {
|
|
3815
|
+
groups.set(own.id, served[id]);
|
|
3816
|
+
reachedByMainline.add(own.id);
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
for (const [id, via] of groups) {
|
|
3820
|
+
let group;
|
|
3821
|
+
try {
|
|
3822
|
+
group = readFrontMatter(groupFile(root, id), groupSchema).data;
|
|
3823
|
+
} catch (error) {
|
|
3824
|
+
if (reachedByMainline.has(id)) continue;
|
|
3825
|
+
throw error;
|
|
3826
|
+
}
|
|
3827
|
+
if (!GRANTING_GROUP_STATUS.includes(group.status)) {
|
|
3828
|
+
closed.push(`${id} 是 ${group.status}`);
|
|
3829
|
+
continue;
|
|
3830
|
+
}
|
|
3831
|
+
served[id] = via;
|
|
3832
|
+
for (const grant of group.grants ?? []) grants.push({
|
|
3833
|
+
owner: id,
|
|
3834
|
+
grant,
|
|
3835
|
+
ref: `${id}#${grant.id}`
|
|
3836
|
+
});
|
|
3837
|
+
}
|
|
3838
|
+
if (Object.keys(served).length === 0) {
|
|
3839
|
+
const what = [closed.some((c) => c.startsWith("m")) ? "主脈絡" : "", closed.some((c) => c.startsWith("w")) ? "組" : ""].filter(Boolean).join("或");
|
|
3840
|
+
return {
|
|
3841
|
+
ok: false,
|
|
3842
|
+
reason: `${action.id} 服務的${what}已不在進行中(${closed.join("、")}),授權不再生效`
|
|
3843
|
+
};
|
|
3724
3844
|
}
|
|
3725
|
-
if (Object.keys(served).length === 0) return {
|
|
3726
|
-
ok: false,
|
|
3727
|
-
reason: `${action.id} 服務的主脈絡已不在進行中(${closed.join("、")}),授權不再生效`
|
|
3728
|
-
};
|
|
3729
3845
|
return {
|
|
3730
3846
|
ok: true,
|
|
3731
3847
|
scope: {
|
|
@@ -3806,8 +3922,18 @@ function setReminderDigest(root, id, digest) {
|
|
|
3806
3922
|
//#region ../core/dist/briefing.js
|
|
3807
3923
|
function describe(root, ref) {
|
|
3808
3924
|
const parsed = parseRef(ref);
|
|
3809
|
-
if (parsed?.kind !== "checklistItem" && parsed?.kind !== "criterion") return null;
|
|
3925
|
+
if (parsed?.kind !== "checklistItem" && parsed?.kind !== "criterion" && parsed?.kind !== "groupCriterion") return null;
|
|
3810
3926
|
try {
|
|
3927
|
+
if (parsed.kind === "groupCriterion") {
|
|
3928
|
+
const group = readFrontMatter(groupFile(root, parsed.group), groupSchema).data;
|
|
3929
|
+
const criterion = group.criteria?.find((r) => r.id === parsed.criterion);
|
|
3930
|
+
return criterion ? {
|
|
3931
|
+
ref,
|
|
3932
|
+
text: criterion.text,
|
|
3933
|
+
checked: criterion.done,
|
|
3934
|
+
where: `${group.id} ${group.title} › 解決條件`
|
|
3935
|
+
} : null;
|
|
3936
|
+
}
|
|
3811
3937
|
const mainline = readFrontMatter(mainlineFile(root, parsed.mainline), mainlineSchema).data;
|
|
3812
3938
|
if (parsed.kind === "criterion") {
|
|
3813
3939
|
const criterion = mainline.criteria.find((r) => r.id === parsed.criterion);
|
|
@@ -4010,7 +4136,7 @@ function sessionBriefing(root, session, options) {
|
|
|
4010
4136
|
lines.push(doing ? `目前行動:${doing.id} ${doing.do}` : "目前沒有進行中的行動。");
|
|
4011
4137
|
if (doing) lines.push(`服務:${servedLine(root, doing)}`);
|
|
4012
4138
|
const grants = grantLines(root, session);
|
|
4013
|
-
if (grants.length > 0) lines.push("
|
|
4139
|
+
if (grants.length > 0) lines.push("目前行動服務的主脈絡與所屬的組預先授權了這些動作:", ...grants);
|
|
4014
4140
|
if (upcoming.length > 0) lines.push(`接下來:${upcoming.map((a) => `${a.id} ${a.do}`).join("、")}`);
|
|
4015
4141
|
const blocked = session.actions.filter((a) => a.status === "blocked");
|
|
4016
4142
|
if (blocked.length > 0) lines.push(`等外部條件:${blocked.map(waitingLine).join(";")}`);
|
|
@@ -4052,7 +4178,7 @@ function turnReminder(root, session, options) {
|
|
|
4052
4178
|
else setReminderDigest(root, session.id, digest);
|
|
4053
4179
|
} catch {}
|
|
4054
4180
|
const once = (lines, summary, full) => lines.length === 0 ? [] : repeated ? [summary] : full;
|
|
4055
|
-
const granted = once(grants, `- 預先授權同上一輪,共 ${grants.length} 條。`, ["-
|
|
4181
|
+
const granted = once(grants, `- 預先授權同上一輪,共 ${grants.length} 條。`, ["- 目前行動服務的主脈絡與所屬的組預先授權了這些動作:", ...grants.map((g) => ` ${g}`)]);
|
|
4056
4182
|
const waiting = once(waiters, `- 等待行同上一輪,共 ${waiters.length} 條。`, waiters.map((w) => `- ${w}`));
|
|
4057
4183
|
const warned = once(warnings, `- 佇列提醒同上一輪,共 ${warnings.length} 條。`, warnings.map((w) => `- ${w}`));
|
|
4058
4184
|
return [
|
|
@@ -8035,6 +8161,77 @@ function migrateHub(root, options = {}) {
|
|
|
8035
8161
|
}).value;
|
|
8036
8162
|
}
|
|
8037
8163
|
//#endregion
|
|
8164
|
+
//#region ../core/dist/group-plan.js
|
|
8165
|
+
/** Sessions that can still hold work: not archived, and active or paused. */
|
|
8166
|
+
const live = (s) => !s.archivedAt && (s.status === "active" || s.status === "paused");
|
|
8167
|
+
/** Order `refs` so each comes after the ones it depends on; members in a dependency loop keep their given order at the end. */
|
|
8168
|
+
function ordered(refs, dependsOn) {
|
|
8169
|
+
const done = /* @__PURE__ */ new Set();
|
|
8170
|
+
const out = [];
|
|
8171
|
+
for (let progressed = true; progressed && out.length < refs.length;) {
|
|
8172
|
+
progressed = false;
|
|
8173
|
+
for (const ref of refs) {
|
|
8174
|
+
if (done.has(ref) || !(dependsOn.get(ref) ?? []).every((d) => done.has(d))) continue;
|
|
8175
|
+
done.add(ref);
|
|
8176
|
+
out.push(ref);
|
|
8177
|
+
progressed = true;
|
|
8178
|
+
break;
|
|
8179
|
+
}
|
|
8180
|
+
}
|
|
8181
|
+
return [...out, ...refs.filter((r) => !done.has(r))];
|
|
8182
|
+
}
|
|
8183
|
+
function groupPlan(hub, sessions, groupId) {
|
|
8184
|
+
const own = hub.config.hub;
|
|
8185
|
+
const mainlines = hub.mainlines.filter((m) => memberOfGroup(m.group, groupId, own));
|
|
8186
|
+
const issues = hub.issues.filter((i) => memberOfGroup(i.group, groupId, own));
|
|
8187
|
+
const docs = [...mainlines.map((m) => ({
|
|
8188
|
+
ref: m.id,
|
|
8189
|
+
kind: "mainline",
|
|
8190
|
+
title: m.title,
|
|
8191
|
+
status: m.status,
|
|
8192
|
+
open: !CLOSED_STATUS.mainline.includes(m.status),
|
|
8193
|
+
links: m.links
|
|
8194
|
+
})), ...issues.map((i) => ({
|
|
8195
|
+
ref: i.id,
|
|
8196
|
+
kind: "issue",
|
|
8197
|
+
title: i.title,
|
|
8198
|
+
status: i.status,
|
|
8199
|
+
open: i.status !== "closed",
|
|
8200
|
+
links: i.links
|
|
8201
|
+
}))];
|
|
8202
|
+
const refs = docs.map((d) => d.ref);
|
|
8203
|
+
const dependsOn = new Map(docs.map((d) => [d.ref, (d.links ?? []).filter((l) => l.rel === "depends-on").map((l) => parseRef(l.to)?.hub === own ? l.to.slice(l.to.indexOf(":") + 1) : l.to).filter((to) => refs.includes(to) && to !== d.ref)]));
|
|
8204
|
+
const active = (matches) => sessions.filter(live).flatMap((s) => s.actions.flatMap((a) => (a.status === "doing" || a.status === "blocked") && a.serves.some(matches) ? [{
|
|
8205
|
+
session: s.id,
|
|
8206
|
+
action: a.id,
|
|
8207
|
+
status: a.status,
|
|
8208
|
+
do: a.do
|
|
8209
|
+
}] : []));
|
|
8210
|
+
const mainlineOf = (served) => {
|
|
8211
|
+
const parsed = parseRef(served);
|
|
8212
|
+
return parsed ? refMainline(parsed) : void 0;
|
|
8213
|
+
};
|
|
8214
|
+
const byRef = new Map(docs.map((d) => [d.ref, d]));
|
|
8215
|
+
return {
|
|
8216
|
+
members: ordered(refs, dependsOn).map((ref) => {
|
|
8217
|
+
const d = byRef.get(ref);
|
|
8218
|
+
return {
|
|
8219
|
+
ref,
|
|
8220
|
+
kind: d.kind,
|
|
8221
|
+
title: d.title,
|
|
8222
|
+
status: d.status,
|
|
8223
|
+
open: d.open,
|
|
8224
|
+
dependsOn: dependsOn.get(ref) ?? [],
|
|
8225
|
+
active: d.kind === "mainline" ? active((s) => mainlineOf(s) === ref) : []
|
|
8226
|
+
};
|
|
8227
|
+
}),
|
|
8228
|
+
active: active((s) => {
|
|
8229
|
+
const parsed = parseRef(s);
|
|
8230
|
+
return parsed?.kind === "groupCriterion" && parsed.group === groupId;
|
|
8231
|
+
})
|
|
8232
|
+
};
|
|
8233
|
+
}
|
|
8234
|
+
//#endregion
|
|
8038
8235
|
//#region src/host.ts
|
|
8039
8236
|
const DEFAULT_UI_PORT = 4320;
|
|
8040
8237
|
const hostName = (hub) => `${hub}.mainflow.localhost`;
|
|
@@ -8233,4 +8430,4 @@ function openInApp(hub, target, closeAfterMs = 150) {
|
|
|
8233
8430
|
return true;
|
|
8234
8431
|
}
|
|
8235
8432
|
//#endregion
|
|
8236
|
-
export {
|
|
8433
|
+
export { unavailableHubs as $, headerLine as A, scopeOfPath as B, loadSessions as C, worktreesOf as Ct, matchGrants as D, contentHash as Dt, grantScope as E, WriteError as Et, applyOffline as F, loadHub as G, checkHubName as H, renumberRequest as I, mainlineEdges as J, sectionText as K, renumberFiles as L, attributeTrace as M, traceTranscript as N, decorateReply as O, splitFrontMatter as Ot, transcriptTitle as P, remoteMembers as Q, renumberPath as R, turnReminder as S, findHubRoot as St, transcriptPath as T, DATA_DIR as Tt, HubError as U, applyOperation as V, loadConfig as W, memberOfGroup as X, linksInto as Y, refIndex as Z, setArchivedMany as _, hubFormatVersion as _t, openInApp as a, ACCESS_HEADERS as at, duplicateClaimLines as b, requireSlug as bt, siteBlock as c, MAX_FILE_BYTES as ct, migrateHub as d, comparePaths as dt, taskFile as et, retroReport as f, exceedsFileLimit as ft, ARCHIVE_KINDS as g, formatMismatch as gt, RETRY_RULES as h, textBytes as ht, hostName as i, nodeStorage as it, mayDecorate as j, endsWithMarker as k, syncHost as l, ROUTES as lt, receiverFromEnv as m, requestTime as mt, DEFAULT_UI_PORT as n, readFrontMatter as nt, removeDanglingLinks as o, BLOB_HASH as ot, retroReportFromReceiver as p, readsAsText as pt, dependencyGraph as q, caddySites as r, readYaml as rt, removeHostLink as s, MAX_BLOB_BYTES as st, APP_BUNDLE_ID as t, nowMinute as tt, groupPlan as u, SOCKET_MAX_AGE_CLOSE as ut, initHub as v, MAINLINE_FILE as vt, readSession as w, ConflictError as wt, sessionBriefing as x, currentBranch as xt, untilState as y, TASK_FILE as yt, renumberText as z };
|
package/dist/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as openInApp, c as siteBlock, i as hostName, l as syncHost, n as DEFAULT_UI_PORT, o as removeDanglingLinks, r as caddySites, s as removeHostLink, t as APP_BUNDLE_ID } from "./host-
|
|
1
|
+
import { a as openInApp, c as siteBlock, i as hostName, l as syncHost, n as DEFAULT_UI_PORT, o as removeDanglingLinks, r as caddySites, s as removeHostLink, t as APP_BUNDLE_ID } from "./host-erQ_Sr8U.js";
|
|
2
2
|
export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, removeDanglingLinks, removeHostLink, siteBlock, syncHost };
|
|
@@ -15,7 +15,10 @@ declare const ID_PATTERNS: {
|
|
|
15
15
|
readonly grant: RegExp;
|
|
16
16
|
};
|
|
17
17
|
declare const CHECKLIST_REF: RegExp;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* What an action may serve: a checklist item, or a mainline's criterion while the mainline has no task for it yet,
|
|
20
|
+
* or a group's criterion (`w017#r001`).
|
|
21
|
+
*/
|
|
19
22
|
declare const SERVES_REF: RegExp;
|
|
20
23
|
declare const SLUG: RegExp;
|
|
21
24
|
/** One source the survey checks: the line's label, what it holds, and how it may be left unchecked. */
|
|
@@ -459,6 +462,31 @@ declare const groupSchema: z.ZodObject<{
|
|
|
459
462
|
P3: "P3";
|
|
460
463
|
}>>;
|
|
461
464
|
trigger: z.ZodOptional<z.ZodString>;
|
|
465
|
+
criteria: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
466
|
+
text: z.ZodString;
|
|
467
|
+
doneWhen: z.ZodOptional<z.ZodString>;
|
|
468
|
+
done: z.ZodBoolean;
|
|
469
|
+
evidence: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
470
|
+
doneAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
471
|
+
dropped: z.ZodOptional<z.ZodBoolean>;
|
|
472
|
+
supersededBy: z.ZodOptional<z.ZodString>;
|
|
473
|
+
note: z.ZodOptional<z.ZodString>;
|
|
474
|
+
id: z.ZodString;
|
|
475
|
+
}, z.core.$strip>>>;
|
|
476
|
+
grants: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
477
|
+
id: z.ZodString;
|
|
478
|
+
action: z.ZodEnum<{
|
|
479
|
+
commit: "commit";
|
|
480
|
+
"install-prompts": "install-prompts";
|
|
481
|
+
"merge-pr": "merge-pr";
|
|
482
|
+
publish: "publish";
|
|
483
|
+
push: "push";
|
|
484
|
+
release: "release";
|
|
485
|
+
"sync-hubs": "sync-hubs";
|
|
486
|
+
}>;
|
|
487
|
+
target: z.ZodString;
|
|
488
|
+
note: z.ZodOptional<z.ZodString>;
|
|
489
|
+
}, z.core.$strip>>>;
|
|
462
490
|
}, z.core.$strip>;
|
|
463
491
|
type Group = z.infer<typeof groupSchema>;
|
|
464
492
|
/** One line of ops/{YYYY-MM}.log: an external operation, append-only. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Et as WriteError, Tt as DATA_DIR, i as hostName, l as syncHost, n as DEFAULT_UI_PORT, s as removeHostLink } from "./host-erQ_Sr8U.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -276,7 +276,7 @@ async function launchUi(root, hub, configured, options) {
|
|
|
276
276
|
}
|
|
277
277
|
let server;
|
|
278
278
|
if (!reuse) {
|
|
279
|
-
const { serveUi } = await import("./ui-
|
|
279
|
+
const { serveUi } = await import("./ui-CFtibt8x.js");
|
|
280
280
|
server = await serveUi(root, choice.port);
|
|
281
281
|
writeUiRecord(root, {
|
|
282
282
|
port: choice.port,
|
package/dist/launch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as networkProbe, c as realRoot, d as withUiLock, i as launchUi, l as startUiInBackground, n as choosePort, o as portCandidates, r as exitCodeFor, s as readUiRecord, t as PORT_SPAN, u as watchHubGone } from "./launch-
|
|
1
|
+
import { a as networkProbe, c as realRoot, d as withUiLock, i as launchUi, l as startUiInBackground, n as choosePort, o as portCandidates, r as exitCodeFor, s as readUiRecord, t as PORT_SPAN, u as watchHubGone } from "./launch-I_JDTAtU.js";
|
|
2
2
|
export { PORT_SPAN, choosePort, exitCodeFor, launchUi, networkProbe, portCandidates, readUiRecord, realRoot, startUiInBackground, watchHubGone, withUiLock };
|
|
Binary file
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as scopeOfPath, Dt as contentHash, Et as WriteError, F as applyOffline, I as renumberRequest, L as renumberFiles, Ot as splitFrontMatter, R as renumberPath, Tt as DATA_DIR, V as applyOperation, at as ACCESS_HEADERS, ct as MAX_FILE_BYTES, dt as comparePaths, ft as exceedsFileLimit, ht as textBytes, it as nodeStorage, lt as ROUTES, mt as requestTime, ot as BLOB_HASH, pt as readsAsText, st as MAX_BLOB_BYTES, tt as nowMinute, ut as SOCKET_MAX_AGE_CLOSE, wt as ConflictError, z as renumberText } from "./host-erQ_Sr8U.js";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as
|
|
1
|
+
import { P as configSchema } from "./dist-zSabglPq.js";
|
|
2
|
+
import { C as loadSessions, Et as WriteError, G as loadHub, J as mainlineEdges, K as sectionText, M as attributeTrace, N as traceTranscript, P as transcriptTitle, Q as remoteMembers, T as transcriptPath, Tt as DATA_DIR, U as HubError, W as loadConfig, Y as linksInto, Z as refIndex, _ as setArchivedMany, a as openInApp, g as ARCHIVE_KINDS, q as dependencyGraph, rt as readYaml, u as groupPlan, w as readSession, wt as ConflictError } from "./host-erQ_Sr8U.js";
|
|
3
3
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
4
|
-
import { A as isSynced, _ as readDaemon, a as remoteMode, b as readRemote, h as callDaemon, j as syncPaths, k as SYNC_DIR, m as DaemonUnavailable, n as hubWriter, r as writtenLines, y as watchTree } from "./operation-
|
|
5
|
-
import { c as realRoot, f as CLI_VERSION, s as readUiRecord } from "./launch-
|
|
4
|
+
import { A as isSynced, _ as readDaemon, a as remoteMode, b as readRemote, h as callDaemon, j as syncPaths, k as SYNC_DIR, m as DaemonUnavailable, n as hubWriter, r as writtenLines, y as watchTree } from "./operation-Bf6DtnfD.js";
|
|
5
|
+
import { c as realRoot, f as CLI_VERSION, s as readUiRecord } from "./launch-I_JDTAtU.js";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { closeSync, existsSync, lstatSync, openSync, readFileSync, readSync, realpathSync, statSync } from "node:fs";
|
|
8
8
|
import { basename, dirname, extname, join, normalize, relative, resolve, sep } from "node:path";
|
|
@@ -133,11 +133,12 @@ function summarize(m) {
|
|
|
133
133
|
}
|
|
134
134
|
/** The first paragraph of a group's 說明 on one line, for the heading of its group in a list. */
|
|
135
135
|
function groupSummary(body) {
|
|
136
|
-
return ((sectionText(body,
|
|
136
|
+
return ((sectionText(body, "說明") ?? "").split(/\n\s*\n/)[0] ?? "").replace(/\s+/g, " ").trim();
|
|
137
137
|
}
|
|
138
|
-
const groupView = (g) => ({
|
|
138
|
+
const groupView = (hub, g) => ({
|
|
139
139
|
...g,
|
|
140
|
-
summary: groupSummary(g.body)
|
|
140
|
+
summary: groupSummary(g.body),
|
|
141
|
+
order: groupPlan(hub, [], g.id).members.map((m) => m.ref)
|
|
141
142
|
});
|
|
142
143
|
/** The 503 body for a config.yaml that cannot be used, with the hub's root left out of paths. */
|
|
143
144
|
function configErrorView(root, error) {
|
|
@@ -241,7 +242,7 @@ function createApp(root, options = {}) {
|
|
|
241
242
|
version: CLI_VERSION,
|
|
242
243
|
repos: hub.config.repos,
|
|
243
244
|
mainlines: hub.mainlines.map(summarize),
|
|
244
|
-
groups: hub.groups.map(groupView),
|
|
245
|
+
groups: hub.groups.map((g) => groupView(hub, g)),
|
|
245
246
|
openIssues: hub.issues.filter((i) => i.status !== "closed").length,
|
|
246
247
|
validFindings: hub.findings.filter((f) => f.status === "valid").length,
|
|
247
248
|
problems: hub.problems
|