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

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 (51) hide show
  1. package/dist/__build_info__-Bsb1kjgF.mjs +340 -0
  2. package/dist/{store-CM3cERGl.mjs → attach-BHO3X3cf.mjs} +36 -91
  3. package/dist/{attach-E6NNyA3T.mjs → attach-CPespxlQ.mjs} +6 -5
  4. package/dist/bin.mjs +148 -1444
  5. package/dist/brief-za0ozN_E.mjs +58 -0
  6. package/dist/{builtin-files-n6o6oWpB.mjs → builtin-files-2bHf76x-.mjs} +1 -1
  7. package/dist/{chips-CyeB_rhR.mjs → chips-BqcT58qr.mjs} +1 -1
  8. package/dist/{completion-SNNNxnv9.mjs → completion-CQ2UYKuJ.mjs} +52 -11
  9. package/dist/context-BNqDw9z1.mjs +106 -0
  10. package/dist/{find-CjsNYAAf.mjs → find-BYLlszqj.mjs} +1 -1
  11. package/dist/follow-ups-B4GJ3Vzw.mjs +461 -0
  12. package/dist/{format-C60TFpRe.mjs → format-C8MsFj8_.mjs} +30 -16
  13. package/dist/{help-groups-CyfPWlST.mjs → help-groups-CQ98mLjF.mjs} +20 -6
  14. package/dist/{in-a-run-BYjTXaST.mjs → in-a-run-Bp0k0J09.mjs} +1 -1
  15. package/dist/{__build_info__-Da73_nBs.mjs → install-DWtXTq4y.mjs} +3 -227
  16. package/dist/{limits-C5QKZNLT.mjs → limits-B1qzS4cX.mjs} +1 -1
  17. package/dist/{mcp-bcctR-wc.mjs → mcp-BOrVkyhN.mjs} +6 -6
  18. package/dist/{once-DPQu5Q3H.mjs → once-C1fL3GVo.mjs} +4 -4
  19. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  20. package/dist/{output-CnRzrbUM.mjs → output-B1kwo0YU.mjs} +17 -1
  21. package/dist/{pause-BPvlDIvz.mjs → pause-DOMK-R8V.mjs} +4 -4
  22. package/dist/post-BzA6Qobq.mjs +44 -0
  23. package/dist/{relay-C8W0t_zc.mjs → relay-OWuu4fib.mjs} +2 -2
  24. package/dist/run--4-1xAF2.mjs +1451 -0
  25. package/dist/{run-CQIfq918.mjs → run-CiPsW9Kf.mjs} +1 -1
  26. package/dist/send-CnNNW-ot.mjs +105 -0
  27. package/dist/{src-CqRqsb2t.mjs → src-B9zWIVqo.mjs} +226 -44
  28. package/dist/{src-CGJGL1sZ.mjs → src-DQKz2eOH.mjs} +326 -81
  29. package/dist/{src-D93tHZ5B.mjs → src-DcrhgipG.mjs} +107 -47
  30. package/dist/{src-BeR6f70A.mjs → src-Dr_fTSu-.mjs} +5858 -3746
  31. package/dist/store-oKJ-cCf3.mjs +16 -0
  32. package/dist/ui/assets/app-Bdc6OBeZ.css +1 -0
  33. package/dist/ui/assets/app-Drp8Dm9y.js +111 -0
  34. package/dist/ui/assets/{index-CWwm8HTG.js → index-BtwkftKe.js} +2 -2
  35. package/dist/ui/index.html +1 -1
  36. package/dist/{ui-D2Ydofq0.mjs → ui-CzKlsCnm.mjs} +4 -4
  37. package/dist/{usage-6WIowblz.mjs → usage-DHGNk2JL.mjs} +13 -141
  38. package/dist/{context-DHudLA5e.mjs → validate-Bs_gg22g.mjs} +17 -107
  39. package/dist/{verdict-CIy8tAyX.mjs → verdict-CMvPCpWC.mjs} +2 -2
  40. package/dist/{watch-uOU_WeBI.mjs → watch-CCCfw3n7.mjs} +24 -8
  41. package/dist/{workflows-7tQdMnrA.mjs → workflows-CW28ZU-z.mjs} +3 -3
  42. package/dist/{worktrees-ZCII298F.mjs → worktrees-CyE8aF6j.mjs} +3 -5
  43. package/package.json +2 -2
  44. package/dist/clean-Bh4KZgzc.mjs +0 -91
  45. package/dist/decide-DuIHACvH.mjs +0 -50
  46. package/dist/list-BUb5Uc3H.mjs +0 -117
  47. package/dist/remove-BkFWGVYo.mjs +0 -201
  48. package/dist/report-CEN8VoUb.mjs +0 -140
  49. package/dist/send-CaZlGG5a.mjs +0 -163
  50. package/dist/ui/assets/app-DYRC0V4p.js +0 -111
  51. 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-BtwkftKe.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__-Bsb1kjgF.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-Bp0k0J09.mjs";
