mulch-cli 0.4.3 → 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 (193) hide show
  1. package/README.md +24 -4
  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/src/utils/scoring.ts +101 -0
  41. package/src/utils/version.ts +46 -0
  42. package/dist/cli.d.ts +0 -3
  43. package/dist/cli.d.ts.map +0 -1
  44. package/dist/cli.js +0 -50
  45. package/dist/cli.js.map +0 -1
  46. package/dist/commands/add.d.ts +0 -3
  47. package/dist/commands/add.d.ts.map +0 -1
  48. package/dist/commands/add.js +0 -47
  49. package/dist/commands/add.js.map +0 -1
  50. package/dist/commands/compact.d.ts +0 -5
  51. package/dist/commands/compact.d.ts.map +0 -1
  52. package/dist/commands/compact.js +0 -709
  53. package/dist/commands/compact.js.map +0 -1
  54. package/dist/commands/delete.d.ts +0 -3
  55. package/dist/commands/delete.d.ts.map +0 -1
  56. package/dist/commands/delete.js +0 -82
  57. package/dist/commands/delete.js.map +0 -1
  58. package/dist/commands/diff.d.ts +0 -11
  59. package/dist/commands/diff.d.ts.map +0 -1
  60. package/dist/commands/diff.js +0 -170
  61. package/dist/commands/diff.js.map +0 -1
  62. package/dist/commands/doctor.d.ts +0 -3
  63. package/dist/commands/doctor.d.ts.map +0 -1
  64. package/dist/commands/doctor.js +0 -391
  65. package/dist/commands/doctor.js.map +0 -1
  66. package/dist/commands/edit.d.ts +0 -3
  67. package/dist/commands/edit.d.ts.map +0 -1
  68. package/dist/commands/edit.js +0 -210
  69. package/dist/commands/edit.js.map +0 -1
  70. package/dist/commands/init.d.ts +0 -3
  71. package/dist/commands/init.d.ts.map +0 -1
  72. package/dist/commands/init.js +0 -30
  73. package/dist/commands/init.js.map +0 -1
  74. package/dist/commands/learn.d.ts +0 -12
  75. package/dist/commands/learn.d.ts.map +0 -1
  76. package/dist/commands/learn.js +0 -130
  77. package/dist/commands/learn.js.map +0 -1
  78. package/dist/commands/onboard.d.ts +0 -10
  79. package/dist/commands/onboard.d.ts.map +0 -1
  80. package/dist/commands/onboard.js +0 -286
  81. package/dist/commands/onboard.js.map +0 -1
  82. package/dist/commands/prime.d.ts +0 -3
  83. package/dist/commands/prime.d.ts.map +0 -1
  84. package/dist/commands/prime.js +0 -242
  85. package/dist/commands/prime.js.map +0 -1
  86. package/dist/commands/prune.d.ts +0 -8
  87. package/dist/commands/prune.d.ts.map +0 -1
  88. package/dist/commands/prune.js +0 -90
  89. package/dist/commands/prune.js.map +0 -1
  90. package/dist/commands/query.d.ts +0 -3
  91. package/dist/commands/query.d.ts.map +0 -1
  92. package/dist/commands/query.js +0 -118
  93. package/dist/commands/query.js.map +0 -1
  94. package/dist/commands/ready.d.ts +0 -3
  95. package/dist/commands/ready.d.ts.map +0 -1
  96. package/dist/commands/ready.js +0 -160
  97. package/dist/commands/ready.js.map +0 -1
  98. package/dist/commands/record.d.ts +0 -13
  99. package/dist/commands/record.d.ts.map +0 -1
  100. package/dist/commands/record.js +0 -688
  101. package/dist/commands/record.js.map +0 -1
  102. package/dist/commands/search.d.ts +0 -3
  103. package/dist/commands/search.d.ts.map +0 -1
  104. package/dist/commands/search.js +0 -163
  105. package/dist/commands/search.js.map +0 -1
  106. package/dist/commands/setup.d.ts +0 -29
  107. package/dist/commands/setup.d.ts.map +0 -1
  108. package/dist/commands/setup.js +0 -548
  109. package/dist/commands/setup.js.map +0 -1
  110. package/dist/commands/status.d.ts +0 -3
  111. package/dist/commands/status.d.ts.map +0 -1
  112. package/dist/commands/status.js +0 -61
  113. package/dist/commands/status.js.map +0 -1
  114. package/dist/commands/sync.d.ts +0 -3
  115. package/dist/commands/sync.d.ts.map +0 -1
  116. package/dist/commands/sync.js +0 -176
  117. package/dist/commands/sync.js.map +0 -1
  118. package/dist/commands/update.d.ts +0 -3
  119. package/dist/commands/update.d.ts.map +0 -1
  120. package/dist/commands/update.js +0 -72
  121. package/dist/commands/update.js.map +0 -1
  122. package/dist/commands/validate.d.ts +0 -3
  123. package/dist/commands/validate.d.ts.map +0 -1
  124. package/dist/commands/validate.js +0 -86
  125. package/dist/commands/validate.js.map +0 -1
  126. package/dist/index.d.ts +0 -7
  127. package/dist/index.d.ts.map +0 -1
  128. package/dist/index.js +0 -8
  129. package/dist/index.js.map +0 -1
  130. package/dist/schemas/config.d.ts +0 -17
  131. package/dist/schemas/config.d.ts.map +0 -1
  132. package/dist/schemas/config.js +0 -16
  133. package/dist/schemas/config.js.map +0 -1
  134. package/dist/schemas/index.d.ts +0 -5
  135. package/dist/schemas/index.d.ts.map +0 -1
  136. package/dist/schemas/index.js +0 -3
  137. package/dist/schemas/index.js.map +0 -1
  138. package/dist/schemas/record-schema.d.ts +0 -379
  139. package/dist/schemas/record-schema.d.ts.map +0 -1
  140. package/dist/schemas/record-schema.js +0 -148
  141. package/dist/schemas/record-schema.js.map +0 -1
  142. package/dist/schemas/record.d.ts +0 -60
  143. package/dist/schemas/record.d.ts.map +0 -1
  144. package/dist/schemas/record.js +0 -2
  145. package/dist/schemas/record.js.map +0 -1
  146. package/dist/utils/bm25.d.ts +0 -39
  147. package/dist/utils/bm25.d.ts.map +0 -1
  148. package/dist/utils/bm25.js +0 -171
  149. package/dist/utils/bm25.js.map +0 -1
  150. package/dist/utils/budget.d.ts +0 -35
  151. package/dist/utils/budget.d.ts.map +0 -1
  152. package/dist/utils/budget.js +0 -114
  153. package/dist/utils/budget.js.map +0 -1
  154. package/dist/utils/config.d.ts +0 -12
  155. package/dist/utils/config.d.ts.map +0 -1
  156. package/dist/utils/config.js +0 -89
  157. package/dist/utils/config.js.map +0 -1
  158. package/dist/utils/expertise.d.ts +0 -57
  159. package/dist/utils/expertise.d.ts.map +0 -1
  160. package/dist/utils/expertise.js +0 -264
  161. package/dist/utils/expertise.js.map +0 -1
  162. package/dist/utils/format.d.ts +0 -31
  163. package/dist/utils/format.d.ts.map +0 -1
  164. package/dist/utils/format.js +0 -556
  165. package/dist/utils/format.js.map +0 -1
  166. package/dist/utils/git.d.ts +0 -6
  167. package/dist/utils/git.d.ts.map +0 -1
  168. package/dist/utils/git.js +0 -81
  169. package/dist/utils/git.js.map +0 -1
  170. package/dist/utils/index.d.ts +0 -8
  171. package/dist/utils/index.d.ts.map +0 -1
  172. package/dist/utils/index.js +0 -8
  173. package/dist/utils/index.js.map +0 -1
  174. package/dist/utils/json-output.d.ts +0 -8
  175. package/dist/utils/json-output.d.ts.map +0 -1
  176. package/dist/utils/json-output.js +0 -7
  177. package/dist/utils/json-output.js.map +0 -1
  178. package/dist/utils/lock.d.ts +0 -6
  179. package/dist/utils/lock.d.ts.map +0 -1
  180. package/dist/utils/lock.js +0 -70
  181. package/dist/utils/lock.js.map +0 -1
  182. package/dist/utils/markers.d.ts +0 -22
  183. package/dist/utils/markers.d.ts.map +0 -1
  184. package/dist/utils/markers.js +0 -42
  185. package/dist/utils/markers.js.map +0 -1
  186. package/dist/utils/scoring.d.ts +0 -73
  187. package/dist/utils/scoring.d.ts.map +0 -1
  188. package/dist/utils/scoring.js +0 -80
  189. package/dist/utils/scoring.js.map +0 -1
  190. package/dist/utils/version.d.ts +0 -15
  191. package/dist/utils/version.d.ts.map +0 -1
  192. package/dist/utils/version.js +0 -48
  193. 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
