@krapht/ork 1.0.0-alpha.13 → 1.0.0-alpha.14

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.
Files changed (42) hide show
  1. package/dist/{__build_info__-Da73_nBs.mjs → __build_info__-B82-dGJY.mjs} +2 -2
  2. package/dist/{attach-E6NNyA3T.mjs → attach-CdtGX9se.mjs} +5 -5
  3. package/dist/bin.mjs +194 -215
  4. package/dist/{builtin-files-n6o6oWpB.mjs → builtin-files-Bdq23IIi.mjs} +1 -1
  5. package/dist/{chips-CyeB_rhR.mjs → chips-BqcT58qr.mjs} +1 -1
  6. package/dist/{completion-SNNNxnv9.mjs → completion--ywde-56.mjs} +52 -11
  7. package/dist/{context-DHudLA5e.mjs → context-Y_I4Mofq.mjs} +17 -7
  8. package/dist/{find-CjsNYAAf.mjs → find-BVX-bL4u.mjs} +1 -1
  9. package/dist/{format-C60TFpRe.mjs → format-CvTE5CZQ.mjs} +12 -12
  10. package/dist/{help-groups-CyfPWlST.mjs → help-groups-CpR2UzSn.mjs} +16 -6
  11. package/dist/{in-a-run-BYjTXaST.mjs → in-a-run-cL5N9gpH.mjs} +1 -1
  12. package/dist/{limits-C5QKZNLT.mjs → limits-Dhe--wmX.mjs} +1 -1
  13. package/dist/{mcp-bcctR-wc.mjs → mcp-CsmboyXv.mjs} +6 -6
  14. package/dist/{once-DPQu5Q3H.mjs → once-CrwI-ZdN.mjs} +4 -4
  15. package/dist/{output-CnRzrbUM.mjs → output-B1kwo0YU.mjs} +17 -1
  16. package/dist/{pause-BPvlDIvz.mjs → pause-DssrYcda.mjs} +4 -4
  17. package/dist/{relay-C8W0t_zc.mjs → relay-DASRgsTQ.mjs} +2 -2
  18. package/dist/{run-CQIfq918.mjs → run-D8divGCs.mjs} +1 -1
  19. package/dist/send-CXv8bx0i.mjs +105 -0
  20. package/dist/{src-CGJGL1sZ.mjs → src-8Mul0695.mjs} +8 -10
  21. package/dist/{src-D93tHZ5B.mjs → src-C7z_6fhn.mjs} +92 -39
  22. package/dist/{src-CqRqsb2t.mjs → src-njdtejgZ.mjs} +98 -33
  23. package/dist/{src-BeR6f70A.mjs → src-tsuZRu0H.mjs} +2353 -918
  24. package/dist/{store-CM3cERGl.mjs → store-CleF7KeS.mjs} +21 -73
  25. package/dist/ui/assets/app-D_nAPnHM.css +1 -0
  26. package/dist/ui/assets/{app-DYRC0V4p.js → app-DqBAtXSZ.js} +19 -19
  27. package/dist/ui/assets/{index-CWwm8HTG.js → index-D-_uPI2v.js} +2 -2
  28. package/dist/ui/index.html +1 -1
  29. package/dist/{ui-D2Ydofq0.mjs → ui-CNO1M0qT.mjs} +4 -4
  30. package/dist/{usage-6WIowblz.mjs → usage-BVXi0iFY.mjs} +13 -141
  31. package/dist/{verdict-CIy8tAyX.mjs → verdict-CMvPCpWC.mjs} +2 -2
  32. package/dist/{watch-uOU_WeBI.mjs → watch-DVNIciHp.mjs} +10 -8
  33. package/dist/{workflows-7tQdMnrA.mjs → workflows-B30QkwTk.mjs} +3 -3
  34. package/dist/{worktrees-ZCII298F.mjs → worktrees-DD4_TpkH.mjs} +3 -5
  35. package/package.json +2 -2
  36. package/dist/clean-Bh4KZgzc.mjs +0 -91
  37. package/dist/decide-DuIHACvH.mjs +0 -50
  38. package/dist/list-BUb5Uc3H.mjs +0 -117
  39. package/dist/remove-BkFWGVYo.mjs +0 -201
  40. package/dist/report-CEN8VoUb.mjs +0 -140
  41. package/dist/send-CaZlGG5a.mjs +0 -163
  42. package/dist/ui/assets/app-QoHVi-mb.css +0 -1
@@ -19,7 +19,7 @@
19
19
  href="/favicon.svg"
20
20
  type="image/svg+xml"
21
21
  />
22
- <script type="module" crossorigin src="/assets/index-CWwm8HTG.js"></script>
22
+ <script type="module" crossorigin src="/assets/index-D-_uPI2v.js"></script>
23
23
  <link rel="modulepreload" crossorigin href="/assets/theme-CdXvqTQ6.js">
