mulch-cli 0.5.0 → 0.6.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 (196) hide show
  1. package/README.md +12 -1
  2. package/package.json +11 -16
  3. package/src/api.ts +310 -0
  4. package/src/cli.ts +54 -0
  5. package/src/commands/add.ts +61 -0
  6. package/src/commands/compact.ts +924 -0
  7. package/src/commands/delete.ts +103 -0
  8. package/src/commands/diff.ts +209 -0
  9. package/src/commands/doctor.ts +586 -0
  10. package/src/commands/edit.ts +253 -0
  11. package/src/commands/init.ts +33 -0
  12. package/src/commands/learn.ts +170 -0
  13. package/src/commands/onboard.ts +362 -0
  14. package/src/commands/prime.ts +327 -0
  15. package/src/commands/prune.ts +128 -0
  16. package/src/commands/query.ts +177 -0
  17. package/src/commands/ready.ts +194 -0
  18. package/src/commands/record.ts +959 -0
  19. package/src/commands/search.ts +234 -0
  20. package/src/commands/setup.ts +823 -0
  21. package/src/commands/status.ts +83 -0
  22. package/src/commands/sync.ts +224 -0
  23. package/src/commands/update.ts +112 -0
  24. package/src/commands/validate.ts +107 -0
  25. package/src/index.ts +50 -0
  26. package/src/schemas/config.ts +31 -0
  27. package/src/schemas/index.ts +18 -0
  28. package/src/schemas/record-schema.ts +177 -0
  29. package/src/schemas/record.ts +83 -0
  30. package/src/utils/bm25.ts +243 -0
  31. package/src/utils/budget.ts +157 -0
  32. package/src/utils/config.ts +117 -0
  33. package/src/utils/expertise.ts +379 -0
  34. package/src/utils/format.ts +767 -0
  35. package/src/utils/git.ts +89 -0
  36. package/src/utils/index.ts +54 -0
  37. package/src/utils/json-output.ts +13 -0
  38. package/src/utils/lock.ts +82 -0
  39. package/src/utils/markers.ts +51 -0
  40. package/{dist/utils/scoring.d.ts → src/utils/scoring.ts} +53 -9
  41. package/src/utils/version.ts +46 -0
  42. package/dist/api.d.ts +0 -65
  43. package/dist/api.d.ts.map +0 -1
  44. package/dist/api.js +0 -196
  45. package/dist/api.js.map +0 -1
  46. package/dist/cli.d.ts +0 -3
  47. package/dist/cli.d.ts.map +0 -1
  48. package/dist/cli.js +0 -50
  49. package/dist/cli.js.map +0 -1
  50. package/dist/commands/add.d.ts +0 -3
  51. package/dist/commands/add.d.ts.map +0 -1
  52. package/dist/commands/add.js +0 -47
  53. package/dist/commands/add.js.map +0 -1
  54. package/dist/commands/compact.d.ts +0 -5
  55. package/dist/commands/compact.d.ts.map +0 -1
  56. package/dist/commands/compact.js +0 -709
  57. package/dist/commands/compact.js.map +0 -1
  58. package/dist/commands/delete.d.ts +0 -3
  59. package/dist/commands/delete.d.ts.map +0 -1
  60. package/dist/commands/delete.js +0 -82
  61. package/dist/commands/delete.js.map +0 -1
  62. package/dist/commands/diff.d.ts +0 -11
  63. package/dist/commands/diff.d.ts.map +0 -1
  64. package/dist/commands/diff.js +0 -170
  65. package/dist/commands/diff.js.map +0 -1
  66. package/dist/commands/doctor.d.ts +0 -3
  67. package/dist/commands/doctor.d.ts.map +0 -1
  68. package/dist/commands/doctor.js +0 -391
  69. package/dist/commands/doctor.js.map +0 -1
  70. package/dist/commands/edit.d.ts +0 -3
  71. package/dist/commands/edit.d.ts.map +0 -1
  72. package/dist/commands/edit.js +0 -198
  73. package/dist/commands/edit.js.map +0 -1
  74. package/dist/commands/init.d.ts +0 -3
  75. package/dist/commands/init.d.ts.map +0 -1
  76. package/dist/commands/init.js +0 -30
  77. package/dist/commands/init.js.map +0 -1
  78. package/dist/commands/learn.d.ts +0 -12
  79. package/dist/commands/learn.d.ts.map +0 -1
  80. package/dist/commands/learn.js +0 -130
  81. package/dist/commands/learn.js.map +0 -1
  82. package/dist/commands/onboard.d.ts +0 -10
  83. package/dist/commands/onboard.d.ts.map +0 -1
  84. package/dist/commands/onboard.js +0 -286
  85. package/dist/commands/onboard.js.map +0 -1
  86. package/dist/commands/prime.d.ts +0 -3
  87. package/dist/commands/prime.d.ts.map +0 -1
  88. package/dist/commands/prime.js +0 -242
  89. package/dist/commands/prime.js.map +0 -1
  90. package/dist/commands/prune.d.ts +0 -8
  91. package/dist/commands/prune.d.ts.map +0 -1
  92. package/dist/commands/prune.js +0 -90
  93. package/dist/commands/prune.js.map +0 -1
  94. package/dist/commands/query.d.ts +0 -3
  95. package/dist/commands/query.d.ts.map +0 -1
  96. package/dist/commands/query.js +0 -133
  97. package/dist/commands/query.js.map +0 -1
  98. package/dist/commands/ready.d.ts +0 -3
  99. package/dist/commands/ready.d.ts.map +0 -1
  100. package/dist/commands/ready.js +0 -160
  101. package/dist/commands/ready.js.map +0 -1
  102. package/dist/commands/record.d.ts +0 -13
  103. package/dist/commands/record.d.ts.map +0 -1
  104. package/dist/commands/record.js +0 -689
  105. package/dist/commands/record.js.map +0 -1
  106. package/dist/commands/search.d.ts +0 -3
  107. package/dist/commands/search.d.ts.map +0 -1
  108. package/dist/commands/search.js +0 -163
  109. package/dist/commands/search.js.map +0 -1
  110. package/dist/commands/setup.d.ts +0 -29
  111. package/dist/commands/setup.d.ts.map +0 -1
  112. package/dist/commands/setup.js +0 -548
  113. package/dist/commands/setup.js.map +0 -1
  114. package/dist/commands/status.d.ts +0 -3
  115. package/dist/commands/status.d.ts.map +0 -1
  116. package/dist/commands/status.js +0 -61
  117. package/dist/commands/status.js.map +0 -1
  118. package/dist/commands/sync.d.ts +0 -3
  119. package/dist/commands/sync.d.ts.map +0 -1
  120. package/dist/commands/sync.js +0 -176
  121. package/dist/commands/sync.js.map +0 -1
  122. package/dist/commands/update.d.ts +0 -3
  123. package/dist/commands/update.d.ts.map +0 -1
  124. package/dist/commands/update.js +0 -72
  125. package/dist/commands/update.js.map +0 -1
  126. package/dist/commands/validate.d.ts +0 -3
  127. package/dist/commands/validate.d.ts.map +0 -1
  128. package/dist/commands/validate.js +0 -86
  129. package/dist/commands/validate.js.map +0 -1
  130. package/dist/index.d.ts +0 -9
  131. package/dist/index.d.ts.map +0 -1
  132. package/dist/index.js +0 -10
  133. package/dist/index.js.map +0 -1
  134. package/dist/schemas/config.d.ts +0 -17
  135. package/dist/schemas/config.d.ts.map +0 -1
  136. package/dist/schemas/config.js +0 -16
  137. package/dist/schemas/config.js.map +0 -1
  138. package/dist/schemas/index.d.ts +0 -5
  139. package/dist/schemas/index.d.ts.map +0 -1
  140. package/dist/schemas/index.js +0 -3
  141. package/dist/schemas/index.js.map +0 -1
  142. package/dist/schemas/record-schema.d.ts +0 -403
  143. package/dist/schemas/record-schema.d.ts.map +0 -1
  144. package/dist/schemas/record-schema.js +0 -150
  145. package/dist/schemas/record-schema.js.map +0 -1
  146. package/dist/schemas/record.d.ts +0 -62
  147. package/dist/schemas/record.d.ts.map +0 -1
  148. package/dist/schemas/record.js +0 -2
  149. package/dist/schemas/record.js.map +0 -1
  150. package/dist/utils/bm25.d.ts +0 -39
  151. package/dist/utils/bm25.d.ts.map +0 -1
  152. package/dist/utils/bm25.js +0 -171
  153. package/dist/utils/bm25.js.map +0 -1
  154. package/dist/utils/budget.d.ts +0 -35
  155. package/dist/utils/budget.d.ts.map +0 -1
  156. package/dist/utils/budget.js +0 -114
  157. package/dist/utils/budget.js.map +0 -1
  158. package/dist/utils/config.d.ts +0 -12
  159. package/dist/utils/config.d.ts.map +0 -1
  160. package/dist/utils/config.js +0 -89
  161. package/dist/utils/config.js.map +0 -1
  162. package/dist/utils/expertise.d.ts +0 -57
  163. package/dist/utils/expertise.d.ts.map +0 -1
  164. package/dist/utils/expertise.js +0 -276
  165. package/dist/utils/expertise.js.map +0 -1
  166. package/dist/utils/format.d.ts +0 -31
  167. package/dist/utils/format.d.ts.map +0 -1
  168. package/dist/utils/format.js +0 -562
  169. package/dist/utils/format.js.map +0 -1
  170. package/dist/utils/git.d.ts +0 -6
  171. package/dist/utils/git.d.ts.map +0 -1
  172. package/dist/utils/git.js +0 -81
  173. package/dist/utils/git.js.map +0 -1
  174. package/dist/utils/index.d.ts +0 -8
  175. package/dist/utils/index.d.ts.map +0 -1
  176. package/dist/utils/index.js +0 -8
  177. package/dist/utils/index.js.map +0 -1
  178. package/dist/utils/json-output.d.ts +0 -8
  179. package/dist/utils/json-output.d.ts.map +0 -1
  180. package/dist/utils/json-output.js +0 -7
  181. package/dist/utils/json-output.js.map +0 -1
  182. package/dist/utils/lock.d.ts +0 -6
  183. package/dist/utils/lock.d.ts.map +0 -1
  184. package/dist/utils/lock.js +0 -70
  185. package/dist/utils/lock.js.map +0 -1
  186. package/dist/utils/markers.d.ts +0 -22
  187. package/dist/utils/markers.d.ts.map +0 -1
  188. package/dist/utils/markers.js +0 -42
  189. package/dist/utils/markers.js.map +0 -1
  190. package/dist/utils/scoring.d.ts.map +0 -1
  191. package/dist/utils/scoring.js +0 -80
  192. package/dist/utils/scoring.js.map +0 -1
  193. package/dist/utils/version.d.ts +0 -15
  194. package/dist/utils/version.d.ts.map +0 -1
  195. package/dist/utils/version.js +0 -48
  196. package/dist/utils/version.js.map +0 -1
