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,83 @@
1
+ import { existsSync } from "node:fs";
2
+ import chalk from "chalk";
3
+ import type { Command } from "commander";
4
+ import { getExpertisePath, getMulchDir, readConfig } from "../utils/config.ts";
5
+ import {
6
+ calculateDomainHealth,
7
+ countRecords,
8
+ getFileModTime,
9
+ readExpertiseFile,
10
+ } from "../utils/expertise.ts";
11
+ import { formatStatusOutput } from "../utils/format.ts";
12
+ import { outputJson, outputJsonError } from "../utils/json-output.ts";
13
+
14
+ export function registerStatusCommand(program: Command): void {
15
+ program
16
+ .command("status")
17
+ .description("Show status of expertise records")
18
+ .action(async () => {
19
+ const jsonMode = program.opts().json === true;
20
+ const mulchDir = getMulchDir();
21
+
22
+ if (!existsSync(mulchDir)) {
23
+ if (jsonMode) {
24
+ outputJsonError(
25
+ "status",
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
+ const domainStats = await Promise.all(
40
+ config.domains.map(async (domain) => {
41
+ const filePath = getExpertisePath(domain);
42
+ const records = await readExpertiseFile(filePath);
43
+ const lastUpdated = await getFileModTime(filePath);
44
+ const health = calculateDomainHealth(
45
+ records,
46
+ config.governance.max_entries,
47
+ config.classification_defaults.shelf_life,
48
+ );
49
+ return {
50
+ domain,
51
+ count: countRecords(records),
52
+ lastUpdated,
53
+ health,
54
+ records,
55
+ };
56
+ }),
57
+ );
58
+
59
+ if (jsonMode) {
60
+ outputJson({
61
+ success: true,
62
+ command: "status",
63
+ domains: domainStats.map((s) => ({
64
+ domain: s.domain,
65
+ count: s.count,
66
+ lastUpdated: s.lastUpdated?.toISOString() ?? null,
67
+ health: s.health,
68
+ })),
69
+ governance: config.governance,
70
+ });
71
+ } else {
72
+ const output = formatStatusOutput(
73
+ domainStats.map((s) => ({
74
+ domain: s.domain,
75
+ count: s.count,
76
+ lastUpdated: s.lastUpdated,
77
+ })),
78
+ config.governance,
79
+ );
80
+ console.log(output);
81
+ }
82
+ });
83
+ }
@@ -0,0 +1,224 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { readFile } from "node:fs/promises";
3
+ import Ajv from "ajv";
4
+ import chalk from "chalk";
5
+ import type { Command } from "commander";
6
+ import { recordSchema } from "../schemas/record-schema.ts";
7
+ import { getExpertisePath, readConfig } from "../utils/config.ts";
8
+ import { outputJson, outputJsonError } from "../utils/json-output.ts";
9
+
10
+ function isGitRepo(cwd: string): boolean {
11
+ try {
12
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
13
+ cwd,
14
+ stdio: "pipe",
15
+ });
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ function gitHasChanges(cwd: string): boolean {
23
+ try {
24
+ const status = execFileSync("git", ["status", "--porcelain", ".mulch/"], {
25
+ cwd,
26
+ encoding: "utf-8",
27
+ stdio: ["pipe", "pipe", "pipe"],
28
+ });
29
+ return status.trim().length > 0;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function gitAdd(cwd: string): void {
36
+ execFileSync("git", ["add", ".mulch/"], { cwd, stdio: "pipe" });
37
+ }
38
+
39
+ function gitCommit(cwd: string, message: string): string {
40
+ return execFileSync("git", ["commit", "-m", message], {
41
+ cwd,
42
+ encoding: "utf-8",
43
+ stdio: ["pipe", "pipe", "pipe"],
44
+ });
45
+ }
46
+
47
+ interface ValidateResult {
48
+ valid: boolean;
49
+ totalRecords: number;
50
+ errors: Array<{ domain: string; line: number; message: string }>;
51
+ }
52
+
53
+ async function validateExpertise(cwd?: string): Promise<ValidateResult> {
54
+ const config = await readConfig(cwd);
55
+ const domains = config.domains;
56
+
57
+ const ajv = new Ajv();
58
+ const validate = ajv.compile(recordSchema);
59
+
60
+ let totalRecords = 0;
61
+ const errors: Array<{ domain: string; line: number; message: string }> = [];
62
+
63
+ for (const domain of domains) {
64
+ const filePath = getExpertisePath(domain, cwd);
65
+ let content: string;
66
+ try {
67
+ content = await readFile(filePath, "utf-8");
68
+ } catch {
69
+ continue;
70
+ }
71
+
72
+ const lines = content.split("\n");
73
+ for (let i = 0; i < lines.length; i++) {
74
+ const line = lines[i].trim();
75
+ if (line.length === 0) continue;
76
+ totalRecords++;
77
+ const lineNumber = i + 1;
78
+
79
+ let parsed: unknown;
80
+ try {
81
+ parsed = JSON.parse(line);
82
+ } catch {
83
+ errors.push({
84
+ domain,
85
+ line: lineNumber,
86
+ message: "Invalid JSON: failed to parse",
87
+ });
88
+ continue;
89
+ }
90
+ if (!validate(parsed)) {
91
+ const schemaErrors = (validate.errors ?? [])
92
+ .map((err) => `${err.instancePath} ${err.message}`)
93
+ .join("; ");
94
+ errors.push({
95
+ domain,
96
+ line: lineNumber,
97
+ message: `Schema validation failed: ${schemaErrors}`,
98
+ });
99
+ }
100
+ }
101
+ }
102
+
103
+ return { valid: errors.length === 0, totalRecords, errors };
104
+ }
105
+
106
+ export function registerSyncCommand(program: Command): void {
107
+ program
108
+ .command("sync")
109
+ .description("Validate, stage, and commit .mulch/ changes")
110
+ .option("--message <message>", "custom commit message")
111
+ .option("--no-validate", "skip validation step")
112
+ .action(async (options: { message?: string; validate?: boolean }) => {
113
+ const jsonMode = program.opts().json === true;
114
+ const cwd = process.cwd();
115
+
116
+ // Check if we're in a git repo
117
+ if (!isGitRepo(cwd)) {
118
+ if (jsonMode) {
119
+ outputJsonError(
120
+ "sync",
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
+ // Validate (unless --no-validate)
131
+ if (options.validate !== false) {
132
+ try {
133
+ const result = await validateExpertise();
134
+ if (!result.valid) {
135
+ if (jsonMode) {
136
+ outputJson({
137
+ success: false,
138
+ command: "sync",
139
+ validated: false,
140
+ committed: false,
141
+ errors: result.errors,
142
+ });
143
+ } else {
144
+ console.error(chalk.red("Validation failed:"));
145
+ for (const err of result.errors) {
146
+ console.error(
147
+ chalk.red(` ${err.domain}:${err.line} - ${err.message}`),
148
+ );
149
+ }
150
+ console.error(
151
+ chalk.red(
152
+ "\nFix errors and retry, or use --no-validate to skip.",
153
+ ),
154
+ );
155
+ }
156
+ process.exitCode = 1;
157
+ return;
158
+ }
159
+ if (!jsonMode) {
160
+ console.log(
161
+ chalk.green(`✔ Validated ${result.totalRecords} records`),
162
+ );
163
+ }
164
+ } catch (err) {
165
+ if (jsonMode) {
166
+ outputJsonError(
167
+ "sync",
168
+ `Validation error: ${(err as Error).message}`,
169
+ );
170
+ } else {
171
+ console.error(
172
+ chalk.red(`Validation error: ${(err as Error).message}`),
173
+ );
174
+ }
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ }
179
+
180
+ // Check for changes
181
+ if (!gitHasChanges(cwd)) {
182
+ if (jsonMode) {
183
+ outputJson({
184
+ success: true,
185
+ command: "sync",
186
+ validated: options.validate !== false,
187
+ committed: false,
188
+ message: "No changes to commit",
189
+ });
190
+ } else {
191
+ console.log("No .mulch/ changes to commit.");
192
+ }
193
+ return;
194
+ }
195
+
196
+ // Git add + commit
197
+ const commitMessage = options.message ?? "mulch: update expertise";
198
+ try {
199
+ gitAdd(cwd);
200
+ gitCommit(cwd, commitMessage);
201
+
202
+ if (jsonMode) {
203
+ outputJson({
204
+ success: true,
205
+ command: "sync",
206
+ validated: options.validate !== false,
207
+ committed: true,
208
+ message: commitMessage,
209
+ });
210
+ } else {
211
+ console.log(
212
+ chalk.green(`✔ Committed .mulch/ changes: "${commitMessage}"`),
213
+ );
214
+ }
215
+ } catch (err) {
216
+ if (jsonMode) {
217
+ outputJsonError("sync", `Git error: ${(err as Error).message}`);
218
+ } else {
219
+ console.error(chalk.red(`Git error: ${(err as Error).message}`));
220
+ }
221
+ process.exitCode = 1;
222
+ }
223
+ });
224
+ }
@@ -0,0 +1,112 @@
1
+ import { execSync } from "node:child_process";
2
+ import chalk from "chalk";
3
+ import type { Command } from "commander";
4
+ import { outputJson, outputJsonError } from "../utils/json-output.ts";
5
+ import {
6
+ compareSemver,
7
+ getCurrentVersion,
8
+ getLatestVersion,
9
+ } from "../utils/version.ts";
10
+
11
+ export function registerUpdateCommand(program: Command): void {
12
+ program
13
+ .command("update")
14
+ .description("Check for and install mulch-cli updates")
15
+ .option("--check", "only check for updates, do not install")
16
+ .action(async (options: { check?: boolean }) => {
17
+ const jsonMode = program.opts().json === true;
18
+ const current = getCurrentVersion();
19
+ const latest = getLatestVersion();
20
+
21
+ if (latest === null) {
22
+ if (jsonMode) {
23
+ outputJsonError(
24
+ "update",
25
+ "Unable to reach npm registry. Check your internet connection.",
26
+ );
27
+ } else {
28
+ console.error(
29
+ chalk.red(
30
+ "Unable to reach npm registry. Check your internet connection.",
31
+ ),
32
+ );
33
+ }
34
+ process.exitCode = 1;
35
+ return;
36
+ }
37
+
38
+ const cmp = compareSemver(current, latest);
39
+
40
+ if (cmp >= 0) {
41
+ if (jsonMode) {
42
+ outputJson({
43
+ success: true,
44
+ command: "update",
45
+ current,
46
+ latest,
47
+ upToDate: true,
48
+ updated: false,
49
+ });
50
+ } else {
51
+ console.log(chalk.green(`mulch-cli ${current} is up to date`));
52
+ }
53
+ return;
54
+ }
55
+
56
+ if (options.check) {
57
+ if (jsonMode) {
58
+ outputJson({
59
+ success: true,
60
+ command: "update",
61
+ current,
62
+ latest,
63
+ upToDate: false,
64
+ updated: false,
65
+ });
66
+ } else {
67
+ console.log(
68
+ `Update available: ${chalk.yellow(current)} → ${chalk.green(latest)}`,
69
+ );
70
+ console.log(`Run ${chalk.cyan("mulch update")} to install.`);
71
+ }
72
+ return;
73
+ }
74
+
75
+ if (!jsonMode) {
76
+ console.log(
77
+ `Updating mulch-cli: ${chalk.yellow(current)} → ${chalk.green(latest)}`,
78
+ );
79
+ }
80
+
81
+ try {
82
+ execSync("npm update -g mulch-cli", {
83
+ encoding: "utf-8",
84
+ timeout: 60000,
85
+ stdio: jsonMode ? ["pipe", "pipe", "pipe"] : "inherit",
86
+ });
87
+
88
+ if (jsonMode) {
89
+ outputJson({
90
+ success: true,
91
+ command: "update",
92
+ current,
93
+ latest,
94
+ upToDate: false,
95
+ updated: true,
96
+ });
97
+ } else {
98
+ console.log(chalk.green(`Updated to mulch-cli ${latest}`));
99
+ }
100
+ } catch (err) {
101
+ if (jsonMode) {
102
+ outputJsonError("update", `Update failed: ${(err as Error).message}`);
103
+ } else {
104
+ console.error(chalk.red(`Update failed: ${(err as Error).message}`));
105
+ console.error(
106
+ chalk.yellow("Try running manually: npm update -g mulch-cli"),
107
+ );
108
+ }
109
+ process.exitCode = 1;
110
+ }
111
+ });
112
+ }
@@ -0,0 +1,107 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import Ajv from "ajv";
3
+ import chalk from "chalk";
4
+ import type { Command } from "commander";
5
+ import { recordSchema } from "../schemas/record-schema.ts";
6
+ import { getExpertisePath, readConfig } from "../utils/config.ts";
7
+ import { outputJson, outputJsonError } from "../utils/json-output.ts";
8
+
9
+ export function registerValidateCommand(program: Command): void {
10
+ program
11
+ .command("validate")
12
+ .description("Validate expertise records against schemas")
13
+ .action(async () => {
14
+ const jsonMode = program.opts().json === true;
15
+ const config = await readConfig();
16
+ const domains = config.domains;
17
+
18
+ const ajv = new Ajv();
19
+ const validate = ajv.compile(recordSchema);
20
+
21
+ let totalRecords = 0;
22
+ let totalErrors = 0;
23
+ const errors: Array<{ domain: string; line: number; message: string }> =
24
+ [];
25
+
26
+ for (const domain of domains) {
27
+ const filePath = getExpertisePath(domain);
28
+ let content: string;
29
+ try {
30
+ content = await readFile(filePath, "utf-8");
31
+ } catch {
32
+ // File doesn't exist yet, skip
33
+ continue;
34
+ }
35
+
36
+ const lines = content.split("\n");
37
+ for (let i = 0; i < lines.length; i++) {
38
+ const line = lines[i].trim();
39
+ if (line.length === 0) continue;
40
+
41
+ totalRecords++;
42
+ const lineNumber = i + 1;
43
+
44
+ let parsed: unknown;
45
+ try {
46
+ parsed = JSON.parse(line);
47
+ } catch {
48
+ totalErrors++;
49
+ const msg = "Invalid JSON: failed to parse";
50
+ errors.push({ domain, line: lineNumber, message: msg });
51
+ if (!jsonMode) {
52
+ console.error(chalk.red(`${domain}:${lineNumber} - ${msg}`));
53
+ }
54
+ continue;
55
+ }
56
+
57
+ if (!validate(parsed)) {
58
+ totalErrors++;
59
+ const schemaErrors = (validate.errors ?? [])
60
+ .map((err) => `${err.instancePath} ${err.message}`)
61
+ .join("; ");
62
+ const msg = `Schema validation failed: ${schemaErrors}`;
63
+ errors.push({ domain, line: lineNumber, message: msg });
64
+ if (!jsonMode) {
65
+ console.error(
66
+ chalk.red(
67
+ `${domain}:${lineNumber} - Schema validation failed:`,
68
+ ),
69
+ );
70
+ for (const err of validate.errors ?? []) {
71
+ console.error(
72
+ chalk.red(` ${err.instancePath} ${err.message}`),
73
+ );
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ if (jsonMode) {
81
+ outputJson({
82
+ success: totalErrors === 0,
83
+ command: "validate",
84
+ valid: totalErrors === 0,
85
+ totalRecords,
86
+ totalErrors,
87
+ errors,
88
+ });
89
+ } else if (totalErrors > 0) {
90
+ console.log(
91
+ chalk.red(
92
+ `${totalRecords} records validated, ${totalErrors} errors found`,
93
+ ),
94
+ );
95
+ } else {
96
+ console.log(
97
+ chalk.green(
98
+ `${totalRecords} records validated, ${totalErrors} errors found`,
99
+ ),
100
+ );
101
+ }
102
+
103
+ if (totalErrors > 0) {
104
+ process.exitCode = 1;
105
+ }
106
+ });
107
+ }
package/src/index.ts ADDED
@@ -0,0 +1,50 @@
1
+ // Type exports
2
+ export type {
3
+ RecordType,
4
+ Classification,
5
+ Evidence,
6
+ Outcome,
7
+ ConventionRecord,
8
+ PatternRecord,
9
+ FailureRecord,
10
+ DecisionRecord,
11
+ ReferenceRecord,
12
+ GuideRecord,
13
+ ExpertiseRecord,
14
+ } from "./schemas/index.ts";
15
+
16
+ export type { MulchConfig } from "./schemas/index.ts";
17
+ export { DEFAULT_CONFIG } from "./schemas/index.ts";
18
+
19
+ // Schema exports
20
+ export { recordSchema } from "./schemas/record-schema.ts";
21
+
22
+ // Config utilities
23
+ export { readConfig, getExpertisePath } from "./utils/config.ts";
24
+
25
+ // Expertise utilities
26
+ export {
27
+ readExpertiseFile,
28
+ searchRecords,
29
+ appendRecord,
30
+ writeExpertiseFile,
31
+ findDuplicate,
32
+ generateRecordId,
33
+ } from "./utils/expertise.ts";
34
+
35
+ // Programmatic API
36
+ export {
37
+ recordExpertise,
38
+ searchExpertise,
39
+ queryDomain,
40
+ editRecord,
41
+ } from "./api.ts";
42
+ export type {
43
+ RecordOptions,
44
+ RecordResult,
45
+ SearchOptions,
46
+ SearchResult,
47
+ QueryOptions,
48
+ EditOptions,
49
+ RecordUpdates,
50
+ } from "./api.ts";
@@ -0,0 +1,31 @@
1
+ export interface MulchConfig {
2
+ version: string;
3
+ domains: string[];
4
+ governance: {
5
+ max_entries: number;
6
+ warn_entries: number;
7
+ hard_limit: number;
8
+ };
9
+ classification_defaults: {
10
+ shelf_life: {
11
+ tactical: number;
12
+ observational: number;
13
+ };
14
+ };
15
+ }
16
+
17
+ export const DEFAULT_CONFIG: MulchConfig = {
18
+ version: "1",
19
+ domains: [],
20
+ governance: {
21
+ max_entries: 100,
22
+ warn_entries: 150,
23
+ hard_limit: 200,
24
+ },
25
+ classification_defaults: {
26
+ shelf_life: {
27
+ tactical: 14,
28
+ observational: 30,
29
+ },
30
+ },
31
+ };
@@ -0,0 +1,18 @@
1
+ export type {
2
+ RecordType,
3
+ Classification,
4
+ Evidence,
5
+ Outcome,
6
+ ConventionRecord,
7
+ PatternRecord,
8
+ FailureRecord,
9
+ DecisionRecord,
10
+ ReferenceRecord,
11
+ GuideRecord,
12
+ ExpertiseRecord,
13
+ } from "./record.ts";
14
+
15
+ export type { MulchConfig } from "./config.ts";
16
+ export { DEFAULT_CONFIG } from "./config.ts";
17
+
18
+ export { recordSchema } from "./record-schema.ts";