24
24
  <link rel="stylesheet" crossorigin href="/assets/index-BPIbqRRb.css">
25
25
  </head>
@@ -1,7 +1,7 @@
1
- import { n as ORK_VERSION, t as ORK_NAME } from "./__build_info__-Da73_nBs.mjs";
2
- import { a as style, o as write, s as writeErr } from "./output-CnRzrbUM.mjs";
3
- import { o as mayShowSignIn } from "./in-a-run-BYjTXaST.mjs";
4
- import { i as uiFilePath, l as newToken, n as startServer, r as runningServer, t as DEFAULT_PORT } from "./src-CGJGL1sZ.mjs";
1
+ import { n as ORK_VERSION, t as ORK_NAME } from "./__build_info__-B82-dGJY.mjs";
2
+ import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
3
+ import { o as mayShowSignIn } from "./in-a-run-cL5N9gpH.mjs";
4
+ import { i as uiFilePath, l as newToken, n as startServer, r as runningServer, t as DEFAULT_PORT } from "./src-8Mul0695.mjs";
5
5
  import path from "node:path";
6
6
  import { constants } from "node:os";
7
7
  import { spawn } from "node:child_process";
@@ -1,137 +1,9 @@
1
- import { Gn as runDirOf, Un as placeFor, _t as liveStatus, it as usd, w as openStore } from "./src-BeR6f70A.mjs";
2
- import { n as listOrkWorktrees, t as createdWorktrees } from "./list-BUb5Uc3H.mjs";
3
- import { n as usageReport, t as USAGE_DIMENSIONS } from "./report-CEN8VoUb.mjs";
4
- import { a as style, n as cost, o as write, s as writeErr, t as confirm, x as termOf } from "./output-CnRzrbUM.mjs";
5
- import { n as chip, t as CHIP_LABELS } from "./chips-CyeB_rhR.mjs";
6
- import { n as limitsHere } from "./limits-C5QKZNLT.mjs";
7
- import { a as header, o as table } from "./format-C60TFpRe.mjs";
8
- import { existsSync, rmSync } from "node:fs";
1
+ import { At as usd, G as applyPrune, K as planPrune, S as usageReport, U as openStore, mr as placeFor, x as USAGE_DIMENSIONS } from "./src-tsuZRu0H.mjs";
2
+ import { C as termOf, c as write, l as writeErr, n as cost, o as shortCount, r as costOf, s as style, t as confirm } from "./output-B1kwo0YU.mjs";
3
+ import { n as chip, t as CHIP_LABELS } from "./chips-BqcT58qr.mjs";
4
+ import { n as limitsHere } from "./limits-Dhe--wmX.mjs";
5
+ import { a as header, o as table } from "./format-CvTE5CZQ.mjs";
9
6
  import { defineCommand } from "citty";
