@krapht/ork 0.3.0 → 0.4.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.
Files changed (31) hide show
  1. package/dist/__build_info__-CtSVVuZE.mjs +691 -0
  2. package/dist/bin.mjs +826 -526
  3. package/dist/clean-Dcr807be.mjs +88 -0
  4. package/dist/{context-BFGC_C02.mjs → context-C_bksb0C.mjs} +45 -129
  5. package/dist/{find-BcvhT6UN.mjs → find-DptmbhGd.mjs} +1 -1
  6. package/dist/{format-DqpObyMl.mjs → format-J-a21Yvn.mjs} +3 -163
  7. package/dist/{in-a-run-BbLhZOFW.mjs → in-a-run-D1Qc5Mit.mjs} +1 -1
  8. package/dist/{limits-BKYAmqCR.mjs → limits-BDZCmGEu.mjs} +1 -1
  9. package/dist/list-ZvY2OcLq.mjs +110 -0
  10. package/dist/{mcp-BWsdQyri.mjs → mcp-CHVKKg3B.mjs} +6 -6
  11. package/dist/{output-Q2EdeVUt.mjs → output-CXSJO00X.mjs} +68 -24
  12. package/dist/{pause-DKACDfV1.mjs → pause-BPSmg4yz.mjs} +4 -4
  13. package/dist/{relay-CbK-Wme9.mjs → relay-Do-aNbSD.mjs} +2 -2
  14. package/dist/remove-AwaMMaHf.mjs +194 -0
  15. package/dist/{send-C6yr04Fe.mjs → send-DhGBkowy.mjs} +3 -3
  16. package/dist/{src-CZOVF2yU.mjs → src-BHnxpJyH.mjs} +1 -1
  17. package/dist/{src-Cz1yOUTg.mjs → src-CGqMati3.mjs} +1411 -747
  18. package/dist/{src-D5ruZFgD.mjs → src-CpDKIulI.mjs} +1200 -55
  19. package/dist/{src-DtN1k-B3.mjs → src-UWkk1sl9.mjs} +1 -1
  20. package/dist/ui/assets/app-BgVDC_5l.js +67 -0
  21. package/dist/ui/assets/{app-C722xNZ3.css → app-DVf68fj5.css} +1 -1
  22. package/dist/ui/assets/{index-BoG6O-Lp.js → index-MQNNgsyT.js} +2 -2
  23. package/dist/ui/index.html +1 -1
  24. package/dist/ui-DhutUMgQ.mjs +472 -0
  25. package/dist/{usage-BIjf9Xdt.mjs → usage-CT7Z-yjr.mjs} +5 -4
  26. package/dist/{watch-B8o4Szd_.mjs → watch-0Qr2UfCQ.mjs} +6 -6
  27. package/dist/worktrees-DHl80k96.mjs +212 -0
  28. package/package.json +4 -4
  29. package/dist/__build_info__-CXXMLvjt.mjs +0 -104
  30. package/dist/ui/assets/app-BlDdP396.js +0 -61
  31. package/dist/ui-CZS1XKla.mjs +0 -160
@@ -1,8 +1,8 @@
1
- import "./src-Cz1yOUTg.mjs";
2
- import { n as ORK_VERSION } from "./__build_info__-CXXMLvjt.mjs";
3
- import { a as openMcp, c as refused, o as tool, s as answer } from "./src-D5ruZFgD.mjs";
4
- import { n as explainNode, p as isNewer } from "./context-BFGC_C02.mjs";
5
- import { p as runMarkerIn } from "./src-DtN1k-B3.mjs";
1
+ import { m as isNewer } from "./src-CGqMati3.mjs";
2
+ import { n as ORK_VERSION } from "./__build_info__-CtSVVuZE.mjs";
3
+ import { a as openMcp, c as refused, o as tool, s as answer } from "./src-CpDKIulI.mjs";
4
+ import { n as explainNode } from "./context-C_bksb0C.mjs";
5
+ import { p as runMarkerIn } from "./src-UWkk1sl9.mjs";
6
6
  import { defineCommand } from "citty";