4
+ import { i as uiFilePath, l as newToken, n as startServer, r as runningServer, t as DEFAULT_PORT } from "./src-DQKz2eOH.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 { Dr as placeFor, G as applyPrune, K as planPrune, S as usageReport, St as usd, U as openStore, x as USAGE_DIMENSIONS } from "./src-Dr_fTSu-.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-B1qzS4cX.mjs";
5
+ import { a as header, o as table } from "./format-C8MsFj8_.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,8 +1,8 @@
1
- import { Ar as readyDriver, Cr as retiredDriverHint, Fr as firstLine, Hn as placeFiles, Ir as EXPERIMENTS, Lr as experimentsOn, N as driverQueues, Nr as AGENT_DRIVERS, Pr as DRIVER_IDS, Rr as knownExperiments, T as storePath, Tr as INTEGRATIONS, Un as placeFor, Ut as namingNoDriver, Xn as LESSON_CAP_TOKENS, Yt as loadProject, dt as walkChecks, ft as walkSummary, j as driverRefusals, kr as driverChecks, m as triggersReport, ot as explainSession, p as lateTriggers, qn as workflowDirOf, ut as checkWorkflowText, v as readProjectTriggers, w as openStore, wn as socketRoom, xr as DriverSchema, y as workflowTextHash } from "./src-BeR6f70A.mjs";
2
- import { c as checkDue, d as pendingNotice, f as readUpdateCache, h as writeUpdateCache, i as registryOf, m as updateCachePath, n as ORK_VERSION, o as updateSwitchOff, p as refreshUpdateCache, r as isSwitchedOn, t as ORK_NAME, u as newerVersion } from "./__build_info__-Da73_nBs.mjs";
3
- import { S as cut, T as wrap, a as style, i as plain, o as write, p as paint, s as writeErr, w as widthOf, x as termOf, y as plainEnv } from "./output-CnRzrbUM.mjs";
4
- import { n as chip, t as CHIP_LABELS } from "./chips-CyeB_rhR.mjs";
5
- import { c as registeredProjects, i as refusedBuiltin, o as workflowArg, r as findWorkflowFiles, s as projectAt } from "./workflows-7tQdMnrA.mjs";
1
+ import { At as walkSummary, Dr as placeFor, Er as placeFiles, In as loadProject, L as readProjectTriggers, M as lateTriggers, N as triggersReport, Ot as checkWorkflowText, Qt as namingNoDriver, R as workflowTextHash, Si as knownExperiments, U as openStore, W as storePath, _i as AGENT_DRIVERS, ai as DriverSchema, bi as EXPERIMENTS, et as budgetWarnings, jr as workflowDirOf, kt as walkChecks, li as INTEGRATIONS, lr as socketRoom, mi as readyDriver, pi as driverChecks, rt as driverQueues, si as retiredDriverHint, tt as driverRefusals, vi as DRIVER_IDS, xi as experimentsOn, yi as firstLine } from "./src-Dr_fTSu-.mjs";
2
+ import { d as readUpdateCache, f as refreshUpdateCache, i as registryOf, l as newerVersion, m as writeUpdateCache, n as ORK_VERSION, o as updateSwitchOff, p as updateCachePath, r as isSwitchedOn, s as checkDue, t as ORK_NAME, u as pendingNotice } from "./__build_info__-Bsb1kjgF.mjs";
3
+ import { C as termOf, D as wrap, E as widthOf, a as plain, c as write, h as paint, l as writeErr, s as style, w as cut, x as plainEnv } from "./output-B1kwo0YU.mjs";
4
+ import { n as chip, t as CHIP_LABELS } from "./chips-BqcT58qr.mjs";
5
+ import { c as registeredProjects, i as refusedBuiltin, r as findWorkflowFiles, s as projectAt } from "./workflows-CW28ZU-z.mjs";
6
6
  import path from "node:path";
