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
@@ -0,0 +1,234 @@
1
+ import { type Command, Option } from "commander";
2
+ import { getExpertisePath, readConfig } from "../utils/config.ts";
3
+ import {
4
+ filterByClassification,
5
+ filterByFile,
6
+ filterByType,
7
+ getFileModTime,
8
+ readExpertiseFile,
9
+ } from "../utils/expertise.ts";
10
+ import { searchRecords } from "../utils/expertise.ts";
11
+ import { formatDomainExpertise } from "../utils/format.ts";
12
+ import { outputJson, outputJsonError } from "../utils/json-output.ts";
13
+ import {
14
+ type ScoredRecord,
15
+ sortByConfirmationScore,
16
+ } from "../utils/scoring.ts";
17
+
18
+ export function registerSearchCommand(program: Command): void {
19
+ program
20
+ .command("search")
21
+ .argument("[query]", "search string (case-insensitive substring match)")
22
+ .description("Search expertise records across domains")
23
+ .option("--domain <domain>", "limit search to a specific domain")
24
+ .addOption(
25
+ new Option("--type <type>", "filter by record type").choices([
26
+ "convention",
27
+ "pattern",
28
+ "failure",
29
+ "decision",
30
+ "reference",
31
+ "guide",
32
+ ]),
33
+ )
34
+ .option("--tag <tag>", "filter by tag")
35
+ .addOption(
36
+ new Option(
37
+ "--classification <classification>",
38
+ "filter by classification",
39
+ ).choices(["foundational", "tactical", "observational"]),
40
+ )
41
+ .option("--file <file>", "filter by associated file path (substring match)")
42
+ .addOption(
43
+ new Option(
44
+ "--outcome-status <status>",
45
+ "filter by outcome status",
46
+ ).choices(["success", "failure", "partial"]),
47
+ )
48
+ .option(
49
+ "--sort-by-score",
50
+ "sort results by confirmation-frequency score (highest first)",
51
+ )
52
+ .action(
53
+ async (
54
+ query: string | undefined,
55
+ options: {
56
+ domain?: string;
57
+ type?: string;
58
+ tag?: string;
59
+ classification?: string;
60
+ file?: string;
61
+ outcomeStatus?: string;
62
+ sortByScore?: boolean;
63
+ },
64
+ ) => {
65
+ const jsonMode = program.opts().json === true;
66
+ try {
67
+ if (
68
+ !query &&
69
+ !options.type &&
70
+ !options.domain &&
71
+ !options.tag &&
72
+ !options.classification &&
73
+ !options.file &&
74
+ !options.outcomeStatus
75
+ ) {
76
+ if (jsonMode) {
77
+ outputJsonError(
78
+ "search",
79
+ "Provide a search query or use --type, --domain, --tag, --classification, --file, or --outcome-status to filter.",
80
+ );
81
+ } else {
82
+ console.error(
83
+ "Error: Provide a search query or use --type, --domain, --tag, --classification, --file, or --outcome-status to filter.",
84
+ );
85
+ }
86
+ process.exitCode = 1;
87
+ return;
88
+ }
89
+
90
+ const config = await readConfig();
91
+
92
+ let domainsToSearch: string[];
93
+
94
+ if (options.domain) {
95
+ if (!config.domains.includes(options.domain)) {
96
+ if (jsonMode) {
97
+ outputJsonError(
98
+ "search",
99
+ `Domain "${options.domain}" not found in config. Available domains: ${config.domains.join(", ")}`,
100
+ );
101
+ } else {
102
+ console.error(
103
+ `Error: Domain "${options.domain}" not found in config. Available domains: ${config.domains.join(", ")}`,
104
+ );
105
+ }
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+ domainsToSearch = [options.domain];
110
+ } else {
111
+ domainsToSearch = config.domains;
112
+ }
113
+
114
+ let totalMatches = 0;
115
+
116
+ if (jsonMode) {
117
+ const result: Array<{ domain: string; matches: unknown[] }> = [];
118
+ for (const domain of domainsToSearch) {
119
+ const filePath = getExpertisePath(domain);
120
+ let records = await readExpertiseFile(filePath);
121
+ if (options.type) {
122
+ records = filterByType(records, options.type);
123
+ }
124
+ if (options.tag) {
125
+ const tagLower = options.tag.toLowerCase();
126
+ records = records.filter((r) =>
127
+ r.tags?.some((t) => t.toLowerCase() === tagLower),
128
+ );
129
+ }
130
+ if (options.classification) {
131
+ records = filterByClassification(
132
+ records,
133
+ options.classification,
134
+ );
135
+ }
136
+ if (options.file) {
137
+ records = filterByFile(records, options.file);
138
+ }
139
+ if (options.outcomeStatus) {
140
+ records = records.filter((r) =>
141
+ r.outcomes?.some((o) => o.status === options.outcomeStatus),
142
+ );
143
+ }
144
+ let matches = query ? searchRecords(records, query) : records;
145
+ if (options.sortByScore) {
146
+ matches = sortByConfirmationScore(matches as ScoredRecord[]);
147
+ }
148
+ if (matches.length > 0) {
149
+ totalMatches += matches.length;
150
+ result.push({ domain, matches });
151
+ }
152
+ }
153
+ outputJson({
154
+ success: true,
155
+ command: "search",
156
+ query: query ?? null,
157
+ total: totalMatches,
158
+ domains: result,
159
+ });
160
+ } else {
161
+ const sections: string[] = [];
162
+ for (const domain of domainsToSearch) {
163
+ const filePath = getExpertisePath(domain);
164
+ let records = await readExpertiseFile(filePath);
165
+ const lastUpdated = await getFileModTime(filePath);
166
+ if (options.type) {
167
+ records = filterByType(records, options.type);
168
+ }
169
+ if (options.tag) {
170
+ const tagLower = options.tag.toLowerCase();
171
+ records = records.filter((r) =>
172
+ r.tags?.some((t) => t.toLowerCase() === tagLower),
173
+ );
174
+ }
175
+ if (options.classification) {
176
+ records = filterByClassification(
177
+ records,
178
+ options.classification,
179
+ );
180
+ }
181
+ if (options.file) {
182
+ records = filterByFile(records, options.file);
183
+ }
184
+ if (options.outcomeStatus) {
185
+ records = records.filter((r) =>
186
+ r.outcomes?.some((o) => o.status === options.outcomeStatus),
187
+ );
188
+ }
189
+ let matches = query ? searchRecords(records, query) : records;
190
+ if (options.sortByScore) {
191
+ matches = sortByConfirmationScore(matches as ScoredRecord[]);
192
+ }
193
+ if (matches.length > 0) {
194
+ totalMatches += matches.length;
195
+ sections.push(
196
+ formatDomainExpertise(domain, matches, lastUpdated),
197
+ );
198
+ }
199
+ }
200
+
201
+ const label = query ? `matching "${query}"` : "matching filters";
202
+ if (sections.length === 0) {
203
+ console.log(`No records ${label} found.`);
204
+ } else {
205
+ console.log(sections.join("\n\n"));
206
+ console.log(
207
+ `\n${totalMatches} match${totalMatches === 1 ? "" : "es"} found.`,
208
+ );
209
+ }
210
+ }
211
+ } catch (err) {
212
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
213
+ if (jsonMode) {
214
+ outputJsonError(
215
+ "search",
216
+ "No .mulch/ directory found. Run `mulch init` first.",
217
+ );
218
+ } else {
219
+ console.error(
220
+ "Error: No .mulch/ directory found. Run `mulch init` first.",
221
+ );
222
+ }
223
+ } else {
224
+ if (jsonMode) {
225
+ outputJsonError("search", (err as Error).message);
226
+ } else {
227
+ console.error(`Error: ${(err as Error).message}`);
228
+ }
229
+ }
230
+ process.exitCode = 1;
231
+ }
232
+ },
233
+ );
234
+ }