@hizliemre/horse-code 0.1.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/dist/app-SB2L34JW.js +6217 -0
  4. package/dist/chunk-2DGO2BUB.js +4490 -0
  5. package/dist/chunk-2SVAHH5N.js +60 -0
  6. package/dist/chunk-3XVZXTB6.js +4469 -0
  7. package/dist/chunk-5UWA2UBM.js +69 -0
  8. package/dist/chunk-7TBYMFMG.js +147 -0
  9. package/dist/chunk-B67BK5GQ.js +34 -0
  10. package/dist/chunk-BY4DP7IE.js +20 -0
  11. package/dist/chunk-DKVIN43T.js +54 -0
  12. package/dist/chunk-DTWKSZXY.js +162 -0
  13. package/dist/chunk-F2IALVBU.js +212 -0
  14. package/dist/chunk-FFYBY2NA.js +392 -0
  15. package/dist/chunk-FGVJFMK5.js +123 -0
  16. package/dist/chunk-H2FDGPVW.js +42 -0
  17. package/dist/chunk-HBSC2HT2.js +85 -0
  18. package/dist/chunk-IW2KBAVZ.js +21 -0
  19. package/dist/chunk-JWAEW7AJ.js +121 -0
  20. package/dist/chunk-NNTIACT4.js +163 -0
  21. package/dist/chunk-O74BDQKS.js +28 -0
  22. package/dist/chunk-PGOYDOI4.js +426 -0
  23. package/dist/chunk-QF4MP6BS.js +69 -0
  24. package/dist/chunk-SSDLHWSF.js +35 -0
  25. package/dist/chunk-TOPZL5SU.js +1052 -0
  26. package/dist/chunk-YBWTCXUS.js +153 -0
  27. package/dist/chunk-YILDXPSI.js +1363 -0
  28. package/dist/clean-YOQATBMZ.js +18 -0
  29. package/dist/cli.js +1495 -0
  30. package/dist/discover-5URG7C4J.js +52 -0
  31. package/dist/fix-HBBOTUWM.js +34 -0
  32. package/dist/frontmatter-UNIPNLLO.js +6 -0
  33. package/dist/git-VTSZALSR.js +6 -0
  34. package/dist/install-O34KMWJB.js +113 -0
  35. package/dist/main-branch-KGWUINYQ.js +19 -0
  36. package/dist/ongoing-OV5XROTU.js +70 -0
  37. package/dist/project-graph-IOPCSZUA.js +56 -0
  38. package/dist/run-LQOZ5I7Z.js +610 -0
  39. package/dist/save-skills-OHYGVTQ4.js +13 -0
  40. package/dist/source-cache-XEK5WN7I.js +29 -0
  41. package/dist/trace-ZMB7LT7W.js +66 -0
  42. package/dist/trace-adopt-C6TUWFJL.js +79 -0
  43. package/dist/trace-run-F23MFTY4.js +24 -0
  44. package/dist/triage-2J3T5PVQ.js +30 -0
  45. package/dist/verify-WQ3GHION.js +479 -0
  46. package/dist/worktree-F7TWLWLN.js +87 -0
  47. package/package.json +64 -0