7
7
  import { readFileSync } from "node:fs";
8
8
  import { execFile, spawn } from "node:child_process";
@@ -356,6 +356,12 @@ const runReadyDriver = async (workflow, options) => {
356
356
  * (`drivers` default: the real descriptors; tests pass their own).
357
357
  */
358
358
  const unfitLines = (workflow, drivers, choice) => driverRefusals(workflow, drivers ?? AGENT_DRIVERS, choice).map(({ message }) => message);
359
+ /**
360
+ * What a dollar budget can't see on a session node's driver, one line each
361
+ * (RFC-015): warnings, never a reason to refuse. Same arguments as
362
+ * `unfitLines`.
363
+ */
364
+ const budgetWarningLines = (workflow, drivers, choice) => budgetWarnings(workflow, drivers ?? AGENT_DRIVERS, choice).map(({ message }) => message);
359
365
  // src/run/driver-check.ts
360
366
  /**
361
367
  * @file `ork validate`'s driver check: each session node against what its
@@ -384,7 +390,11 @@ const driverDiagnostics = async (workflow, options) => {
384
390
  }
385
391
  return found;
386
392
  };
387
- return [...driverRefusals(workflow, drivers, choice).map(({ node, message }) => at(node, "error", "driver.unfit", message)), ...driverQueues(workflow, drivers, choice).filter((found) => found.owner === void 0).flatMap(({ nodes, message }) => nodes.slice(0, 1).map((node) => at(node, "warning", "driver.queues", message)))];
393
+ return [
394
+ ...driverRefusals(workflow, drivers, choice).map(({ node, message }) => at(node, "error", "driver.unfit", message)),
395
+ ...budgetWarnings(workflow, drivers, choice).map(({ node, message }) => at(node, "warning", "driver.budget", message)),
396
+ ...driverQueues(workflow, drivers, choice).filter((found) => found.owner === void 0).flatMap(({ nodes, message }) => nodes.slice(0, 1).map((node) => at(node, "warning", "driver.queues", message)))
397
+ ];
388
398
  };
389
399
  /** The `--driver` value, or why it is not a driver. */
390
400
  const driverArg = (value) => {
@@ -592,104 +602,4 @@ const validate = defineCommand({
592
602
  if (reports.some((r) => hasErrors(r.diagnostics))) process.exitCode = 1;
593
603
  }
594
604
  });
595
- // src/commands/context.ts
596
- /**
597
- * @file `ork context explain <wf> <node>`: what one node's session would load,
598
- * layer by layer, as core's `explainSession` plans it without writing
599
- * anything (one of Ork's own `ork:` workflows is written into the project's
600
- * run folder first, as `ork run` does): its project instructions, every skill
601
- * with the layer it came from and whether it is live, shadowed or switched
602
- * off, its context files, its practices, and estimated tokens.
603
- */
604
- /** Loads a workflow file and plans one node's session in it, in place. */
605
- const explainNode = (file, nodeId, cwd, home) => {
606
- const { report, workflow, graph, place, workflowDir } = loadWorkflow(file, cwd, home);
607
- if (workflow === null) return {
608
- ok: false,
609
- lines: formatReport(report)
610
- };
611
- const result = explainSession(workflow, graph, nodeId, {
612
- root: place.root,
613
- orkDir: place.dir,
614
- workflowDir,
615
- ...home === void 0 ? {} : { home }
616
- });
617
- return result.ok ? result : {
618
- ok: false,
619
- lines: [result.error]
620
- };
621
- };
622
- const approx = (count) => style("dim", `≈${count.toLocaleString("en")} tokens`);
623
- const formatExplanation = (e) => {
624
- const width = Math.max(8, ...e.skills.map((s) => s.name.length));
625
- const sourceWidth = Math.max(0, ...e.skills.map((s) => s.source.length));
626
- const lines = [
627
- `${style("bold", e.node)} ${style("dim", `(${e.type}) in ${e.workflow}`)}${e.template === void 0 ? "" : style("dim", `, planned as ${e.template}, the node it was spawned from`)}`,
628
- "",
629
- ...e.agent === void 0 ? [] : [`${style("bold", "Agent")} ${e.agent.name} ${style("dim", `(${e.agent.where})`)} ${approx(e.agent.tokens)}`],
630
- `${style("bold", "Instructions")} ${e.instructions}`,
631
- "",
632
- style("bold", "Skills")
633
- ];
634
- if (e.skills.length === 0) lines.push(style("dim", " none"));
635
- for (const s of e.skills) {
636
- const mark = s.state === "live" ? style("green", "✓") : style("dim", "−");
637
- const state = s.why === void 0 ? approx(s.tokens).replace("tokens", "tokens when used") : style("yellow", `off: ${s.why}`);
638
- lines.push(` ${mark} ${s.name.padEnd(width)} ${s.layer.padEnd(8)} ${style("dim", s.source.padEnd(sourceWidth))} ${state}`);
639
- }
640
- lines.push("", style("bold", "Context files"));
641
- if (e.files.length === 0) lines.push(style("dim", " none"));
642
- for (const f of e.files) {
643
- const cut = f.cutFrom === void 0 ? "" : ` ${style("yellow", `cut at the ${LESSON_CAP_TOKENS.toLocaleString("en")}-token cap; the whole file is ${approx(f.cutFrom)}`)}`;
644
- lines.push(` ${f.layer.padEnd(8)} ${f.name} ${approx(f.tokens)}${cut}`);
645
- }
646
- lines.push("", style("bold", "Practices"));
647
- for (const p of e.practices) lines.push(` ${p.name.padEnd(8)} ${approx(p.tokens)}`);
648
- for (const name of e.skippedPractices) lines.push(` ${name.padEnd(8)} ${style("dim", "the project's own skill wins")}`);
649
- if (e.practices.length === 0 && e.skippedPractices.length === 0) lines.push(style("dim", " off"));
650
- lines.push("", `Appended to Claude Code's system prompt: ${approx(e.appendedTokens)}`);
651
- return lines;
652
- };
653
- const explain = defineCommand({
654
- meta: {
655
- name: "explain",
656
- description: "Show what one node's session loads, layer by layer"
657
- },
658
- args: {
659
- workflow: {
660
- type: "positional",
661
- description: "A workflow file, the name of one in the project's workflows (.ork/workflows/), or one of Ork's own (ork:feedback-apply)",
662
- required: true
663
- },
664
- node: {
665
- type: "positional",
666
- description: "Node id",
667
- required: true
668
- },
669
- json: {
670
- type: "boolean",
671
- description: "Print JSON",
672
- default: false
673
- }
674
- },
675
- run: ({ args }) => {
676
- const cwd = process.cwd();
677
- if (refusedBuiltin([args.workflow], cwd)) return;
678
- const result = explainNode(workflowArg(args.workflow, cwd).file, args.node, cwd);
679
- if (!result.ok) {
680
- for (const line of result.lines) process.stderr.write(`${line}\n`);
681
- process.exitCode = 1;
682
- return;
683
- }
684
- if (args.json) write(JSON.stringify(result.explanation, void 0, 2));
685
- else for (const line of formatExplanation(result.explanation)) write(line);
686
- }
687
- });
688
- const context = defineCommand({
689
- meta: {
690
- name: "context",
691
- description: "Inspect the context Ork compiles for each node"
692
- },
693
- subCommands: { explain }
694
- });
695
- export { startUpdateCheck as C, showUpdateNotice as S, runChecks as _, validateFile as a, UPDATE_CHILD_ENV as b, warningLines as c, runReadyDriver as d, unfitLines as f, orkCheck as g, headWarning as h, validate as i, checksHere as l, experimentsCheck as m, explainNode as n, walkLoaded as o, exec as p, loadWorkflow as r, formatReport as s, context as t, chooseReadyDriver as u, socketCheck as v, updateCheckOff as w, runUpdateChild as x, triggerChecks as y };
605
+ export { updateCheckOff as C, startUpdateCheck as S, socketCheck as _, formatReport as a, runUpdateChild as b, checksHere as c, unfitLines as d, exec as f, runChecks as g, orkCheck as h, walkLoaded as i, chooseReadyDriver as l, headWarning as m, validate as n, warningLines as o, experimentsCheck as p, validateFile as r, budgetWarningLines as s, loadWorkflow as t, runReadyDriver as u, triggerChecks as v, showUpdateNotice as x, UPDATE_CHILD_ENV as y };
@@ -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 { $r as CheckReportSchema, An as stillTheSameProcess, B as watchingPid, Cn as liveStatus, Er as placeFiles, F as triggerStanding, I as declaredTriggers, L as readProjectTriggers, N as triggersReport, P as createWatcher, U as openStore, Yn as runGh, ci as timeoutMs, gt as detachRun, ii as isToolNode, ln as startFollowUpChecks, z as takeWatchLock } from "./src-Dr_fTSu-.mjs";
2
+ import { n as newRunId } from "./find-BYLlszqj.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-DcrhgipG.mjs";
6
+ import { i as runLimits, r as limitsRefusal } from "./limits-B1qzS4cX.mjs";
7
+ import { t as acceptRefusal } from "./in-a-run-Bp0k0J09.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",
@@ -148,6 +150,8 @@ const acceptLines = (item, standing, term = termOf(process.stdout)) => [
148
150
  ];
149
151
  /** The triggers `--accept` can't offer, so they aren't silently left out. */
150
152
  const unacceptableLines = (item, term = termOf(process.stdout)) => [`${style("bold", plain(item.id))} ${style("dim", plain(item.place.root))}`, ...item.diagnostics.filter(({ severity }) => severity === "error").map(({ message }) => ` ${failMark(term)} ${plain(message)}`)];
153
+ /** What `ork watch` says when follow-ups come due. */
154
+ const dueLines = (due) => due.map((f) => `▸ follow-up due (${plain(f.for)}): ${plain(f.title)}${f.note === void 0 ? "" : ` (${plain(f.note)})`}, \`ork follow-ups show ${f.id}\``);
151
155
  // src/watch/start.ts
152
156
  /**
153
157
  * @file Starting a trigger's run (RFC-011): an ordinary run in the background,
@@ -271,7 +275,19 @@ const watchLoop = async (store) => {
271
275
  const timer = setInterval(() => {
272
276
  watcher.tick();
273
277
  }, TICK_MS);
278
+ const stopChecks = startFollowUpChecks({
279
+ rows: store,
280
+ gh: runGh,
281
+ projects: () => store.projects().map((project) => project.root),
282
+ onDue: (due) => {
283
+ for (const line of dueLines(due)) write(line);
284
+ },
285
+ onError: (error) => {
286
+ writeErr(`follow-ups: ${error}`);
287
+ }
288
+ });
274
289
  await stopped;
290
+ stopChecks();
275
291
  clearInterval(timer);
276
292
  stopping.abort();
277
293
  await watcher.settled();
@@ -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 { Dr as placeFor, Er as placeFiles, Mr as inside, U as openStore, W as storePath, kr as runDirOf } from "./src-Dr_fTSu-.mjs";
2
+ import { a as isBuiltinArg, n as builtinWorkflowFile, t as builtinProblem } from "./builtin-files-2bHf76x-.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-Dr_fTSu-.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.15",
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 };