@@ -0,0 +1,103 @@
1
+ import chalk from "chalk";
2
+ import type { Command } from "commander";
3
+ import { getExpertisePath, readConfig } from "../utils/config.ts";
4
+ import {
5
+ readExpertiseFile,
6
+ resolveRecordId,
7
+ writeExpertiseFile,
8
+ } from "../utils/expertise.ts";
9
+ import { getRecordSummary } from "../utils/format.ts";
10
+ import { outputJson, outputJsonError } from "../utils/json-output.ts";
11
+ import { withFileLock } from "../utils/lock.ts";
12
+
13
+ export function registerDeleteCommand(program: Command): void {
14
+ program
15
+ .command("delete")
16
+ .argument("<domain>", "expertise domain")
17
+ .argument("<id>", "record ID (e.g. mx-abc123, abc123, or abc)")
18
+ .description("Delete an expertise record")
19
+ .action(async (domain: string, id: string) => {
20
+ const jsonMode = program.opts().json === true;
21
+ try {
22
+ const config = await readConfig();
23
+
24
+ if (!config.domains.includes(domain)) {
25
+ if (jsonMode) {
26
+ outputJsonError(
27
+ "delete",
28
+ `Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`,
29
+ );
30
+ } else {
31
+ console.error(
32
+ chalk.red(`Error: domain "${domain}" not found in config.`),
33
+ );
34
+ console.error(
35
+ chalk.red(
36
+ `Available domains: ${config.domains.join(", ") || "(none)"}`,
37
+ ),
38
+ );
39
+ }
40
+ process.exitCode = 1;
41
+ return;
42
+ }
43
+
44
+ const filePath = getExpertisePath(domain);
45
+ await withFileLock(filePath, async () => {
46
+ const records = await readExpertiseFile(filePath);
47
+
48
+ const resolved = resolveRecordId(records, id);
49
+ if (!resolved.ok) {
50
+ if (jsonMode) {
51
+ outputJsonError("delete", resolved.error);
52
+ } else {
53
+ console.error(chalk.red(`Error: ${resolved.error}`));
54
+ }
55
+ process.exitCode = 1;
56
+ return;
57
+ }
58
+ const targetIndex = resolved.index;
59
+
60
+ const deleted = records[targetIndex];
61
+ records.splice(targetIndex, 1);
62
+ await writeExpertiseFile(filePath, records);
63
+
64
+ if (jsonMode) {
65
+ outputJson({
66
+ success: true,
67
+ command: "delete",
68
+ domain,
69
+ id: deleted.id ?? null,
70
+ type: deleted.type,
71
+ summary: getRecordSummary(deleted),
72
+ });
73
+ } else {
74
+ console.log(
75
+ chalk.green(
76
+ `✔ Deleted ${deleted.type} ${deleted.id ?? ""} from ${domain}: ${getRecordSummary(deleted)}`,
77
+ ),
78
+ );
79
+ }
80
+ });
81
+ } catch (err) {
82
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
83
+ if (jsonMode) {
84
+ outputJsonError(
85
+ "delete",
86
+ "No .mulch/ directory found. Run `mulch init` first.",
87
+ );
88
+ } else {
89
+ console.error(
90
+ "Error: No .mulch/ directory found. Run `mulch init` first.",
91
+ );
92
+ }
93
+ } else {
94
+ if (jsonMode) {
95
+ outputJsonError("delete", (err as Error).message);
96
+ } else {
97
+ console.error(`Error: ${(err as Error).message}`);
98
+ }
99
+ }
100
+ process.exitCode = 1;
101
+ }
102
+ });
103
+ }
@@ -0,0 +1,209 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import chalk from "chalk";
3
+ import type { Command } from "commander";
4
+ import type { ExpertiseRecord } from "../schemas/record.ts";
5
+ import { getRecordSummary } from "../utils/format.ts";
6
+ import { isGitRepo } from "../utils/git.ts";
7
+ import { outputJson, outputJsonError } from "../utils/json-output.ts";
8
+
9
+ export interface DiffEntry {
10
+ domain: string;
11
+ added: ExpertiseRecord[];
12
+ removed: ExpertiseRecord[];
13
+ }
14
+
15
+ export function parseExpertiseDiff(diffOutput: string): DiffEntry[] {
16
+ const lines = diffOutput.split("\n");
17
+ const entriesMap = new Map<string, DiffEntry>();
18
+ let currentDomain: string | null = null;
19
+
20
+ for (const line of lines) {
21
+ // Extract domain from 'diff --git' headers
22
+ if (line.startsWith("diff --git")) {
23
+ const match = line.match(/\.mulch\/expertise\/([^/]+)\.jsonl/);
24
+ if (match) {
25
+ currentDomain = match[1];
26
+ if (!entriesMap.has(currentDomain)) {
27
+ entriesMap.set(currentDomain, {
28
+ domain: currentDomain,
29
+ added: [],
30
+ removed: [],
31
+ });
32
+ }
33
+ }
34
+ continue;
35
+ }
36
+
37
+ // Skip file metadata lines
38
+ if (
39
+ line.startsWith("+++") ||
40
+ line.startsWith("---") ||
41
+ line.startsWith("@@")
42
+ ) {
43
+ continue;
44
+ }
45
+
46
+ // Parse added records (lines starting with '+')
47
+ if (line.startsWith("+") && currentDomain) {
48
+ const jsonStr = line.slice(1);
49
+ try {
50
+ const record = JSON.parse(jsonStr) as ExpertiseRecord;
51
+ const entry = entriesMap.get(currentDomain);
52
+ if (entry) {
53
+ entry.added.push(record);
54
+ }
55
+ } catch {
56
+ // Skip lines that fail JSON.parse (context lines, hunk headers, etc.)
57
+ }
58
+ continue;
59
+ }
60
+
61
+ // Parse removed records (lines starting with '-')
62
+ if (line.startsWith("-") && currentDomain) {
63
+ const jsonStr = line.slice(1);
64
+ try {
65
+ const record = JSON.parse(jsonStr) as ExpertiseRecord;
66
+ const entry = entriesMap.get(currentDomain);
67
+ if (entry) {
68
+ entry.removed.push(record);
69
+ }
70
+ } catch {
71
+ // Skip lines that fail JSON.parse (context lines, hunk headers, etc.)
72
+ }
73
+ }
74
+ }
75
+
76
+ // Return entries sorted by domain, filtered to non-empty
77
+ return [...entriesMap.values()]
78
+ .filter((entry) => entry.added.length > 0 || entry.removed.length > 0)
79
+ .sort((a, b) => a.domain.localeCompare(b.domain));
80
+ }
81
+
82
+ export function formatDiffOutput(entries: DiffEntry[], since: string): string {
83
+ const lines: string[] = [];
84
+ lines.push(`Expertise changes since ${since}`);
85
+ lines.push("");
86
+
87
+ for (const entry of entries) {
88
+ const changeCount = entry.added.length + entry.removed.length;
89
+ const plural = changeCount === 1 ? "change" : "changes";
90
+ lines.push(`${entry.domain} (${changeCount} ${plural}):`);
91
+
92
+ for (const record of entry.added) {
93
+ const summary = getRecordSummary(record);
94
+ lines.push(` + [${record.type}] ${record.id} ${summary}`);
95
+ }
96
+
97
+ for (const record of entry.removed) {
98
+ const summary = getRecordSummary(record);
99
+ lines.push(` - [${record.type}] ${record.id} ${summary}`);
100
+ }
101
+
102
+ lines.push("");
103
+ }
104
+
105
+ return lines.join("\n").trimEnd();
106
+ }
107
+
108
+ export function registerDiffCommand(program: Command): void {
109
+ program
110
+ .command("diff")
111
+ .description("Show expertise record changes since a git ref")
112
+ .option("--since <ref>", "git ref to diff against", "HEAD~1")
113
+ .action(async (options: { since: string }) => {
114
+ const jsonMode = program.opts().json === true;
115
+ const cwd = process.cwd();
116
+
117
+ if (!isGitRepo(cwd)) {
118
+ if (jsonMode) {
119
+ outputJsonError(
120
+ "diff",
121
+ "Not in a git repository. Run this command from within a git repository.",
122
+ );
123
+ } else {
124
+ console.error(chalk.red("Error: not in a git repository."));
125
+ }
126
+ process.exitCode = 1;
127
+ return;
128
+ }
129
+
130
+ try {
131
+ let diffOutput: string;
132
+ try {
133
+ diffOutput = execFileSync(
134
+ "git",
135
+ ["diff", options.since, "--", ".mulch/expertise/"],
136
+ {
137
+ cwd,
138
+ encoding: "utf-8",
139
+ stdio: ["pipe", "pipe", "pipe"],
140
+ },
141
+ );
142
+ } catch {
143
+ // ref doesn't exist or no changes, treat as empty diff
144
+ diffOutput = "";
145
+ }
146
+
147
+ const entries = parseExpertiseDiff(diffOutput);
148
+
149
+ if (entries.length === 0) {
150
+ if (jsonMode) {
151
+ outputJson({
152
+ success: true,
153
+ command: "diff",
154
+ since: options.since,
155
+ domains: [],
156
+ message: "No expertise changes found.",
157
+ });
158
+ } else {
159
+ console.log("No expertise changes found.");
160
+ }
161
+ return;
162
+ }
163
+
164
+ if (jsonMode) {
165
+ outputJson({
166
+ success: true,
167
+ command: "diff",
168
+ since: options.since,
169
+ domains: entries,
170
+ });
171
+ return;
172
+ }
173
+
174
+ // Plain text output with colors
175
+ console.log(chalk.bold(`\nExpertise changes since ${options.since}\n`));
176
+
177
+ for (const entry of entries) {
178
+ const changeCount = entry.added.length + entry.removed.length;
179
+ const plural = changeCount === 1 ? "change" : "changes";
180
+ console.log(
181
+ chalk.cyan(`${entry.domain} (${changeCount} ${plural}):`),
182
+ );
183
+
184
+ for (const record of entry.added) {
185
+ const summary = getRecordSummary(record);
186
+ console.log(
187
+ chalk.green(` + [${record.type}] ${record.id} ${summary}`),
188
+ );
189
+ }
190
+
191
+ for (const record of entry.removed) {
192
+ const summary = getRecordSummary(record);
193
+ console.log(
194
+ chalk.red(` - [${record.type}] ${record.id} ${summary}`),
195
+ );
196
+ }
197
+
198
+ console.log("");
199
+ }
200
+ } catch (err) {
201
+ if (jsonMode) {
202
+ outputJsonError("diff", (err as Error).message);
203
+ } else {
204
+ console.error(chalk.red(`Error: ${(err as Error).message}`));
205
+ }
206
+ process.exitCode = 1;
207
+ }
208
+ });
209
+ }