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,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
+ }