7
7
  import { z } from "zod";
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -43,7 +43,7 @@ const orkMcpServer = (options = {}) => {
43
43
  ...options.script === void 0 ? {} : { script: options.script }
44
44
  });
45
45
  const all = [...opened.tools, explainTool(opened, options.home)];
46
- const tools = options.expects !== void 0 && isNewer(options.expects, "0.3.0") ? tooOld(all, options.expects) : all;
46
+ const tools = options.expects !== void 0 && isNewer(options.expects, "0.4.0") ? tooOld(all, options.expects) : all;
47
47
  const server = new McpServer({
48
48
  name: "ork",
49
49
  version: ORK_VERSION
@@ -1,4 +1,4 @@
1
- import { Ct as FEEDBACK_SEVERITIES, G as usageQueries, J as text, K as eventOf, Ot as maskReplacer, St as FEEDBACK_KINDS, W as backfillUsage, kt as maskSecrets, q as inTransaction, qt as PROJECT_MODES, wt as FEEDBACK_TARGETS } from "./src-Cz1yOUTg.mjs";
1
+ import { At as FEEDBACK_KINDS, Ht as maskReplacer, J as eventOf, K as backfillUsage, Mt as FEEDBACK_TARGETS, Ut as maskSecrets, X as text, Y as inTransaction, jt as FEEDBACK_SEVERITIES, pt as ownProcessStart, q as usageQueries, sn as PROJECT_MODES } from "./src-CGqMati3.mjs";
2
2
  import { createInterface } from "node:readline/promises";
3
3
  import path from "node:path";
4
4
  import { mkdirSync } from "node:fs";
@@ -213,8 +213,10 @@ const recordOf$3 = (row) => {
213
213
  };
214
214
  const itemOf = (row) => {
215
215
  const severities = strings(row, "severities");
216
+ const id = text(row, "fingerprint");
216
217
  return {
217
- id: text(row, "fingerprint"),
218
+ id,
219
+ merged: strings(row, "fingerprints").filter((f) => f !== id),
218
220
  status: oneOf(STATUSES$1, text(row, "status"), "new"),
219
221
  title: text(row, "title"),
220
222
  target: oneOf(FEEDBACK_TARGETS, text(row, "target"), "workflow"),
@@ -229,16 +231,20 @@ const itemOf = (row) => {
229
231
  models: strings(row, "models")
230
232
  };
231
233
  };
232
- const ITEMS = `SELECT f.fingerprint, i.status,
233
- (SELECT title FROM feedback WHERE fingerprint = f.fingerprint ORDER BY id LIMIT 1) AS title,
234
+ /** Each record with the item it lists under: its own, or the one it merged into. */
235
+ const ITEMS = `SELECT r.fingerprint, r.status,
236
+ (SELECT title FROM feedback WHERE fingerprint = r.fingerprint ORDER BY id LIMIT 1) AS title,
234
237
  MIN(f.target) AS target, MIN(f.kind) AS kind,
235
238
  json_group_array(DISTINCT f.severity) AS severities,
236
239
  json_group_array(DISTINCT f.source) AS sources,
237
240
  COUNT(*) AS count, MIN(f.at) AS first_seen, MAX(f.at) AS last_seen,
238
241
  json_group_array(DISTINCT f.run_id) AS runs,
239
242
  json_group_array(DISTINCT f.workflow) AS workflows,
240
- json_group_array(DISTINCT f.model) AS models
241
- FROM feedback f JOIN feedback_items i ON i.fingerprint = f.fingerprint`;
243
+ json_group_array(DISTINCT f.model) AS models,
244
+ json_group_array(DISTINCT f.fingerprint) AS fingerprints
245
+ FROM feedback f
246
+ JOIN feedback_items i ON i.fingerprint = f.fingerprint
247
+ JOIN feedback_items r ON r.fingerprint = COALESCE(i.merged_into, i.fingerprint)`;
242
248
  const feedbackQueries = (db) => {
243
249
  const insert = db.prepare(`INSERT INTO feedback (run_id, seq, at, node, source, kind, target, severity,
244
250
  title, body, evidence, code, fingerprint, workflow, workflow_hash,
@@ -246,21 +252,51 @@ const feedbackQueries = (db) => {
246
252
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
247
253
  const insertItem = db.prepare(`INSERT INTO feedback_items (fingerprint, status, updated_at)
248
254
  VALUES (?, 'new', ?) ON CONFLICT (fingerprint) DO NOTHING`);
249
- const shown = db.prepare(`${ITEMS} WHERE i.status != 'dismissed' GROUP BY f.fingerprint ORDER BY last_seen DESC`);
250
- const every = db.prepare(`${ITEMS} GROUP BY f.fingerprint ORDER BY last_seen DESC`);
251
- const byPrefix = db.prepare(`${ITEMS} WHERE substr(f.fingerprint, 1, ?) = ? GROUP BY f.fingerprint ORDER BY last_seen DESC`);
252
- const records = db.prepare("SELECT * FROM feedback WHERE fingerprint = ? ORDER BY id");
255
+ const shown = db.prepare(`${ITEMS} WHERE r.status != 'dismissed' GROUP BY r.fingerprint ORDER BY last_seen DESC`);
256
+ const every = db.prepare(`${ITEMS} GROUP BY r.fingerprint ORDER BY last_seen DESC`);
257
+ const byPrefix = db.prepare(`${ITEMS} WHERE r.fingerprint IN (
258
+ SELECT COALESCE(merged_into, fingerprint) FROM feedback_items
259
+ WHERE substr(fingerprint, 1, ?) = ?
260
+ ) GROUP BY r.fingerprint ORDER BY last_seen DESC`);
261
+ const records = db.prepare(`SELECT * FROM feedback
262
+ WHERE fingerprint = ? OR fingerprint IN (
263
+ SELECT fingerprint FROM feedback_items WHERE merged_into = ?
264
+ ) ORDER BY id`);
265
+ const rootOf = db.prepare("SELECT COALESCE(merged_into, fingerprint) AS root FROM feedback_items WHERE fingerprint = ?");
266
+ const mergeInto = db.prepare(`UPDATE feedback_items SET merged_into = ?, updated_at = ?
267
+ WHERE (fingerprint = ? OR merged_into = ?) AND fingerprint != ?`);
268
+ const exported = db.prepare(`SELECT f.* FROM feedback f
269
+ LEFT JOIN feedback_items i ON i.fingerprint = f.fingerprint
270
+ LEFT JOIN feedback_items r ON r.fingerprint = COALESCE(i.merged_into, f.fingerprint)
271
+ WHERE ? = 1 OR COALESCE(r.status, 'new') != 'dismissed' ORDER BY f.id`);
272
+ const known = db.prepare("SELECT 1 FROM feedback WHERE run_id = ? AND seq = ?");
273
+ const split = db.prepare("UPDATE feedback_items SET merged_into = NULL, updated_at = ? WHERE merged_into = ?");
253
274
  const setStatus = db.prepare("UPDATE feedback_items SET status = ?, updated_at = ? WHERE fingerprint = ?");
275
+ /** Writes a record, its secrets masked, and its item when it's new. */
276
+ const insertRecord = (runId, seq, at, node, feedback) => {
277
+ const { origin } = feedback;
278
+ insert.run(runId, seq, at, node ?? null, feedback.source, feedback.kind, feedback.target, feedback.severity, maskSecrets(feedback.title), maskSecrets(feedback.body), JSON.stringify(feedback.evidence.map((e) => maskSecrets(e))), feedback.code ?? null, feedback.fingerprint, origin.workflow ?? null, origin.workflowHash ?? null, origin.nodeType ?? null, origin.model ?? null, origin.driver ?? null, origin.version ?? null, 1);
279
+ insertItem.run(feedback.fingerprint, at);
280
+ };
254
281
  return {
255
- insertFeedback: (runId, seq, at, node, feedback) => {
256
- const { origin } = feedback;
257
- insert.run(runId, seq, at, node ?? null, feedback.source, feedback.kind, feedback.target, feedback.severity, maskSecrets(feedback.title), maskSecrets(feedback.body), JSON.stringify(feedback.evidence.map((e) => maskSecrets(e))), feedback.code ?? null, feedback.fingerprint, origin.workflow ?? null, origin.workflowHash ?? null, origin.nodeType ?? null, origin.model ?? null, origin.driver ?? null, origin.version ?? null, 1);
258
- insertItem.run(feedback.fingerprint, at);
259
- },
282
+ insertFeedback: insertRecord,
260
283
  feedbackItems: (filter = {}) => (filter.all === true ? every : shown).all().map((r) => itemOf(r)),
261
284
  findFeedback: (prefix) => byPrefix.all(prefix.length, prefix).map((r) => itemOf(r)),
262
- feedbackRecords: (fingerprint) => records.all(fingerprint).map((r) => recordOf$3(r)),
263
- setFeedbackStatus: (fingerprint, status) => Number(setStatus.run(status, (/* @__PURE__ */ new Date()).toISOString(), fingerprint).changes) > 0
285
+ feedbackRecords: (fingerprint) => records.all(fingerprint, fingerprint).map((r) => recordOf$3(r)),
286
+ setFeedbackStatus: (fingerprint, status) => Number(setStatus.run(status, (/* @__PURE__ */ new Date()).toISOString(), fingerprint).changes) > 0,
287
+ mergeFeedback: (into, from) => {
288
+ const root = text(rootOf.get(into), "root");
289
+ if (root === "") return 0;
290
+ const at = (/* @__PURE__ */ new Date()).toISOString();
291
+ return inTransaction(db, () => from.filter((fingerprint) => fingerprint !== root).reduce((moved, fingerprint) => moved + Number(mergeInto.run(root, at, fingerprint, fingerprint, root).changes), 0));
292
+ },
293
+ splitFeedback: (fingerprint) => Number(split.run((/* @__PURE__ */ new Date()).toISOString(), fingerprint).changes),
294
+ exportFeedback: (filter = {}) => exported.all(filter.all === true ? 1 : 0).map((r) => recordOf$3(r)),
295
+ importFeedback: (record) => {
296
+ if (known.get(record.runId, record.seq) !== void 0) return false;
297
+ insertRecord(record.runId, record.seq, record.at, record.node, record.feedback);
298
+ return true;
299
+ }
264
300
  };
265
301
  };
266
302
  // ../core/src/store/links.ts
@@ -681,7 +717,10 @@ const MIGRATIONS = [
681
717
  id INTEGER PRIMARY KEY CHECK (id = 1),
682
718
  pid INTEGER NOT NULL,
683
719
  at TEXT NOT NULL
684
- );`
720
+ );`,
721
+ `ALTER TABLE feedback_items ADD COLUMN merged_into TEXT;
722
+ CREATE INDEX feedback_items_merged ON feedback_items (merged_into);`,
723
+ `ALTER TABLE runs ADD COLUMN process_start TEXT;`
685
724
  ];
686
725
  const versionOf = (db) => Number(db.prepare("PRAGMA user_version").get()?.["user_version"] ?? 0);
687
726
  /** Applies the migrations the database has not had yet. */
@@ -728,6 +767,8 @@ const recordOf = (row) => {
728
767
  costUsd: Number(row?.["cost_usd"]),
729
768
  depth: Number(row?.["depth"] ?? 0)
730
769
  };
770
+ const processStart = text(row, "process_start");
771
+ if (processStart !== "") record.processStart = processStart;
731
772
  const parent = text(row, "parent");
732
773
  if (parent !== "") record.parent = parent;
733
774
  const finished = text(row, "finished_at");
@@ -741,7 +782,8 @@ const nodeOf = (event) => {
741
782
  return null;
742
783
  };
743
784
  /** Opens (and creates) the store at `file`; `:memory:` for tests. */
744
- const openStore = (file = storePath()) => {
785
+ const openStore = (file = storePath(), options = {}) => {
786
+ const ownStart = options.processStart ?? ownProcessStart;
745
787
  if (file !== ":memory:") mkdirSync(path.dirname(file), { recursive: true });
746
788
  const db = new DatabaseSync(file);
747
789
  db.exec("PRAGMA busy_timeout = 5000");
@@ -749,12 +791,12 @@ const openStore = (file = storePath()) => {
749
791
  db.exec("PRAGMA synchronous = NORMAL");
750
792
  db.exec("PRAGMA foreign_keys = ON");
751
793
  migrate(db);
752
- const insertRun = db.prepare(`INSERT INTO runs (id, workflow, file, project, status, pid, started_at, parent, depth)
753
- VALUES (?, ?, ?, ?, 'running', ?, ?, ?, ?)`);
794
+ const insertRun = db.prepare(`INSERT INTO runs (id, workflow, file, project, status, pid, process_start, started_at, parent, depth)
795
+ VALUES (?, ?, ?, ?, 'running', ?, ?, ?, ?, ?)`);
754
796
  const lastSeq = db.prepare("SELECT COALESCE(MAX(seq), 0) AS seq FROM events WHERE run_id = ?");
755
797
  const insertEvent = db.prepare("INSERT INTO events (run_id, seq, at, type, node, data) VALUES (?, ?, ?, ?, ?, ?)");
756
798
  const finishRun = db.prepare("UPDATE runs SET status = ?, finished_at = ?, cost_usd = ? WHERE id = ?");
757
- const reopenRun = db.prepare("UPDATE runs SET status = 'running', pid = ?, finished_at = NULL WHERE id = ?");
799
+ const reopenRun = db.prepare("UPDATE runs SET status = 'running', pid = ?, process_start = ?, finished_at = NULL WHERE id = ?");
758
800
  const addCost = db.prepare("UPDATE runs SET cost_usd = cost_usd + ? WHERE id = ?");
759
801
  const byId = db.prepare("SELECT * FROM runs WHERE id = ?");
760
802
  const byPrefix = db.prepare("SELECT * FROM runs WHERE substr(id, 1, ?) = ? ORDER BY started_at DESC, rowid DESC");
@@ -767,18 +809,20 @@ const openStore = (file = storePath()) => {
767
809
  begin: (run) => {
768
810
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
769
811
  const depth = run.parent === void 0 ? 0 : Number(byId.get(run.parent)?.["depth"] ?? 0) + 1;
770
- insertRun.run(run.id, run.workflow, run.file, run.project, process.pid, startedAt, run.parent ?? null, depth);
812
+ const processStart = ownStart();
813
+ insertRun.run(run.id, run.workflow, run.file, run.project, process.pid, processStart ?? null, startedAt, run.parent ?? null, depth);
771
814
  return {
772
815
  ...run,
773
816
  depth,
774
817
  status: "running",
775
818
  pid: process.pid,
819
+ ...processStart === void 0 ? {} : { processStart },
776
820
  startedAt,
777
821
  costUsd: 0
778
822
  };
779
823
  },
780
824
  reopen: (runId) => {
781
- reopenRun.run(process.pid, runId);
825
+ reopenRun.run(process.pid, ownStart() ?? null, runId);
782
826
  },
783
827
  append: (runId, event) => inTransaction(db, () => {
784
828
  const at = (/* @__PURE__ */ new Date()).toISOString();
@@ -1,7 +1,7 @@
1
- import { B as requestContinue, V as requestPause } from "./src-Cz1yOUTg.mjs";
2
- import { t as findRun } from "./find-BcvhT6UN.mjs";
3
- import { a as write, c as openStore, i as style, o as writeErr } from "./output-Q2EdeVUt.mjs";
4
- import { r as continueRefusal } from "./in-a-run-BbLhZOFW.mjs";
1
+ import { H as requestContinue, U as requestPause } from "./src-CGqMati3.mjs";
2
+ import { t as findRun } from "./find-DptmbhGd.mjs";
3
+ import { a as write, c as openStore, i as style, o as writeErr } from "./output-CXSJO00X.mjs";
4
+ import { r as continueRefusal } from "./in-a-run-D1Qc5Mit.mjs";
5
5
  import { defineCommand } from "citty";
6
6
  // src/commands/pause.ts
7
7
  /**
@@ -1,5 +1,5 @@
1
- import { F as linkNodes, M as addressOf, R as unlinkNodes } from "./src-Cz1yOUTg.mjs";
2
- import { a as write, c as openStore, i as style, o as writeErr } from "./output-Q2EdeVUt.mjs";
1
+ import { B as unlinkNodes, L as linkNodes, P as addressOf } from "./src-CGqMati3.mjs";
2
+ import { a as write, c as openStore, i as style, o as writeErr } from "./output-CXSJO00X.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  // src/commands/relay.ts
5
5
  /**
@@ -0,0 +1,194 @@
1
+ import { D as git, k as lines, lt as liveStatus } from "./src-CGqMati3.mjs";
2
+ import { n as listOrkWorktrees, t as createdWorktrees } from "./list-ZvY2OcLq.mjs";
3
+ import { i as keyOf, n as applyStep, r as cleanStep, s as stepPrunes } from "./clean-Dcr807be.mjs";
4
+ // ../core/src/worktree/words.ts
5
+ /**
6
+ * @file The plain words for what `ork worktrees` shows, in one place, so the
7
+ * CLI and the Worktrees panel say the same thing: an item's name, its merge
8
+ * state, its state as parts (each marked when it's work that removing would
9
+ * lose), a plan's line, and why an item can't be removed. The CLI colors
10
+ * them; the panel lays them out.
11
+ */
12
+ /** An item's name: its branch, or its run and node when created detached. */
13
+ const labelOf = (item) => item.branch ?? `${item.runId}/${item.nodeId}`;
14
+ /** Merged into the current branch, not, or detached on no branch at all. */
15
+ const mergeStateOf = (item) => {
16
+ if (item.branch === void 0 && item.checkedOut === void 0) return "detached";
17
+ return item.merged ? "merged" : "unmerged";
18
+ };
19
+ /** `3 commits`, `1 commit`. */
20
+ const counted = (count, noun) => `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
21
+ /**
22
+ * An item's state, in order: where its worktree is when it isn't plain, the
23
+ * workflow's own branch, uncommitted changes, unmerged or unreachable commits,
24
+ * and the main checkout or another worktree holding the branch. `into` names
25
+ * the branch "not merged" is measured against, when the words should say it.
26
+ */
27
+ const stateParts = (item, into) => {
28
+ const parts = [];
29
+ const part = (text, risk = false) => {
30
+ parts.push({
31
+ text,
32
+ risk
33
+ });
34
+ };
35
+ if (item.path === null) part("no worktree");
36
+ else if (item.missing) part("missing (prunable)");
37
+ if (item.checkedOut !== void 0) part(`on the workflow's branch ${item.checkedOut}`);
38
+ if (item.dirty) part("uncommitted changes", true);
39
+ const state = mergeStateOf(item);
40
+ if (state === "unmerged") {
41
+ const where = into === void 0 ? "" : ` into ${into}`;
42
+ part(`${counted(item.ahead, "commit")} not merged${where}`);
43
+ }
44
+ if (state === "detached") {
45
+ if (item.ahead > 0) part(`${counted(item.ahead, "commit")} on no branch`, true);
46
+ else if (item.path !== null && !item.missing) part("nothing committed");
47
+ }
48
+ if (item.inMainCheckout) part("checked out in the main checkout");
49
+ if (item.heldBy !== void 0) part(`checked out in ${item.heldBy}`);
50
+ return parts;
51
+ };
52
+ /** What a step does, as a plan says it. */
53
+ const planWhat = (step) => {
54
+ const actions = [
55
+ ...step.removeWorktree ? ["remove worktree"] : [],
56
+ ...stepPrunes(step) && !step.removeWorktree ? ["prune worktree"] : [],
57
+ ...step.deleteBranch ? ["delete branch"] : []
58
+ ];
59
+ return actions.length > 0 ? actions.join(" + ") : "keep";
60
+ };
61
+ /** `ork/r1/lead: remove worktree`, `r1/check (detached): keep (…)`. */
62
+ const planLine = (step) => {
63
+ const { item } = step;
64
+ return {
65
+ text: `${item.branch === void 0 && item.checkedOut === void 0 ? `${labelOf(item)} (detached)` : labelOf(item)}: ${planWhat(step)}`,
66
+ ...step.skip === void 0 ? {} : { reason: step.skip }
67
+ };
68
+ };
69
+ /**
70
+ * Why an item can't be removed at all, even forced, if it can't: the main
71
+ * checkout or a worktree Ork didn't create holds it, or its run is still
72
+ * going.
73
+ */
74
+ const removeRefusal = (item, live) => {
75
+ if (item.inMainCheckout) return "checked out in the main checkout; switch it to another branch first";
76
+ if (item.heldBy !== void 0) return `checked out in ${item.heldBy}, a worktree Ork didn't create; switch it to another branch first`;
77
+ return live ? `run ${item.runId} is still going; stop it first` : void 0;
78
+ };
79
+ // ../core/src/worktree/remove.ts
80
+ /** The ids of the project's runs whose process is still going. */
81
+ const liveRunIds = (store, root) => new Set(store.runs({ project: root }).filter((run) => liveStatus(run) === "running").map((run) => run.id));
82
+ /**
83
+ * Whether `root` is a repository with a commit, and the branch checked out
84
+ * there, which "merged" means merged into (null when its HEAD is detached).
85
+ */
86
+ const projectHead = async (root) => {
87
+ if ((await git(root, [
88
+ "rev-parse",
89
+ "--verify",
90
+ "--quiet",
91
+ "HEAD"
92
+ ])).code !== 0) return { ok: false };
93
+ const answer = await git(root, [
94
+ "symbolic-ref",
95
+ "--short",
96
+ "-q",
97
+ "HEAD"
98
+ ]);
99
+ return {
100
+ ok: true,
101
+ branch: answer.code === 0 ? answer.stdout.trim() : null
102
+ };
103
+ };
104
+ /** What Ork created in the repo at `root`, from its runs' records and git. */
105
+ const readWorktrees = async (store, root) => ({
106
+ items: await listOrkWorktrees(root, createdWorktrees(store, root)),
107
+ live: liveRunIds(store, root)
108
+ });
109
+ /**
110
+ * The plan to remove one item, named by `keyOf`. A branch with no worktree is
111
+ * removed by deleting it, so `deleteBranch` is implied there.
112
+ */
113
+ const planRemove = ({ items, live }, key, deleteBranch) => {
114
+ const item = items.find((i) => keyOf(i) === key);
115
+ if (item === void 0) return {
116
+ ok: false,
117
+ error: `No Ork worktree or branch ${key}.`,
118
+ gone: true
119
+ };
120
+ const refusal = removeRefusal(item, live.has(item.runId));
121
+ if (refusal !== void 0) return {
122
+ ok: false,
123
+ error: `Can't remove ${labelOf(item)}: ${refusal}.`,
124
+ gone: false
125
+ };
126
+ const branches = item.branch !== void 0 && (deleteBranch || item.path === null);
127
+ const careful = cleanStep(item, {
128
+ branches,
129
+ force: false,
130
+ live
131
+ });
132
+ if (careful.skip === void 0) return {
133
+ ok: true,
134
+ plan: {
135
+ step: careful,
136
+ force: false
137
+ }
138
+ };
139
+ return {
140
+ ok: true,
141
+ plan: {
142
+ step: cleanStep(item, {
143
+ branches,
144
+ force: true,
145
+ live
146
+ }),
147
+ force: true
148
+ }
149
+ };
150
+ };
151
+ const out = async (cwd, args) => lines((await git(cwd, args)).stdout);
152
+ /** What carrying out `plan` would lose, read from git. */
153
+ const lostWork = async (root, { step }) => {
154
+ const { item } = step;
155
+ const inside = item.path !== null && !item.missing ? item.path : null;
156
+ const changes = inside !== null && item.dirty && step.removeWorktree ? await out(inside, ["status", "--porcelain"]) : [];
157
+ if (step.deleteBranch && item.branch !== void 0 && !item.merged) return {
158
+ changes,
159
+ commits: await out(root, [
160
+ "log",
161
+ "--oneline",
162
+ `HEAD..${item.branch}`
163
+ ]),
164
+ commitsOn: "unmerged"
165
+ };
166
+ if (item.branch === void 0 && item.checkedOut === void 0 && inside !== null && item.ahead > 0 && step.removeWorktree) return {
167
+ changes,
168
+ commits: await out(inside, [
169
+ "log",
170
+ "--oneline",
171
+ "HEAD",
172
+ "--not",
173
+ "--branches",
174
+ "--tags",
175
+ "--remotes"
176
+ ]),
177
+ commitsOn: "no branch"
178
+ };
179
+ return {
180
+ changes,
181
+ commits: [],
182
+ commitsOn: null
183
+ };
184
+ };
185
+ /**
186
+ * Carries out a remove plan; what it did goes into `done` as it goes. The
187
+ * caller has checked that a plan needing `force` was asked to force. It touches
188
+ * only its own item, as each of clean's steps does.
189
+ */
190
+ const applyRemove = async (root, { step, force }, done = []) => {
191
+ await applyStep(root, step, force, done);
192
+ return done;
193
+ };
194
+ export { readWorktrees as a, planLine as c, projectHead as i, removeRefusal as l, lostWork as n, labelOf as o, planRemove as r, mergeStateOf as s, applyRemove as t, stateParts as u };
@@ -1,6 +1,6 @@
1
- import { Ft as socketPath, It as wakeRun, gt as scriptContactsOf, rt as liveStatus } from "./src-Cz1yOUTg.mjs";
2
- import { c as openStore, o as writeErr } from "./output-Q2EdeVUt.mjs";
3
- import { u as inheritedScrubber } from "./src-DtN1k-B3.mjs";
1
+ import { Xt as wakeRun, Yt as socketPath, lt as liveStatus, wt as scriptContactsOf } from "./src-CGqMati3.mjs";
2
+ import { c as openStore, o as writeErr } from "./output-CXSJO00X.mjs";
3
+ import { u as inheritedScrubber } from "./src-UWkk1sl9.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  // ../core/src/remote/send.ts
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { Ct as FEEDBACK_SEVERITIES, D as parseReport, St as FEEDBACK_KINDS, wt as FEEDBACK_TARGETS } from "./src-Cz1yOUTg.mjs";
1
+ import { At as FEEDBACK_KINDS, Mt as FEEDBACK_TARGETS, ct as parseReport, jt as FEEDBACK_SEVERITIES } from "./src-CGqMati3.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { createInterface } from "node:readline";