@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.
- package/dist/__build_info__-CtSVVuZE.mjs +691 -0
- package/dist/bin.mjs +826 -526
- package/dist/clean-Dcr807be.mjs +88 -0
- package/dist/{context-BFGC_C02.mjs → context-C_bksb0C.mjs} +45 -129
- package/dist/{find-BcvhT6UN.mjs → find-DptmbhGd.mjs} +1 -1
- package/dist/{format-DqpObyMl.mjs → format-J-a21Yvn.mjs} +3 -163
- package/dist/{in-a-run-BbLhZOFW.mjs → in-a-run-D1Qc5Mit.mjs} +1 -1
- package/dist/{limits-BKYAmqCR.mjs → limits-BDZCmGEu.mjs} +1 -1
- package/dist/list-ZvY2OcLq.mjs +110 -0
- package/dist/{mcp-BWsdQyri.mjs → mcp-CHVKKg3B.mjs} +6 -6
- package/dist/{output-Q2EdeVUt.mjs → output-CXSJO00X.mjs} +68 -24
- package/dist/{pause-DKACDfV1.mjs → pause-BPSmg4yz.mjs} +4 -4
- package/dist/{relay-CbK-Wme9.mjs → relay-Do-aNbSD.mjs} +2 -2
- package/dist/remove-AwaMMaHf.mjs +194 -0
- package/dist/{send-C6yr04Fe.mjs → send-DhGBkowy.mjs} +3 -3
- package/dist/{src-CZOVF2yU.mjs → src-BHnxpJyH.mjs} +1 -1
- package/dist/{src-Cz1yOUTg.mjs → src-CGqMati3.mjs} +1411 -747
- package/dist/{src-D5ruZFgD.mjs → src-CpDKIulI.mjs} +1200 -55
- package/dist/{src-DtN1k-B3.mjs → src-UWkk1sl9.mjs} +1 -1
- package/dist/ui/assets/app-BgVDC_5l.js +67 -0
- package/dist/ui/assets/{app-C722xNZ3.css → app-DVf68fj5.css} +1 -1
- package/dist/ui/assets/{index-BoG6O-Lp.js → index-MQNNgsyT.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/ui-DhutUMgQ.mjs +472 -0
- package/dist/{usage-BIjf9Xdt.mjs → usage-CT7Z-yjr.mjs} +5 -4
- package/dist/{watch-B8o4Szd_.mjs → watch-0Qr2UfCQ.mjs} +6 -6
- package/dist/worktrees-DHl80k96.mjs +212 -0
- package/package.json +4 -4
- package/dist/__build_info__-CXXMLvjt.mjs +0 -104
- package/dist/ui/assets/app-BlDdP396.js +0 -61
- package/dist/ui-CZS1XKla.mjs +0 -160
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { E as removeWorktree, O as gitOk } from "./src-CGqMati3.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 kept = (item, skip) => ({
|
|
15
|
+
item,
|
|
16
|
+
removeWorktree: false,
|
|
17
|
+
deleteBranch: false,
|
|
18
|
+
skip
|
|
19
|
+
});
|
|
20
|
+
/** What clean does with one item. */
|
|
21
|
+
const cleanStep = (item, options) => {
|
|
22
|
+
if (item.inMainCheckout) return kept(item, MAIN_CHECKOUT);
|
|
23
|
+
if (item.heldBy !== void 0) return kept(item, `checked out in ${item.heldBy}`);
|
|
24
|
+
if (options.live?.has(item.runId) === true) return kept(item, RUN_GOING);
|
|
25
|
+
if (item.dirty && !options.force) return kept(item, "uncommitted changes");
|
|
26
|
+
if (item.branch === void 0 && item.checkedOut === void 0 && item.ahead > 0 && !options.force) return kept(item, "commits on no branch");
|
|
27
|
+
const removable = item.path !== null && !item.missing;
|
|
28
|
+
if (options.branches && item.branch !== void 0 && !item.merged && !options.force) return {
|
|
29
|
+
item,
|
|
30
|
+
removeWorktree: removable,
|
|
31
|
+
deleteBranch: false,
|
|
32
|
+
skip: "branch not merged"
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
item,
|
|
36
|
+
removeWorktree: removable,
|
|
37
|
+
deleteBranch: options.branches && item.branch !== void 0
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/** Whether a step drops a worktree whose folder is gone, by its own path. */
|
|
41
|
+
const stepPrunes = (step) => step.item.missing && step.item.path !== null && step.skip === void 0;
|
|
42
|
+
/** Whether a step does anything. */
|
|
43
|
+
const stepActs = (step) => step.removeWorktree || step.deleteBranch || stepPrunes(step);
|
|
44
|
+
/**
|
|
45
|
+
* What `ork worktrees clean` would do. A worktree with uncommitted changes, an
|
|
46
|
+
* unmerged branch, or a detached worktree holding commits no branch has, is
|
|
47
|
+
* kept unless `force`; one checked out in the main checkout or in a worktree
|
|
48
|
+
* Ork didn't create, or whose run is still going, is kept even then.
|
|
49
|
+
*/
|
|
50
|
+
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));
|
|
51
|
+
/**
|
|
52
|
+
* Carries out one step; what it did goes into `done` as it goes. It touches
|
|
53
|
+
* only its own item: a missing worktree is dropped by its own path (`git
|
|
54
|
+
* worktree remove`), never by `git worktree prune`, which would drop every
|
|
55
|
+
* missing worktree in the repository, the user's own included.
|
|
56
|
+
*/
|
|
57
|
+
const applyStep = async (root, step, force, done) => {
|
|
58
|
+
const { item } = step;
|
|
59
|
+
if (stepPrunes(step) && !step.removeWorktree && item.path !== null) {
|
|
60
|
+
await gitOk(root, [
|
|
61
|
+
"worktree",
|
|
62
|
+
"remove",
|
|
63
|
+
item.path
|
|
64
|
+
]);
|
|
65
|
+
done.push(`pruned ${item.path}`);
|
|
66
|
+
}
|
|
67
|
+
if (step.removeWorktree && item.path !== null) {
|
|
68
|
+
await removeWorktree(root, item.path, force);
|
|
69
|
+
done.push(`removed ${item.path}`);
|
|
70
|
+
}
|
|
71
|
+
if (step.deleteBranch && item.branch !== void 0) {
|
|
72
|
+
await gitOk(root, [
|
|
73
|
+
"branch",
|
|
74
|
+
force ? "-D" : "-d",
|
|
75
|
+
item.branch
|
|
76
|
+
]);
|
|
77
|
+
done.push(`deleted ${item.branch}`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Carries out a clean plan, step by step. What it did goes into `done` as it
|
|
82
|
+
* goes, so a caller still has it when git fails halfway.
|
|
83
|
+
*/
|
|
84
|
+
const applyClean = async (root, steps, force, done = []) => {
|
|
85
|
+
for (const step of steps) await applyStep(root, step, force, done);
|
|
86
|
+
return done;
|
|
87
|
+
};
|
|
88
|
+
export { planClean as a, keyOf as i, applyStep as n, stepActs as o, cleanStep as r, stepPrunes as s, applyClean as t };
|
|
@@ -1,55 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { _n as inside, _t as walkChecks, b as readProjectTriggers, dn as placeFiles, fn as placeFor, gn as workflowDirOf, gt as checkWorkflowText, mn as runDirOf, ot as explainSession, vt as walkSummary, x as workflowTextHash, yn as LESSON_CAP_TOKENS } from "./src-CGqMati3.mjs";
|
|
2
|
+
import { b as builtinWorkflowFile, x as isBuiltinArg, y as builtinProblem } from "./__build_info__-CtSVVuZE.mjs";
|
|
3
|
+
import { a as write, c as openStore, i as style, l as storePath, o as writeErr, r as plain } from "./output-CXSJO00X.mjs";
|
|
3
4
|
import { defineCommand } from "citty";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
6
|
-
// ../core/src/update/version.ts
|
|
7
|
-
const VERSION = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
8
|
-
/** `1.2.3`, `v1.2.3-beta.1` and the like; `null` for anything else. */
|
|
9
|
-
const parseVersion = (text) => {
|
|
10
|
-
const match = VERSION.exec(text.trim());
|
|
11
|
-
if (match === null) return null;
|
|
12
|
-
const [, major = "0", minor = "0", patch = "0", pre] = match;
|
|
13
|
-
return {
|
|
14
|
-
core: [
|
|
15
|
-
Number(major),
|
|
16
|
-
Number(minor),
|
|
17
|
-
Number(patch)
|
|
18
|
-
],
|
|
19
|
-
pre: pre === void 0 ? [] : pre.split(".")
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
const NUMERIC = /^\d+$/u;
|
|
23
|
-
/** One pre-release identifier against another: numbers sort below words. */
|
|
24
|
-
const compareIdentifier = (a, b) => {
|
|
25
|
-
const an = NUMERIC.test(a);
|
|
26
|
-
const bn = NUMERIC.test(b);
|
|
27
|
-
if (an && bn) return Number(a) - Number(b);
|
|
28
|
-
if (an !== bn) return an ? -1 : 1;
|
|
29
|
-
return a < b ? -1 : Number(a > b);
|
|
30
|
-
};
|
|
31
|
-
const comparePre = (a, b) => {
|
|
32
|
-
if (a.length === 0 || b.length === 0) return b.length - a.length;
|
|
33
|
-
for (let i = 0; i < Math.min(a.length, b.length); i += 1) {
|
|
34
|
-
const order = compareIdentifier(a[i] ?? "", b[i] ?? "");
|
|
35
|
-
if (order !== 0) return order;
|
|
36
|
-
}
|
|
37
|
-
return a.length - b.length;
|
|
38
|
-
};
|
|
39
|
-
/** Semver precedence: negative when `a` is older, 0 when equal. */
|
|
40
|
-
const compareVersions = (a, b) => {
|
|
41
|
-
for (let i = 0; i < 3; i += 1) {
|
|
42
|
-
const order = (a.core[i] ?? 0) - (b.core[i] ?? 0);
|
|
43
|
-
if (order !== 0) return order;
|
|
44
|
-
}
|
|
45
|
-
return comparePre(a.pre, b.pre);
|
|
46
|
-
};
|
|
47
|
-
/** Whether `candidate` is newer than `current`; false when either is invalid. */
|
|
48
|
-
const isNewer = (candidate, current) => {
|
|
49
|
-
const a = parseVersion(candidate);
|
|
50
|
-
const b = parseVersion(current);
|
|
51
|
-
return a !== null && b !== null && compareVersions(a, b) > 0;
|
|
52
|
-
};
|
|
53
7
|
// src/project/place.ts
|
|
54
8
|
/**
|
|
55
9
|
* @file Which project a folder belongs to, from the projects the store has
|
|
@@ -74,8 +28,9 @@ const fromCwd = (target, cwd) => inside(cwd, target) ? path.relative(cwd, target
|
|
|
74
28
|
// src/workflows.ts
|
|
75
29
|
/**
|
|
76
30
|
* @file Finding workflow files: the project's workflows folder by default
|
|
77
|
-
* (`.ork/workflows/`, or an attached project's), directories expanded,
|
|
78
|
-
* workflow given by name
|
|
31
|
+
* (`.ork/workflows/`, or an attached project's), directories expanded, a
|
|
32
|
+
* workflow given by name, and one of Ork's own by its `ork:` name, written
|
|
33
|
+
* into the project's run folder.
|
|
79
34
|
*/
|
|
80
35
|
const WORKFLOW_DIR = path.join(".ork", "workflows");
|
|
81
36
|
const WORKFLOW_SUFFIX = ".ork.yaml";
|
|
@@ -93,16 +48,32 @@ const findWorkflowFiles = (paths, cwd, workflows = path.join(cwd, WORKFLOW_DIR))
|
|
|
93
48
|
});
|
|
94
49
|
};
|
|
95
50
|
/**
|
|
96
|
-
* A workflow given by path,
|
|
97
|
-
*
|
|
98
|
-
*
|
|
51
|
+
* A workflow given by path, by name for one in the project's workflows folder
|
|
52
|
+
* (`hello` is `.ork/workflows/hello.ork.yaml` when no file `hello` exists), or
|
|
53
|
+
* by its `ork:` name for one of Ork's own. An unknown `ork:` name comes back as
|
|
54
|
+
* it is: commands refuse it first (`unknownBuiltin`).
|
|
99
55
|
*/
|
|
100
56
|
const resolveWorkflowArg = (arg, cwd, workflows = path.join(cwd, WORKFLOW_DIR)) => {
|
|
57
|
+
if (isBuiltinArg(arg)) {
|
|
58
|
+
const file = builtinWorkflowFile(projectAt(cwd), arg);
|
|
59
|
+
return file === void 0 ? arg : fromCwd(file, cwd);
|
|
60
|
+
}
|
|
101
61
|
if (existsSync(path.resolve(cwd, arg)) || arg.endsWith(".ork.yaml")) return arg;
|
|
102
62
|
const named = path.join(workflows, `${arg}${WORKFLOW_SUFFIX}`);
|
|
103
63
|
return existsSync(named) ? fromCwd(named, cwd) : arg;
|
|
104
64
|
};
|
|
105
65
|
/**
|
|
66
|
+
* Refuses an `ork:` name that isn't one of Ork's workflows, saying which are,
|
|
67
|
+
* or whose file's place in the project is a link; true when it did, so the
|
|
68
|
+
* command stops.
|
|
69
|
+
*/
|
|
70
|
+
const refusedBuiltin = (args, cwd) => {
|
|
71
|
+
const problems = args.filter((arg) => isBuiltinArg(arg)).flatMap((arg) => builtinProblem(projectAt(cwd), arg) ?? []);
|
|
72
|
+
for (const problem of problems) writeErr(problem);
|
|
73
|
+
if (problems.length > 0) process.exitCode = 1;
|
|
74
|
+
return problems.length > 0;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
106
77
|
* A command's workflow argument, by path or by name in the project `cwd` is in,
|
|
107
78
|
* with the project the file belongs to and a run's folder there.
|
|
108
79
|
*/
|
|
@@ -251,6 +222,7 @@ const validate = defineCommand({
|
|
|
251
222
|
},
|
|
252
223
|
run: async ({ args }) => {
|
|
253
224
|
const cwd = process.cwd();
|
|
225
|
+
if (refusedBuiltin(args._, cwd)) return;
|
|
254
226
|
const files = findWorkflowFiles(args._, cwd, placeFiles(projectAt(cwd)).workflows);
|
|
255
227
|
if (files.length === 0) {
|
|
256
228
|
write("No workflow files found. Run `ork init`, or pass a path.");
|
|
@@ -279,88 +251,28 @@ const validate = defineCommand({
|
|
|
279
251
|
// src/commands/context.ts
|
|
280
252
|
/**
|
|
281
253
|
* @file `ork context explain <wf> <node>`: what one node's session would load,
|
|
282
|
-
* layer by layer
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
254
|
+
* layer by layer, as core's `explainSession` plans it without writing
|
|
255
|
+
* anything (one of Ork's own `ork:` workflows is written into the project's
|
|
256
|
+
* run folder first, as `ork run` does): its project instructions, every skill
|
|
257
|
+
* with the layer it came from and whether it is live, shadowed or switched
|
|
258
|
+
* off, its context files, its practices, and estimated tokens.
|
|
286
259
|
*/
|
|
287
|
-
/**
|
|
288
|
-
const tokens = (text) => Math.ceil(text.length / 4);
|
|
289
|
-
/** Which project instruction file Claude Code reads from the checkout. */
|
|
290
|
-
const instructionsOf = (stack, checkout) => {
|
|
291
|
-
if (stack.instructions === "ignore") return "left out (project.instructions: ignore)";
|
|
292
|
-
const has = (file) => existsSync(path.join(checkout, file));
|
|
293
|
-
if ([
|
|
294
|
-
"CLAUDE.md",
|
|
295
|
-
".claude/CLAUDE.md",
|
|
296
|
-
"CLAUDE.local.md"
|
|
297
|
-
].some((file) => has(file))) return "CLAUDE.md, loaded by Claude Code";
|
|
298
|
-
if (has("AGENTS.md")) return "AGENTS.md, loaded by Claude Code";
|
|
299
|
-
return "none in the project";
|
|
300
|
-
};
|
|
301
|
-
const skillLines = (stack, root) => stack.skills.map((skill) => {
|
|
302
|
-
const source = skill.text === void 0 ? path.relative(root, skill.dir) || "." : "built-in";
|
|
303
|
-
const line = {
|
|
304
|
-
name: skill.layer !== "project" ? `ork:${skill.name}` : skill.name,
|
|
305
|
-
layer: skill.layer,
|
|
306
|
-
source: source.startsWith("..") ? skill.dir : source,
|
|
307
|
-
state: skill.shadowedBy === void 0 && skill.ignored === void 0 ? "live" : "off",
|
|
308
|
-
tokens: tokens(skill.text ?? readFileSync(path.join(skill.dir, "SKILL.md"), "utf8"))
|
|
309
|
-
};
|
|
310
|
-
if (skill.shadowedBy !== void 0) line.why = `shadowed by the ${skill.shadowedBy} layer`;
|
|
311
|
-
else if (skill.ignored !== void 0) line.why = skill.ignored === "ignore" ? "project.skills: ignore" : "not in project.skills.allow";
|
|
312
|
-
return line;
|
|
313
|
-
});
|
|
260
|
+
/** Loads a workflow file and plans one node's session in it, in place. */
|
|
314
261
|
const explainNode = (file, nodeId, cwd, home) => {
|
|
315
262
|
const { report, workflow, graph, place, workflowDir } = loadWorkflow(file, cwd, home);
|
|
316
263
|
if (workflow === null) return {
|
|
317
264
|
ok: false,
|
|
318
265
|
lines: formatReport(report)
|
|
319
266
|
};
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
cwd: root,
|
|
323
|
-
inputs: {},
|
|
324
|
-
inPlace: true,
|
|
325
|
-
workflowDir,
|
|
326
|
-
projectRoot: root,
|
|
267
|
+
const result = explainSession(workflow, graph, nodeId, {
|
|
268
|
+
root: place.root,
|
|
327
269
|
orkDir: place.dir,
|
|
270
|
+
workflowDir,
|
|
328
271
|
...home === void 0 ? {} : { home }
|
|
329
272
|
});
|
|
330
|
-
|
|
273
|
+
return result.ok ? result : {
|
|
331
274
|
ok: false,
|
|
332
|
-
lines: [
|
|
333
|
-
};
|
|
334
|
-
const { context } = plan;
|
|
335
|
-
const practices = practiceInstructions(resolvePractices(workflow, nodeId), root);
|
|
336
|
-
const names = ["caveman", "ponytail"].filter((n) => !practices.skipped.includes(n));
|
|
337
|
-
const ref = graph?.agents.get(nodeId);
|
|
338
|
-
const role = ref === void 0 ? void 0 : workflow.agents?.[ref.name]?.instructions;
|
|
339
|
-
return {
|
|
340
|
-
ok: true,
|
|
341
|
-
explanation: {
|
|
342
|
-
node: nodeId,
|
|
343
|
-
type: workflow.nodes[nodeId]?.type ?? "",
|
|
344
|
-
workflow: workflow.name,
|
|
345
|
-
...ref === void 0 ? {} : { agent: {
|
|
346
|
-
name: ref.name,
|
|
347
|
-
where: ref.where,
|
|
348
|
-
tokens: tokens(role ?? "")
|
|
349
|
-
} },
|
|
350
|
-
instructions: instructionsOf(context, root),
|
|
351
|
-
skills: skillLines(context, root),
|
|
352
|
-
files: context.files.map((f) => ({
|
|
353
|
-
layer: f.layer,
|
|
354
|
-
name: f.name,
|
|
355
|
-
tokens: tokens(f.text)
|
|
356
|
-
})),
|
|
357
|
-
practices: practices.text.map((text, index) => ({
|
|
358
|
-
name: names[index] ?? "practice",
|
|
359
|
-
tokens: tokens(text)
|
|
360
|
-
})),
|
|
361
|
-
skippedPractices: practices.skipped,
|
|
362
|
-
appendedTokens: tokens(plan.spec.systemAppend)
|
|
363
|
-
}
|
|
275
|
+
lines: [result.error]
|
|
364
276
|
};
|
|
365
277
|
};
|
|
366
278
|
const approx = (count) => style("dim", `≈${count.toLocaleString("en")} tokens`);
|
|
@@ -368,7 +280,7 @@ const formatExplanation = (e) => {
|
|
|
368
280
|
const width = Math.max(8, ...e.skills.map((s) => s.name.length));
|
|
369
281
|
const sourceWidth = Math.max(0, ...e.skills.map((s) => s.source.length));
|
|
370
282
|
const lines = [
|
|
371
|
-
`${style("bold", e.node)} ${style("dim", `(${e.type}) in ${e.workflow}`)}`,
|
|
283
|
+
`${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`)}`,
|
|
372
284
|
"",
|
|
373
285
|
...e.agent === void 0 ? [] : [`${style("bold", "Agent")} ${e.agent.name} ${style("dim", `(${e.agent.where})`)} ${approx(e.agent.tokens)}`],
|
|
374
286
|
`${style("bold", "Instructions")} ${e.instructions}`,
|
|
@@ -383,7 +295,10 @@ const formatExplanation = (e) => {
|
|
|
383
295
|
}
|
|
384
296
|
lines.push("", style("bold", "Context files"));
|
|
385
297
|
if (e.files.length === 0) lines.push(style("dim", " none"));
|
|
386
|
-
for (const f of e.files)
|
|
298
|
+
for (const f of e.files) {
|
|
299
|
+
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)}`)}`;
|
|
300
|
+
lines.push(` ${f.layer.padEnd(8)} ${f.name} ${approx(f.tokens)}${cut}`);
|
|
301
|
+
}
|
|
387
302
|
lines.push("", style("bold", "Practices"));
|
|
388
303
|
for (const p of e.practices) lines.push(` ${p.name.padEnd(8)} ${approx(p.tokens)}`);
|
|
389
304
|
for (const name of e.skippedPractices) lines.push(` ${name.padEnd(8)} ${style("dim", "the project's own skill wins")}`);
|
|
@@ -399,7 +314,7 @@ const explain = defineCommand({
|
|
|
399
314
|
args: {
|
|
400
315
|
workflow: {
|
|
401
316
|
type: "positional",
|
|
402
|
-
description: "A workflow file,
|
|
317
|
+
description: "A workflow file, the name of one in the project's workflows (.ork/workflows/), or one of Ork's own (ork:feedback-apply)",
|
|
403
318
|
required: true
|
|
404
319
|
},
|
|
405
320
|
node: {
|
|
@@ -415,6 +330,7 @@ const explain = defineCommand({
|
|
|
415
330
|
},
|
|
416
331
|
run: ({ args }) => {
|
|
417
332
|
const cwd = process.cwd();
|
|
333
|
+
if (refusedBuiltin([args.workflow], cwd)) return;
|
|
418
334
|
const result = explainNode(workflowArg(args.workflow, cwd).file, args.node, cwd);
|
|
419
335
|
if (!result.ok) {
|
|
420
336
|
for (const line of result.lines) process.stderr.write(`${line}\n`);
|
|
@@ -432,4 +348,4 @@ const context = defineCommand({
|
|
|
432
348
|
},
|
|
433
349
|
subCommands: { explain }
|
|
434
350
|
});
|
|
435
|
-
export { validate as a, warningLines as c,
|
|
351
|
+
export { validate as a, warningLines as c, refusedBuiltin as d, workflowArg as f, loadWorkflow as i, WORKFLOW_DIR as l, registeredProjects as m, explainNode as n, validateFile as o, projectAt as p, formatReport as r, walkLoaded as s, context as t, WORKFLOW_SUFFIX as u };
|
|
@@ -1,166 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as style, n as cost } from "./output-
|
|
1
|
+
import { lt as liveStatus } from "./src-CGqMati3.mjs";
|
|
2
|
+
import { i as style, n as cost } from "./output-CXSJO00X.mjs";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
|
-
// ../core/src/worktree/list.ts
|
|
6
|
-
/**
|
|
7
|
-
* @file What `ork worktrees` shows and cleans: every worktree and branch Ork's
|
|
8
|
-
* runs recorded creating, whatever the project's git rules named them, and
|
|
9
|
-
* any `ork/*` branch besides, with their state read from git itself.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* The worktrees the project's runs recorded creating, oldest run first; one
|
|
13
|
-
* entry per branch or folder, from the latest run that created it.
|
|
14
|
-
*/
|
|
15
|
-
const createdWorktrees = (store, project) => {
|
|
16
|
-
const byName = /* @__PURE__ */ new Map();
|
|
17
|
-
for (const run of store.runs({ project }).toReversed()) for (const { event } of store.events(run.id)) {
|
|
18
|
-
if (event.type !== "worktree" || event.action !== "created") continue;
|
|
19
|
-
byName.set(event.branch ?? event.path, {
|
|
20
|
-
runId: run.id,
|
|
21
|
-
nodeId: event.id,
|
|
22
|
-
path: event.path,
|
|
23
|
-
...event.branch === void 0 ? {} : { branch: event.branch }
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
return [...byName.values()];
|
|
27
|
-
};
|
|
28
|
-
const parsePorcelain = (text) => text.split("\n\n").map((block) => lines(block)).map((fields) => {
|
|
29
|
-
const value = (key) => fields.find((f) => f.startsWith(`${key} `))?.slice(key.length + 1);
|
|
30
|
-
return {
|
|
31
|
-
path: value("worktree") ?? "",
|
|
32
|
-
branch: (value("branch") ?? "").replace(/^refs\/heads\//u, ""),
|
|
33
|
-
prunable: fields.some((f) => f.startsWith("prunable"))
|
|
34
|
-
};
|
|
35
|
-
}).filter((entry) => entry.path !== "");
|
|
36
|
-
/** The `ork/<run>/<node>` branches of runs that recorded nothing. */
|
|
37
|
-
const prefixed = (branch) => {
|
|
38
|
-
const [, runId = "", ...rest] = branch.split("/");
|
|
39
|
-
return {
|
|
40
|
-
runId,
|
|
41
|
-
nodeId: rest.join("/"),
|
|
42
|
-
path: "",
|
|
43
|
-
branch
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
/** How many commits a detached worktree holds that no branch, tag or remote has. */
|
|
47
|
-
const onNoBranch = async (entry) => entry === void 0 ? "0" : (await git(entry.path, [
|
|
48
|
-
"rev-list",
|
|
49
|
-
"--count",
|
|
50
|
-
"HEAD",
|
|
51
|
-
"--not",
|
|
52
|
-
"--branches",
|
|
53
|
-
"--tags",
|
|
54
|
-
"--remotes"
|
|
55
|
-
])).stdout;
|
|
56
|
-
/** One created worktree or branch, with its state read from git. */
|
|
57
|
-
const stateOf = async (root, entries, item) => {
|
|
58
|
-
const { branch } = item;
|
|
59
|
-
const entry = branch === void 0 ? entries.find((e) => e.path === item.path) : entries.find((e) => e.branch === branch);
|
|
60
|
-
const missing = entry !== void 0 && (entry.prunable || !existsSync(entry.path));
|
|
61
|
-
const status = entry === void 0 || missing ? "" : (await git(entry.path, ["status", "--porcelain"])).stdout;
|
|
62
|
-
const checkedOut = branch === void 0 && entry !== void 0 && entry.branch !== "" ? entry.branch : void 0;
|
|
63
|
-
const on = branch ?? checkedOut;
|
|
64
|
-
const counted = on === void 0 ? await onNoBranch(entry === void 0 || missing ? void 0 : entry) : (await git(root, [
|
|
65
|
-
"rev-list",
|
|
66
|
-
"--count",
|
|
67
|
-
`HEAD..${on}`
|
|
68
|
-
])).stdout;
|
|
69
|
-
const ahead = Number(counted);
|
|
70
|
-
return {
|
|
71
|
-
...branch === void 0 ? {} : { branch },
|
|
72
|
-
...checkedOut === void 0 ? {} : { checkedOut },
|
|
73
|
-
runId: item.runId,
|
|
74
|
-
nodeId: item.nodeId,
|
|
75
|
-
path: entry?.path ?? null,
|
|
76
|
-
missing,
|
|
77
|
-
dirty: lines(status).length > 0,
|
|
78
|
-
ahead: Number.isNaN(ahead) ? 0 : ahead,
|
|
79
|
-
merged: on !== void 0 && ahead === 0,
|
|
80
|
-
inMainCheckout: entry?.path === root
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
/**
|
|
84
|
-
* Every worktree and branch Ork's runs created in the repository at `root`,
|
|
85
|
-
* with its state: those the runs recorded (`created`, whatever the project's
|
|
86
|
-
* git rules named them), and any `ork/` branch besides. A recorded one that git
|
|
87
|
-
* no longer has is left out.
|
|
88
|
-
*/
|
|
89
|
-
const listOrkWorktrees = async (root, created = []) => {
|
|
90
|
-
const entries = parsePorcelain(await gitOk(root, [
|
|
91
|
-
"worktree",
|
|
92
|
-
"list",
|
|
93
|
-
"--porcelain"
|
|
94
|
-
]));
|
|
95
|
-
const branches = lines(await gitOk(root, [
|
|
96
|
-
"for-each-ref",
|
|
97
|
-
"--format=%(refname:short)",
|
|
98
|
-
"refs/heads/"
|
|
99
|
-
]));
|
|
100
|
-
const recordedBranches = new Set(created.flatMap((c) => c.branch ?? []));
|
|
101
|
-
const items = [...created.filter((c) => c.branch === void 0 ? entries.some((e) => e.path === c.path) : branches.includes(c.branch)), ...branches.filter((b) => b.startsWith("ork/") && !recordedBranches.has(b)).map((b) => prefixed(b))];
|
|
102
|
-
return Promise.all(items.map((item) => stateOf(root, entries, item)));
|
|
103
|
-
};
|
|
104
|
-
const cleanStep = (item, options) => {
|
|
105
|
-
if (item.inMainCheckout) return {
|
|
106
|
-
item,
|
|
107
|
-
removeWorktree: false,
|
|
108
|
-
deleteBranch: false,
|
|
109
|
-
skip: "checked out in the main checkout"
|
|
110
|
-
};
|
|
111
|
-
if (item.dirty && !options.force) return {
|
|
112
|
-
item,
|
|
113
|
-
removeWorktree: false,
|
|
114
|
-
deleteBranch: false,
|
|
115
|
-
skip: "uncommitted changes"
|
|
116
|
-
};
|
|
117
|
-
if (item.branch === void 0 && item.checkedOut === void 0 && item.ahead > 0 && !options.force) return {
|
|
118
|
-
item,
|
|
119
|
-
removeWorktree: false,
|
|
120
|
-
deleteBranch: false,
|
|
121
|
-
skip: "commits on no branch"
|
|
122
|
-
};
|
|
123
|
-
const removable = item.path !== null && !item.missing;
|
|
124
|
-
if (options.branches && item.branch !== void 0 && !item.merged && !options.force) return {
|
|
125
|
-
item,
|
|
126
|
-
removeWorktree: removable,
|
|
127
|
-
deleteBranch: false,
|
|
128
|
-
skip: "branch not merged"
|
|
129
|
-
};
|
|
130
|
-
return {
|
|
131
|
-
item,
|
|
132
|
-
removeWorktree: removable,
|
|
133
|
-
deleteBranch: options.branches && item.branch !== void 0
|
|
134
|
-
};
|
|
135
|
-
};
|
|
136
|
-
/**
|
|
137
|
-
* What `ork worktrees clean` would do. A worktree with uncommitted changes, an
|
|
138
|
-
* unmerged branch, or a detached worktree holding commits no branch has, is
|
|
139
|
-
* kept unless `force`.
|
|
140
|
-
*/
|
|
141
|
-
const planClean = (items, filter, options) => items.filter((item) => filter.runId === void 0 || item.runId === filter.runId).filter((item) => !filter.merged || item.merged).map((item) => cleanStep(item, options));
|
|
142
|
-
/** Carries out a clean plan, then prunes stale worktree entries. */
|
|
143
|
-
const applyClean = async (root, steps, force) => {
|
|
144
|
-
const done = [];
|
|
145
|
-
await gitOk(root, ["worktree", "prune"]);
|
|
146
|
-
for (const step of steps) {
|
|
147
|
-
const { item } = step;
|
|
148
|
-
if (step.removeWorktree && item.path !== null) {
|
|
149
|
-
await removeWorktree(root, item.path, force);
|
|
150
|
-
done.push(`removed ${item.path}`);
|
|
151
|
-
}
|
|
152
|
-
if (step.deleteBranch && item.branch !== void 0) {
|
|
153
|
-
await gitOk(root, [
|
|
154
|
-
"branch",
|
|
155
|
-
force ? "-D" : "-d",
|
|
156
|
-
item.branch
|
|
157
|
-
]);
|
|
158
|
-
done.push(`deleted ${item.branch}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
await gitOk(root, ["worktree", "prune"]);
|
|
162
|
-
return done;
|
|
163
|
-
};
|
|
164
4
|
// src/human.ts
|
|
165
5
|
/**
|
|
166
6
|
* @file The terminal `HumanChannel`: answers `ask_user` questions, gates and
|
|
@@ -550,4 +390,4 @@ const formatTree = (roots) => {
|
|
|
550
390
|
return `${text.padEnd(label)} ${style("dim", node.type.padEnd(type))} ${state}${usage}`;
|
|
551
391
|
});
|
|
552
392
|
};
|
|
553
|
-
export { formatRunEvent as a,
|
|
393
|
+
export { formatRunEvent as a, formatEvent as i, formatRuns as n, createTerminalHuman as o, formatTree as r, ago as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as limitsNow, i as NOT_STARTED, o as limitsReport, r as capsHere } from "./src-
|
|
1
|
+
import { a as limitsNow, i as NOT_STARTED, o as limitsReport, r as capsHere } from "./src-CGqMati3.mjs";
|
|
2
2
|
// src/run/limits.ts
|
|
3
3
|
/**
|
|
4
4
|
* @file The limits across runs that apply here (RFC-007): the user's settings
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { D as git, O as gitOk, k as lines } from "./src-CGqMati3.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) => {
|
|
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
|
+
path: entry?.path ?? null,
|
|
80
|
+
missing,
|
|
81
|
+
dirty: changes > 0,
|
|
82
|
+
changes,
|
|
83
|
+
ahead: Number.isNaN(ahead) ? 0 : ahead,
|
|
84
|
+
merged: on !== void 0 && ahead === 0,
|
|
85
|
+
inMainCheckout: entry?.path === root || holder?.path === root,
|
|
86
|
+
...holder === void 0 || holder.path === root ? {} : { heldBy: holder.path }
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Every worktree and branch Ork's runs created in the repository at `root`,
|
|
91
|
+
* with its state: those the runs recorded (`created`, whatever the project's
|
|
92
|
+
* git rules named them), and any `ork/` branch besides. A recorded one that git
|
|
93
|
+
* no longer has is left out.
|
|
94
|
+
*/
|
|
95
|
+
const listOrkWorktrees = async (root, created = []) => {
|
|
96
|
+
const entries = parsePorcelain(await gitOk(root, [
|
|
97
|
+
"worktree",
|
|
98
|
+
"list",
|
|
99
|
+
"--porcelain"
|
|
100
|
+
]));
|
|
101
|
+
const branches = lines(await gitOk(root, [
|
|
102
|
+
"for-each-ref",
|
|
103
|
+
"--format=%(refname:short)",
|
|
104
|
+
"refs/heads/"
|
|
105
|
+
]));
|
|
106
|
+
const recordedBranches = new Set(created.flatMap((c) => c.branch ?? []));
|
|
107
|
+
const items = [...created.filter((c) => c.branch === void 0 ? entries.some((e) => e.path === c.path) : branches.includes(c.branch)), ...branches.filter((b) => b.startsWith("ork/") && !recordedBranches.has(b)).map((b) => prefixed(b))];
|
|
108
|
+
return Promise.all(items.map((item) => stateOf(root, entries, item)));
|
|
109
|
+
};
|
|
110
|
+
export { listOrkWorktrees as n, createdWorktrees as t };
|