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
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/mulch-cli)](https://www.npmjs.com/package/mulch-cli)
4
4
  [![CI](https://img.shields.io/github/actions/workflow/status/jayminwest/mulch/ci.yml?branch=main)](https://github.com/jayminwest/mulch/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
- [![node](https://img.shields.io/node/v/mulch-cli)](https://nodejs.org)
6
+ [![bun](https://img.shields.io/badge/runtime-bun-f472b6)](https://bun.sh)
7
7
 
8
8
  Structured expertise files that accumulate over time, live in git, work with any agent, and run locally with zero dependencies.
9
9
 
@@ -23,6 +23,17 @@ Or use directly with npx — no install required:
23
23
  npx mulch-cli <command>
24
24
  ```
25
25
 
26
+ ### Development
27
+
28
+ Mulch uses [Bun](https://bun.sh) as its development runtime and test runner:
29
+
30
+ ```bash
31
+ bun install # Install dependencies
32
+ bun test # Run all tests
33
+ bun run lint # Lint with Biome
34
+ bun run typecheck # Type-check with tsc
35
+ ```
36
+
26
37
  ## Quick Start
27
38
 
28
39
  ```bash
package/package.json CHANGED
@@ -1,25 +1,20 @@
1
1
  {
2
2
  "name": "mulch-cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Let your agents grow 🌱 — structured expertise files that accumulate over time, live in git, work with any agent",
5
5
  "type": "module",
6
6
  "bin": {
7
- "mulch": "./dist/cli.js"
7
+ "mulch": "src/cli.ts"
8
8
  },
9
- "main": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
11
- "files": [
12
- "dist"
13
- ],
9
+ "main": "src/index.ts",
10
+ "files": ["src"],
14
11
  "scripts": {
15
- "build": "tsc",
16
- "dev": "tsc --watch",
17
- "test": "vitest run",
18
- "test:watch": "vitest",
19
- "lint": "tsc --noEmit"
12
+ "test": "bun test",
13
+ "lint": "bunx biome check .",
14
+ "typecheck": "tsc --noEmit"
20
15
  },
21
16
  "engines": {
22
- "node": ">=18"
17
+ "bun": ">=1.0.0"
23
18
  },
24
19
  "keywords": [
25
20
  "ai",
@@ -42,9 +37,9 @@
42
37
  "js-yaml": "^4.1.1"
43
38
  },
44
39
  "devDependencies": {
40
+ "@biomejs/biome": "^1.9.0",
41
+ "@types/bun": "latest",
45
42
  "@types/js-yaml": "^4.0.9",
46
- "@types/node": "^25.2.2",
47
- "typescript": "^5.9.3",
48
- "vitest": "^4.0.18"
43
+ "typescript": "^5.9.3"
49
44
  }
50
45
  }
package/src/api.ts ADDED
@@ -0,0 +1,310 @@
1
+ import type {
2
+ Classification,
3
+ ExpertiseRecord,
4
+ Outcome,
5
+ } from "./schemas/record.ts";
6
+ import { getExpertisePath, readConfig } from "./utils/config.ts";
7
+ import {
8
+ appendRecord,
9
+ filterByClassification,
10
+ filterByFile,
11
+ filterByType,
12
+ findDuplicate,
13
+ readExpertiseFile,
14
+ resolveRecordId,
15
+ searchRecords,
16
+ writeExpertiseFile,
17
+ } from "./utils/expertise.ts";
18
+ import { withFileLock } from "./utils/lock.ts";
19
+
20
+ export interface RecordOptions {
21
+ force?: boolean;
22
+ cwd?: string;
23
+ }
24
+
25
+ export interface RecordResult {
26
+ action: "created" | "updated" | "skipped";
27
+ record: ExpertiseRecord;
28
+ }
29
+
30
+ export interface SearchOptions {
31
+ domain?: string;
32
+ type?: string;
33
+ tag?: string;
34
+ classification?: string;
35
+ file?: string;
36
+ cwd?: string;
37
+ }
38
+
39
+ export interface SearchResult {
40
+ domain: string;
41
+ records: ExpertiseRecord[];
42
+ }
43
+
44
+ export interface QueryOptions {
45
+ type?: string;
46
+ classification?: string;
47
+ file?: string;
48
+ cwd?: string;
49
+ }
50
+
51
+ export interface EditOptions {
52
+ cwd?: string;
53
+ }
54
+
55
+ export interface RecordUpdates {
56
+ classification?: Classification;
57
+ tags?: string[];
58
+ relates_to?: string[];
59
+ supersedes?: string[];
60
+ outcomes?: Outcome[];
61
+ // type-specific fields
62
+ content?: string;
63
+ name?: string;
64
+ description?: string;
65
+ resolution?: string;
66
+ title?: string;
67
+ rationale?: string;
68
+ files?: string[];
69
+ }
70
+
71
+ /**
72
+ * Record an expertise record in the given domain.
73
+ * Named record types (pattern, decision, reference, guide) are upserted on
74
+ * duplicate key; convention and failure duplicates are skipped unless force=true.
75
+ */
76
+ export async function recordExpertise(
77
+ domain: string,
78
+ record: ExpertiseRecord,
79
+ options: RecordOptions = {},
80
+ ): Promise<RecordResult> {
81
+ const { force = false, cwd } = options;
82
+
83
+ const config = await readConfig(cwd);
84
+ if (!config.domains.includes(domain)) {
85
+ throw new Error(
86
+ `Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`,
87
+ );
88
+ }
89
+
90
+ const filePath = getExpertisePath(domain, cwd);
91
+
92
+ return withFileLock(filePath, async () => {
93
+ const existing = await readExpertiseFile(filePath);
94
+ const dup = findDuplicate(existing, record);
95
+
96
+ if (dup && !force) {
97
+ const isNamed =
98
+ record.type === "pattern" ||
99
+ record.type === "decision" ||
100
+ record.type === "reference" ||
101
+ record.type === "guide";
102
+
103
+ if (isNamed) {
104
+ existing[dup.index] = record;
105
+ await writeExpertiseFile(filePath, existing);
106
+ return { action: "updated" as const, record };
107
+ }
108
+ return { action: "skipped" as const, record: dup.record };
109
+ }
110
+ await appendRecord(filePath, record);
111
+ return { action: "created" as const, record };
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Search expertise records across domains using BM25 ranking.
117
+ * Returns domains with at least one matching record.
118
+ */
119
+ export async function searchExpertise(
120
+ query: string,
121
+ options: SearchOptions = {},
122
+ ): Promise<SearchResult[]> {
123
+ const { cwd } = options;
124
+ const config = await readConfig(cwd);
125
+
126
+ let domainsToSearch: string[];
127
+ if (options.domain) {
128
+ if (!config.domains.includes(options.domain)) {
129
+ throw new Error(
130
+ `Domain "${options.domain}" not found in config. Available domains: ${config.domains.join(", ")}`,
131
+ );
132
+ }
133
+ domainsToSearch = [options.domain];
134
+ } else {
135
+ domainsToSearch = config.domains;
136
+ }
137
+
138
+ const results: SearchResult[] = [];
139
+
140
+ for (const d of domainsToSearch) {
141
+ const filePath = getExpertisePath(d, cwd);
142
+ let records = await readExpertiseFile(filePath);
143
+
144
+ if (options.type) {
145
+ records = filterByType(records, options.type);
146
+ }
147
+ if (options.tag) {
148
+ const tagLower = options.tag.toLowerCase();
149
+ records = records.filter((r) =>
150
+ r.tags?.some((t) => t.toLowerCase() === tagLower),
151
+ );
152
+ }
153
+ if (options.classification) {
154
+ records = filterByClassification(records, options.classification);
155
+ }
156
+ if (options.file) {
157
+ records = filterByFile(records, options.file);
158
+ }
159
+
160
+ const matches = searchRecords(records, query);
161
+ if (matches.length > 0) {
162
+ results.push({ domain: d, records: matches });
163
+ }
164
+ }
165
+
166
+ return results;
167
+ }
168
+
169
+ /**
170
+ * Query all records in a domain with optional filtering.
171
+ */
172
+ export async function queryDomain(
173
+ domain: string,
174
+ options: QueryOptions = {},
175
+ ): Promise<ExpertiseRecord[]> {
176
+ const { cwd } = options;
177
+ const config = await readConfig(cwd);
178
+
179
+ if (!config.domains.includes(domain)) {
180
+ throw new Error(
181
+ `Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`,
182
+ );
183
+ }
184
+
185
+ const filePath = getExpertisePath(domain, cwd);
186
+ let records = await readExpertiseFile(filePath);
187
+
188
+ if (options.type) {
189
+ records = filterByType(records, options.type);
190
+ }
191
+ if (options.classification) {
192
+ records = filterByClassification(records, options.classification);
193
+ }
194
+ if (options.file) {
195
+ records = filterByFile(records, options.file);
196
+ }
197
+
198
+ return records;
199
+ }
200
+
201
+ /**
202
+ * Edit an existing record by ID in the given domain.
203
+ * Only provided fields in updates are modified; all other fields are preserved.
204
+ */
205
+ export async function editRecord(
206
+ domain: string,
207
+ id: string,
208
+ updates: RecordUpdates,
209
+ options: EditOptions = {},
210
+ ): Promise<ExpertiseRecord> {
211
+ const { cwd } = options;
212
+ const config = await readConfig(cwd);
213
+
214
+ if (!config.domains.includes(domain)) {
215
+ throw new Error(
216
+ `Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`,
217
+ );
218
+ }
219
+
220
+ const filePath = getExpertisePath(domain, cwd);
221
+
222
+ return withFileLock(filePath, async () => {
223
+ const records = await readExpertiseFile(filePath);
224
+ const resolved = resolveRecordId(records, id);
225
+
226
+ if (!resolved.ok) {
227
+ throw new Error(resolved.error);
228
+ }
229
+
230
+ const targetIndex = resolved.index;
231
+ const record = { ...records[targetIndex] };
232
+
233
+ // Apply common updates
234
+ if (updates.classification !== undefined) {
235
+ record.classification = updates.classification;
236
+ }
237
+ if (updates.tags !== undefined) {
238
+ record.tags = updates.tags;
239
+ }
240
+ if (updates.relates_to !== undefined) {
241
+ record.relates_to = updates.relates_to;
242
+ }
243
+ if (updates.supersedes !== undefined) {
244
+ record.supersedes = updates.supersedes;
245
+ }
246
+ if (updates.outcomes !== undefined) {
247
+ record.outcomes = updates.outcomes;
248
+ }
249
+
250
+ // Apply type-specific updates
251
+ switch (record.type) {
252
+ case "convention":
253
+ if (updates.content !== undefined) {
254
+ record.content = updates.content;
255
+ }
256
+ break;
257
+ case "pattern":
258
+ if (updates.name !== undefined) {
259
+ record.name = updates.name;
260
+ }
261
+ if (updates.description !== undefined) {
262
+ record.description = updates.description;
263
+ }
264
+ if (updates.files !== undefined) {
265
+ record.files = updates.files;
266
+ }
267
+ break;
268
+ case "failure":
269
+ if (updates.description !== undefined) {
270
+ record.description = updates.description;
271
+ }
272
+ if (updates.resolution !== undefined) {
273
+ record.resolution = updates.resolution;
274
+ }
275
+ break;
276
+ case "decision":
277
+ if (updates.title !== undefined) {
278
+ record.title = updates.title;
279
+ }
280
+ if (updates.rationale !== undefined) {
281
+ record.rationale = updates.rationale;
282
+ }
283
+ break;
284
+ case "reference":
285
+ if (updates.name !== undefined) {
286
+ record.name = updates.name;
287
+ }
288
+ if (updates.description !== undefined) {
289
+ record.description = updates.description;
290
+ }
291
+ if (updates.files !== undefined) {
292
+ record.files = updates.files;
293
+ }
294
+ break;
295
+ case "guide":
296
+ if (updates.name !== undefined) {
297
+ record.name = updates.name;
298
+ }
299
+ if (updates.description !== undefined) {
300
+ record.description = updates.description;
301
+ }
302
+ break;
303
+ }
304
+
305
+ records[targetIndex] = record;
306
+ await writeExpertiseFile(filePath, records);
307
+
308
+ return record;
309
+ });
310
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import { registerAddCommand } from "./commands/add.ts";
5
+ import { registerCompactCommand } from "./commands/compact.ts";
6
+ import { registerDeleteCommand } from "./commands/delete.ts";
7
+ import { registerDiffCommand } from "./commands/diff.ts";
8
+ import { registerDoctorCommand } from "./commands/doctor.ts";
9
+ import { registerEditCommand } from "./commands/edit.ts";
10
+ import { registerInitCommand } from "./commands/init.ts";
11
+ import { registerLearnCommand } from "./commands/learn.ts";
12
+ import { registerOnboardCommand } from "./commands/onboard.ts";
13
+ import { registerPrimeCommand } from "./commands/prime.ts";
14
+ import { registerPruneCommand } from "./commands/prune.ts";
15
+ import { registerQueryCommand } from "./commands/query.ts";
16
+ import { registerReadyCommand } from "./commands/ready.ts";
17
+ import { registerRecordCommand } from "./commands/record.ts";
18
+ import { registerSearchCommand } from "./commands/search.ts";
19
+ import { registerSetupCommand } from "./commands/setup.ts";
20
+ import { registerStatusCommand } from "./commands/status.ts";
21
+ import { registerSyncCommand } from "./commands/sync.ts";
22
+ import { registerUpdateCommand } from "./commands/update.ts";
23
+ import { registerValidateCommand } from "./commands/validate.ts";
24
+
25
+ const program = new Command();
26
+
27
+ program
28
+ .name("mulch")
29
+ .description("Let your agents grow 🌱")
30
+ .version("0.6.0")
31
+ .option("--json", "output as structured JSON");
32
+
33
+ registerInitCommand(program);
34
+ registerAddCommand(program);
35
+ registerRecordCommand(program);
36
+ registerEditCommand(program);
37
+ registerQueryCommand(program);
38
+ registerSetupCommand(program);
39
+ registerPrimeCommand(program);
40
+ registerOnboardCommand(program);
41
+ registerStatusCommand(program);
42
+ registerValidateCommand(program);
43
+ registerPruneCommand(program);
44
+ registerSearchCommand(program);
45
+ registerDoctorCommand(program);
46
+ registerReadyCommand(program);
47
+ registerSyncCommand(program);
48
+ registerDeleteCommand(program);
49
+ registerLearnCommand(program);
50
+ registerCompactCommand(program);
51
+ registerDiffCommand(program);
52
+ registerUpdateCommand(program);
53
+
54
+ program.parse();
@@ -0,0 +1,61 @@
1
+ import { existsSync } from "node:fs";
2
+ import chalk from "chalk";
3
+ import type { Command } from "commander";
4
+ import {
5
+ getExpertisePath,
6
+ getMulchDir,
7
+ readConfig,
8
+ writeConfig,
9
+ } from "../utils/config.ts";
10
+ import { createExpertiseFile } from "../utils/expertise.ts";
11
+ import { outputJson, outputJsonError } from "../utils/json-output.ts";
12
+
13
+ export function registerAddCommand(program: Command): void {
14
+ program
15
+ .command("add")
16
+ .argument("<domain>", "expertise domain to add")
17
+ .description("Add a new expertise domain")
18
+ .action(async (domain: string) => {
19
+ const jsonMode = program.opts().json === true;
20
+ const mulchDir = getMulchDir();
21
+
22
+ if (!existsSync(mulchDir)) {
23
+ if (jsonMode) {
24
+ outputJsonError(
25
+ "add",
26
+ "No .mulch/ directory found. Run `mulch init` first.",
27
+ );
28
+ } else {
29
+ console.error(
30
+ chalk.red("No .mulch/ directory found. Run `mulch init` first."),
31
+ );
32
+ }
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+
37
+ const config = await readConfig();
38
+
39
+ if (config.domains.includes(domain)) {
40
+ if (jsonMode) {
41
+ outputJsonError("add", `Domain "${domain}" already exists.`);
42
+ } else {
43
+ console.error(chalk.red(`Domain "${domain}" already exists.`));
44
+ }
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+
49
+ const expertisePath = getExpertisePath(domain);
50
+ await createExpertiseFile(expertisePath);
51
+
52
+ config.domains.push(domain);
53
+ await writeConfig(config);
54
+
55
+ if (jsonMode) {
56
+ outputJson({ success: true, command: "add", domain });
57
+ } else {
58
+ console.log(chalk.green(`Added domain "${domain}".`));
59
+ }
60
+ });
61
+ }