10
- // ../core/src/store/prune.ts
11
- /**
12
- * @file `ork prune` (ADR-046): which runs may lose their transcript, and doing
13
- * it. A run goes with the runs it called or was called by, or not at all, and
14
- * not while it is live, is linked, or has a worktree or branch left: those
15
- * read the run's events. The usage ledger and feedback records stay.
16
- */
17
- const messageOf = (error) => error instanceof Error ? error.message : String(error);
18
- /**
19
- * The runs in a project that still have a worktree or branch. With its folder
20
- * gone, or no longer a repository, there is nothing left to clean up; any other
21
- * failure says nothing about them, so it rejects.
22
- */
23
- const runsWithWorktrees = (store) => async (root) => {
24
- if (!existsSync(root)) return /* @__PURE__ */ new Set();
25
- try {
26
- const found = await listOrkWorktrees(root, createdWorktrees(store, root));
27
- return new Set(found.map((w) => w.runId));
28
- } catch (error) {
29
- if (/not a git repository/iu.test(messageOf(error))) return /* @__PURE__ */ new Set();
30
- throw error;
31
- }
32
- };
33
- /** The run at the top of a run's chain of calls. */
34
- const callRootOf = (run, byId) => {
35
- let top = run;
36
- const seen = /* @__PURE__ */ new Set([run.id]);
37
- while (top.parent !== void 0) {
38
- const parent = byId.get(top.parent);
39
- if (parent === void 0 || seen.has(parent.id)) break;
40
- seen.add(parent.id);
41
- top = parent;
42
- }
43
- return top.id;
44
- };
45
- /** Sorts the runs before the cutoff into those to remove and those to keep. */
46
- const planPrune = async (store, filter, worktreesOf = runsWithWorktrees(store)) => {
47
- const all = store.runs();
48
- const byId = new Map(all.map((run) => [run.id, run]));
49
- const inScope = (run) => filter.project === void 0 || run.project === filter.project;
50
- const old = (run) => (store.lastEventAt(run.id) ?? run.finishedAt ?? run.startedAt) < filter.before;
51
- const trees = /* @__PURE__ */ new Map();
52
- for (const run of all) {
53
- const root = callRootOf(run, byId);
54
- trees.set(root, [...trees.get(root) ?? [], run]);
55
- }
56
- const projects = [...new Set(all.filter((r) => old(r) && inScope(r)).map((r) => r.project))];
57
- const withWorktrees = new Map(await Promise.all(projects.map(async (project) => {
58
- try {
59
- return [project, await worktreesOf(project)];
60
- } catch (error) {
61
- return [project, messageOf(error)];
62
- }
63
- })));
64
- /** Why this run itself cannot go, or `null` when it can. */
65
- const blocker = (run) => {
66
- if (liveStatus(run) === "running") return "it is still running";
67
- const worktrees = withWorktrees.get(run.project);
68
- if (typeof worktrees === "string") return `cannot tell whether it has a worktree left: ${worktrees}`;
69
- if (worktrees?.has(run.id) === true) return "it has a worktree or branch left (ork worktrees clean)";
70
- if (store.links({
71
- run: run.id,
72
- live: true
73
- }).length > 0) return "it has a live link";
74
- return null;
75
- };
76
- const plan = {
77
- remove: [],
78
- keep: []
79
- };
80
- for (const tree of trees.values()) {
81
- const held = tree.map((run) => {
82
- if (!inScope(run)) return `run ${run.id} of its call is in another project (--all)`;
83
- if (!old(run)) return `run ${run.id} of its call is newer`;
84
- const why = blocker(run);
85
- return why === null ? null : `run ${run.id} of its call stays: ${why}`;
86
- }).find((why) => why !== null);
87
- for (const run of tree) {
88
- if (!inScope(run) || !old(run)) continue;
89
- if (held === void 0) plan.remove.push(run);
90
- else plan.keep.push({
91
- run,
92
- reason: blocker(run) ?? held
93
- });
94
- }
95
- }
96
- return plan;
97
- };
98
- const SAFE_ID = /^[\w-]+$/u;
99
- /** Forgets the plan's runs and removes their folders. */
100
- const applyPrune = (store, plan) => {
101
- const outcome = {
102
- removed: 0,
103
- folders: 0,
104
- failed: []
105
- };
106
- const places = store.projects();
107
- for (const run of plan.remove) {
108
- if (!store.deleteRun(run.id)) {
109
- outcome.failed.push({
110
- run: run.id,
111
- reason: "it is running again"
112
- });
113
- continue;
114
- }
115
- outcome.removed += 1;
116
- if (!SAFE_ID.test(run.id)) continue;
117
- const place = places.find((p) => p.root === run.project) ?? placeFor(run.project, places);
118
- const folder = runDirOf(place, run.id);
119
- if (!existsSync(folder)) continue;
120
- try {
121
- rmSync(folder, {
122
- recursive: true,
123
- force: true
124
- });
125
- outcome.folders += 1;
126
- } catch (error) {
127
- outcome.failed.push({
128
- run: run.id,
129
- reason: `its folder stays, at ${folder}: ${messageOf(error)}`
130
- });
131
- }
132
- }
133
- return outcome;
134
- };
135
7
  // src/runs/usage.ts
136
8
  /**
137
9
  * @file How usage reads on the terminal (`ork usage`, specs/usage.md), and the
@@ -181,11 +53,7 @@ const sinceTime = (text, now) => {
181
53
  /** `--until`: the last day it includes. */
182
54
  const untilTime = (text) => dayTime(text, 1);
183
55
  /** `950`, `12.3k`, `4.5M`. */
184
- const tokens = (count) => {
185
- if (count < 1e3) return String(count);
186
- if (count < 1e6) return `${(count / 1e3).toFixed(1)}k`;
187
- return `${(count / 1e6).toFixed(1)}M`;
188
- };
56
+ const tokens = shortCount;
189
57
  const percent = (share) => `${String(Math.round(share * 100))}%`;
