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.
- package/README.md +24 -4
- package/package.json +11 -16
- package/src/api.ts +310 -0
- package/src/cli.ts +54 -0
- package/src/commands/add.ts +61 -0
- package/src/commands/compact.ts +924 -0
- package/src/commands/delete.ts +103 -0
- package/src/commands/diff.ts +209 -0
- package/src/commands/doctor.ts +586 -0
- package/src/commands/edit.ts +253 -0
- package/src/commands/init.ts +33 -0
- package/src/commands/learn.ts +170 -0
- package/src/commands/onboard.ts +362 -0
- package/src/commands/prime.ts +327 -0
- package/src/commands/prune.ts +128 -0
- package/src/commands/query.ts +177 -0
- package/src/commands/ready.ts +194 -0
- package/src/commands/record.ts +959 -0
- package/src/commands/search.ts +234 -0
- package/src/commands/setup.ts +823 -0
- package/src/commands/status.ts +83 -0
- package/src/commands/sync.ts +224 -0
- package/src/commands/update.ts +112 -0
- package/src/commands/validate.ts +107 -0
- package/src/index.ts +50 -0
- package/src/schemas/config.ts +31 -0
- package/src/schemas/index.ts +18 -0
- package/src/schemas/record-schema.ts +177 -0
- package/src/schemas/record.ts +83 -0
- package/src/utils/bm25.ts +243 -0
- package/src/utils/budget.ts +157 -0
- package/src/utils/config.ts +117 -0
- package/src/utils/expertise.ts +379 -0
- package/src/utils/format.ts +767 -0
- package/src/utils/git.ts +89 -0
- package/src/utils/index.ts +54 -0
- package/src/utils/json-output.ts +13 -0
- package/src/utils/lock.ts +82 -0
- package/src/utils/markers.ts +51 -0
- package/src/utils/scoring.ts +101 -0
- package/src/utils/version.ts +46 -0
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -50
- package/dist/cli.js.map +0 -1
- package/dist/commands/add.d.ts +0 -3
- package/dist/commands/add.d.ts.map +0 -1
- package/dist/commands/add.js +0 -47
- package/dist/commands/add.js.map +0 -1
- package/dist/commands/compact.d.ts +0 -5
- package/dist/commands/compact.d.ts.map +0 -1
- package/dist/commands/compact.js +0 -709
- package/dist/commands/compact.js.map +0 -1
- package/dist/commands/delete.d.ts +0 -3
- package/dist/commands/delete.d.ts.map +0 -1
- package/dist/commands/delete.js +0 -82
- package/dist/commands/delete.js.map +0 -1
- package/dist/commands/diff.d.ts +0 -11
- package/dist/commands/diff.d.ts.map +0 -1
- package/dist/commands/diff.js +0 -170
- package/dist/commands/diff.js.map +0 -1
- package/dist/commands/doctor.d.ts +0 -3
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -391
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/edit.d.ts +0 -3
- package/dist/commands/edit.d.ts.map +0 -1
- package/dist/commands/edit.js +0 -210
- package/dist/commands/edit.js.map +0 -1
- package/dist/commands/init.d.ts +0 -3
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -30
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/learn.d.ts +0 -12
- package/dist/commands/learn.d.ts.map +0 -1
- package/dist/commands/learn.js +0 -130
- package/dist/commands/learn.js.map +0 -1
- package/dist/commands/onboard.d.ts +0 -10
- package/dist/commands/onboard.d.ts.map +0 -1
- package/dist/commands/onboard.js +0 -286
- package/dist/commands/onboard.js.map +0 -1
- package/dist/commands/prime.d.ts +0 -3
- package/dist/commands/prime.d.ts.map +0 -1
- package/dist/commands/prime.js +0 -242
- package/dist/commands/prime.js.map +0 -1
- package/dist/commands/prune.d.ts +0 -8
- package/dist/commands/prune.d.ts.map +0 -1
- package/dist/commands/prune.js +0 -90
- package/dist/commands/prune.js.map +0 -1
- package/dist/commands/query.d.ts +0 -3
- package/dist/commands/query.d.ts.map +0 -1
- package/dist/commands/query.js +0 -118
- package/dist/commands/query.js.map +0 -1
- package/dist/commands/ready.d.ts +0 -3
- package/dist/commands/ready.d.ts.map +0 -1
- package/dist/commands/ready.js +0 -160
- package/dist/commands/ready.js.map +0 -1
- package/dist/commands/record.d.ts +0 -13
- package/dist/commands/record.d.ts.map +0 -1
- package/dist/commands/record.js +0 -688
- package/dist/commands/record.js.map +0 -1
- package/dist/commands/search.d.ts +0 -3
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js +0 -163
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -29
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -548
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/status.d.ts +0 -3
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -61
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/sync.d.ts +0 -3
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js +0 -176
- package/dist/commands/sync.js.map +0 -1
- package/dist/commands/update.d.ts +0 -3
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js +0 -72
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/validate.d.ts +0 -3
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/validate.js +0 -86
- package/dist/commands/validate.js.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -8
- package/dist/index.js.map +0 -1
- package/dist/schemas/config.d.ts +0 -17
- package/dist/schemas/config.d.ts.map +0 -1
- package/dist/schemas/config.js +0 -16
- package/dist/schemas/config.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -5
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -3
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/record-schema.d.ts +0 -379
- package/dist/schemas/record-schema.d.ts.map +0 -1
- package/dist/schemas/record-schema.js +0 -148
- package/dist/schemas/record-schema.js.map +0 -1
- package/dist/schemas/record.d.ts +0 -60
- package/dist/schemas/record.d.ts.map +0 -1
- package/dist/schemas/record.js +0 -2
- package/dist/schemas/record.js.map +0 -1
- package/dist/utils/bm25.d.ts +0 -39
- package/dist/utils/bm25.d.ts.map +0 -1
- package/dist/utils/bm25.js +0 -171
- package/dist/utils/bm25.js.map +0 -1
- package/dist/utils/budget.d.ts +0 -35
- package/dist/utils/budget.d.ts.map +0 -1
- package/dist/utils/budget.js +0 -114
- package/dist/utils/budget.js.map +0 -1
- package/dist/utils/config.d.ts +0 -12
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js +0 -89
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/expertise.d.ts +0 -57
- package/dist/utils/expertise.d.ts.map +0 -1
- package/dist/utils/expertise.js +0 -264
- package/dist/utils/expertise.js.map +0 -1
- package/dist/utils/format.d.ts +0 -31
- package/dist/utils/format.d.ts.map +0 -1
- package/dist/utils/format.js +0 -556
- package/dist/utils/format.js.map +0 -1
- package/dist/utils/git.d.ts +0 -6
- package/dist/utils/git.d.ts.map +0 -1
- package/dist/utils/git.js +0 -81
- package/dist/utils/git.js.map +0 -1
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -8
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/json-output.d.ts +0 -8
- package/dist/utils/json-output.d.ts.map +0 -1
- package/dist/utils/json-output.js +0 -7
- package/dist/utils/json-output.js.map +0 -1
- package/dist/utils/lock.d.ts +0 -6
- package/dist/utils/lock.d.ts.map +0 -1
- package/dist/utils/lock.js +0 -70
- package/dist/utils/lock.js.map +0 -1
- package/dist/utils/markers.d.ts +0 -22
- package/dist/utils/markers.d.ts.map +0 -1
- package/dist/utils/markers.js +0 -42
- package/dist/utils/markers.js.map +0 -1
- package/dist/utils/scoring.d.ts +0 -73
- package/dist/utils/scoring.d.ts.map +0 -1
- package/dist/utils/scoring.js +0 -80
- package/dist/utils/scoring.js.map +0 -1
- package/dist/utils/version.d.ts +0 -15
- package/dist/utils/version.d.ts.map +0 -1
- package/dist/utils/version.js +0 -48
- 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";
|