mulmoterminal 8.5.0 → 8.6.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/README.md +12 -0
- package/common/blueprint/targets.ts +3 -0
- package/common/sessionProcesses.ts +64 -0
- package/common/worktreeCleanup.ts +95 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-DyCC_1de.js → abnfDiagram-O67JEVCF-DXlZgdtc.js} +1 -1
- package/dist/assets/architecture-7GRP2DOG-XtAJZbRy.js +1 -0
- package/dist/assets/{architectureDiagram-NJMV4G6O-CElWMJBE.js → architectureDiagram-NJMV4G6O-DaxTUb1T.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-DlQFWNAZ.js → blockDiagram-BEXU5L5S-CMJVFioO.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-CXSZDDoB.js → c4Diagram-YGBWAQC7-DICUsVCB.js} +1 -1
- package/dist/assets/channel-8a6suaSc.js +1 -0
- package/dist/assets/{chunk-3FUC2YCW-D7X8Rwzi.js → chunk-3FUC2YCW-CXCYZio6.js} +1 -1
- package/dist/assets/{chunk-5DYCD2WN-CaUW3mZV.js → chunk-5DYCD2WN-J-KMASno.js} +1 -1
- package/dist/assets/{chunk-742MDFTN-6yPbPSMM.js → chunk-742MDFTN-DyDR-dy2.js} +1 -1
- package/dist/assets/{chunk-7INBJB4K-DsMF-LP1.js → chunk-7INBJB4K-OgeP0o2t.js} +1 -1
- package/dist/assets/{chunk-7M6MHVWA-CypgU6i0.js → chunk-7M6MHVWA-CMMNdMOL.js} +1 -1
- package/dist/assets/{chunk-7PRAP22T-Dg02FcL4.js → chunk-7PRAP22T-BbHJYkCX.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-D-ijksGR.js → chunk-GTNCS2PH-nGmKdp-b.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-cdxgZkt9.js → chunk-GWA4HPMP-Bb_rpXW6.js} +1 -1
- package/dist/assets/{chunk-MBY4JIJT-NM9lwnbY.js → chunk-MBY4JIJT-B2GHBF8g.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-BOB5DgHb.js → chunk-NETBCI7D-2LLlIwhj.js} +1 -1
- package/dist/assets/{chunk-O7XYJQB3-CBAglyfH.js → chunk-O7XYJQB3-B5s8ZEtK.js} +1 -1
- package/dist/assets/{chunk-UA2S7LBM-Sl2yutXV.js → chunk-UA2S7LBM-C_9rO4Bq.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-Bolj8idN.js → chunk-WEXAMYUT-BfmBAXJU.js} +1 -1
- package/dist/assets/{chunk-Z7XXMR3K-D7tNRnxY.js → chunk-Z7XXMR3K-C5wqMFJZ.js} +1 -1
- package/dist/assets/{chunk-ZIGJFQKS-CldXetHK.js → chunk-ZIGJFQKS-Cb2HKlt1.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-C6RYXAf4.js → classDiagram-v2-NBCMYWYE-BYjo8rpu.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-Czb--uYb.js → cynefin-OW5HDTMX-DlEtHItL.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-ZJzYvnKK.js → cynefinDiagram-VND7K2PF-D4B2zpSZ.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-BgNOuHtW.js → dagre-6A5THRUB-DrtmzUL5.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-DSoVjxU3.js → diagram-22UHCM2B-KwctRlfY.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-DJRpzLRm.js → diagram-3UASUU5V-nfsnD-a_.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-LlgL_dRG.js → diagram-ATOU4E4O-zlzXPxjH.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CRNWS0cS.js → diagram-CDSNMT55-CU1kUC3a.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-4UrdpA4l.js → diagram-MLGK6HIB-JZQTQ-hZ.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-Da_XaskV.js → diagram-MPIPVDR6-lTEXn3AL.js} +1 -1
- package/dist/assets/{dist-SK33V2vr.js → dist-BSKq0-F1.js} +1 -1
- package/dist/assets/{dist-BLSkJMFh.js → dist-DXTMKuwj.js} +1 -1
- package/dist/assets/{dist-CeCMlFiz.js → dist-Dspk0d4E.js} +1 -1
- package/dist/assets/{dist-NiAgrplo.js → dist-Gvs7vfCp.js} +1 -1
- package/dist/assets/{dist-DFwiLw8u.js → dist-UwUnjgvi.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DhoYIPQP.js → ebnfDiagram-ZINNZB2B-CE-wOhYF.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-DwLxf-UK.js → elk-276RUBZZ-uyZC1Z8S.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BpWKJ3vv.js → erDiagram-OPXOYQCR-CNCRGsHi.js} +1 -1
- package/dist/assets/eventmodeling-NTZA5JFV-BxDsNL_j.js +1 -0
- package/dist/assets/flowDiagram-KWPJA3E3-C2MPbtsK.js +1 -0
- package/dist/assets/{ganttDiagram-FUAMR5RP-CJ3xg9nX.js → ganttDiagram-FUAMR5RP-PdhJQjbl.js} +1 -1
- package/dist/assets/{gitGraph-4MIJSDKK-BT7gRD5M.js → gitGraph-4MIJSDKK-Tk81Gb8L.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-CIyczVL_.js → gitGraphDiagram-X574FWY7-wR1C12PS.js} +1 -1
- package/dist/assets/{index-CSlVGVVi.js → index-DenS80L_.js} +395 -347
- package/dist/assets/index-qZoK1jNq.css +1 -0
- package/dist/assets/{info-A6RAGUB7-NzW7Bude.js → info-A6RAGUB7-BWvwsVf_.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-C_r4QhTl.js → infoDiagram-VRGFBTTK-DsdCyD7V.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-MXSVQib_.js → ishikawaDiagram-OU5B5YK6-DKiYpDek.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DUV5oKcf.js → journeyDiagram-ZHPQQLJL-DWJid8J8.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BoEZram1.js → kanban-definition-PNTS6WVX-DabMxz-B.js} +1 -1
- package/dist/assets/{line-5ZVBQEXI.js → line-DvVCM2H2.js} +1 -1
- package/dist/assets/{marp-BZuPB_AE.js → marp-Kv19Jld7.js} +3 -3
- package/dist/assets/{mermaid-parser.core-Bta0VZsa.js → mermaid-parser.core-BvQdcEc9.js} +3 -3
- package/dist/assets/{mermaid.core--AlGb2iN.js → mermaid.core-DLkUQwjT.js} +4 -4
- package/dist/assets/{mindmap-definition-NLK3R4M7-BG6CkPNZ.js → mindmap-definition-NLK3R4M7-vVxCaMqG.js} +1 -1
- package/dist/assets/{packet-AYTQ26CC-DibVOHBx.js → packet-AYTQ26CC-CSyTAe0w.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BWOJlyOR.js → pegDiagram-GJSIUBJH-Da_ZhFlr.js} +1 -1
- package/dist/assets/{pie-WAS4IAKB-Df0zCUrS.js → pie-WAS4IAKB-BeQ346mv.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DwU-nZck.js → pieDiagram-5QR66LMP-DMEWUCEd.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-CPYruJxn.js → quadrantDiagram-O4NWA36T-BHVDzC7U.js} +1 -1
- package/dist/assets/{radar-RG4KPBEZ-VBJmKqlV.js → radar-RG4KPBEZ-B9xiIWpv.js} +1 -1
- package/dist/assets/{railroad-74A4TZTK-DJxmOnHl.js → railroad-74A4TZTK-CFpolYIs.js} +1 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-Bksw2TLw.js +1 -0
- package/dist/assets/railroad-ebnf-LZEXJU2U-C4Af36yh.js +1 -0
- package/dist/assets/railroad-peg-WCYAUIDC-BKOkIuwi.js +1 -0
- package/dist/assets/{railroadDiagram-XR7U4H2S-BmkdSuur.js → railroadDiagram-XR7U4H2S-BD1jI_5o.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CuOWgGsU.js → requirementDiagram-PLB6GJNP-CVzgB8N1.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-BgnWJ7Nj.js → sankeyDiagram-IPEJSGJF-BDP5AJHD.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-C9K06g8F.js → sequenceDiagram-PO4LG4MO-CHf6AC-l.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-PPqHC9YG.js → stateDiagram-v2-GCMORJYK-CSKbVid3.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-By3ULjLZ.js → swimlanes-2SLR337P-XatlZPR3.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-DOCNRavw.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-LkTIoeaI.js → timeline-definition-EJHVYXUP-eQgnBadf.js} +1 -1
- package/dist/assets/{treeView-Q6P3EWNA-DNSKHaOO.js → treeView-Q6P3EWNA-BQU1XRcr.js} +1 -1
- package/dist/assets/{treemap-WGGIJYW6-B1NR7Vzs.js → treemap-WGGIJYW6-CmakEgLd.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-C1frOYf0.js → usecaseDiagram-POWQR4AR-C9jTxxCU.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-DJTTZVRm.js → vennDiagram-UO4OBE2U-DmxE0k3v.js} +1 -1
- package/dist/assets/{wardley-WFR3VGLG-t8c8e9Cx.js → wardley-WFR3VGLG-Uqfy1cXg.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-frSDtX1F.js → wardleyDiagram-VNRHLVJA-Cza-TOud.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DRPhoPZG.js → xychartDiagram-PMCCYNJV-DxNOuxul.js} +1 -1
- package/dist/index.html +2 -2
- package/docs/guide/en/v8.6.0.md +75 -0
- package/docs/guide/ja/v8.6.0.md +76 -0
- package/package.json +2 -2
- package/server/backends/sharedApp/appViews.ts +1 -1
- package/server/backends/sharedApp/headlessPreview.ts +3 -2
- package/server/backends/sharedApp/headlessReport.ts +2 -2
- package/server/backends/sharedApp/participate/app.ts +15 -4
- package/server/backends/sharedApp/participate/submit.ts +2 -1
- package/server/backends/sharedApp/preview.ts +49 -11
- package/server/backends/sharedApp/previewIntent.ts +2 -2
- package/server/backends/sharedApp/previewLookup.ts +7 -6
- package/server/backends/sharedApp/previewWatch.ts +7 -11
- package/server/backends/sharedApp/previewWrite.ts +2 -1
- package/server/git/worktree-cleanup.ts +115 -0
- package/server/git/worktree-routes.ts +39 -0
- package/server/index.ts +2 -8
- package/server/infra/process-list.ts +43 -0
- package/server/infra/spawnCapture.ts +11 -2
- package/server/infra/tmux.ts +36 -1
- package/server/routes/app-routes.ts +16 -1
- package/server/routes/process-routes.ts +95 -0
- package/server/session/session-processes.ts +98 -0
- package/server/skills/mulmoterminal-shared-app/SKILL.md +54 -3
- package/server/skills/mulmoterminal-shared-app/templates/class-seats.md +78 -14
- package/server/skills/mulmoterminal-shared-app/templates/gym.md +18 -8
- package/server/skills/mulmoterminal-shared-app/templates/meeting-room.md +79 -74
- package/server/skills/mulmoterminal-shared-app/templates/question-box.md +308 -0
- package/server/skills/mulmoterminal-shared-app/templates/salon.md +30 -101
- package/server/skills/mulmoterminal-shared-app/templates/schedule-poll.md +9 -3
- package/server/skills/mulmoterminal-shared-app/templates/survey.md +4 -2
- package/server/skills/mulmoterminal-shared-app/templates/tally.md +369 -0
- package/dist/assets/architecture-7GRP2DOG-swWiiNTU.js +0 -1
- package/dist/assets/channel-CQxwUcvS.js +0 -1
- package/dist/assets/eventmodeling-NTZA5JFV-ubF7vcc0.js +0 -1
- package/dist/assets/flowDiagram-KWPJA3E3-Dj-XZpvf.js +0 -1
- package/dist/assets/index-BYo1JFJe.css +0 -1
- package/dist/assets/railroad-abnf-HS5TGJTU-DkbzrPAP.js +0 -1
- package/dist/assets/railroad-ebnf-LZEXJU2U-L6Dn3Ypy.js +0 -1
- package/dist/assets/railroad-peg-WCYAUIDC-DA6iP0WF.js +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-DGGJRwyM.js +0 -8
package/README.md
CHANGED
|
@@ -1763,6 +1763,18 @@ narrow and tall for one record you are discussing — and each position keeps it
|
|
|
1763
1763
|
bring, and a warning listing the files that can run code. Nothing is installed from the viewer —
|
|
1764
1764
|
it shows the `npx skills add …` line to copy and run yourself. Your search words go to skills.sh
|
|
1765
1765
|
only when you press **Search**.
|
|
1766
|
+
- **Processes** — **More features** → **Processes** shows what mulmoterminal has left running.
|
|
1767
|
+
The **Processes** tab lists each tmux session with the process tree under its panes — `yarn dev`,
|
|
1768
|
+
vite, MCP bridges, the agent itself — with CPU, memory and how long each has run, and an **End**
|
|
1769
|
+
button per process (SIGTERM, then SIGKILL if the same process is still there a few seconds later).
|
|
1770
|
+
A session's own shell is not offered: close its terminal in the grid, or end a session that is
|
|
1771
|
+
open nowhere from Settings > Sessions. Busy and day-old
|
|
1772
|
+
processes are highlighted, never ended for you. It reads only while the page is open. The
|
|
1773
|
+
**Worktrees** tab lists the managed worktrees of the folders your terminals have run in and what
|
|
1774
|
+
keeps each from going — uncommitted or untracked files, commits not in the base branch, a
|
|
1775
|
+
terminal standing in it. One with none of those gets a **Remove** button (the worktree and its
|
|
1776
|
+
merged branch); nothing is removed automatically. Gitignored files (a local `.env`, `node_modules/`)
|
|
1777
|
+
do not hold a worktree back, but they go with it, so the row and the confirmation name them.
|
|
1766
1778
|
- **Grid of parallel sessions** — the + Terminal / grid view runs many sessions at once,
|
|
1767
1779
|
auto-sizing by count across pages. Cell borders signal state at a glance — **working**
|
|
1768
1780
|
(pulsing blue), **blocked** (amber — needs a permission / answer), **done** (blue —
|
|
@@ -45,6 +45,9 @@ export function targetsViewOf(raw: string | null): TargetsView {
|
|
|
45
45
|
if (!json.ok) return { targets: null, problem: `${TARGETS_FILE} is not JSON` };
|
|
46
46
|
const parsed = targetsFileSchema.safeParse(json.value);
|
|
47
47
|
if (!parsed.success) return { targets: null, problem: `${TARGETS_FILE} does not hold a list of targets` };
|
|
48
|
+
// The id is each row's identity on screen; the pack's own check refuses a repeat for the same reason.
|
|
49
|
+
const ids = parsed.data.targets.map((target) => target.id);
|
|
50
|
+
if (new Set(ids).size !== ids.length) return { targets: null, problem: `${TARGETS_FILE} lists a target id twice` };
|
|
48
51
|
return { targets: parsed.data.targets, problem: null };
|
|
49
52
|
}
|
|
50
53
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// What the Processes page shows: each tmux session and the process tree under its panes (#2219).
|
|
2
|
+
//
|
|
3
|
+
// In `common/` because both sides decide from it: the server builds it from `ps` and tmux
|
|
4
|
+
// (server/session/session-processes.ts), and the page reads it back and highlights from it.
|
|
5
|
+
import { isRecord } from "./isRecord.js";
|
|
6
|
+
import { isUnknownArray } from "./isUnknownArray.js";
|
|
7
|
+
|
|
8
|
+
export interface SessionProcess {
|
|
9
|
+
pid: number;
|
|
10
|
+
ppid: number;
|
|
11
|
+
/** How far below the pane's root process, for indenting. The root is 0. */
|
|
12
|
+
depth: number;
|
|
13
|
+
command: string;
|
|
14
|
+
rssKb: number;
|
|
15
|
+
elapsedSeconds: number;
|
|
16
|
+
/** With `pid`, what a kill request names, so a reused pid is never the one killed. */
|
|
17
|
+
startedAt: string;
|
|
18
|
+
/** Percent of one core since the previous read; null on the first read, which has no baseline. */
|
|
19
|
+
cpuPercent: number | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SessionProcesses {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
/** Where the session runs, when this server remembers it. */
|
|
25
|
+
cwd: string | null;
|
|
26
|
+
/** Preorder, so each process sits under its parent. */
|
|
27
|
+
processes: SessionProcess[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A process this busy is the one a person opened the page to find.
|
|
31
|
+
export const HOT_CPU_PERCENT = 50;
|
|
32
|
+
// A day: a `yarn dev` left running since yesterday is worth a second look, not a kill.
|
|
33
|
+
export const LONG_RUNNING_SECONDS = 86_400;
|
|
34
|
+
|
|
35
|
+
export const isHot = (process: SessionProcess): boolean => process.cpuPercent !== null && process.cpuPercent >= HOT_CPU_PERCENT;
|
|
36
|
+
export const isLongRunning = (process: SessionProcess): boolean => process.elapsedSeconds >= LONG_RUNNING_SECONDS;
|
|
37
|
+
|
|
38
|
+
/** The pane's own root process. Ending it is ending the session, which has its own route. */
|
|
39
|
+
export const isPaneRoot = (process: SessionProcess): boolean => process.depth === 0;
|
|
40
|
+
|
|
41
|
+
const isSessionProcess = (value: unknown): value is SessionProcess =>
|
|
42
|
+
isRecord(value) &&
|
|
43
|
+
typeof value.pid === "number" &&
|
|
44
|
+
typeof value.ppid === "number" &&
|
|
45
|
+
typeof value.depth === "number" &&
|
|
46
|
+
typeof value.command === "string" &&
|
|
47
|
+
typeof value.rssKb === "number" &&
|
|
48
|
+
typeof value.elapsedSeconds === "number" &&
|
|
49
|
+
typeof value.startedAt === "string" &&
|
|
50
|
+
(value.cpuPercent === null || typeof value.cpuPercent === "number");
|
|
51
|
+
|
|
52
|
+
const isSessionProcesses = (value: unknown): value is SessionProcesses =>
|
|
53
|
+
isRecord(value) &&
|
|
54
|
+
typeof value.sessionId === "string" &&
|
|
55
|
+
(value.cwd === null || typeof value.cwd === "string") &&
|
|
56
|
+
isUnknownArray(value.processes) &&
|
|
57
|
+
value.processes.every(isSessionProcess);
|
|
58
|
+
|
|
59
|
+
/** A `GET /api/processes` body, or null when it is not one. A session that does not parse is
|
|
60
|
+
* dropped rather than the whole list: one odd row should not blank the page. */
|
|
61
|
+
export function readProcessesBody(body: unknown): SessionProcesses[] | null {
|
|
62
|
+
if (!isRecord(body) || !isUnknownArray(body.sessions)) return null;
|
|
63
|
+
return body.sessions.filter(isSessionProcesses);
|
|
64
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Which managed worktrees can go (#2219). Nothing is removed automatically: the page lists every
|
|
2
|
+
// worktree with what holds it back, and a person removes a candidate with a button.
|
|
3
|
+
//
|
|
4
|
+
// In `common/` because both sides decide from it: the server gathers the facts
|
|
5
|
+
// (server/git/worktree-cleanup.ts), the page shows the verdict and the reasons.
|
|
6
|
+
import { isRecord } from "./isRecord.js";
|
|
7
|
+
import { isUnknownArray } from "./isUnknownArray.js";
|
|
8
|
+
|
|
9
|
+
export interface WorktreeCleanupRow {
|
|
10
|
+
/** The main checkout — what `POST /api/worktrees/cleanup/remove` takes as `repoDir`. */
|
|
11
|
+
repo: string;
|
|
12
|
+
base: string;
|
|
13
|
+
path: string;
|
|
14
|
+
branch: string | null;
|
|
15
|
+
/** The commit the branch was at when `merged` was decided — the only commit its deletion may take. */
|
|
16
|
+
head: string;
|
|
17
|
+
exists: boolean;
|
|
18
|
+
/** Uncommitted or untracked files: removing it would lose them. */
|
|
19
|
+
dirty: boolean;
|
|
20
|
+
/** Gitignored entries, which `git worktree remove` deletes WITHOUT counting the tree as dirty — a
|
|
21
|
+
* local `.env` as much as a `node_modules`. Not a blocker, since nearly every worktree has some;
|
|
22
|
+
* named to the person instead. At most `IGNORED_LISTED_MAX`; `ignoredCount` is the total. */
|
|
23
|
+
ignored: string[];
|
|
24
|
+
ignoredCount: number;
|
|
25
|
+
/** Every commit on it is already in the base. Ancestry, so a squash-merged branch reads as
|
|
26
|
+
* unmerged — the safe direction. */
|
|
27
|
+
merged: boolean;
|
|
28
|
+
/** An agent session belongs to it, or a pane of ours stands in it (a plain shell counts). */
|
|
29
|
+
inUse: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const IGNORED_LISTED_MAX = 20;
|
|
33
|
+
|
|
34
|
+
/** The entries `git status --porcelain --ignored` marks `!!`. An ignored directory is one entry
|
|
35
|
+
* (`node_modules/`): git does not descend into it. */
|
|
36
|
+
export const parseIgnoredEntries = (porcelain: string): string[] =>
|
|
37
|
+
porcelain.split("\n").flatMap((line) => (line.startsWith("!! ") && line.length > 3 ? [line.slice(3)] : []));
|
|
38
|
+
|
|
39
|
+
/** Dirty and ignored from ONE `git status --porcelain --ignored`. Null output is a status that
|
|
40
|
+
* failed: then nothing is known to be safe to lose, so it reads as dirty — a blocker. */
|
|
41
|
+
export function worktreeStatus(porcelain: string | null): { dirty: boolean; ignored: string[] } {
|
|
42
|
+
if (porcelain === null) return { dirty: true, ignored: [] };
|
|
43
|
+
const lines = porcelain.split("\n").filter((line) => line.trim() !== "");
|
|
44
|
+
return { dirty: lines.some((line) => !line.startsWith("!! ")), ignored: parseIgnoredEntries(porcelain) };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The ignored files a person was shown when they confirmed a removal. */
|
|
48
|
+
export interface ConfirmedIgnored {
|
|
49
|
+
ignored: string[];
|
|
50
|
+
ignoredCount: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A removal request's confirmed ignored files, or null when the request does not carry them. */
|
|
54
|
+
export function readConfirmedIgnored(body: Record<string, unknown>): ConfirmedIgnored | null {
|
|
55
|
+
const { ignored, ignoredCount } = body;
|
|
56
|
+
if (!isUnknownArray(ignored) || !ignored.every((entry) => typeof entry === "string") || typeof ignoredCount !== "number") return null;
|
|
57
|
+
return { ignored, ignoredCount };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Whether what would be deleted now is what was confirmed: an ignored file created since the list
|
|
61
|
+
* was read would otherwise go without ever having been named. */
|
|
62
|
+
export const sameIgnored = (confirmed: ConfirmedIgnored, now: ConfirmedIgnored): boolean =>
|
|
63
|
+
confirmed.ignoredCount === now.ignoredCount && confirmed.ignored.join("\n") === now.ignored.join("\n");
|
|
64
|
+
|
|
65
|
+
export const CLEANUP_BLOCKERS = ["missing", "dirty", "unmerged", "inUse"] as const;
|
|
66
|
+
export type CleanupBlocker = (typeof CLEANUP_BLOCKERS)[number];
|
|
67
|
+
|
|
68
|
+
/** What keeps a worktree from being a removal candidate, in a fixed order. Empty means it can go. */
|
|
69
|
+
export function cleanupBlockers(row: WorktreeCleanupRow): CleanupBlocker[] {
|
|
70
|
+
const holds: Record<CleanupBlocker, boolean> = { missing: !row.exists, dirty: row.dirty, unmerged: !row.merged, inUse: row.inUse };
|
|
71
|
+
return CLEANUP_BLOCKERS.filter((blocker) => holds[blocker]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const isCleanupCandidate = (row: WorktreeCleanupRow): boolean => cleanupBlockers(row).length === 0;
|
|
75
|
+
|
|
76
|
+
const isCleanupRow = (value: unknown): value is WorktreeCleanupRow =>
|
|
77
|
+
isRecord(value) &&
|
|
78
|
+
typeof value.repo === "string" &&
|
|
79
|
+
typeof value.base === "string" &&
|
|
80
|
+
typeof value.path === "string" &&
|
|
81
|
+
(value.branch === null || typeof value.branch === "string") &&
|
|
82
|
+
typeof value.head === "string" &&
|
|
83
|
+
typeof value.exists === "boolean" &&
|
|
84
|
+
typeof value.dirty === "boolean" &&
|
|
85
|
+
isUnknownArray(value.ignored) &&
|
|
86
|
+
value.ignored.every((entry) => typeof entry === "string") &&
|
|
87
|
+
typeof value.ignoredCount === "number" &&
|
|
88
|
+
typeof value.merged === "boolean" &&
|
|
89
|
+
typeof value.inUse === "boolean";
|
|
90
|
+
|
|
91
|
+
/** A `GET /api/worktrees/cleanup` body, or null when it is not one. */
|
|
92
|
+
export function readWorktreeCleanupBody(body: unknown): WorktreeCleanupRow[] | null {
|
|
93
|
+
if (!isRecord(body) || !isUnknownArray(body.worktrees)) return null;
|
|
94
|
+
return body.worktrees.filter(isCleanupRow);
|
|
95
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-BvQdcEc9.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-CllyRBe0.js";import"./chunk-O7XYJQB3-B5s8ZEtK.js";import{n as i,r as a,t as o}from"./chunk-GTNCS2PH-nGmKdp-b.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-DLkUQwjT.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-FOHPRMQF-DzxwRta7.js";import{M as e}from"./mermaid-parser.core-BvQdcEc9.js";export{e as createArchitectureServices};
|