@@ -71,7 +82,7 @@ Everything is git-tracked. Clone a repo and your agents immediately have the pro
71
82
  | `mulch record <domain> --type <type>` | Record an expertise record (`--tags`, `--force`, `--relates-to`, `--supersedes`, `--batch`, `--stdin`, `--dry-run`, `--evidence-bead`) |
72
83
  | `mulch edit <domain> <id>` | Edit an existing record by ID or 1-based index |
73
84
  | `mulch delete <domain> <id>` | Delete a record by ID or 1-based index |
74
- | `mulch query [domain]` | Query expertise (use `--all` for all domains, `--classification`, `--file` filters) |
85
+ | `mulch query [domain]` | Query expertise (use `--all` for all domains, `--classification`, `--file`, `--outcome-status`, `--sort-by-score` filters) |
75
86
  | `mulch prime [domains...]` | Output AI-optimized expertise context (`--budget`, `--no-limit`, `--context`, `--files`, `--exclude-domain`, `--format`, `--export`) |
76
87
  | `mulch search [query]` | Search records across domains with BM25 ranking (`--domain`, `--type`, `--tag`, `--classification`, `--file`, `--sort-by-score` filters) |
77
88
  | `mulch compact [domain]` | Analyze compaction candidates or apply a compaction (`--analyze`, `--auto`, `--apply`, `--dry-run`, `--min-group`, `--max-records`) |