190
58
  const counted = (count, noun) => `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
191
59
  const TITLES = {
@@ -239,8 +107,8 @@ const shortTime = (iso) => new Date(iso).toLocaleString(void 0, {
239
107
  /** A warning line: a `WARN` chip when rich, else the line in yellow. */
240
108
  const warned = (term, text) => term.rich ? ` ${chip(term, CHIP_LABELS.warn, "warn")} ${text}` : style("yellow", ` ${text}`);
241
109
  /** The limits across runs, above a report; nothing with none set. */
242
- const formatLimits = ({ problems, caps, plan }, term = termOf(process.stdout)) => {
243
- if (problems.length === 0 && caps.length === 0 && plan.length === 0) return [];
110
+ const formatLimits = ({ problems, caps, tokenCaps = [], plan }, term = termOf(process.stdout)) => {
111
+ if (problems.length === 0 && caps.length === 0 && tokenCaps.length === 0 && plan.length === 0) return [];
244
112
  return [
245
113
  style("bold", "Limits"),
246
114
  ...problems.map((problem) => warned(term, problem)),
@@ -248,6 +116,10 @@ const formatLimits = ({ problems, caps, plan }, term = termOf(process.stdout)) =
248
116
  const line = ` ${cap.project === void 0 ? "all projects" : "this project"}, this ${cap.window}: ${usd(cap.spentUsd)} of ${usd(cap.capUsd)}, resets ${shortTime(cap.resetsAt)}`;
249
117
  return cap.leftUsd <= 0 ? warned(term, line.trimStart()) : line;
250
118
  }),
119
+ ...tokenCaps.map((cap) => {
120
+ const line = ` ${cap.project === void 0 ? "all projects" : "this project"}, this ${cap.window}: ${tokens(cap.spentTokens)} of ${tokens(cap.capTokens)} tokens, resets ${shortTime(cap.resetsAt)}`;
121
+ return cap.leftTokens <= 0 ? warned(term, line.trimStart()) : line;
122
+ }),
251
123
  ...plan.map((limit) => warned(term, `Claude plan's ${limit.window.replaceAll("_", " ")} limit ${limit.status === "rejected" ? "was reached" : "is close"}${limit.resetsAt === void 0 ? "" : `, resets ${shortTime(limit.resetsAt)}`}`)),
252
124
  ""
253
125
  ];
