mainflow 0.34.0 → 0.35.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,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { B as parseRef, H as refExample, V as parseUntil, Y as stampMs, k as UNTIL_FORMS } from "./dist-IdbnCDqM.js";
3
- import { $ as MAX_FILE_BYTES, B as checkHubName, H as loadHub, S as readSession, V as HubError, Y as nodeStorage, _ as untilState, ct as TASK_FILE, d as retroReportFromReceiver, dt as findHubRoot, f as receiverFromEnv, g as initHub, ht as WriteError, l as migrateHub, lt as requireSlug, mt as DATA_DIR, p as RETRY_RULES, q as nowMinute, st as MAINLINE_FILE, u as retroReport, v as duplicateClaimLines, x as loadSessions, y as sessionBriefing, z as applyOperation } from "./host-79T-_NMu.js";
2
+ import { G as parseRef, K as parseUntil, N as UNTIL_FORMS, et as stampMs, n as CLOSED_STATUS, q as refExample } from "./dist-BdvWGTsb.js";
3
+ import { B as applyOperation, C as readSession, H as HubError, Q as nodeStorage, S as loadSessions, V as checkHubName, W as loadHub, X as nowMinute, _ as initHub, _t as DATA_DIR, b as sessionBriefing, d as retroReport, dt as TASK_FILE, f as retroReportFromReceiver, ft as requireSlug, m as RETRY_RULES, mt as findHubRoot, nt as MAX_FILE_BYTES, p as receiverFromEnv, u as migrateHub, ut as MAINLINE_FILE, v as untilState, vt as WriteError, y as duplicateClaimLines } from "./host-bnHYIwuY.js";
4
4
  import { t as listProgress } from "./progress-1BfPg2zN.js";
5
- import { A as syncPaths, C as ENV, D as SETTINGS_FILE, E as syncSettings, S as ServerError, T as hubName, _ as readDaemon, a as remoteMode, b as oversizedFiles, c as planAnnounce, d as toolCallOf, f as startDaemon, g as probeDaemon, h as callDaemon, i as queuedCount, l as planFetch, m as DaemonUnavailable, n as hubWriter, o as fetchDenial, p as DaemonRunning, r as writtenLines, s as hookAnswer, t as CONFIRM_MS, u as planFlush, v as silentHint, w as earlierToken, x as ServerApi, y as readRemote } from "./operation--fJnUqvA.js";
5
+ import { C as ServerError, D as syncSettings, E as hubName, O as SETTINGS_FILE, S as ServerApi, T as earlierToken, _ as readDaemon, a as remoteMode, b as readRemote, c as planAnnounce, d as toolCallOf, f as startDaemon, g as probeDaemon, h as callDaemon, i as queuedCount, j as syncPaths, l as planFetch, m as DaemonUnavailable, n as hubWriter, o as fetchDenial, p as DaemonRunning, r as writtenLines, s as hookAnswer, t as CONFIRM_MS, u as planFlush, v as silentHint, w as ENV, x as oversizedFiles } from "./operation-DVcJgKM9.js";
6
6
  import { COMMAND_SPECS, OPTIONS } from "./facts.js";
7
7
  import { hookFallback, parsePayload, runHook } from "./hooks.js";
8
- import { c as watchHubGone, l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-DQVyIbpf.js";
8
+ import { f as CLI_VERSION, i as launchUi, l as startUiInBackground, r as exitCodeFor, u as watchHubGone } from "./launch-D80CYPcm.js";
9
9
  import { randomBytes } from "node:crypto";
10
10
  import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
11
11
  import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -654,7 +654,10 @@ const WRITES = {
654
654
  op: "mainline.new",
655
655
  input: ({ args, opts }) => ({
656
656
  slug: args[0],
657
- title: required(opts, "title", "--title \"重建 mainline 任務管理\"")
657
+ title: required(opts, "title", "--title \"重建 mainline 任務管理\""),
658
+ group: opts.group,
659
+ role: opts.role,
660
+ priority: opts.priority
658
661
  }),
659
662
  print: (_, { id, dir }) => done(`created ${id} ${inData(dir, MAINLINE_FILE)}`)
660
663
  }),