@@ -197,9 +208,18 @@ The `--apply`, default (non-dry-run), and `--fix` variants acquire locks and are
197
208
 
198
209
  ## Programmatic API
199
210
 
200
- Mulch exports core utilities for use as a library:
211
+ Mulch exports both low-level utilities and a high-level programmatic API:
201
212
 
202
213
  ```typescript
214
+ // High-level API — recommended for most use cases
215
+ import {
216
+ recordExpertise, // Record a new expertise entry (with dedup and locking)
217
+ searchExpertise, // Search records across domains
218
+ queryDomain, // Query all records for a domain
219
+ editRecord, // Edit an existing record by ID
220
+ } from "mulch-cli";
221
+
222
+ // Low-level utilities
203
223
  import {
204
224
  readConfig,
205
225
  getExpertisePath,
@@ -213,7 +233,7 @@ import {
213
233
  } from "mulch-cli";
214
234
  ```
215
235
 
216
- Types (`ExpertiseRecord`, `MulchConfig`, `RecordType`, `Classification`, `ScoredRecord`, `Outcome`, etc.) are also exported.
236
+ Types (`ExpertiseRecord`, `MulchConfig`, `RecordType`, `Classification`, `ScoredRecord`, `Outcome`, `RecordOptions`, `RecordResult`, `SearchOptions`, `SearchResult`, `QueryOptions`, `EditOptions`, `RecordUpdates`, etc.) are also exported.
217
237
 
218
238
  ## Contributing
219
239
 
package/package.json CHANGED
@@ -1,25 +1,20 @@
1
1
  {
2
2
  "name": "mulch-cli",
3
- "version": "0.4.3",
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
+ }