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,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";