@@ -689,10 +692,37 @@ const WRITES = {
689
692
  slug: args[0],
690
693
  title: required(opts, "title", "--title \"重試不穩定的 VM\""),
691
694
  origin: opts.origin,
695
+ labels: opts.labels ? list(opts.labels) : void 0,
696
+ group: opts.group,
697
+ priority: opts.priority
698
+ }),
699
+ print: (_, { id, file }) => done(`created ${id} ${inData(file)}`)
700
+ }),
701
+ "new group": write({
702
+ op: "group.new",
703
+ input: ({ args, opts }) => ({
704
+ slug: args[0],
705
+ title: required(opts, "title", "--title \"量測修正\""),
706
+ priority: opts.priority,
707
+ trigger: opts.trigger,
708
+ origin: opts.origin,
692
709
  labels: opts.labels ? list(opts.labels) : void 0
693
710
  }),
694
711
  print: (_, { id, file }) => done(`created ${id} ${inData(file)}`)
695
712
  }),
713
+ "group set": write({
714
+ op: "group.set",
715
+ input: ({ args, opts }) => ({
716
+ id: args[0],
717
+ patch: {
718
+ status: opts.status,
719
+ priority: opts.priority,
720
+ trigger: opts.trigger,
721
+ title: opts.title
722
+ }
723
+ }),
724
+ print: (_, g) => done(`updated ${g.id} (${g.status})`)
725
+ }),
696
726
  "new finding": write({
697
727
  op: "finding.new",
698
728
  input: ({ args, opts }) => ({
@@ -707,9 +737,16 @@ const WRITES = {
707
737
  op: "mainline.set",
708
738
  input: ({ args, opts }) => ({
709
739
  id: args[0],
710
- status: required(opts, "status", "--status active")
740
+ status: opts.status,
741
+ group: opts.group,
742
+ role: opts.role,
743
+ priority: opts.priority
711
744
  }),
712
- print: (_, m) => done(`updated ${m.id} (${m.status})`)
745
+ print: (_, m) => done(`updated ${m.id} (${[
746
+ m.status,
747
+ m.group,
748
+ m.role
749
+ ].filter(Boolean).join(", ")})`)
713
750
  }),
714
751
  "mainline archive": write({
715
752
  op: "mainline.archive",
@@ -853,6 +890,17 @@ const WRITES = {
853
890
  }),
854
891
  print: (_, i) => done(`closed ${i.id}`)
855
892
  }),
893
+ "issue set": write({
894
+ op: "issue.set",
895
+ input: ({ args, opts }) => ({
896
+ id: args[0],
897
+ patch: {
898
+ group: opts.group,
899
+ priority: opts.priority
900
+ }
901
+ }),
902
+ print: (_, i) => done(`updated ${i.id}${i.group ? ` (${i.group})` : ""}`)
903
+ }),
856
904
  "issue reopen": write({
857
905
  op: "issue.reopen",
858
906
  input: ({ args }) => ({ id: args[0] }),
@@ -980,6 +1028,8 @@ const RUNS = {
980
1028
  launchUi(root, config.hub, config.ui?.port, {
981
1029
  port: opts.port,
982
1030
  open: !opts["no-open"]
1031
+ }).then((result) => {
1032
+ process.exitCode = exitCodeFor(result);
983
1033
  }).catch((error) => {
984
1034
  console.error(error instanceof WriteError ? `✗ ${error.message}` : error);
985
1035
  process.exitCode = 1;
@@ -1034,9 +1084,10 @@ const RUNS = {
1034
1084
  },
1035
1085
  "issue list": ({ root, opts }) => {
1036
1086
  const shown = loadHub(root).issues.filter((i) => opts.all || i.status !== "closed");
1037
- for (const i of shown) console.log(` ${i.id} ${i.status.padEnd(7)} ${i.title}${i.plannedIn ? ` → ${i.plannedIn}` : ""}${i.origin ? ` (from ${i.origin})` : ""}`);
1087
+ for (const i of shown) console.log(` ${i.id} ${i.status.padEnd(7)} ${i.title}${i.plannedIn ? ` → ${i.plannedIn}` : ""}${i.origin ? ` (from ${i.origin})` : ""}${i.group ? ` [${i.group}]` : ""}`);
1038
1088
  return done(`${shown.length} issues`);
1039
1089
  },
1090
+ "group list": ({ root, opts }) => printGroups(loadHub(root), opts.all ?? false),
1040
1091
  "finding list": ({ root, opts }) => {
1041
1092
  const shown = loadHub(root).findings.filter((f) => opts.all || f.status === "valid");
1042
1093
  for (const f of shown) console.log(` ${f.id} ${f.created} ${f.title}${f.labels?.length ? ` [${f.labels.join(", ")}]` : ""}`);
@@ -1279,18 +1330,39 @@ function required(opts, name, example) {
1279
1330
  if (!value) throw new WriteError(`--${name} is required, e.g. ${example}`);
1280
1331
  return value;
1281
1332
  }
1333
+ /** The group and role a mainline line ends with, like ` [w001, plan]`; nothing for an impl mainline outside a group. */
1334
+ const membership = (m) => {
1335
+ const tags = [m.group, m.role === "plan" ? "plan" : void 0].filter(Boolean);
1336
+ return tags.length ? ` [${tags.join(", ")}]` : "";
1337
+ };
1282
1338
  function printStatus(hub, all) {
1283
1339
  const shown = hub.mainlines.filter((m) => all || !m.archivedAt);
1284
1340
  console.log(`${hub.config.hub}: ${shown.length} mainlines${all ? "" : " (archived hidden)"}`);
1285
1341
  for (const m of shown) {
1286
1342
  const items = listProgress(m.tasks.flatMap((t) => t.checklist));
1287
1343
  const archived = m.archivedAt ? " [archived]" : "";
1288
- console.log(` ${m.id} ${m.status.padEnd(8)} ${items.done}/${items.total} ${m.title}${archived}`);
1344
+ console.log(` ${m.id} ${m.status.padEnd(8)} ${items.done}/${items.total} ${m.title}${membership(m)}${archived}`);
1289
1345
  }
1290
1346
  if (hub.problems.length === 0) return 0;
1291
1347
  console.error(`${hub.problems.length} problems found; run "mainflow check" for details`);
1292
1348
  return 1;
1293
1349
  }
1350
+ /**
1351
+ * Open groups by priority, P0 first and a group without one as P2, then by id; each with its trigger and its members:
1352
+ * plan mainlines first, then impl mainlines, then issues. Archived and closed members are left out unless --all.
1353
+ */
1354
+ function printGroups(hub, all) {
1355
+ const rank = (p) => (p ?? "P2").slice(1);
1356
+ const shown = hub.groups.filter((g) => all || g.status !== "done").sort((a, b) => rank(a.priority).localeCompare(rank(b.priority)) || a.id.localeCompare(b.id));
1357
+ for (const g of shown) {
1358
+ console.log(` ${g.id} ${g.status.padEnd(4)} ${g.priority ?? "P2"} ${g.title}${g.trigger ? ` (時機:${g.trigger})` : ""}`);
1359
+ const mainlines = hub.mainlines.filter((m) => m.group === g.id && (all || !m.archivedAt && !CLOSED_STATUS.mainline.includes(m.status)));
1360
+ const issues = hub.issues.filter((i) => i.group === g.id && (all || !i.archivedAt && i.status !== "closed"));
1361
+ for (const m of [...mainlines.filter((m) => m.role === "plan"), ...mainlines.filter((m) => m.role !== "plan")]) console.log(` ${m.id} ${(m.role ?? "impl").padEnd(4)} ${m.status.padEnd(8)} ${m.title}`);
1362
+ for (const i of issues) console.log(` ${i.id} issue ${i.status.padEnd(8)} ${i.title}`);
1363
+ }
1364
+ return done(`${shown.length} groups`);
1365
+ }
1294
1366
  function printProblems(problems, mark = "✗") {
1295
1367
  for (const problem of problems) console.error(`${mark} ${problem.file}${problem.field ? ` › ${problem.field}` : ""}: ${problem.message}`);
1296
1368
  }
@@ -376,6 +376,8 @@ const ID_PATTERNS = {
376
376
  action: /^a\d{3}$/,
377
377
  issue: /^i\d{3}$/,
378
378
  finding: /^f\d{3}$/,
379
+ /** A hub-level group of mainlines and issues; groups/w{NNN}-{slug}.md */
380
+ group: /^w\d{3}$/,
379
381
  grant: /^g\d{3}$/
380
382
  };
381
383
  const CHECKLIST_REF = /^m\d{3}\/t\d{3}#c\d{3}$/;
@@ -422,6 +424,11 @@ const REF_FORMS = [
422
424
  kind: "finding",
423
425
  pattern: /^(f\d{3})$/,
424
426
  example: "f001"
427
+ },
428
+ {
429
+ kind: "group",
430
+ pattern: /^(w\d{3})$/,
431
+ example: "w001"
425
432
  }
426
433
  ];
427
434
  /** The forms a reference can take, for error messages. */
@@ -488,13 +495,17 @@ function parseLocal(input) {
488
495
  kind: "finding",
489
496
  finding: a
490
497
  };
498
+ case "group": return {
499
+ kind: "group",
500
+ group: a
501
+ };
491
502
  }
492
503
  }
493
504
  return null;
494
505
  }
495
506
  /** The reference a blocked task waits for, when its whole `waitingFor` text is one; free text such as 使用者 gives undefined. */
496
507
  const waitingForRef = (waitingFor) => waitingFor && parseRef(waitingFor) ? waitingFor : void 0;
497
- /** The mainline a reference sits in, or undefined for hub-level issues and findings. */
508
+ /** The mainline a reference sits in, or undefined for hub-level issues, findings and groups. */
498
509
  const refMainline = (ref) => "mainline" in ref ? ref.mainline : void 0;
499
510
  /** The example reference for a kind, used in error messages. */
500
511
  const refExample = (kind) => REF_FORMS.find((f) => f.kind === kind)?.example ?? "";
@@ -549,13 +560,16 @@ const SESSION_STATUS = [
549
560
  "ended",
550
561
  "handed-off"
551
562
  ];
563
+ /** A group is open while any of the work it gathers is still to be done, and done once it is all settled. */
564
+ const GROUP_STATUS = ["open", "done"];
552
565
  /** Statuses after which an entity is closed and carries closedAt. */
553
566
  const CLOSED_STATUS = {
554
567
  mainline: ["done", "dropped"],
555
568
  task: ["done", "dropped"],
556
569
  decision: ["superseded"],
557
570
  issue: ["closed"],
558
- finding: ["obsolete"]
571
+ finding: ["obsolete"],
572
+ group: ["done"]
559
573
  };
560
574
  /** Default categories, aligned with Conventional Commits types; a hub adds its own in config.yaml. */
561
575
  const DEFAULT_CATEGORIES = [
@@ -609,6 +623,8 @@ const FINDING_SECTIONS = [
609
623
  "影響",
610
624
  "做法"
611
625
  ];
626
+ /** 說明 says what the group gathers and why; its first paragraph is the summary the interface shows on the group's heading. */
627
+ const GROUP_SECTIONS = ["說明"];
612
628
  const SURVEY_SECTION = "現況查核";
613
629
  const SURVEY_LINES = [
614
630
  {
@@ -666,7 +682,7 @@ const linkSchema = z.object({
666
682
  note: text.optional()
667
683
  });
668
684
  /**
669
- * Attributes every document carries (mainline, task, decision, issue, finding) besides id, title, status
685
+ * Attributes every document carries (mainline, task, decision, issue, finding, group) besides id, title, status
670
686
  * and priority, which each schema lists first so the front matter reads the same way in every file. `category` is checked
671
687
  * against DEFAULT_CATEGORIES plus config.categories when the hub loads, not here, because the schema does
672
688
  * not see the config. `updated` is not stored: readers derive it from the file.
@@ -683,6 +699,8 @@ const documentShape = {
683
699
  links: z.array(linkSchema).optional()
684
700
  };
685
701
  const priority = z.enum(PRIORITIES).optional();
702
+ /** The hub-level group a mainline or an issue belongs to, at most one; work that goes together, apart from what depends on what */
703
+ const group = id("group").optional();
686
704
  const evidenceRequired = (item, ctx) => {
687
705
  if (item.done && !item.evidence) ctx.addIssue({
688
706
  code: "custom",
@@ -744,12 +762,21 @@ const grantSchema = z.object({
744
762
  message: parsed.message
745
763
  });
746
764
  });
765
+ /**
766
+ * What a mainline is for. A plan mainline sizes the work, splits it into impl mainlines, coordinates them and makes
767
+ * the final check; an impl mainline carries a piece of the work out. A mainline without a role is an impl one, and
768
+ * a group may hold no plan mainline at all: a small piece of work needs none.
769
+ */
770
+ const MAINLINE_ROLES = ["plan", "impl"];
771
+ const DEFAULT_ROLE = "impl";
747
772
  const mainlineSchema = z.object({
748
773
  id: id("mainline"),
749
774
  title: text,
750
775
  status: z.enum(MAINLINE_STATUS),
751
776
  priority,
752
777
  ...documentShape,
778
+ group,
779
+ role: z.enum(MAINLINE_ROLES).optional(),
753
780
  due: date.optional(),
754
781
  /** Hidden from the default listings; independent of status, a done mainline may stay visible */
755
782
  archivedAt: date.optional(),
@@ -980,6 +1007,7 @@ const issueSchema = z.object({
980
1007
  status: z.enum(ISSUE_STATUS),
981
1008
  priority,
982
1009
  ...documentShape,
1010
+ group,
983
1011
  order: z.number().int().positive().optional(),
984
1012
  due: date.optional(),
985
1013
  plannedIn: z.string().regex(/^m\d{3}\/t\d{3}$/).optional(),
@@ -1010,6 +1038,21 @@ const findingSchema = z.object({
1010
1038
  /** Hidden from the default views; independent of status, an obsolete finding stays visible until archived */
1011
1039
  archivedAt: date.optional()
1012
1040
  });
1041
+ /**
1042
+ * Front matter of groups/w{NNN}-{slug}.md: mainlines and issues that belong together, such as a plan mainline with the
1043
+ * impl mainlines it split off, or the issues to take up together when a measurement is fixed. Membership is written
1044
+ * on each member (`group`), so a group file never lists its members and the two cannot disagree. A group is apart
1045
+ * from dependencies: depends-on between groups orders them, and priority and trigger say how urgent and when.
1046
+ */
1047
+ const groupSchema = z.object({
1048
+ id: id("group"),
1049
+ title: text,
1050
+ status: z.enum(GROUP_STATUS),
1051
+ priority,
1052
+ ...documentShape,
1053
+ /** When to take the group's work up, in a phrase, e.g. 量測修正時 */
1054
+ trigger: text.optional()
1055
+ });
1013
1056
  /** One line of ops/{YYYY-MM}.log: an external operation, append-only. */
1014
1057
  const opsEntrySchema = z.object({
1015
1058
  at: datetime,
@@ -1040,4 +1083,4 @@ function parseOpsLine(line) {
1040
1083
  return parsed.success ? parsed.data : null;
1041
1084
  }
1042
1085
  //#endregion
1043
- export { describeRequest as $, WORK_STATUS as A, parseRef as B, SESSION_STATUS as C, TASK_REQUIRED_SECTIONS as D, SURVEY_SECTION as E, issueSchema as F, taskSchema as G, refExample as H, mainlineSchema as I, OFFSET as J, waitingForAction as K, opsEntrySchema as L, decisionSchema as M, findingSchema as N, TASK_SECTIONS as O, formatOpsEntry as P, GRANT_TARGET_FORMS as Q, parseActionRef as R, SESSION_ID as S, SURVEY_LINES as T, refMainline as U, parseUntil as V, sessionSchema as W, GRANT_ACTIONS as X, stampMs as Y, GRANT_FORMS as Z, MERGE_STATES as _, DECISION_SECTIONS as a, RESERVED_HUB_NAMES as b, FINDING_SECTIONS as c, ID_PATTERNS as d, grantCovers as et, ISSUE_SECTIONS as f, MAINLINE_STATUS as g, MAINLINE_SECTIONS as h, DECISION_BASES as i, configSchema as j, UNTIL_FORMS as k, FINDING_STATUS as l, LINK_RELS as m, CLOSED_STATUS as n, grantRepo as nt, DECISION_STATUS as o, ISSUE_STATUS as p, waitingForRef as q, DECIDERS as r, parseGrantTarget as rt, DEFAULT_CATEGORIES as s, CHECKLIST_REF as t, grantForm as tt, FORMAT_VERSION as u, PRIORITIES as v, SLUG as w, SERVES_REF as x, REF_EXAMPLES as y, parseOpsLine as z };
1086
+ export { OFFSET as $, SURVEY_SECTION as A, issueSchema as B, REF_EXAMPLES as C, SESSION_STATUS as D, SESSION_ID as E, configSchema as F, parseRef as G, opsEntrySchema as H, decisionSchema as I, refMainline as J, parseUntil as K, findingSchema as L, TASK_SECTIONS as M, UNTIL_FORMS as N, SLUG as O, WORK_STATUS as P, waitingForRef as Q, formatOpsEntry as R, PRIORITIES as S, SERVES_REF as T, parseActionRef as U, mainlineSchema as V, parseOpsLine as W, taskSchema as X, sessionSchema as Y, waitingForAction as Z, LINK_RELS as _, DECISION_SECTIONS as a, grantCovers as at, MAINLINE_STATUS as b, DEFAULT_ROLE as c, parseGrantTarget as ct, FORMAT_VERSION as d, stampMs as et, GROUP_SECTIONS as f, ISSUE_STATUS as g, ISSUE_SECTIONS as h, DECISION_BASES as i, describeRequest as it, TASK_REQUIRED_SECTIONS as j, SURVEY_LINES as k, FINDING_SECTIONS as l, ID_PATTERNS as m, CLOSED_STATUS as n, GRANT_FORMS as nt, DECISION_STATUS as o, grantForm as ot, GROUP_STATUS as p, refExample as q, DECIDERS as r, GRANT_TARGET_FORMS as rt, DEFAULT_CATEGORIES as s, grantRepo as st, CHECKLIST_REF as t, GRANT_ACTIONS as tt, FINDING_STATUS as u, MAINLINE_ROLES as v, RESERVED_HUB_NAMES as w, MERGE_STATES as x, MAINLINE_SECTIONS as y, groupSchema as z };
package/dist/facts.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as Until, c as ID_PATTERNS, f as SERVES_REF, h as SurveyLine, o as FORMAT_VERSION, p as SLUG, r as CHECKLIST_REF } from "./index-ihgXABQc.js";
1
+ import { g as SurveyLine, l as ID_PATTERNS, m as SLUG, o as FORMAT_VERSION, p as SERVES_REF, r as CHECKLIST_REF, v as Until } from "./index-DgYB5u1f.js";
2
2
  //#region src/facts.d.ts
3
3
  export interface CommandSpec {
4
4
  usage: string;
@@ -106,7 +106,7 @@ export declare const COMMAND_SPECS: {
106
106
  };
107
107
  readonly 'action set': {
108
108
  readonly usage: 'action set <a|session#a> [--status] [--reason --waiting-for --until] [--text] [--serves] [--before <a>] [--note <text>] [--session <session>]';
109
- readonly summary: 'Change, reorder or finish an action; --status blocked needs --reason, and leaving blocked clears it. --status doing reminds like action add. A handed-off action keeps its note';
109
+ readonly summary: 'Change, reorder or finish an action; --status blocked needs --reason, and leaving blocked clears it. --status doing reminds like action add and moves a planning mainline it serves to active. A handed-off action keeps its note';
110
110
  readonly args: 1;
111
111
  readonly needsHub: true;
112
112
  };
@@ -124,8 +124,8 @@ export declare const COMMAND_SPECS: {
124
124
  readonly needsHub: false;
125
125
  };
126
126
  readonly 'new mainline': {
127
- readonly usage: 'new mainline <slug> --title <title>';
128
- readonly summary: 'Create mainline.md with the next id and the section template; write the rest in the file';
127
+ readonly usage: 'new mainline <slug> --title <title> [--group <w>] [--role plan|impl] [--priority P0|P1|P2|P3]';
128
+ readonly summary: 'Create mainline.md with the next id and the section template; write the rest in the file. --group puts it in an existing group; --role plan makes it the mainline that splits the work, coordinates it and makes the final check (without --role it is an impl mainline)';
129
129
  readonly args: 1;
130
130
  readonly needsHub: true;
131
131
  };
@@ -142,8 +142,8 @@ export declare const COMMAND_SPECS: {
142
142
  readonly needsHub: true;
143
143
  };
144
144
  readonly 'new issue': {
145
- readonly usage: 'new issue <slug> --title <title> [--origin <ref>] [--labels a,b]';
146
- readonly summary: 'Create an issue file for a follow-up that must outlive its mainline';
145
+ readonly usage: 'new issue <slug> --title <title> [--origin <ref>] [--labels a,b] [--group <w>] [--priority P0|P1|P2|P3]';
146
+ readonly summary: 'Create an issue file for a follow-up that must outlive its mainline; --group puts it with the work it is taken up with';
147
147
  readonly args: 1;
148
148
  readonly needsHub: true;
149
149
  };
@@ -153,9 +153,27 @@ export declare const COMMAND_SPECS: {
153
153
  readonly args: 1;
154
154
  readonly needsHub: true;
155
155
  };
156
+ readonly 'new group': {
157
+ readonly usage: 'new group <slug> --title <title> [--priority P0|P1|P2|P3] [--trigger <when>] [--origin <ref>] [--labels a,b]';
158
+ readonly summary: 'Create a group file for mainlines and issues taken up together, apart from dependencies; --trigger says when, e.g. "量測修正時". Members join with --group on new mainline, new issue, mainline set or issue set';
159
+ readonly args: 1;
160
+ readonly needsHub: true;
161
+ };
162
+ readonly 'group set': {
163
+ readonly usage: 'group set <w> [--status open|done] [--priority P0|P1|P2|P3] [--trigger <when>] [--title <title>]';
164
+ readonly summary: 'Change a group\'s status, priority, trigger or title; an empty --trigger "" removes it. Relations between groups go in links in the file';
165
+ readonly args: 1;
166
+ readonly needsHub: true;
167
+ };
168
+ readonly 'group list': {
169
+ readonly usage: 'group list [--all]';
170
+ readonly summary: 'List open groups by priority with their trigger and members, plan mainlines first (--all includes done groups)';
171
+ readonly args: 0;
172
+ readonly needsHub: true;
173
+ };
156
174
  readonly 'mainline set': {
157
- readonly usage: 'mainline set <m> --status <status>';
158
- readonly summary: 'Change status; done needs every criterion met and no proposed decision';
175
+ readonly usage: 'mainline set <m> [--status <status>] [--group <w|none>] [--role plan|impl] [--priority P0|P1|P2|P3]';
176
+ readonly summary: 'Change status, group, role or priority; done needs every criterion met and no proposed decision. A planning mainline becomes active by itself when work on it starts, so use --status for paused, done and dropped. --group none leaves the group';
159
177
  readonly args: 1;
160
178
  readonly needsHub: true;
161
179
  };
@@ -197,7 +215,7 @@ export declare const COMMAND_SPECS: {
197
215
  };
198
216
  readonly 'task set': {
199
217
  readonly usage: 'task set <m/t> [--status] [--reason --waiting-for --signal] [--repo] [--branch --worktree --base --merge]';
200
- readonly summary: 'Change status, repository or branch state; --status blocked needs --reason';
218
+ readonly summary: 'Change status, repository or branch state; --status blocked needs --reason. --status doing also moves a planning mainline to active';
201
219
  readonly args: 1;
202
220
  readonly needsHub: true;
203
221
  };
@@ -215,7 +233,7 @@ export declare const COMMAND_SPECS: {
215
233
  };
216
234
  readonly 'item check': {
217
235
  readonly usage: 'item check <m/t#c> --evidence <evidence>';
218
- readonly summary: 'Check an item with evidence';
236
+ readonly summary: 'Check an item with evidence; also moves a planning mainline to active';
219
237
  readonly args: 1;
220
238
  readonly needsHub: true;
221
239
  };
@@ -273,6 +291,12 @@ export declare const COMMAND_SPECS: {
273
291
  readonly args: 1;
274
292
  readonly needsHub: true;
275
293
  };
294
+ readonly 'issue set': {
295
+ readonly usage: 'issue set <i> [--group <w|none>] [--priority P0|P1|P2|P3]';
296
+ readonly summary: 'Put an issue in a group, or take it out with --group none, and change its priority';
297
+ readonly args: 1;
298
+ readonly needsHub: true;
299
+ };
276
300
  readonly 'issue list': {
277
301
  readonly usage: 'issue list [--all]';
278
302
  readonly summary: 'List open and planned issues (--all includes closed)';
@@ -316,7 +340,7 @@ export declare const COMMAND_SPECS: {
316
340
  readonly needsHub: true;
317
341
  };
318
342
  readonly 'section append': {
319
- readonly usage: 'section append <m|m/t|m/d|i|f> <heading> <text>';
343
+ readonly usage: 'section append <m|m/t|m/d|i|f|w> <heading> <text>';
320
344
  readonly summary: 'Add text at the end of a section of a document\'s body, such as a line under a task\'s 紀錄; two appends keep both, in order. The text is taken as written, even when it starts with "-"; a list line follows the list directly, other text comes after a blank line';
321
345
  readonly args: 3;
322
346
  readonly literalLast: true;
@@ -468,6 +492,18 @@ export declare const OPTIONS: {
468
492
  readonly labels: {
469
493
  readonly type: 'string';
470
494
  };
495
+ readonly group: {
496
+ readonly type: 'string';
497
+ };
498
+ readonly role: {
499
+ readonly type: 'string';
500
+ };
501
+ readonly priority: {
502
+ readonly type: 'string';
503
+ };
504
+ readonly trigger: {
505
+ readonly type: 'string';
506
+ };
471
507
  readonly target: {
472
508
  readonly type: 'string';
473
509
  };
@@ -515,6 +551,7 @@ export declare const STATUSES: {
515
551
  readonly decision: readonly ["proposed", "accepted", "superseded"];
516
552
  readonly issue: readonly ["open", "planned", "closed"];
517
553
  readonly finding: readonly ["valid", "obsolete"];
554
+ readonly group: readonly ["open", "done"];
518
555
  readonly session: readonly ["active", "paused", "ended", "handed-off"];
519
556
  readonly merge: readonly ["open", "merged", "abandoned"];
520
557
  readonly decider: readonly ["user", "ai", "relay"];
@@ -549,6 +586,10 @@ export declare const REFERENCES: readonly [{
549
586
  readonly target: 'finding';
550
587
  readonly format: 'f{NNN}';
551
588
  readonly example: 'f001';
589
+ }, {
590
+ readonly target: 'group';
591
+ readonly format: 'w{NNN}';
592
+ readonly example: 'w001';
552
593
  }, {
553
594
  readonly target: 'another hub';
554
595
  readonly format: '{hub}:{reference}';
@@ -616,6 +657,7 @@ export declare const LAYOUT: {
616
657
  readonly task: '.mainflow/mainlines/m{NNN}-{slug}/tasks/t{NNN}-{slug}/task.md';
617
658
  readonly issue: '.mainflow/issues/i{NNN}-{slug}.md';
618
659
  readonly finding: '.mainflow/findings/f{NNN}-{slug}.md';
660
+ readonly group: '.mainflow/groups/w{NNN}-{slug}.md';
619
661
  readonly ops: '.mainflow/ops/{YYYY-MM}.log';
620
662
  readonly sessions: '.mainflow/local/sessions/{sessionId}.yaml';
621
663
  };
@@ -626,6 +668,7 @@ export declare const CLOSED_STATUSES: {
626
668
  readonly decision: readonly ['superseded'];
627
669
  readonly issue: readonly ['closed'];
628
670
  readonly finding: readonly ['obsolete'];
671
+ readonly group: readonly ['done'];
629
672
  };
630
673
  /** Classification every document shares: one category from the list (a hub adds more in config.yaml), P0 to P3. */
631
674
  export declare const CLASSIFICATION: {
@@ -633,6 +676,15 @@ export declare const CLASSIFICATION: {
633
676
  readonly priorities: readonly ["P0", "P1", "P2", "P3"];
634
677
  readonly defaultPriority: "P2";
635
678
  };
679
+ /**
680
+ * What a mainline is for: a plan mainline sizes the work, splits it into impl mainlines, coordinates them and makes
681
+ * the final check; an impl mainline carries a piece out. Without a role a mainline is `default`. A group needs no plan
682
+ * mainline: whether to open one, and how many impl mainlines, follows from how big the work is.
683
+ */
684
+ export declare const ROLES: {
685
+ readonly values: readonly ["plan", "impl"];
686
+ readonly default: "impl";
687
+ };
636
688
  /** Body sections each document is created with; the task sections in `taskRequired` must be filled while it is open. */
637
689
  export declare const SECTIONS: {
638
690
  readonly mainline: readonly ["概述", "背景與前置", "現況查核", "範圍與不做", "限制"];
@@ -641,6 +693,7 @@ export declare const SECTIONS: {
641
693
  readonly decision: readonly ["背景", "選項", "建議", "決定", "理由"];
642
694
  readonly issue: readonly ["背景", "要做的事", "完成條件"];
643
695
  readonly finding: readonly ["發現", "影響", "做法"];
696
+ readonly group: readonly ["說明"];
644
697
  };
645
698
  /**
646
699
  * The mainline section that records what was checked before tasks were written, one `- {label}:{text}`
@@ -659,11 +712,12 @@ export interface FieldSpec {
659
712
  }
660
713
  /** Every front matter field of each document and list entry; facts.test keeps it equal to the schema. */
661
714
  export declare const FIELDS: {
662
- readonly mainline: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
715
+ readonly mainline: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
663
716
  readonly task: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
664
717
  readonly decision: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
665
- readonly issue: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec];
718
+ readonly issue: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
666
719
  readonly finding: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec];
720
+ readonly group: readonly [...FieldSpec[], FieldSpec];
667
721
  readonly criterion: FieldSpec[];
668
722
  readonly checklistItem: readonly [...FieldSpec[], FieldSpec];
669
723
  readonly grant: readonly [FieldSpec, FieldSpec, FieldSpec, FieldSpec];