mainflow 0.27.0 → 0.28.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 +9 -4
- package/dist/{dist-CBGKXsoi.js → dist-BbamrIPW.js} +2 -0
- package/dist/facts.d.ts +7 -1
- package/dist/facts.js +10 -4
- package/dist/grant-commands.js +1 -1
- package/dist/hooks.js +52 -6
- package/dist/{host-sSWrUJeC.js → host-CeN3dvXg.js} +122 -29
- package/dist/host.js +1 -1
- package/dist/{launch-1i4OAsD6.js → launch-BKKoCyTj.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/{ui-DcBLvgqY.js → ui-D_9x6XNs.js} +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
3
|
-
import { B as parseRef, H as refExample, V as parseUntil, k as UNTIL_FORMS } from "./dist-
|
|
2
|
+
import { $ as appendOps, A as untilState, B as setSessionArchived, C as setCriterion, D as supersedeDecision, H as setSessionStatus, I as handoffSession, K as updateAction, L as loadSessions, M as sessionBriefing, O as undropChecklistItem, P as addAction, R as readSession, S as setChecklistItem, T as setMainlineStatus, W as startSession, _ as createMainline, _t as WriteError, at as setFindingArchived, b as initHub, ct as HubError, et as closeIssue, g as createDecision, h as addRepo, ht as TASK_FILE, it as reopenIssue, j as duplicateClaimLines, k as updateTask, l as migrateHub, lt as loadHub, m as addHub, mt as MAINLINE_FILE, nt as createIssue, ot as setFindingStatus, p as acceptDecision, rt as planIssue, st as setIssueArchived, tt as createFinding, u as retroReport, v as createTask, w as setCriterionText, x as setArchived, xt as findHubRoot, y as dropChecklistItem } from "./host-CeN3dvXg.js";
|
|
3
|
+
import { B as parseRef, H as refExample, V as parseUntil, k as UNTIL_FORMS } from "./dist-BbamrIPW.js";
|
|
4
4
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
5
5
|
import { COMMAND_SPECS, OPTIONS } from "./facts.js";
|
|
6
6
|
import { hookFallback, parsePayload, runHook } from "./hooks.js";
|
|
7
|
-
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-BKKoCyTj.js";
|
|
8
8
|
import { readFileSync, realpathSync } from "node:fs";
|
|
9
9
|
import { basename, join, relative } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -206,7 +206,8 @@ const RUNS = {
|
|
|
206
206
|
const { id, file } = createIssue(root, {
|
|
207
207
|
slug: args[0],
|
|
208
208
|
title: required(opts, "title", "--title \"重試不穩定的 VM\""),
|
|
209
|
-
origin: opts.origin
|
|
209
|
+
origin: opts.origin,
|
|
210
|
+
labels: opts.labels ? list(opts.labels) : void 0
|
|
210
211
|
});
|
|
211
212
|
return done(`created ${id} ${relative(root, file)}`);
|
|
212
213
|
},
|
|
@@ -288,6 +289,10 @@ const RUNS = {
|
|
|
288
289
|
});
|
|
289
290
|
return done(`dropped ${args[0]}`);
|
|
290
291
|
},
|
|
292
|
+
"item undrop": ({ root, args }) => {
|
|
293
|
+
undropChecklistItem(root, args[0]);
|
|
294
|
+
return done(`undropped ${args[0]}`);
|
|
295
|
+
},
|
|
291
296
|
"issue plan": ({ root, args }) => done(`planned ${planIssue(root, args[0], args[1]).id} in ${args[1]}`),
|
|
292
297
|
"issue close": ({ root, args, opts }) => done(`closed ${closeIssue(root, args[0], opts.by ?? "").id}`),
|
|
293
298
|
"issue reopen": ({ root, args }) => done(`reopened ${reopenIssue(root, args[0]).id}`),
|
|
@@ -948,6 +948,8 @@ const sessionSchema = z.object({
|
|
|
948
948
|
description: text.optional(),
|
|
949
949
|
depth: z.number().int().nonnegative().optional(),
|
|
950
950
|
summary: text.optional(),
|
|
951
|
+
/** Digest of the grants, waits and queue warnings the last turn reminder listed in full, so an unchanged set is not listed again */
|
|
952
|
+
reminderDigest: text.optional(),
|
|
951
953
|
/** Hidden from the default views; independent of status, an ended session stays visible until archived */
|
|
952
954
|
archivedAt: date.optional()
|
|
953
955
|
});
|
package/dist/facts.d.ts
CHANGED
|
@@ -140,7 +140,7 @@ export declare const COMMAND_SPECS: {
|
|
|
140
140
|
readonly needsHub: true;
|
|
141
141
|
};
|
|
142
142
|
readonly 'new issue': {
|
|
143
|
-
readonly usage: 'new issue <slug> --title <title> [--origin <ref>]';
|
|
143
|
+
readonly usage: 'new issue <slug> --title <title> [--origin <ref>] [--labels a,b]';
|
|
144
144
|
readonly summary: 'Create an issue file for a follow-up that must outlive its mainline';
|
|
145
145
|
readonly args: 1;
|
|
146
146
|
readonly needsHub: true;
|
|
@@ -223,6 +223,12 @@ export declare const COMMAND_SPECS: {
|
|
|
223
223
|
readonly args: 1;
|
|
224
224
|
readonly needsHub: true;
|
|
225
225
|
};
|
|
226
|
+
readonly 'item undrop': {
|
|
227
|
+
readonly usage: 'item undrop <m/t#c>';
|
|
228
|
+
readonly summary: 'Bring a dropped item back as an unchecked item, clearing dropped and supersededBy';
|
|
229
|
+
readonly args: 1;
|
|
230
|
+
readonly needsHub: true;
|
|
231
|
+
};
|
|
226
232
|
readonly 'issue plan': {
|
|
227
233
|
readonly usage: 'issue plan <i> <m/t>';
|
|
228
234
|
readonly summary: 'Schedule an issue into the task that will carry it out';
|
package/dist/facts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as WORK_STATUS, C as SESSION_STATUS, D as TASK_REQUIRED_SECTIONS, E as SURVEY_SECTION, J as GRANT_ACTIONS, O as TASK_SECTIONS, T as SURVEY_LINES, X as GRANT_TARGET_FORMS, Y as GRANT_FORMS, _ as MERGE_STATES, a as DECISION_SECTIONS, c as FINDING_SECTIONS, d as ID_PATTERNS, f as ISSUE_SECTIONS, g as MAINLINE_STATUS, h as MAINLINE_SECTIONS, i as DECISION_BASES, k as UNTIL_FORMS, l as FINDING_STATUS, m as LINK_RELS, n as CLOSED_STATUS, o as DECISION_STATUS, p as ISSUE_STATUS, r as DECIDERS, s as DEFAULT_CATEGORIES, t as CHECKLIST_REF, u as FORMAT_VERSION, v as PRIORITIES, w as SLUG, x as SERVES_REF } from "./dist-
|
|
1
|
+
import { A as WORK_STATUS, C as SESSION_STATUS, D as TASK_REQUIRED_SECTIONS, E as SURVEY_SECTION, J as GRANT_ACTIONS, O as TASK_SECTIONS, T as SURVEY_LINES, X as GRANT_TARGET_FORMS, Y as GRANT_FORMS, _ as MERGE_STATES, a as DECISION_SECTIONS, c as FINDING_SECTIONS, d as ID_PATTERNS, f as ISSUE_SECTIONS, g as MAINLINE_STATUS, h as MAINLINE_SECTIONS, i as DECISION_BASES, k as UNTIL_FORMS, l as FINDING_STATUS, m as LINK_RELS, n as CLOSED_STATUS, o as DECISION_STATUS, p as ISSUE_STATUS, r as DECIDERS, s as DEFAULT_CATEGORIES, t as CHECKLIST_REF, u as FORMAT_VERSION, v as PRIORITIES, w as SLUG, x as SERVES_REF } from "./dist-BbamrIPW.js";
|
|
2
2
|
//#region src/facts.ts
|
|
3
3
|
const COMMAND_SPECS = {
|
|
4
4
|
init: {
|
|
@@ -131,7 +131,7 @@ const COMMAND_SPECS = {
|
|
|
131
131
|
needsHub: true
|
|
132
132
|
},
|
|
133
133
|
"new issue": {
|
|
134
|
-
usage: "new issue <slug> --title <title> [--origin <ref>]",
|
|
134
|
+
usage: "new issue <slug> --title <title> [--origin <ref>] [--labels a,b]",
|
|
135
135
|
summary: "Create an issue file for a follow-up that must outlive its mainline",
|
|
136
136
|
args: 1,
|
|
137
137
|
needsHub: true
|
|
@@ -214,6 +214,12 @@ const COMMAND_SPECS = {
|
|
|
214
214
|
args: 1,
|
|
215
215
|
needsHub: true
|
|
216
216
|
},
|
|
217
|
+
"item undrop": {
|
|
218
|
+
usage: "item undrop <m/t#c>",
|
|
219
|
+
summary: "Bring a dropped item back as an unchecked item, clearing dropped and supersededBy",
|
|
220
|
+
args: 1,
|
|
221
|
+
needsHub: true
|
|
222
|
+
},
|
|
217
223
|
"issue plan": {
|
|
218
224
|
usage: "issue plan <i> <m/t>",
|
|
219
225
|
summary: "Schedule an issue into the task that will carry it out",
|
|
@@ -527,7 +533,7 @@ const LIST_ITEM = [
|
|
|
527
533
|
field("done", true, "是否已完成,用 check 指令改"),
|
|
528
534
|
field("evidence", false, "完成的證據,done 為 true 時必填"),
|
|
529
535
|
field("doneAt", false, "完成的日期,由 CLI 寫入"),
|
|
530
|
-
field("dropped", false, "不再需要時為 true,用 item drop
|
|
536
|
+
field("dropped", false, "不再需要時為 true,用 item drop 標記,item undrop 撤銷;已放棄的條目不能 check"),
|
|
531
537
|
field("supersededBy", false, "被哪個參照取代"),
|
|
532
538
|
field("note", false, "一句備註,例如放棄的原因")
|
|
533
539
|
];
|
|
@@ -549,7 +555,7 @@ const FIELDS = {
|
|
|
549
555
|
field("order", false, "在主脈絡內的順序,小的在前"),
|
|
550
556
|
field("dependsOn", false, "開始前必須先完成的任務"),
|
|
551
557
|
field("repo", false, "主要動到的倉庫,名稱需在 config.yaml 登記"),
|
|
552
|
-
field("touches", false, "預計會改到的路徑或 glob 清單,相對於 repo 的根目錄;兩條進行中的主脈絡各有未結束的任務在同一個 repo 碰到重疊的路徑、彼此又沒有 depends-on 時,check
|
|
558
|
+
field("touches", false, "預計會改到的路徑或 glob 清單,相對於 repo 的根目錄;兩條進行中的主脈絡各有未結束的任務在同一個 repo 碰到重疊的路徑、彼此又沒有 depends-on 時,check 列出警告;有 touches 卻沒填 repo 時路徑不會被比對,check 也列警告"),
|
|
553
559
|
field("startedAt", false, "第一次轉為 doing 時由 CLI 寫入"),
|
|
554
560
|
field("checklist", true, "checklist 項目清單,每項的欄位見 checklistItem"),
|
|
555
561
|
field("blockedBy", false, "卡住時的原因、等待對象與繼續的訊號,blocked 時必填;等待對象整串是參照時(例如 m031/t002)會檢查它存在"),
|
package/dist/grant-commands.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Y as GRANT_FORMS } from "./dist-
|
|
1
|
+
import { Y as GRANT_FORMS } from "./dist-BbamrIPW.js";
|
|
2
2
|
import { closeSync, constants, fstatSync, openSync, readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
package/dist/hooks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Z as describeRequest, j as configSchema } from "./dist-
|
|
1
|
+
import { $ as appendOps, F as ensureSession, H as setSessionStatus, J as matchGrants, L as loadSessions, M as sessionBriefing, N as turnReminder, Q as mayDecorate, R as readSession, St as worktreesOf, U as settleSession, V as setSessionInfo, X as endsWithMarker, Y as decorateReply, Z as headerLine, bt as currentBranch, ct as HubError, i as hostName, lt as loadHub, q as grantScope, vt as readYaml, xt as findHubRoot, yt as DATA_DIR, z as setReminderDigest } from "./host-CeN3dvXg.js";
|
|
2
|
+
import { Z as describeRequest, j as configSchema } from "./dist-BbamrIPW.js";
|
|
3
3
|
import { resolveFixed, scanCommand, scopedCwd } from "./grant-commands.js";
|
|
4
4
|
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -125,10 +125,55 @@ function openMainlinesIn(root, text) {
|
|
|
125
125
|
if (mentioned.size === 0) return [];
|
|
126
126
|
return loadHub(root, { remotes: false }).mainlines.filter((m) => mentioned.has(m.id) && !m.archivedAt && (m.status === "active" || m.status === "planning")).map((m) => m.id);
|
|
127
127
|
}
|
|
128
|
+
/** Tools whose `file_path` input writes a file. */
|
|
129
|
+
const WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
130
|
+
"Edit",
|
|
131
|
+
"Write",
|
|
132
|
+
"MultiEdit",
|
|
133
|
+
"NotebookEdit"
|
|
134
|
+
]);
|
|
135
|
+
/** A Bash command that runs the mainflow CLI: by the form the hook texts give, or as a `mainflow` command word that starts a command, not one that is only an argument. */
|
|
136
|
+
const isMainflowCommand = (command, cli) => command.includes(cli) || /(?:^|[;&|(])\s*mainflow\s+\S|(?:\.bin|mainflow\/dist)\/(?:cli\.js|mainflow)\b/.test(command);
|
|
137
|
+
/** Whether a path lies under a `.mainflow/` directory. */
|
|
138
|
+
const inMainflowData = (path) => /(?:^|\/)\.mainflow\//.test(path);
|
|
139
|
+
/** A user record that holds what the user typed; tool results come back as user records too, but carry only tool_result blocks. */
|
|
140
|
+
const isUserPrompt = (r) => r.type === "user" && !r.isSidechain && (typeof r.message?.content === "string" || Array.isArray(r.message?.content) && r.message.content.some((b) => b.type === "text"));
|
|
141
|
+
/**
|
|
142
|
+
* Whether the turn that is ending, everything after the last user message in the transcript, ran a mainflow
|
|
143
|
+
* command or wrote a file under `.mainflow/`. False when the transcript is missing or unreadable, so a hook that
|
|
144
|
+
* cannot tell never asks.
|
|
145
|
+
*/
|
|
146
|
+
function turnTouchedMainflow(transcript, cli) {
|
|
147
|
+
if (!transcript || !existsSync(transcript)) return false;
|
|
148
|
+
let lines;
|
|
149
|
+
try {
|
|
150
|
+
lines = readFileSync(transcript, "utf8").split("\n");
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
155
|
+
if (!lines[i].trim()) continue;
|
|
156
|
+
let record;
|
|
157
|
+
try {
|
|
158
|
+
record = JSON.parse(lines[i]);
|
|
159
|
+
} catch {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (isUserPrompt(record)) return false;
|
|
163
|
+
const content = Array.isArray(record.message?.content) ? record.message.content : [];
|
|
164
|
+
for (const block of content) {
|
|
165
|
+
if (block.type !== "tool_use") continue;
|
|
166
|
+
if (block.name === "Bash" && typeof block.input?.command === "string" && isMainflowCommand(block.input.command, cli)) return true;
|
|
167
|
+
if (block.name && WRITE_TOOLS.has(block.name) && typeof block.input?.file_path === "string" && inMainflowData(block.input.file_path)) return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
128
172
|
/**
|
|
129
173
|
* What to ask for when a turn ends without its header line: the header itself when an action is in progress, or
|
|
130
|
-
* an action first when the queue is empty
|
|
131
|
-
* the session's own action in progress.
|
|
174
|
+
* an action first when the queue is empty, the reply talks about an open mainline and the turn ran a mainflow
|
|
175
|
+
* command or wrote under `.mainflow/`, since the header only names the session's own action in progress. A reply
|
|
176
|
+
* that only mentions a mainline, in a draft or a question, asks for nothing. Null when there is nothing to ask for.
|
|
132
177
|
*/
|
|
133
178
|
function missingHeaderRequest(root, { payload, cli }) {
|
|
134
179
|
if (!payload.session_id || payload.stop_hook_active) return null;
|
|
@@ -139,8 +184,8 @@ function missingHeaderRequest(root, { payload, cli }) {
|
|
|
139
184
|
if (header) return `[mainflow] 這一輪最後一則回覆的最後一行不是標頭。請只回一則內容為「${header}」的訊息,不加其他文字。`;
|
|
140
185
|
if (session && session.actions.length > 0) return null;
|
|
141
186
|
const mainlines = openMainlinesIn(root, message);
|
|
142
|
-
if (mainlines.length === 0) return null;
|
|
143
|
-
return `[mainflow] 這一輪的回覆提到 ${mainlines.join("、")}
|
|
187
|
+
if (mainlines.length === 0 || !turnTouchedMainflow(payload.transcript_path, cli)) return null;
|
|
188
|
+
return `[mainflow] 這一輪的回覆提到 ${mainlines.join("、")},也執行了 mainflow 指令或寫入 .mainflow/,但這場會話沒有行動,所以回覆結尾沒有標頭。這一輪若在推進它,先用 ${cli} action add "<做法>" --serves <m/t#c>(還沒拆任務時用解決條件 <m#r>)列出行動並用 ${cli} action set <a編號> --status doing 標為進行中,或用 ${cli} session handoff <會話> 接手上一場沒做完的行動;再只回一則內容為「▸ <m/t> · <a編號>」的訊息,不加其他文字。只是提到、沒有推進時,不必處理。`;
|
|
144
189
|
}
|
|
145
190
|
/**
|
|
146
191
|
* Stop: ask for what the turn's header needs, then settle the session. Only the model knows which message closes a
|
|
@@ -162,6 +207,7 @@ const CLAUDE_CODE_EVENTS = {
|
|
|
162
207
|
if (!payload.session_id) return null;
|
|
163
208
|
const id = claudeCodeSessionId(payload.session_id);
|
|
164
209
|
const session = recordSession(root, payload, id, true);
|
|
210
|
+
setReminderDigest(root, id, null);
|
|
165
211
|
if (envFile) appendFileSync(envFile, `export MAINFLOW_SESSION=${id}\n`);
|
|
166
212
|
startUi?.(root);
|
|
167
213
|
return sessionBriefing(root, session, {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as grantForm, A as WORK_STATUS, B as parseRef, C as SESSION_STATUS, D as TASK_REQUIRED_SECTIONS, E as SURVEY_SECTION, F as issueSchema, G as taskSchema, H as refExample, I as mainlineSchema, K as waitingForAction, L as opsEntrySchema, M as decisionSchema, N as findingSchema, O as TASK_SECTIONS, P as formatOpsEntry, Q as grantCovers, R as parseActionRef, S as SESSION_ID, T as SURVEY_LINES, U as refMainline, V as parseUntil, W as sessionSchema, Z as describeRequest, _ as MERGE_STATES, a as DECISION_SECTIONS, b as RESERVED_HUB_NAMES, c as FINDING_SECTIONS, et as grantRepo, f as ISSUE_SECTIONS, g as MAINLINE_STATUS, h as MAINLINE_SECTIONS, i as DECISION_BASES, j as configSchema, l as FINDING_STATUS, n as CLOSED_STATUS, q as waitingForRef, r as DECIDERS, s as DEFAULT_CATEGORIES, tt as parseGrantTarget, w as SLUG, x as SERVES_REF, y as REF_EXAMPLES, z as parseOpsLine } from "./dist-
|
|
1
|
+
import { $ as grantForm, A as WORK_STATUS, B as parseRef, C as SESSION_STATUS, D as TASK_REQUIRED_SECTIONS, E as SURVEY_SECTION, F as issueSchema, G as taskSchema, H as refExample, I as mainlineSchema, K as waitingForAction, L as opsEntrySchema, M as decisionSchema, N as findingSchema, O as TASK_SECTIONS, P as formatOpsEntry, Q as grantCovers, R as parseActionRef, S as SESSION_ID, T as SURVEY_LINES, U as refMainline, V as parseUntil, W as sessionSchema, Z as describeRequest, _ as MERGE_STATES, a as DECISION_SECTIONS, b as RESERVED_HUB_NAMES, c as FINDING_SECTIONS, et as grantRepo, f as ISSUE_SECTIONS, g as MAINLINE_STATUS, h as MAINLINE_SECTIONS, i as DECISION_BASES, j as configSchema, l as FINDING_STATUS, n as CLOSED_STATUS, q as waitingForRef, r as DECIDERS, s as DEFAULT_CATEGORIES, tt as parseGrantTarget, w as SLUG, x as SERVES_REF, y as REF_EXAMPLES, z as parseOpsLine } from "./dist-BbamrIPW.js";
|
|
2
2
|
import { appendFileSync, closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, matchesGlob, parse, posix, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -451,16 +451,27 @@ function dependsOn(edges, from, to) {
|
|
|
451
451
|
const lands = (ref) => ref === to.mainline || ref.startsWith(`${to.mainline}#`) || ref === `${to.mainline}/${to.task.id}` || ref.startsWith(`${to.mainline}/${to.task.id}#`);
|
|
452
452
|
return edges.some((e) => fromRefs.includes(e.from) && lands(e.to));
|
|
453
453
|
}
|
|
454
|
+
/** Unfinished tasks of planning or active mainlines that list `touches`, each with the mainline it sits in. */
|
|
455
|
+
function openTouching(mainlines) {
|
|
456
|
+
return mainlines.filter((m) => COMPARED_MAINLINE.includes(m.status)).flatMap((m) => m.tasks.filter((t) => !CLOSED_STATUS.task.includes(t.status) && t.touches?.length).map((task) => ({
|
|
457
|
+
mainline: m.id,
|
|
458
|
+
task
|
|
459
|
+
})));
|
|
460
|
+
}
|
|
461
|
+
/** Unfinished tasks that list `touches` but no `repo`: their paths have no root, so touchOverlaps skips them. */
|
|
462
|
+
function touchesWithoutRepo(mainlines) {
|
|
463
|
+
return openTouching(mainlines).filter((p) => !p.task.repo).map((p) => ({
|
|
464
|
+
task: `${p.mainline}/${p.task.id}`,
|
|
465
|
+
file: p.task.file
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
454
468
|
/**
|
|
455
469
|
* Open tasks of different open mainlines whose `touches` overlap in the same repo and which have no depends-on
|
|
456
470
|
* between them in either direction, written on the mainline or on the task. Tasks without a repo are not compared,
|
|
457
|
-
* since their paths have no root to share.
|
|
471
|
+
* since their paths have no root to share; touchesWithoutRepo names them.
|
|
458
472
|
*/
|
|
459
473
|
function touchOverlaps(mainlines, edges) {
|
|
460
|
-
const open = mainlines
|
|
461
|
-
mainline: m.id,
|
|
462
|
-
task
|
|
463
|
-
})));
|
|
474
|
+
const open = openTouching(mainlines).filter((p) => p.task.repo);
|
|
464
475
|
const overlaps = [];
|
|
465
476
|
open.forEach((a, i) => {
|
|
466
477
|
for (const b of open.slice(i + 1)) {
|
|
@@ -715,11 +726,18 @@ function loadHub(root, options = {}) {
|
|
|
715
726
|
});
|
|
716
727
|
const hubIssues = hubDocs(join(dataDir, "issues"), issueSchema);
|
|
717
728
|
const taskRefs = new Set(mainlines.flatMap((m) => m.tasks.map((t) => `${m.id}/${t.id}`)));
|
|
718
|
-
for (const issue of hubIssues)
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
729
|
+
for (const issue of hubIssues) {
|
|
730
|
+
if (issue.plannedIn && !taskRefs.has(issue.plannedIn)) problems.push({
|
|
731
|
+
file: issue.file,
|
|
732
|
+
field: "plannedIn",
|
|
733
|
+
message: `task ${issue.plannedIn} does not exist`
|
|
734
|
+
});
|
|
735
|
+
if (issue.status !== "closed" && !issue.archivedAt && !issue.labels?.length) warnings.push({
|
|
736
|
+
file: issue.file,
|
|
737
|
+
field: "labels",
|
|
738
|
+
message: `issue ${issue.id} is ${issue.status} but has no labels; add labels so it can be found by topic`
|
|
739
|
+
});
|
|
740
|
+
}
|
|
723
741
|
const findings = hubDocs(join(dataDir, "findings"), findingSchema);
|
|
724
742
|
const ops = [];
|
|
725
743
|
const opsPath = join(dataDir, "ops");
|
|
@@ -772,6 +790,11 @@ function loadHub(root, options = {}) {
|
|
|
772
790
|
message: `${o.tasks[0]} and ${o.tasks[1]} both touch ${pairs} in repo ${o.repo}, and neither depends on the other; coordinate before both run, or add a depends-on link`
|
|
773
791
|
});
|
|
774
792
|
}
|
|
793
|
+
for (const t of touchesWithoutRepo(mainlines)) warnings.push({
|
|
794
|
+
file: t.file,
|
|
795
|
+
field: "repo",
|
|
796
|
+
message: `${t.task} lists touches but no repo, so its paths are not compared with other tasks; add the repo they are relative to`
|
|
797
|
+
});
|
|
775
798
|
return {
|
|
776
799
|
root,
|
|
777
800
|
config,
|
|
@@ -1093,7 +1116,8 @@ function createIssue(root, input) {
|
|
|
1093
1116
|
title: input.title,
|
|
1094
1117
|
status: "open",
|
|
1095
1118
|
created: today(),
|
|
1096
|
-
...input.origin ? { origin: input.origin } : {}
|
|
1119
|
+
...input.origin ? { origin: input.origin } : {},
|
|
1120
|
+
...input.labels?.length ? { labels: input.labels } : {}
|
|
1097
1121
|
}, "issue");
|
|
1098
1122
|
const file = join(dir, `${id}-${input.slug}.md`);
|
|
1099
1123
|
writeAtomic(file, frontMatterText(issue, ISSUE_BODY));
|
|
@@ -1519,9 +1543,11 @@ function requireServes(root, serves) {
|
|
|
1519
1543
|
}
|
|
1520
1544
|
}
|
|
1521
1545
|
const isOpen$1 = (a) => a.status === "todo" || a.status === "doing";
|
|
1546
|
+
const isCarried$1 = (a) => isOpen$1(a) || a.status === "blocked";
|
|
1522
1547
|
/**
|
|
1523
1548
|
* The previous session's unfinished actions, numbered after `existing`, with the previous session marked
|
|
1524
|
-
* handed-off.
|
|
1549
|
+
* handed-off. Blocked actions go along with their `blockedBy`, since the wait is still on and whoever is
|
|
1550
|
+
* waited for keeps seeing it. Each carried action is dropped in the previous session with a note naming its new reference,
|
|
1525
1551
|
* so the work stays open in one place only and the old session still shows where it went. Runs inside the
|
|
1526
1552
|
* caller's lock.
|
|
1527
1553
|
*/
|
|
@@ -1532,7 +1558,7 @@ function takeUnfinished(root, from, to, existing) {
|
|
|
1532
1558
|
const carried = [];
|
|
1533
1559
|
const at = nowMinute();
|
|
1534
1560
|
const left = previous.actions.map((a) => {
|
|
1535
|
-
if (!
|
|
1561
|
+
if (!isCarried$1(a)) return a;
|
|
1536
1562
|
const id = nextId("a", ids);
|
|
1537
1563
|
ids.push(id);
|
|
1538
1564
|
carried.push({
|
|
@@ -1540,8 +1566,9 @@ function takeUnfinished(root, from, to, existing) {
|
|
|
1540
1566
|
id
|
|
1541
1567
|
});
|
|
1542
1568
|
const handedOff = handedOffNote(`${to}#${id}`);
|
|
1569
|
+
const { blockedBy: _moved, ...rest } = a;
|
|
1543
1570
|
return {
|
|
1544
|
-
...
|
|
1571
|
+
...rest,
|
|
1545
1572
|
status: "dropped",
|
|
1546
1573
|
note: a.note ? `${a.note}; ${handedOff}` : handedOff,
|
|
1547
1574
|
doneAt: at
|
|
@@ -1617,7 +1644,7 @@ function handoffSession(root, id, from) {
|
|
|
1617
1644
|
if (session.handoffFrom && session.handoffFrom !== from) throw new WriteError(`session ${id} already took over from ${session.handoffFrom}`);
|
|
1618
1645
|
const open = session.actions.filter(isOpen$1);
|
|
1619
1646
|
if (open.length > 0) throw new WriteError(`session ${id} has unfinished actions (${open.map((a) => a.id).join(", ")}); finish or drop them before taking over from ${from}`);
|
|
1620
|
-
if (!readYaml(requireSession(root, from), sessionSchema).data.actions.some(
|
|
1647
|
+
if (!readYaml(requireSession(root, from), sessionSchema).data.actions.some(isCarried$1)) throw new WriteError(`session ${from} has no unfinished actions to take over`);
|
|
1621
1648
|
const carried = takeUnfinished(root, from, id, session.actions.map((a) => a.id));
|
|
1622
1649
|
const next = validate(sessionSchema, {
|
|
1623
1650
|
...session,
|
|
@@ -1638,6 +1665,13 @@ function setSessionInfo(root, id, info) {
|
|
|
1638
1665
|
for (const [key, value] of Object.entries(info)) if (value !== void 0 && value !== null && value !== "") s[key] = value;
|
|
1639
1666
|
});
|
|
1640
1667
|
}
|
|
1668
|
+
/** Remember the digest of what the last turn reminder listed in full; null forgets it, so the next reminder lists everything again. */
|
|
1669
|
+
function setReminderDigest(root, id, digest) {
|
|
1670
|
+
mutateYaml(root, requireSession(root, id), sessionSchema, (s) => {
|
|
1671
|
+
if (digest === null) delete s.reminderDigest;
|
|
1672
|
+
else s.reminderDigest = digest;
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1641
1675
|
function setSessionStatus(root, id, status) {
|
|
1642
1676
|
const value = requireOneOf(status, SESSION_STATUS, "session status");
|
|
1643
1677
|
return mutateYaml(root, requireSession(root, id), sessionSchema, (s) => {
|
|
@@ -1734,6 +1768,8 @@ function updateAction(root, ref, patch) {
|
|
|
1734
1768
|
const index = s.actions.findIndex((a) => a.id === action);
|
|
1735
1769
|
if (index < 0) throw new WriteError(`action ${ref} not found`);
|
|
1736
1770
|
const [current] = s.actions.splice(index, 1);
|
|
1771
|
+
const movedTo = HANDED_OFF.exec(current.note ?? "")?.[1];
|
|
1772
|
+
if (movedTo && current.status === "dropped" && status !== void 0 && status !== "dropped") throw new WriteError(`${ref} was handed off to ${movedTo}, and references to ${ref} follow its note only while it stays dropped; change ${movedTo} instead`);
|
|
1737
1773
|
const handedTo = patch.note ? HANDED_OFF.exec(current.note ?? "")?.[1] : void 0;
|
|
1738
1774
|
if (handedTo) throw new WriteError(`${ref} was handed off to ${handedTo}, and references to ${ref} follow its note there, so the note stays as it is; add the note to ${handedTo} instead`);
|
|
1739
1775
|
const next = {
|
|
@@ -1834,6 +1870,8 @@ function queueWarnings(root, session, cli) {
|
|
|
1834
1870
|
}
|
|
1835
1871
|
const current = (session) => session.actions.find((a) => a.status === "doing");
|
|
1836
1872
|
const isOpen = (a) => a.status === "todo" || a.status === "doing";
|
|
1873
|
+
/** What a handoff takes over: open actions and blocked ones, which are still unfinished. */
|
|
1874
|
+
const isCarried = (a) => isOpen(a) || a.status === "blocked";
|
|
1837
1875
|
/** A blocked action and what it waits for: the reason, the other party and the condition, as `action set --status blocked` recorded them. */
|
|
1838
1876
|
function waitingLine(a) {
|
|
1839
1877
|
const block = a.blockedBy;
|
|
@@ -1925,14 +1963,32 @@ function duplicateClaimLines(root, sessionId, actionId) {
|
|
|
1925
1963
|
* over into this one. Subagent sessions are left out: their work returns to the session that delegated it.
|
|
1926
1964
|
*/
|
|
1927
1965
|
function handoffLines(root, session, cli) {
|
|
1928
|
-
const others = loadSessions(root).sessions.filter((s) => s.id !== session.id && !s.delegatedFrom && !s.archivedAt && (s.status === "active" || s.status === "paused") && s.actions.some(
|
|
1966
|
+
const others = loadSessions(root).sessions.filter((s) => s.id !== session.id && !s.delegatedFrom && !s.archivedAt && (s.status === "active" || s.status === "paused") && s.actions.some(isCarried));
|
|
1929
1967
|
if (others.length === 0) return [];
|
|
1930
|
-
return [`其他會話還有沒做完的行動。要接續的是其中一場已經停下的工作時,先用 ${cli} session handoff <會話> 把它的行動接進這場會話,再開始做:`, ...others.map((s) => `- ${s.id}${mayBeRunning(root, s) ? "(15 分鐘內還有寫入對話紀錄,可能還在執行,不要接手)" : ""}:${s.actions.filter(
|
|
1968
|
+
return [`其他會話還有沒做完的行動。要接續的是其中一場已經停下的工作時,先用 ${cli} session handoff <會話> 把它的行動接進這場會話,再開始做:`, ...others.map((s) => `- ${s.id}${mayBeRunning(root, s) ? "(15 分鐘內還有寫入對話紀錄,可能還在執行,不要接手)" : ""}:${s.actions.filter(isCarried).map((a) => `${a.id} ${a.do}(${a.status},服務 ${a.serves.join("、") || "未指定"})`).join(";")}`)];
|
|
1931
1969
|
}
|
|
1932
1970
|
const servedLine = (root, action) => action.serves.map((ref) => {
|
|
1933
1971
|
const s = describe(root, ref);
|
|
1934
1972
|
return s ? `${ref} ${s.text}(${s.where})` : `${ref}(找不到這個項目或條件)`;
|
|
1935
1973
|
}).join(";") || "沒有指定服務的 checklist 項目或解決條件";
|
|
1974
|
+
/**
|
|
1975
|
+
* Lines for each pair of overlapping touches that involves a mainline an action in progress serves: the other
|
|
1976
|
+
* mainline and task, and the paths they share. Empty when the session serves nothing that overlaps.
|
|
1977
|
+
*/
|
|
1978
|
+
function overlapLines(root, session) {
|
|
1979
|
+
const mine = new Set(session.actions.filter((a) => a.status === "doing").flatMap((a) => a.serves.map((ref) => parseRef(ref))).flatMap((parsed) => parsed && "mainline" in parsed ? [parsed.mainline] : []));
|
|
1980
|
+
if (mine.size === 0) return [];
|
|
1981
|
+
const hub = loadHub(root, { remotes: false });
|
|
1982
|
+
const lines = [];
|
|
1983
|
+
for (const o of touchOverlaps(hub.mainlines, dependencyGraph(hub).edges)) {
|
|
1984
|
+
const [own, other] = mine.has(o.tasks[0].split("/")[0]) ? [0, 1] : mine.has(o.tasks[1].split("/")[0]) ? [1, 0] : [-1, -1];
|
|
1985
|
+
if (own < 0) continue;
|
|
1986
|
+
const paths = o.paths.map(([a, b]) => own === 0 ? [a, b] : [b, a]).map(([a, b]) => a === b ? a : `${a} ~ ${b}`).join("、");
|
|
1987
|
+
const title = hub.mainlines.find((m) => m.id === o.tasks[other].split("/")[0])?.title;
|
|
1988
|
+
lines.push(`- ${o.tasks[own]} 和 ${o.tasks[other]}${title ? `(${title})` : ""} 都會動到 ${paths}(倉庫 ${o.repo}),兩邊沒有 depends-on`);
|
|
1989
|
+
}
|
|
1990
|
+
return lines;
|
|
1991
|
+
}
|
|
1936
1992
|
/** Full briefing for the start of a session, after resuming, clearing or compacting. */
|
|
1937
1993
|
function sessionBriefing(root, session, options) {
|
|
1938
1994
|
const { cli } = options;
|
|
@@ -1957,13 +2013,18 @@ function sessionBriefing(root, session, options) {
|
|
|
1957
2013
|
if (blocked.length > 0) lines.push(`等外部條件:${blocked.map(waitingLine).join(";")}`);
|
|
1958
2014
|
}
|
|
1959
2015
|
lines.push(...waiterLines(root, session));
|
|
2016
|
+
const overlaps = overlapLines(root, session);
|
|
2017
|
+
if (overlaps.length > 0) lines.push("目前行動服務的主脈絡和其他進行中的主脈絡預計動到同樣的路徑,開始改之前先和對方協調,或加 depends-on:", ...overlaps);
|
|
1960
2018
|
if (!session.actions.some(isOpen)) lines.push(...handoffLines(root, session, cli));
|
|
1961
2019
|
const warnings = queueWarnings(root, session, cli);
|
|
1962
2020
|
if (warnings.length > 0) lines.push("需要處理:", ...warnings.map((w) => `- ${w}`));
|
|
1963
2021
|
lines.push("工作方式:", `- 開始一件新工作前,先列進佇列:${cli} action add "<做法>" --serves <m/t#c>。主脈絡還沒拆任務時(查核與規劃階段),--serves 指向解決條件 <m#r>。`, `- 開始做標 doing,做完標 done:${cli} action set <a編號> --status doing。`, `- 勾選 checklist 要附證據:${cli} item check <m/t#c> --evidence <commit、檔案:行號或指令輸出>。`, `- 內容直接寫進檔案:意圖或需求寫進 mainline.md 的內文,任務的目標與完成定義寫進 task.md,選擇寫成決策(${cli} new decision <m> <slug> --title <標題> 建檔後再寫內文)。`, `- 改完 .mainflow/ 的檔案跑 ${cli} check,把它列出的問題修到沒有。`, "- 用完其他 skill 或子流程後,回到上面的目前行動再繼續。", "- 要開主脈絡、拆任務、記決策、延後或收尾時,用 mainflow skill,它依序帶過並告訴你資訊該寫在哪裡。");
|
|
1964
2022
|
return lines.join("\n");
|
|
1965
2023
|
}
|
|
1966
|
-
/**
|
|
2024
|
+
/**
|
|
2025
|
+
* One or two lines for every user turn; null when the session has no actions and the hub has no open mainline.
|
|
2026
|
+
* Records a digest of the grants, waits and warnings it lists in the session file, only when the digest changes.
|
|
2027
|
+
*/
|
|
1967
2028
|
function turnReminder(root, session, options) {
|
|
1968
2029
|
if (session.actions.length === 0) return openMainlines(root).length > 0 ? `[mainflow] ${session.id} 還沒有行動。這一輪要推進主脈絡時,先用 ${options.cli} action add 列出行動並標為 doing,或用 ${options.cli} session handoff <會話> 接手上一場沒做完的行動,再開始做。回覆結尾的標頭只看這場會話進行中的行動。` : null;
|
|
1969
2030
|
const doing = current(session);
|
|
@@ -1973,13 +2034,28 @@ function turnReminder(root, session, options) {
|
|
|
1973
2034
|
const header = headerLine(session);
|
|
1974
2035
|
const marker = header ? [`- 這一輪最後一則回覆的最後一行寫「${header}」,回覆範例等其他內容都放在它前面。有顯示 hook 時,它會換成標題與連結。`] : [];
|
|
1975
2036
|
const grants = grantLines(root, session);
|
|
1976
|
-
const
|
|
2037
|
+
const waiters = waiterLines(root, session);
|
|
2038
|
+
const warnings = queueWarnings(root, session, options.cli);
|
|
2039
|
+
const digest = contentHash(JSON.stringify([
|
|
2040
|
+
doing?.id ?? null,
|
|
2041
|
+
grants,
|
|
2042
|
+
waiters,
|
|
2043
|
+
warnings
|
|
2044
|
+
]));
|
|
2045
|
+
const repeated = session.reminderDigest === digest;
|
|
2046
|
+
if (!repeated && (session.reminderDigest !== void 0 || grants.length + waiters.length + warnings.length > 0) && readSession(root, session.id)) try {
|
|
2047
|
+
setReminderDigest(root, session.id, digest);
|
|
2048
|
+
} catch {}
|
|
2049
|
+
const once = (lines, summary, full) => lines.length === 0 ? [] : repeated ? [summary] : full;
|
|
2050
|
+
const granted = once(grants, `- 預先授權同上一輪,共 ${grants.length} 條。`, ["- 目前行動服務的主脈絡預先授權了這些動作:", ...grants.map((g) => ` ${g}`)]);
|
|
2051
|
+
const waiting = once(waiters, `- 等待行同上一輪,共 ${waiters.length} 條。`, waiters.map((w) => `- ${w}`));
|
|
2052
|
+
const warned = once(warnings, `- 佇列提醒同上一輪,共 ${warnings.length} 條。`, warnings.map((w) => `- ${w}`));
|
|
1977
2053
|
return [
|
|
1978
2054
|
head,
|
|
1979
|
-
...
|
|
2055
|
+
...waiting,
|
|
1980
2056
|
...granted,
|
|
1981
2057
|
...marker,
|
|
1982
|
-
...
|
|
2058
|
+
...warned
|
|
1983
2059
|
].join("\n");
|
|
1984
2060
|
}
|
|
1985
2061
|
//#endregion
|
|
@@ -2203,7 +2279,7 @@ function setCriterion(root, refInput, done, evidence, expectedVersion) {
|
|
|
2203
2279
|
return mutateFrontMatter(root, mainlineFile(root, ref.mainline), mainlineSchema, (m) => {
|
|
2204
2280
|
const criterion = m.criteria.find((c) => c.id === ref.criterion);
|
|
2205
2281
|
if (!criterion) throw new WriteError(`criterion ${refInput} not found`);
|
|
2206
|
-
setListItem(criterion, done, evidence);
|
|
2282
|
+
setListItem(criterion, done, evidence, `${refInput} is dropped; remove dropped and supersededBy in ${MAINLINE_FILE} to bring it back before checking it`);
|
|
2207
2283
|
}, expectedVersion);
|
|
2208
2284
|
}
|
|
2209
2285
|
/** Rewrite a criterion's text or doneWhen, leaving done and evidence as they are. */
|
|
@@ -2217,14 +2293,17 @@ function setCriterionText(root, refInput, patch, expectedVersion) {
|
|
|
2217
2293
|
if (patch.doneWhen !== void 0) criterion.doneWhen = patch.doneWhen;
|
|
2218
2294
|
}, expectedVersion);
|
|
2219
2295
|
}
|
|
2220
|
-
/**
|
|
2221
|
-
|
|
2296
|
+
/**
|
|
2297
|
+
* Check or uncheck a criterion or checklist item. A dropped item cannot be checked, because a drop is a
|
|
2298
|
+
* ruling that the item is no longer needed; `droppedHint` says how to bring it back. Neither direction
|
|
2299
|
+
* touches dropped or supersededBy.
|
|
2300
|
+
*/
|
|
2301
|
+
function setListItem(item, done, evidence, droppedHint) {
|
|
2302
|
+
if (done && item.dropped) throw new WriteError(droppedHint);
|
|
2222
2303
|
item.done = done;
|
|
2223
2304
|
if (done) {
|
|
2224
2305
|
item.evidence = evidence;
|
|
2225
2306
|
item.doneAt = today();
|
|
2226
|
-
delete item.dropped;
|
|
2227
|
-
delete item.supersededBy;
|
|
2228
2307
|
} else {
|
|
2229
2308
|
delete item.evidence;
|
|
2230
2309
|
delete item.doneAt;
|
|
@@ -2299,7 +2378,7 @@ function setChecklistItem(root, refInput, done, evidence, expectedVersion) {
|
|
|
2299
2378
|
return mutateFrontMatter(root, taskFile(root, ref.mainline, ref.task), taskSchema, (t) => {
|
|
2300
2379
|
const item = t.checklist.find((c) => c.id === ref.item);
|
|
2301
2380
|
if (!item) throw new WriteError(`checklist item ${refInput} not found`);
|
|
2302
|
-
setListItem(item, done, evidence);
|
|
2381
|
+
setListItem(item, done, evidence, `${refInput} is dropped; run item undrop ${refInput} to bring it back before checking it`);
|
|
2303
2382
|
}, expectedVersion);
|
|
2304
2383
|
}
|
|
2305
2384
|
/** Mark an item as no longer needed, naming what replaced it when something did. */
|
|
@@ -2314,6 +2393,20 @@ function dropChecklistItem(root, refInput, input = {}, expectedVersion) {
|
|
|
2314
2393
|
if (input.note) item.note = input.note;
|
|
2315
2394
|
}, expectedVersion);
|
|
2316
2395
|
}
|
|
2396
|
+
/** Bring a dropped item back as an open one: clears dropped and supersededBy, keeps the note. */
|
|
2397
|
+
function undropChecklistItem(root, refInput, expectedVersion) {
|
|
2398
|
+
const ref = requireRef(refInput, "checklistItem");
|
|
2399
|
+
return mutateFrontMatter(root, taskFile(root, ref.mainline, ref.task), taskSchema, (t) => {
|
|
2400
|
+
const item = t.checklist.find((c) => c.id === ref.item);
|
|
2401
|
+
if (!item) throw new WriteError(`checklist item ${refInput} not found`);
|
|
2402
|
+
if (!item.dropped) throw new WriteError(`${refInput} is not dropped`);
|
|
2403
|
+
delete item.dropped;
|
|
2404
|
+
delete item.supersededBy;
|
|
2405
|
+
item.done = false;
|
|
2406
|
+
delete item.evidence;
|
|
2407
|
+
delete item.doneAt;
|
|
2408
|
+
}, expectedVersion);
|
|
2409
|
+
}
|
|
2317
2410
|
function createDecision(root, mainline, input) {
|
|
2318
2411
|
requireRef(mainline, "mainline");
|
|
2319
2412
|
requireSlug(input.slug);
|
|
@@ -3172,4 +3265,4 @@ function openInApp(hub, target, closeAfterMs = 150) {
|
|
|
3172
3265
|
return true;
|
|
3173
3266
|
}
|
|
3174
3267
|
//#endregion
|
|
3175
|
-
export {
|
|
3268
|
+
export { appendOps as $, untilState as A, setSessionArchived as B, setCriterion as C, supersedeDecision as D, setMainlineTitle as E, ensureSession as F, transcriptPath as G, setSessionStatus as H, handoffSession as I, matchGrants as J, updateAction as K, loadSessions as L, sessionBriefing as M, turnReminder as N, undropChecklistItem as O, addAction as P, mayDecorate as Q, readSession as R, setChecklistItem as S, worktreesOf as St, setMainlineStatus as T, settleSession as U, setSessionInfo as V, startSession as W, endsWithMarker as X, decorateReply as Y, headerLine as Z, createMainline as _, WriteError as _t, openInApp as a, setFindingArchived as at, initHub as b, currentBranch as bt, syncHost as c, HubError as ct, ARCHIVE_KINDS as d, mainlineEdges as dt, closeIssue as et, setArchivedMany as f, linksInto as ft, createDecision as g, ConflictError as gt, addRepo as h, TASK_FILE as ht, hostName as i, reopenIssue as it, duplicateClaimLines as j, updateTask as k, migrateHub as l, loadHub as lt, addHub as m, MAINLINE_FILE as mt, DEFAULT_UI_PORT as n, createIssue as nt, removeHostLink as o, setFindingStatus as ot, acceptDecision as p, refIndex as pt, grantScope as q, caddySites as r, planIssue as rt, siteBlock as s, setIssueArchived as st, APP_BUNDLE_ID as t, createFinding as tt, retroReport as u, dependencyGraph as ut, createTask as v, readYaml as vt, setCriterionText as w, setArchived as x, findHubRoot as xt, dropChecklistItem as y, DATA_DIR as yt, setReminderDigest as z };
|
package/dist/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-
|
|
1
|
+
import { a as openInApp, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, r as caddySites, s as siteBlock, t as APP_BUNDLE_ID } from "./host-CeN3dvXg.js";
|
|
2
2
|
export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, removeHostLink, siteBlock, syncHost };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _t as
|
|
1
|
+
import { _t as WriteError, c as syncHost, i as hostName, n as DEFAULT_UI_PORT, o as removeHostLink, yt as DATA_DIR } from "./host-CeN3dvXg.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -176,7 +176,7 @@ async function launchUi(root, hub, configured, options) {
|
|
|
176
176
|
}
|
|
177
177
|
let server;
|
|
178
178
|
if (!reuse) {
|
|
179
|
-
const { serveUi } = await import("./ui-
|
|
179
|
+
const { serveUi } = await import("./ui-D_9x6XNs.js");
|
|
180
180
|
server = await serveUi(root, choice.port);
|
|
181
181
|
writeUiRecord(root, {
|
|
182
182
|
port: choice.port,
|
package/dist/launch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-
|
|
1
|
+
import { a as portCandidates, c as watchHubGone, i as networkProbe, n as choosePort, o as readUiRecord, r as launchUi, s as startUiInBackground, t as PORT_SPAN } from "./launch-BKKoCyTj.js";
|
|
2
2
|
export { PORT_SPAN, choosePort, launchUi, networkProbe, portCandidates, readUiRecord, startUiInBackground, watchHubGone };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as setCriterion, E as setMainlineTitle,
|
|
1
|
+
import { B as setSessionArchived, C as setCriterion, E as setMainlineTitle, G as transcriptPath, L as loadSessions, R as readSession, S as setChecklistItem, T as setMainlineStatus, _t as WriteError, a as openInApp, at as setFindingArchived, d as ARCHIVE_KINDS, dt as mainlineEdges, f as setArchivedMany, ft as linksInto, gt as ConflictError, k as updateTask, lt as loadHub, pt as refIndex, st as setIssueArchived, ut as dependencyGraph, x as setArchived, yt as DATA_DIR } from "./host-CeN3dvXg.js";
|
|
2
2
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
3
|
-
import { l as CLI_VERSION } from "./launch-
|
|
3
|
+
import { l as CLI_VERSION } from "./launch-BKKoCyTj.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { closeSync, existsSync, openSync, readFileSync, readSync, statSync, watch } from "node:fs";
|
|
6
6
|
import { basename, dirname, extname, join, normalize, relative, sep } from "node:path";
|