mainflow 0.11.2 → 0.13.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 +50 -3
- package/dist/{dist-D2NK-QQP.js → dist-BQmXEfAf.js} +7 -2
- package/dist/facts.d.ts +29 -2
- package/dist/facts.js +29 -4
- package/dist/hooks.js +36 -12
- package/dist/{host-C3MyDGB_.js → host-daQGe7no.js} +339 -100
- package/dist/host.js +1 -1
- package/dist/{launch-T0sXbTgT.js → launch-JK8l8BYu.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/{ui-BTLe6I--.js → ui-BoBBdpZH.js} +14 -4
- package/dist/web/assets/index-Ckyguktw.css +1 -0
- package/dist/web/assets/index-LWzVLj2W.js +81 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-CZjPKVxY.js +0 -78
- package/dist/web/assets/index-miwi9q2z.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { $ as setFindingStatus, A as addAction, C as setCriterionText, D as updateTask, E as supersedeDecision, F as setSessionArchived, J as createFinding, K as appendOps, L as setSessionStatus, M as handoffSession, O as sessionBriefing, P as readSession, Q as setFindingArchived, S as setCriterion, V as updateAction, X as planIssue, Y as createIssue, Z as reopenIssue, _ as createTask, b as setArchived, ct as TASK_FILE, et as setIssueArchived, f as acceptDecision, g as createMainline, h as createDecision, l as migrateHub, m as addRepo, nt as loadHub, p as addHub, pt as findHubRoot, q as closeIssue, st as MAINLINE_FILE, tt as HubError, ut as WriteError, v as dropChecklistItem, w as setMainlineStatus, x as setChecklistItem, y as initHub, z as startSession } from "./host-daQGe7no.js";
|
|
3
|
+
import { N as parseRef, P as refExample } from "./dist-BQmXEfAf.js";
|
|
3
4
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
4
5
|
import { COMMAND_SPECS, OPTIONS } from "./facts.js";
|
|
5
6
|
import { runHook } from "./hooks.js";
|
|
6
|
-
import { l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-
|
|
7
|
+
import { l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-JK8l8BYu.js";
|
|
7
8
|
import { readFileSync, realpathSync } from "node:fs";
|
|
8
9
|
import { join, relative } from "node:path";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -54,6 +55,10 @@ const RUNS = {
|
|
|
54
55
|
});
|
|
55
56
|
return done(`started ${session.id}${session.actions.length ? ` with ${session.actions.length} carried actions` : ""}`);
|
|
56
57
|
},
|
|
58
|
+
"session handoff": ({ root, args, opts }) => {
|
|
59
|
+
const session = handoffSession(root, sessionOf(opts), args[0]);
|
|
60
|
+
return done(`${session.id} took over ${session.actions.filter((a) => a.status === "todo" || a.status === "doing").length} actions from ${args[0]}`);
|
|
61
|
+
},
|
|
57
62
|
"session show": ({ root, args, opts }) => {
|
|
58
63
|
const id = sessionOf(opts, args[0]);
|
|
59
64
|
const session = readSession(root, id);
|
|
@@ -155,6 +160,13 @@ const RUNS = {
|
|
|
155
160
|
setCriterion(root, args[0], false);
|
|
156
161
|
return done(`unchecked ${args[0]}`);
|
|
157
162
|
},
|
|
163
|
+
"criterion set": ({ root, args, opts }) => {
|
|
164
|
+
setCriterionText(root, args[0], {
|
|
165
|
+
text: opts.text,
|
|
166
|
+
doneWhen: opts["done-when"]
|
|
167
|
+
});
|
|
168
|
+
return done(`updated ${args[0]}`);
|
|
169
|
+
},
|
|
158
170
|
"task set": ({ root, args, opts }) => {
|
|
159
171
|
const blockedBy = opts.reason ? {
|
|
160
172
|
reason: opts.reason,
|
|
@@ -172,6 +184,18 @@ const RUNS = {
|
|
|
172
184
|
});
|
|
173
185
|
return done(`updated ${args[0]} (${t.status})`);
|
|
174
186
|
},
|
|
187
|
+
"task show": ({ root, args }) => {
|
|
188
|
+
const { mainline, task } = findTask(loadHub(root), args[0]);
|
|
189
|
+
console.log(`${mainline.id}/${task.id} ${task.status.padEnd(8)} ${task.kind} ${task.title}`);
|
|
190
|
+
if (task.checklist.length === 0) console.log(" (no checklist items)");
|
|
191
|
+
for (const c of task.checklist) {
|
|
192
|
+
const mark = c.done ? "[x]" : "[ ]";
|
|
193
|
+
const state = c.dropped ? " (dropped)" : "";
|
|
194
|
+
console.log(` ${mark} ${c.id} ${c.text}${state}`);
|
|
195
|
+
}
|
|
196
|
+
for (const link of task.links ?? []) console.log(` ${link.rel} → ${link.to}${link.note ? ` ${link.note}` : ""}`);
|
|
197
|
+
return done(`${task.checklist.length} checklist items`);
|
|
198
|
+
},
|
|
175
199
|
"item check": ({ root, args, opts }) => {
|
|
176
200
|
setChecklistItem(root, args[0], true, opts.evidence);
|
|
177
201
|
return done(`checked ${args[0]}`);
|
|
@@ -216,13 +240,18 @@ const RUNS = {
|
|
|
216
240
|
return done(`logged ${entry.at} ${entry.operation}`);
|
|
217
241
|
},
|
|
218
242
|
"ops list": ({ root, opts }) => {
|
|
219
|
-
const shown = loadHub(root).ops.filter((o) => !opts.month || o.at.startsWith(opts.month));
|
|
243
|
+
const shown = loadHub(root).ops.filter((o) => (!opts.month || o.at.startsWith(opts.month)) && (!opts.ref || o.ref === opts.ref));
|
|
220
244
|
for (const o of shown) console.log(` ${o.at} ${o.target} ${o.operation} ${o.result}${o.ref ? ` ${o.ref}` : ""}`);
|
|
221
245
|
return done(`${shown.length} operations`);
|
|
222
246
|
},
|
|
223
247
|
"decision supersede": ({ root, args, opts }) => {
|
|
224
248
|
const d = supersedeDecision(root, args[0], required(opts, "by", "--by d002"));
|
|
225
249
|
return done(`superseded ${args[0]} by ${d.supersededBy}`);
|
|
250
|
+
},
|
|
251
|
+
"decision list": ({ root, args }) => {
|
|
252
|
+
const mainline = findMainline(loadHub(root), args[0]);
|
|
253
|
+
for (const d of mainline.decisions) console.log(` ${mainline.id}/${d.id} ${d.status.padEnd(10)} ${d.title}`);
|
|
254
|
+
return done(`${mainline.decisions.length} decisions`);
|
|
226
255
|
}
|
|
227
256
|
};
|
|
228
257
|
const COMMANDS = Object.fromEntries(Object.keys(COMMAND_SPECS).map((name) => [name, {
|
|
@@ -300,6 +329,24 @@ function sessionOf(opts, arg) {
|
|
|
300
329
|
return id;
|
|
301
330
|
}
|
|
302
331
|
const list = (value) => (value ?? "").split(",").map((v) => v.trim()).filter(Boolean);
|
|
332
|
+
/** The mainline a reference names; throws with the same message resolve.ts gives when it is missing. */
|
|
333
|
+
function findMainline(hub, id) {
|
|
334
|
+
const mainline = hub.mainlines.find((m) => m.id === id);
|
|
335
|
+
if (!mainline) throw new WriteError(`mainline ${id} not found; run "mainflow status --all" to list mainlines`);
|
|
336
|
+
return mainline;
|
|
337
|
+
}
|
|
338
|
+
/** The task a `m/t` reference names, with the mainline it sits in. */
|
|
339
|
+
function findTask(hub, refInput) {
|
|
340
|
+
const ref = parseRef(refInput);
|
|
341
|
+
if (ref?.kind !== "task") throw new WriteError(`expected a task reference like ${refExample("task")}, got "${refInput}"`);
|
|
342
|
+
const mainline = findMainline(hub, ref.mainline);
|
|
343
|
+
const task = mainline.tasks.find((t) => t.id === ref.task);
|
|
344
|
+
if (!task) throw new WriteError(`task ${refInput} not found`);
|
|
345
|
+
return {
|
|
346
|
+
mainline,
|
|
347
|
+
task
|
|
348
|
+
};
|
|
349
|
+
}
|
|
303
350
|
function required(opts, name, example) {
|
|
304
351
|
const value = opts[name];
|
|
305
352
|
if (!value) throw new WriteError(`--${name} is required, e.g. ${example}`);
|
|
@@ -124,6 +124,8 @@ function parseLocal(input) {
|
|
|
124
124
|
}
|
|
125
125
|
return null;
|
|
126
126
|
}
|
|
127
|
+
/** The reference a blocked task waits for, when its whole `waitingFor` text is one; free text such as 使用者 gives undefined. */
|
|
128
|
+
const waitingForRef = (waitingFor) => waitingFor && parseRef(waitingFor) ? waitingFor : void 0;
|
|
127
129
|
/** The mainline a reference sits in, or undefined for hub-level issues and findings. */
|
|
128
130
|
const refMainline = (ref) => "mainline" in ref ? ref.mainline : void 0;
|
|
129
131
|
/** The example reference for a kind, used in error messages. */
|
|
@@ -382,7 +384,10 @@ const taskSchema = z.object({
|
|
|
382
384
|
repo: z.string().min(1).nullable().optional(),
|
|
383
385
|
startedAt: date.optional(),
|
|
384
386
|
checklist: z.array(checklistItemSchema),
|
|
385
|
-
/**
|
|
387
|
+
/**
|
|
388
|
+
* Why a blocked task cannot move, what it waits for, and which signal says the wait is over. `waitingFor`
|
|
389
|
+
* is free text, or a reference such as m031/t002 when the whole text is one (see waitingForRef).
|
|
390
|
+
*/
|
|
386
391
|
blockedBy: z.object({
|
|
387
392
|
reason: text,
|
|
388
393
|
waitingFor: text.optional(),
|
|
@@ -508,4 +513,4 @@ function parseOpsLine(line) {
|
|
|
508
513
|
return parsed.success ? parsed.data : null;
|
|
509
514
|
}
|
|
510
515
|
//#endregion
|
|
511
|
-
export { mainlineSchema as A, TASK_SECTIONS as C, findingSchema as D, decisionSchema as E, refMainline as F, sessionSchema as I, taskSchema as L, parseOpsLine as M, parseRef as N, formatOpsEntry as O, refExample as P, TASK_REQUIRED_SECTIONS as S, configSchema as T, PRIORITIES as _, DECISION_STATUS as a, SESSION_STATUS as b, FINDING_STATUS as c, ISSUE_SECTIONS as d, ISSUE_STATUS as f, MERGE_STATES as g, MAINLINE_STATUS as h, DECISION_SECTIONS as i, opsEntrySchema as j, issueSchema as k, FORMAT_VERSION as l, MAINLINE_SECTIONS as m, CLOSED_STATUS as n, DEFAULT_CATEGORIES as o, LINK_RELS as p, DECIDERS as r, FINDING_SECTIONS as s, CHECKLIST_REF as t, ID_PATTERNS as u, REF_EXAMPLES as v, WORK_STATUS as w, SLUG as x, RESERVED_HUB_NAMES as y };
|
|
516
|
+
export { mainlineSchema as A, TASK_SECTIONS as C, findingSchema as D, decisionSchema as E, refMainline as F, sessionSchema as I, taskSchema as L, parseOpsLine as M, parseRef as N, formatOpsEntry as O, refExample as P, waitingForRef as R, TASK_REQUIRED_SECTIONS as S, configSchema as T, PRIORITIES as _, DECISION_STATUS as a, SESSION_STATUS as b, FINDING_STATUS as c, ISSUE_SECTIONS as d, ISSUE_STATUS as f, MERGE_STATES as g, MAINLINE_STATUS as h, DECISION_SECTIONS as i, opsEntrySchema as j, issueSchema as k, FORMAT_VERSION as l, MAINLINE_SECTIONS as m, CLOSED_STATUS as n, DEFAULT_CATEGORIES as o, LINK_RELS as p, DECIDERS as r, FINDING_SECTIONS as s, CHECKLIST_REF as t, ID_PATTERNS as u, REF_EXAMPLES as v, WORK_STATUS as w, SLUG as x, RESERVED_HUB_NAMES as y };
|
package/dist/facts.d.ts
CHANGED
|
@@ -73,6 +73,12 @@ export declare const COMMAND_SPECS: {
|
|
|
73
73
|
readonly args: 0;
|
|
74
74
|
readonly needsHub: true;
|
|
75
75
|
};
|
|
76
|
+
readonly 'session handoff': {
|
|
77
|
+
readonly usage: 'session handoff <session> [--session <session>]';
|
|
78
|
+
readonly summary: 'Take over the unfinished actions of another session into this one';
|
|
79
|
+
readonly args: 1;
|
|
80
|
+
readonly needsHub: true;
|
|
81
|
+
};
|
|
76
82
|
readonly 'session show': {
|
|
77
83
|
readonly usage: 'session show [session]';
|
|
78
84
|
readonly summary: 'Print the briefing a hook would give this session';
|
|
@@ -183,12 +189,24 @@ export declare const COMMAND_SPECS: {
|
|
|
183
189
|
readonly args: 1;
|
|
184
190
|
readonly needsHub: true;
|
|
185
191
|
};
|
|
192
|
+
readonly 'criterion set': {
|
|
193
|
+
readonly usage: 'criterion set <m#r> [--text <text>] [--done-when <text>]';
|
|
194
|
+
readonly summary: 'Rewrite a criterion\'s text or doneWhen; done stays as it is (use criterion check/uncheck)';
|
|
195
|
+
readonly args: 1;
|
|
196
|
+
readonly needsHub: true;
|
|
197
|
+
};
|
|
186
198
|
readonly 'task set': {
|
|
187
199
|
readonly usage: 'task set <m/t> [--status] [--reason --waiting-for --signal] [--repo] [--branch --worktree --base --merge]';
|
|
188
200
|
readonly summary: 'Change status, repository or branch state; --status blocked needs --reason';
|
|
189
201
|
readonly args: 1;
|
|
190
202
|
readonly needsHub: true;
|
|
191
203
|
};
|
|
204
|
+
readonly 'task show': {
|
|
205
|
+
readonly usage: 'task show <m/t>';
|
|
206
|
+
readonly summary: 'Print a task: status, kind, checklist and links';
|
|
207
|
+
readonly args: 1;
|
|
208
|
+
readonly needsHub: true;
|
|
209
|
+
};
|
|
192
210
|
readonly 'item check': {
|
|
193
211
|
readonly usage: 'item check <m/t#c> --evidence <evidence>';
|
|
194
212
|
readonly summary: 'Check an item with evidence';
|
|
@@ -280,8 +298,8 @@ export declare const COMMAND_SPECS: {
|
|
|
280
298
|
readonly needsHub: true;
|
|
281
299
|
};
|
|
282
300
|
readonly 'ops list': {
|
|
283
|
-
readonly usage: 'ops list [--month YYYY-MM]';
|
|
284
|
-
readonly summary: 'List logged external operations';
|
|
301
|
+
readonly usage: 'ops list [--month YYYY-MM] [--ref <ref>]';
|
|
302
|
+
readonly summary: 'List logged external operations, optionally filtered by month or by the exact ref that was logged';
|
|
285
303
|
readonly args: 0;
|
|
286
304
|
readonly needsHub: true;
|
|
287
305
|
};
|
|
@@ -291,6 +309,12 @@ export declare const COMMAND_SPECS: {
|
|
|
291
309
|
readonly args: 1;
|
|
292
310
|
readonly needsHub: true;
|
|
293
311
|
};
|
|
312
|
+
readonly 'decision list': {
|
|
313
|
+
readonly usage: 'decision list <m>';
|
|
314
|
+
readonly summary: 'List a mainline\'s decisions';
|
|
315
|
+
readonly args: 1;
|
|
316
|
+
readonly needsHub: true;
|
|
317
|
+
};
|
|
294
318
|
};
|
|
295
319
|
export type CommandName = keyof typeof COMMAND_SPECS;
|
|
296
320
|
export declare const OPTIONS: {
|
|
@@ -340,6 +364,9 @@ export declare const OPTIONS: {
|
|
|
340
364
|
readonly text: {
|
|
341
365
|
readonly type: 'string';
|
|
342
366
|
};
|
|
367
|
+
readonly 'done-when': {
|
|
368
|
+
readonly type: 'string';
|
|
369
|
+
};
|
|
343
370
|
readonly serves: {
|
|
344
371
|
readonly type: 'string';
|
|
345
372
|
};
|
package/dist/facts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as TASK_SECTIONS, S as TASK_REQUIRED_SECTIONS, _ as PRIORITIES, a as DECISION_STATUS, b as SESSION_STATUS, c as FINDING_STATUS, d as ISSUE_SECTIONS, f as ISSUE_STATUS, g as MERGE_STATES, h as MAINLINE_STATUS, i as DECISION_SECTIONS, l as FORMAT_VERSION, m as MAINLINE_SECTIONS, n as CLOSED_STATUS, o as DEFAULT_CATEGORIES, p as LINK_RELS, r as DECIDERS, s as FINDING_SECTIONS, t as CHECKLIST_REF, u as ID_PATTERNS, w as WORK_STATUS, x as SLUG } from "./dist-
|
|
1
|
+
import { C as TASK_SECTIONS, S as TASK_REQUIRED_SECTIONS, _ as PRIORITIES, a as DECISION_STATUS, b as SESSION_STATUS, c as FINDING_STATUS, d as ISSUE_SECTIONS, f as ISSUE_STATUS, g as MERGE_STATES, h as MAINLINE_STATUS, i as DECISION_SECTIONS, l as FORMAT_VERSION, m as MAINLINE_SECTIONS, n as CLOSED_STATUS, o as DEFAULT_CATEGORIES, p as LINK_RELS, r as DECIDERS, s as FINDING_SECTIONS, t as CHECKLIST_REF, u as ID_PATTERNS, w as WORK_STATUS, x as SLUG } from "./dist-BQmXEfAf.js";
|
|
2
2
|
//#region src/facts.ts
|
|
3
3
|
const COMMAND_SPECS = {
|
|
4
4
|
init: {
|
|
@@ -49,6 +49,12 @@ const COMMAND_SPECS = {
|
|
|
49
49
|
args: 0,
|
|
50
50
|
needsHub: true
|
|
51
51
|
},
|
|
52
|
+
"session handoff": {
|
|
53
|
+
usage: "session handoff <session> [--session <session>]",
|
|
54
|
+
summary: "Take over the unfinished actions of another session into this one",
|
|
55
|
+
args: 1,
|
|
56
|
+
needsHub: true
|
|
57
|
+
},
|
|
52
58
|
"session show": {
|
|
53
59
|
usage: "session show [session]",
|
|
54
60
|
summary: "Print the briefing a hook would give this session",
|
|
@@ -159,12 +165,24 @@ const COMMAND_SPECS = {
|
|
|
159
165
|
args: 1,
|
|
160
166
|
needsHub: true
|
|
161
167
|
},
|
|
168
|
+
"criterion set": {
|
|
169
|
+
usage: "criterion set <m#r> [--text <text>] [--done-when <text>]",
|
|
170
|
+
summary: "Rewrite a criterion's text or doneWhen; done stays as it is (use criterion check/uncheck)",
|
|
171
|
+
args: 1,
|
|
172
|
+
needsHub: true
|
|
173
|
+
},
|
|
162
174
|
"task set": {
|
|
163
175
|
usage: "task set <m/t> [--status] [--reason --waiting-for --signal] [--repo] [--branch --worktree --base --merge]",
|
|
164
176
|
summary: "Change status, repository or branch state; --status blocked needs --reason",
|
|
165
177
|
args: 1,
|
|
166
178
|
needsHub: true
|
|
167
179
|
},
|
|
180
|
+
"task show": {
|
|
181
|
+
usage: "task show <m/t>",
|
|
182
|
+
summary: "Print a task: status, kind, checklist and links",
|
|
183
|
+
args: 1,
|
|
184
|
+
needsHub: true
|
|
185
|
+
},
|
|
168
186
|
"item check": {
|
|
169
187
|
usage: "item check <m/t#c> --evidence <evidence>",
|
|
170
188
|
summary: "Check an item with evidence",
|
|
@@ -256,8 +274,8 @@ const COMMAND_SPECS = {
|
|
|
256
274
|
needsHub: true
|
|
257
275
|
},
|
|
258
276
|
"ops list": {
|
|
259
|
-
usage: "ops list [--month YYYY-MM]",
|
|
260
|
-
summary: "List logged external operations",
|
|
277
|
+
usage: "ops list [--month YYYY-MM] [--ref <ref>]",
|
|
278
|
+
summary: "List logged external operations, optionally filtered by month or by the exact ref that was logged",
|
|
261
279
|
args: 0,
|
|
262
280
|
needsHub: true
|
|
263
281
|
},
|
|
@@ -266,6 +284,12 @@ const COMMAND_SPECS = {
|
|
|
266
284
|
summary: "Mark a decision as replaced by another",
|
|
267
285
|
args: 1,
|
|
268
286
|
needsHub: true
|
|
287
|
+
},
|
|
288
|
+
"decision list": {
|
|
289
|
+
usage: "decision list <m>",
|
|
290
|
+
summary: "List a mainline's decisions",
|
|
291
|
+
args: 1,
|
|
292
|
+
needsHub: true
|
|
269
293
|
}
|
|
270
294
|
};
|
|
271
295
|
const OPTIONS = {
|
|
@@ -287,6 +311,7 @@ const OPTIONS = {
|
|
|
287
311
|
"delegated-from": { type: "string" },
|
|
288
312
|
"handoff-from": { type: "string" },
|
|
289
313
|
text: { type: "string" },
|
|
314
|
+
"done-when": { type: "string" },
|
|
290
315
|
serves: { type: "string" },
|
|
291
316
|
before: { type: "string" },
|
|
292
317
|
note: { type: "string" },
|
|
@@ -477,7 +502,7 @@ const FIELDS = {
|
|
|
477
502
|
field("repo", false, "主要動到的倉庫,名稱需在 config.yaml 登記"),
|
|
478
503
|
field("startedAt", false, "第一次轉為 doing 時由 CLI 寫入"),
|
|
479
504
|
field("checklist", true, "checklist 項目清單,每項的欄位見 checklistItem"),
|
|
480
|
-
field("blockedBy", false, "卡住時的原因、等待對象與繼續的訊號,blocked
|
|
505
|
+
field("blockedBy", false, "卡住時的原因、等待對象與繼續的訊號,blocked 時必填;等待對象整串是參照時(例如 m031/t002)會檢查它存在"),
|
|
481
506
|
field("branch", false, "工作所在的分支"),
|
|
482
507
|
field("worktree", false, "worktree 的路徑"),
|
|
483
508
|
field("base", false, "分支基於哪個 commit"),
|
package/dist/hooks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as mayDecorate, H as decorateReply, I as setSessionInfo, L as setSessionStatus, O as sessionBriefing, P as readSession, R as settleSession, U as endsWithMarker, W as headerLine, dt as DATA_DIR, ft as currentBranch, i as hostName, j as ensureSession, k as turnReminder, mt as worktreesOf, nt as loadHub, pt as findHubRoot, tt as HubError } from "./host-daQGe7no.js";
|
|
2
2
|
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
//#region src/hooks.ts
|
|
@@ -75,21 +75,45 @@ const displayReply = (root, { payload }) => {
|
|
|
75
75
|
displayContent: shown
|
|
76
76
|
} });
|
|
77
77
|
};
|
|
78
|
+
/** Mainline ids a reply mentions, e.g. m001 in `m001/t003`; not inside a longer word or path. */
|
|
79
|
+
const MAINLINE_MENTION = /(?<![\w/#.[-])(m\d{3})(?![\w\]])/g;
|
|
80
|
+
/** The open mainlines the text mentions; loads the hub only when the text mentions one. */
|
|
81
|
+
function openMainlinesIn(root, text) {
|
|
82
|
+
const mentioned = new Set([...text.matchAll(MAINLINE_MENTION)].map((m) => m[1]));
|
|
83
|
+
if (mentioned.size === 0) return [];
|
|
84
|
+
return loadHub(root, { remotes: false }).mainlines.filter((m) => mentioned.has(m.id) && !m.archivedAt && (m.status === "active" || m.status === "planning")).map((m) => m.id);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* What to ask for when a turn ends without its header line: the header itself when an action is in progress, or
|
|
88
|
+
* an action first when the queue is empty but the reply talks about an open mainline, since the header only names
|
|
89
|
+
* the session's own action in progress. Null when there is nothing to ask for.
|
|
90
|
+
*/
|
|
91
|
+
function missingHeaderRequest(root, { payload, cli }) {
|
|
92
|
+
if (!payload.session_id || payload.stop_hook_active) return null;
|
|
93
|
+
const message = payload.last_assistant_message ?? "";
|
|
94
|
+
if (endsWithMarker(message)) return null;
|
|
95
|
+
const session = readSession(root, claudeCodeSessionId(payload.session_id));
|
|
96
|
+
const header = headerLine(session);
|
|
97
|
+
if (header) return `[mainflow] 這一輪最後一則回覆的最後一行不是標頭。請只回一則內容為「${header}」的訊息,不加其他文字。`;
|
|
98
|
+
if (session && session.actions.length > 0) return null;
|
|
99
|
+
const mainlines = openMainlinesIn(root, message);
|
|
100
|
+
if (mainlines.length === 0) return null;
|
|
101
|
+
return `[mainflow] 這一輪的回覆提到 ${mainlines.join("、")},但這場會話沒有行動,所以回覆結尾沒有標頭。這一輪若在推進它,先用 ${cli} action add "<做法>" --serves <m/t#c> 列出行動並用 ${cli} action set <a編號> --status doing 標為進行中,或用 ${cli} session handoff <會話> 接手上一場沒做完的行動;再只回一則內容為「▸ <m/t> · <a編號>」的訊息,不加其他文字。只是提到、沒有推進時,不必處理。`;
|
|
102
|
+
}
|
|
78
103
|
/**
|
|
79
|
-
* Stop:
|
|
80
|
-
* turn
|
|
81
|
-
*
|
|
104
|
+
* Stop: ask for what the turn's header needs, then settle the session. Only the model knows which message closes a
|
|
105
|
+
* turn, so the header is its to write; this makes a forgotten one cost a single extra message. stop_hook_active
|
|
106
|
+
* stops it from asking twice in a row. A session asked to list an action is not settled yet: settling removes a
|
|
107
|
+
* session with no actions, and the action it is asked to add needs the session to exist.
|
|
82
108
|
*/
|
|
83
109
|
const settleAndCheckMarker = (root, context) => {
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
87
|
-
|
|
88
|
-
if (!header) return null;
|
|
89
|
-
return JSON.stringify({ hookSpecificOutput: {
|
|
110
|
+
const request = missingHeaderRequest(root, context);
|
|
111
|
+
const session = context.payload.session_id ? readSession(root, claudeCodeSessionId(context.payload.session_id)) : null;
|
|
112
|
+
if (!request || session && session.actions.length > 0) settleOnEnd(root, context);
|
|
113
|
+
return request ? JSON.stringify({ hookSpecificOutput: {
|
|
90
114
|
hookEventName: "Stop",
|
|
91
|
-
additionalContext:
|
|
92
|
-
} });
|
|
115
|
+
additionalContext: request
|
|
116
|
+
} }) : null;
|
|
93
117
|
};
|
|
94
118
|
const CLAUDE_CODE_EVENTS = {
|
|
95
119
|
"session-start": (root, { payload, envFile, cli, startUi }) => {
|