@@ -256,7 +128,7 @@ const formatLimits = ({ problems, caps, plan }, term = termOf(process.stdout)) =
256
128
  const formatUsage = (report, scope, by, term = termOf(process.stdout)) => {
257
129
  const { totals } = report;
258
130
  if (totals.sessions === 0) return [`No Claude sessions in ${scope}.`];
259
- const lines = [`${style("bold", cost(totals.costUsd))} in ${scope}`, `${counted(totals.runs, "run")} · ${counted(totals.sessions, "session")} · ${counted(totals.turns, "turn")} · ${tokens(totals.inputTokens + totals.cacheReadTokens + totals.cacheWriteTokens)} in / ${tokens(totals.outputTokens)} out · ${percent(totals.cacheHitRate)} from cache`];
131
+ const lines = [`${style("bold", costOf(totals.costUsd, totals.partial, totals.tokens))} in ${scope}`, `${counted(totals.runs, "run")} · ${counted(totals.sessions, "session")} · ${counted(totals.turns, "turn")} · ${tokens(totals.inputTokens + totals.cacheReadTokens + totals.cacheWriteTokens)} in / ${tokens(totals.outputTokens)} out · ${percent(totals.cacheHitRate)} from cache`];
260
132
  for (const dimension of by === void 0 ? OVERVIEW : [by]) {
261
133
  const groups = report.groups[dimension];
262
134
  const shown = by === void 0 ? dimension === "day" ? groups.slice(-10) : groups.slice(0, OVERVIEW_ROWS) : groups;
@@ -1,5 +1,5 @@
1
- import { d as apply, x as termOf } from "./output-CnRzrbUM.mjs";
2
- import { n as chip, t as CHIP_LABELS } from "./chips-CyeB_rhR.mjs";
1
+ import { C as termOf, p as apply } from "./output-B1kwo0YU.mjs";
2
+ import { n as chip, t as CHIP_LABELS } from "./chips-BqcT58qr.mjs";
3
3
  // src/run/view.ts
4
4
  /**
5
5
  * @file How a run's lines are drawn: the terminal kit's {@link Term} for the
@@ -1,10 +1,10 @@
1
- import { Hn as placeFiles, Q as detachRun, _ as declaredTriggers, _t as liveStatus, b as takeWatchLock, br as isToolNode, g as triggerStanding, h as createWatcher, hr as CheckReportSchema, m as triggersReport, v as readProjectTriggers, w as openStore, wr as timeoutMs, wt as stillTheSameProcess, x as watchingPid } from "./src-BeR6f70A.mjs";
2
- import { n as newRunId } from "./find-CjsNYAAf.mjs";
3
- import { S as cut, a as style, i as plain, o as write, s as writeErr, t as confirm, w as widthOf, x as termOf } from "./output-CnRzrbUM.mjs";
4
- import { n as chip, t as CHIP_LABELS } from "./chips-CyeB_rhR.mjs";
5
- import { c as toolExecutor, o as spawnTool, s as mcpCallExecutor } from "./src-D93tHZ5B.mjs";
6
- import { i as runLimits, r as limitsRefusal } from "./limits-C5QKZNLT.mjs";
7
- import { t as acceptRefusal } from "./in-a-run-BYjTXaST.mjs";
1
+ import { B as watchingPid, Br as CheckReportSchema, F as triggerStanding, Gr as isToolNode, I as declaredTriggers, L as readProjectTriggers, N as triggersReport, P as createWatcher, U as openStore, Ut as liveStatus, Xt as stillTheSameProcess, Yr as timeoutMs, pr as placeFiles, wt as detachRun, z as takeWatchLock } from "./src-tsuZRu0H.mjs";
2
+ import { n as newRunId } from "./find-BVX-bL4u.mjs";
3
+ import { C as termOf, E as widthOf, a as plain, c as write, l as writeErr, s as style, t as confirm, w as cut } from "./output-B1kwo0YU.mjs";
4
+ import { n as chip, t as CHIP_LABELS } from "./chips-BqcT58qr.mjs";
5
+ import { c as toolExecutor, o as spawnTool, s as mcpCallExecutor } from "./src-C7z_6fhn.mjs";
6
+ import { i as runLimits, r as limitsRefusal } from "./limits-Dhe--wmX.mjs";
7
+ import { t as acceptRefusal } from "./in-a-run-cL5N9gpH.mjs";
8
8
  import path from "node:path";
9
9
  import { mkdirSync } from "node:fs";
10
10
  import { homedir } from "node:os";
@@ -132,7 +132,9 @@ const checkLines = (item) => {
132
132
  const workflowLines = ({ workflow, workflowFile }) => {
133
133
  if (workflow === null) return [];
134
134
  const budget = workflow.limits?.budgetUsd;
135
- return [` workflow: ${plain(workflowFile)}${budget === void 0 ? "" : `, up to $${String(budget)} a run`}`, ...Object.entries(workflow.nodes).map(([id, node]) => ` ${plain(id)}: ${plain(node.type)}`)];
135
+ const tokens = workflow.limits?.budgetTokens;
136
+ const caps = [...budget === void 0 ? [] : [`$${String(budget)}`], ...tokens === void 0 ? [] : [`${String(tokens)} tokens`]];
137
+ return [` workflow: ${plain(workflowFile)}${caps.length === 0 ? "" : `, up to ${caps.join(" and ")} a run`}`, ...Object.entries(workflow.nodes).map(([id, node]) => ` ${plain(id)}: ${plain(node.type)}`)];
136
138
  };
137
139
  const STANDS = {
138
140
  new: "new",
@@ -1,6 +1,6 @@
1
- import { Gn as runDirOf, Hn as placeFiles, Jn as inside, T as storePath, Un as placeFor, w as openStore } from "./src-BeR6f70A.mjs";
2
- import { a as isBuiltinArg, n as builtinWorkflowFile, t as builtinProblem } from "./builtin-files-n6o6oWpB.mjs";
3
- import { s as writeErr } from "./output-CnRzrbUM.mjs";
1
+ import { U as openStore, W as storePath, gr as runDirOf, mr as placeFor, pr as placeFiles, yr as inside } from "./src-tsuZRu0H.mjs";
2
+ import { a as isBuiltinArg, n as builtinWorkflowFile, t as builtinProblem } from "./builtin-files-Bdq23IIi.mjs";
3
+ import { l as writeErr } from "./output-B1kwo0YU.mjs";
4
4
  import path from "node:path";
5
5
  import { existsSync, readdirSync, statSync } from "node:fs";
6
6
  // src/project/place.ts
@@ -1,8 +1,6 @@
1
- import { D as inspectProject, w as openStore } from "./src-BeR6f70A.mjs";
2
- import { a as planClean, i as keyOf, o as stepActs, t as applyClean } from "./clean-Bh4KZgzc.mjs";
3
- import { a as readWorktrees, c as mergeStateOf, d as stateParts, l as planLine, n as lostWork, o as repoRoot, r as planRemove, s as labelOf, t as applyRemove } from "./remove-BkFWGVYo.mjs";
4
- import { S as cut, a as style, o as write, p as paint, s as writeErr, t as confirm, x as termOf } from "./output-CnRzrbUM.mjs";
5
- import { n as chip } from "./chips-CyeB_rhR.mjs";
1
+ import { U as openStore, Y as inspectProject, _ as keyOf, a as planRemove, c as repoRoot, d as planLine, h as applyClean, i as lostWork, l as labelOf, p as stateParts, r as applyRemove, s as readWorktrees, u as mergeStateOf, v as planClean, y as stepActs } from "./src-tsuZRu0H.mjs";
2
+ import { C as termOf, c as write, h as paint, l as writeErr, s as style, t as confirm, w as cut } from "./output-B1kwo0YU.mjs";
3
+ import { n as chip } from "./chips-BqcT58qr.mjs";
6
4
  import path from "node:path";
7
5
  import { realpathSync } from "node:fs";
8
6
  import { defineCommand } from "citty";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krapht/ork",
3
- "version": "1.0.0-alpha.13",
3
+ "version": "1.0.0-alpha.14",
4
4
  "description": "Orchestrate Claude agents on your machine: hierarchical, serializable workflows with a CLI and a localhost UI.",
5
5
  "keywords": [
6
6
  "claude",
@@ -54,6 +54,6 @@
54
54
  "tsdown": "0.23.4"
55
55
  },
56
56
  "scripts": {
57
- "build": "pnpm --filter @krapht/ork-ui build && node ../../tools/release/build-info.ts && node ../../tools/release/plugin-skills.ts && node ../../tools/release/builtin-workflows.ts && tsdown && cp ../../LICENSE ../../THIRD_PARTY_NOTICES.md . && cp -R ../ui/dist dist/ui"
57
+ "build": "pnpm --filter @krapht/ork-ui build && node ../../tools/release/build-info.ts && node ../../tools/release/plugin-skills.ts && node ../../tools/release/builtin-workflows.ts && node ../../tools/release/issues-workflow.ts && tsdown && cp ../../LICENSE ../../THIRD_PARTY_NOTICES.md . && cp -R ../ui/dist dist/ui"
58
58
  }
59
59
  }
@@ -1,91 +0,0 @@
1
- import { O as removeWorktree, zt as gitOk } from "./src-BeR6f70A.mjs";
2
- // ../core/src/worktree/clean.ts
3
- /**
4
- * @file What `ork worktrees clean` and `ork worktrees remove` do with what
5
- * list.ts reads: the plan (what goes, what's kept and why), by one rule for
6
- * every caller, and carrying it out. The CLI and the Worktrees panel both
7
- * plan here, so neither removes what the other would keep.
8
- */
9
- /** What names an item for `remove`: its branch, or its worktree's path. */
10
- const keyOf = (item) => item.branch ?? item.path ?? `${item.runId}/${item.nodeId}`;
11
- /** The reasons no `force` overrides: removing would break something. */
12
- const MAIN_CHECKOUT = "checked out in the main checkout";
13
- const RUN_GOING = "its run is still going";
14
- const NOT_ORKS = "not created by Ork";
15
- const kept = (item, skip) => ({
16
- item,
17
- removeWorktree: false,
18
- deleteBranch: false,
19
- skip
20
- });
21
- /** What clean does with one item. */
22
- const cleanStep = (item, options) => {
23
- if (!item.recorded) return kept(item, NOT_ORKS);
24
- if (item.inMainCheckout) return kept(item, MAIN_CHECKOUT);
25
- if (item.heldBy !== void 0) return kept(item, `checked out in ${item.heldBy}`);
26
- if (options.live?.has(item.runId) === true) return kept(item, RUN_GOING);
27
- if (item.dirty && !options.force) return kept(item, "uncommitted changes");
28
- if (item.branch === void 0 && item.checkedOut === void 0 && item.ahead > 0 && !options.force) return kept(item, "commits on no branch");
29
- const removable = item.path !== null && !item.missing;
30
- if (options.branches && item.branch !== void 0 && !item.merged && !options.force) return {
31
- item,
32
- removeWorktree: removable,
33
- deleteBranch: false,
34
- skip: "branch not merged"
35
- };
36
- return {
37
- item,
38
- removeWorktree: removable,
39
- deleteBranch: options.branches && item.branch !== void 0
40
- };
41
- };
42
- /** Whether a step drops a worktree whose folder is gone, by its own path. */
43
- const stepPrunes = (step) => step.item.missing && step.item.path !== null && step.skip === void 0;
44
- /** Whether a step does anything. */
45
- const stepActs = (step) => step.removeWorktree || step.deleteBranch || stepPrunes(step);
46
- /**
47
- * What `ork worktrees clean` would do. A worktree with uncommitted changes, an
48
- * unmerged branch, or a detached worktree holding commits no branch has, is
49
- * kept unless `force`; one checked out in the main checkout or in a worktree
50
- * Ork didn't create, whose run is still going, or an `ork/` branch no run
51
- * recorded creating, is kept even then.
52
- */
53
- const planClean = (items, filter, options) => items.filter((item) => filter.runId === void 0 || item.runId === filter.runId).filter((item) => filter.key === void 0 || keyOf(item) === filter.key).filter((item) => !filter.merged || item.merged).map((item) => cleanStep(item, options));
54
- /**
55
- * Carries out one step; what it did goes into `done` as it goes. It touches
56
- * only its own item: a missing worktree is dropped by its own path (`git
57
- * worktree remove`), never by `git worktree prune`, which would drop every
58
- * missing worktree in the repository, the user's own included.
59
- */
60
- const applyStep = async (root, step, force, done) => {
61
- const { item } = step;
62
- if (stepPrunes(step) && !step.removeWorktree && item.path !== null) {
63
- await gitOk(root, [
64
- "worktree",
65
- "remove",
66
- item.path
67
- ]);
68
- done.push(`pruned ${item.path}`);
69
- }
70
- if (step.removeWorktree && item.path !== null) {
71
- await removeWorktree(root, item.path, force);
72
- done.push(`removed ${item.path}`);
73
- }
74
- if (step.deleteBranch && item.branch !== void 0) {
75
- await gitOk(root, [
76
- "branch",
77
- force ? "-D" : "-d",
78
- item.branch
79
- ]);
80
- done.push(`deleted ${item.branch}`);
81
- }
82
- };
83
- /**
84
- * Carries out a clean plan, step by step. What it did goes into `done` as it
85
- * goes, so a caller still has it when git fails halfway.
86
- */
87
- const applyClean = async (root, steps, force, done = []) => {
88
- for (const step of steps) await applyStep(root, step, force, done);
89
- return done;
90
- };
91
- export { planClean as a, keyOf as i, applyStep as n, stepActs as o, cleanStep as r, stepPrunes as s, applyClean as t };
@@ -1,50 +0,0 @@
1
- import { Cn as socketPath, Tn as wakeRun, _t as liveStatus } from "./src-BeR6f70A.mjs";
2
- // ../core/src/remote/decide.ts
3
- /**
4
- * @file The open decisions of live runs, and answering one from another process
5
- * (`ork decide`, the local server), with the person's note if they gave one.
6
- * An answer goes to the store (the first one wins), then the run is woken on
7
- * its socket so it picks the answer up at once (specs/messaging.md).
8
- */
9
- /** Open decisions whose run is still live, oldest first, ANDing the filter. */
10
- const pendingDecisions = (store, filter) => {
11
- const live = /* @__PURE__ */ new Map();
12
- return store.decisions({
13
- ...filter,
14
- status: "open"
15
- }).filter((decision) => {
16
- if (!live.has(decision.runId)) {
17
- const [run] = store.lookup(decision.runId);
18
- live.set(decision.runId, run !== void 0 && liveStatus(run) === "running" && (filter.workflow === void 0 || run.workflow === filter.workflow));
19
- }
20
- return live.get(decision.runId) === true;
21
- });
22
- };
23
- /**
24
- * Answers an open decision with one of its options, and wakes its run. `home`
25
- * is `~`, where the sockets are (tests pass a folder); `by` says who answered;
26
- * `note` is what they said with it, if anything (blank is nothing).
27
- */
28
- const answerDecision = async (store, id, choice, home, by = "decide", note) => {
29
- const decision = store.decision(id);
30
- if (decision === void 0) return {
31
- ok: false,
32
- error: `No decision \`${id}\`.`
33
- };
34
- if (!decision.options.some((o) => o.id === choice)) return {
35
- ok: false,
36
- error: `\`${choice}\` is not an option of \`${id}\`: \`${decision.options.map((o) => o.id).join("`, `")}\``
37
- };
38
- if (!store.answerDecision(id, choice, by, note)) {
39
- const now = store.decision(id);
40
- return {
41
- ok: false,
42
- error: now?.status === "answered" ? `${id} was already answered: ${now.answer ?? ""} (${now.answeredBy ?? ""})` : `${id} was withdrawn`
43
- };
44
- }
45
- return {
46
- ok: true,
47
- woke: await wakeRun(socketPath(decision.runId, home))
48
- };
49
- };
50
- export { pendingDecisions as n, answerDecision as t };
@@ -1,117 +0,0 @@
1
- import { Bt as lines, Rt as git, zt as gitOk } from "./src-BeR6f70A.mjs";
2
- import { existsSync } from "node:fs";
3
- // ../core/src/worktree/list.ts
4
- /**
5
- * @file What `ork worktrees` shows: every worktree and branch Ork's runs
6
- * recorded creating, whatever the project's git rules named them, and any
7
- * `ork/*` branch besides, with their state read from git itself. What clean
8
- * and remove do with them is clean.ts.
9
- */
10
- /**
11
- * The worktrees the project's runs recorded creating, oldest run first; one
12
- * entry per branch or folder, from the latest run that created it.
13
- */
14
- const createdWorktrees = (store, project) => {
15
- const byName = /* @__PURE__ */ new Map();
16
- for (const run of store.runs({ project }).toReversed()) for (const { event } of store.events(run.id)) {
17
- if (event.type !== "worktree" || event.action !== "created") continue;
18
- byName.set(event.branch ?? event.path, {
19
- runId: run.id,
20
- nodeId: event.id,
21
- path: event.path,
22
- ...event.branch === void 0 ? {} : { branch: event.branch }
23
- });
24
- }
25
- return [...byName.values()];
26
- };
27
- const parsePorcelain = (text) => text.split("\n\n").map((block) => lines(block)).map((fields) => {
28
- const value = (key) => fields.find((f) => f.startsWith(`${key} `))?.slice(key.length + 1);
29
- return {
30
- path: value("worktree") ?? "",
31
- branch: (value("branch") ?? "").replace(/^refs\/heads\//u, ""),
32
- prunable: fields.some((f) => f.startsWith("prunable"))
33
- };
34
- }).filter((entry) => entry.path !== "");
35
- /**
36
- * The `ork/<run>/<node>` branches of runs that recorded nothing; with no path
37
- * recorded, the worktree is whichever has the branch checked out.
38
- */
39
- const prefixed = (branch) => {
40
- const [, runId = "", ...rest] = branch.split("/");
41
- return {
42
- runId,
43
- nodeId: rest.join("/"),
44
- path: "",
45
- branch
46
- };
47
- };
48
- /** How many commits a detached worktree holds that no branch, tag or remote has. */
49
- const onNoBranch = async (entry) => entry === void 0 ? "0" : (await git(entry.path, [
50
- "rev-list",
51
- "--count",
52
- "HEAD",
53
- "--not",
54
- "--branches",
55
- "--tags",
56
- "--remotes"
57
- ])).stdout;
58
- /** One created worktree or branch, with its state read from git. */
59
- const stateOf = async (root, entries, item, recorded) => {
60
- const { branch } = item;
61
- const entry = branch === void 0 || item.path !== "" ? entries.find((e) => e.path === item.path) : entries.find((e) => e.branch === branch);
62
- const holder = branch === void 0 ? void 0 : entries.find((e) => e.branch === branch && e.path !== entry?.path);
63
- const missing = entry !== void 0 && (entry.prunable || !existsSync(entry.path));
64
- const status = entry === void 0 || missing ? "" : (await git(entry.path, ["status", "--porcelain"])).stdout;
65
- const checkedOut = branch === void 0 && entry !== void 0 && entry.branch !== "" ? entry.branch : void 0;
66
- const on = branch ?? checkedOut;
67
- const counted = on === void 0 ? await onNoBranch(entry === void 0 || missing ? void 0 : entry) : (await git(root, [
68
- "rev-list",
69
- "--count",
70
- `HEAD..${on}`
71
- ])).stdout;
72
- const ahead = Number(counted);
73
- const changes = lines(status).length;
74
- return {
75
- ...branch === void 0 ? {} : { branch },
76
- ...checkedOut === void 0 ? {} : { checkedOut },
77
- runId: item.runId,
78
- nodeId: item.nodeId,
79
- recorded,
80
- path: entry?.path ?? null,
81
- missing,
82
- dirty: changes > 0,
83
- changes,
84
- ahead: Number.isNaN(ahead) ? 0 : ahead,
85
- merged: on !== void 0 && ahead === 0,
86
- inMainCheckout: entry?.path === root || holder?.path === root,
87
- ...holder === void 0 || holder.path === root ? {} : { heldBy: holder.path }
88
- };
89
- };
90
- /**
91
- * Every worktree and branch Ork's runs created in the repository at `root`,
92
- * with its state: those the runs recorded (`created`, whatever the project's
93
- * git rules named them), and any `ork/` branch besides. A recorded one that git
94
- * no longer has is left out.
95
- */
96
- const listOrkWorktrees = async (root, created = []) => {
97
- const entries = parsePorcelain(await gitOk(root, [
98
- "worktree",
99
- "list",
100
- "--porcelain"
101
- ]));
102
- const branches = lines(await gitOk(root, [
103
- "for-each-ref",
104
- "--format=%(refname:short)",
105
- "refs/heads/"
106
- ]));
107
- const recordedBranches = new Set(created.flatMap((c) => c.branch ?? []));
108
- const items = [...created.filter((c) => c.branch === void 0 ? entries.some((e) => e.path === c.path) : branches.includes(c.branch)).map((item) => ({
109
- item,
110
- recorded: true
111
- })), ...branches.filter((b) => b.startsWith("ork/") && !recordedBranches.has(b)).map((b) => ({
112
- item: prefixed(b),
113
- recorded: false
114
- }))];
115
- return Promise.all(items.map(({ item, recorded }) => stateOf(root, entries, item, recorded)));
116
- };
117
- export { listOrkWorktrees as n, createdWorktrees as t };