@@ -0,0 +1,153 @@
1
+ // src/worktree/clean.ts
2
+ import { rm } from "fs/promises";
3
+ import { existsSync, readdirSync, realpathSync } from "fs";
4
+ import { join } from "path";
5
+ var SESSIONS_DIR = join(".horsecode", "worktrees");
6
+ async function registered(git, repoRoot) {
7
+ const r = await git(["worktree", "list", "--porcelain"], repoRoot);
8
+ const out = [];
9
+ for (const line of r.stdout.split("\n")) {
10
+ if (!line.startsWith("worktree ")) continue;
11
+ const p = line.slice("worktree ".length).trim();
12
+ try {
13
+ out.push(realpathSync(p));
14
+ } catch {
15
+ out.push(p);
16
+ }
17
+ }
18
+ return out;
19
+ }
20
+ async function isMerged(git, repoRoot, branch, target) {
21
+ const ancestor = await git(["merge-base", "--is-ancestor", branch, target], repoRoot);
22
+ if (ancestor.code === 0) return true;
23
+ const mb = await git(["merge-base", target, branch], repoRoot);
24
+ if (mb.code !== 0 || !mb.stdout.trim()) return false;
25
+ const tree = await git(["rev-parse", `${branch}^{tree}`], repoRoot);
26
+ if (tree.code !== 0 || !tree.stdout.trim()) return false;
27
+ const squashed = await git(
28
+ ["commit-tree", tree.stdout.trim(), "-p", mb.stdout.trim(), "-m", "hc: squash-merge probe"],
29
+ repoRoot
30
+ );
31
+ if (squashed.code !== 0 || !squashed.stdout.trim()) return false;
32
+ const cherry = await git(["cherry", target, squashed.stdout.trim()], repoRoot);
33
+ if (cherry.code !== 0) return false;
34
+ return cherry.stdout.trim().startsWith("-");
35
+ }
36
+ var OWN_STATE = /^\.horsecode\//;
37
+ async function uncommitted(git, worktree) {
38
+ const r = await git(["status", "--porcelain"], worktree);
39
+ if (r.code !== 0) return [];
40
+ return r.stdout.split("\n").map((l) => l.slice(3).trim()).filter(Boolean);
41
+ }
42
+ function usersOwn(files) {
43
+ return files.filter((f) => !OWN_STATE.test(f.replace(/\\/g, "/")));
44
+ }
45
+ async function surveySessions(git, repoRoot, target) {
46
+ const dir = join(repoRoot, SESSIONS_DIR);
47
+ if (!existsSync(dir)) return [];
48
+ const slugs = readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
49
+ if (!slugs.length) return [];
50
+ const live = await registered(git, repoRoot);
51
+ const listed = await git(["for-each-ref", "--format=%(refname:short)", "refs/heads/hc/"], repoRoot);
52
+ const allBranches = listed.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
53
+ const out = [];
54
+ for (const slug of slugs) {
55
+ const root = join(dir, slug);
56
+ let real = root;
57
+ try {
58
+ real = realpathSync(root);
59
+ } catch {
60
+ }
61
+ const worktrees = live.filter((p) => p === real || p.startsWith(`${real}/`)).sort((a, b) => a.length - b.length);
62
+ const branches = allBranches.filter((b) => b === `hc/${slug}/base` || b.startsWith(`hc/${slug}/`));
63
+ const baseBranch = `hc/${slug}/base`;
64
+ const row = { slug, root, baseBranch, worktrees, branches };
65
+ if (!worktrees.length) {
66
+ out.push({
67
+ ...row,
68
+ verdict: "orphan",
69
+ detail: "git no longer tracks a worktree here \u2014 what it held cannot be checked, so it is left alone."
70
+ });
71
+ continue;
72
+ }
73
+ if (!branches.includes(baseBranch)) {
74
+ out.push({ ...row, verdict: "orphan", detail: `its branch \`${baseBranch}\` is gone \u2014 nothing to judge it by.` });
75
+ continue;
76
+ }
77
+ if (!await isMerged(git, repoRoot, baseBranch, target)) {
78
+ const ahead = await git(["rev-list", "--count", `${target}..${baseBranch}`], repoRoot);
79
+ const n = ahead.code === 0 ? ahead.stdout.trim() : "?";
80
+ out.push({ ...row, verdict: "unmerged", detail: `${n} commit(s) not in \`${target}\` \u2014 removing it would lose them.` });
81
+ continue;
82
+ }
83
+ const dirty = [];
84
+ let ownState = 0;
85
+ for (const w of worktrees) {
86
+ const files = await uncommitted(git, w);
87
+ const theirs = usersOwn(files);
88
+ ownState += files.length - theirs.length;
89
+ if (theirs.length) dirty.push(`${w.slice(root.length + 1) || "base"} (${theirs.length})`);
90
+ }
91
+ if (dirty.length) {
92
+ out.push({ ...row, verdict: "dirty", detail: `merged, but uncommitted changes remain in ${dirty.join(", ")}.` });
93
+ continue;
94
+ }
95
+ const aside = ownState ? ` (${ownState} file(s) of horse-code's own state under \`.horsecode/\` were modified and are not counted)` : "";
96
+ out.push({
97
+ ...row,
98
+ verdict: "merged",
99
+ detail: `every commit is in \`${target}\` and nothing of yours is uncommitted${aside} \u2014 ${worktrees.length} worktree(s), ${branches.length} branch(es).`
100
+ });
101
+ }
102
+ return out;
103
+ }
104
+ async function cleanSessions(git, repoRoot, target) {
105
+ const survey = await surveySessions(git, repoRoot, target);
106
+ const out = { removed: [], failed: [], kept: survey.filter((s) => s.verdict !== "merged") };
107
+ for (const s of survey.filter((x) => x.verdict === "merged")) {
108
+ try {
109
+ for (const w of [...s.worktrees].sort((a, b) => b.length - a.length)) {
110
+ await git(["worktree", "remove", "--force", w], repoRoot);
111
+ }
112
+ await rm(s.root, { recursive: true, force: true });
113
+ await git(["worktree", "prune"], repoRoot);
114
+ for (const b of s.branches) await git(["branch", "-D", b], repoRoot);
115
+ out.removed.push(s.slug);
116
+ } catch (e) {
117
+ out.failed.push({ slug: s.slug, error: e instanceof Error ? e.message : String(e) });
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+ var MARK = { merged: "\u{1F9F9}", unmerged: "\u23F3", dirty: "\u270B", orphan: "\u2753" };
123
+ function describeSurvey(survey, target) {
124
+ if (!survey.length) return "No horse-code worktrees \u2014 `.horsecode/worktrees` is empty or absent.";
125
+ const rows = survey.map((s) => `${MARK[s.verdict]} \`${s.slug}\` \u2014 ${s.detail}`);
126
+ const removable = survey.filter((s) => s.verdict === "merged");
127
+ const head = `Judged against \`${target}\`:`;
128
+ const tail = removable.length ? `
129
+
130
+ \`/clean-worktrees go\` removes ${removable.length === 1 ? "it" : `the ${removable.length} marked \u{1F9F9}`} \u2014 directories and branches together. Everything else is left as it is.` : `
131
+
132
+ Nothing to remove.`;
133
+ return `${head}
134
+ ${rows.join("\n")}${tail}`;
135
+ }
136
+ function describeClean(res, target) {
137
+ const bits = [];
138
+ if (res.removed.length) bits.push(`\u{1F9F9} Removed ${res.removed.length} merged session(s): ${res.removed.map((s) => `\`${s}\``).join(", ")}.`);
139
+ else bits.push(`Nothing was merged into \`${target}\` \u2014 nothing removed.`);
140
+ if (res.failed.length) bits.push(`\u26A0\uFE0F Could not remove ${res.failed.map((f) => `\`${f.slug}\` (${f.error})`).join(", ")}.`);
141
+ if (res.kept.length) bits.push(`Kept: ${res.kept.map((s) => `\`${s.slug}\` (${s.verdict})`).join(", ")}.`);
142
+ return bits.join("\n\n");
143
+ }
144
+
145
+ export {
146
+ SESSIONS_DIR,
147
+ isMerged,
148
+ usersOwn,
149
+ surveySessions,
150
+ cleanSessions,
151
+ describeSurvey,
152
+ describeClean
153
+ };