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.
- package/README.md +12 -1
- 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/{dist/utils/scoring.d.ts → src/utils/scoring.ts} +53 -9
- package/src/utils/version.ts +46 -0
- package/dist/api.d.ts +0 -65
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -196
- package/dist/api.js.map +0 -1
- 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 -198
- 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 -133
- 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 -689
- 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 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -10
- 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 -403
- package/dist/schemas/record-schema.d.ts.map +0 -1
- package/dist/schemas/record-schema.js +0 -150
- package/dist/schemas/record-schema.js.map +0 -1
- package/dist/schemas/record.d.ts +0 -62
- 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 -276
- 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 -562
- 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.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
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/mulch-cli)
|
|
4
4
|
[](https://github.com/jayminwest/mulch/actions/workflows/ci.yml)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
[](https://bun.sh)
|
|
7
7
|
|
|
8
8
|
Structured expertise files that accumulate over time, live in git, work with any agent, and run locally with zero dependencies.
|
|
9
9
|
|
|
@@ -23,6 +23,17 @@ Or use directly with npx — no install required:
|
|
|
23
23
|
npx mulch-cli <command>
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
### Development
|
|
27
|
+
|
|
28
|
+
Mulch uses [Bun](https://bun.sh) as its development runtime and test runner:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bun install # Install dependencies
|
|
32
|
+
bun test # Run all tests
|
|
33
|
+
bun run lint # Lint with Biome
|
|
34
|
+
bun run typecheck # Type-check with tsc
|
|
35
|
+
```
|
|
36
|
+
|
|
26
37
|
## Quick Start
|
|
27
38
|
|
|
28
39
|
```bash
|
package/package.json
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mulch-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Let your agents grow 🌱 — structured expertise files that accumulate over time, live in git, work with any agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"mulch": "
|
|
7
|
+
"mulch": "src/cli.ts"
|
|
8
8
|
},
|
|
9
|
-
"main": "
|
|
10
|
-
"
|
|
11
|
-
"files": [
|
|
12
|
-
"dist"
|
|
13
|
-
],
|
|
9
|
+
"main": "src/index.ts",
|
|
10
|
+
"files": ["src"],
|
|
14
11
|
"scripts": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"test:watch": "vitest",
|
|
19
|
-
"lint": "tsc --noEmit"
|
|
12
|
+
"test": "bun test",
|
|
13
|
+
"lint": "bunx biome check .",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
20
15
|
},
|
|
21
16
|
"engines": {
|
|
22
|
-
"
|
|
17
|
+
"bun": ">=1.0.0"
|
|
23
18
|
},
|
|
24
19
|
"keywords": [
|
|
25
20
|
"ai",
|
|
@@ -42,9 +37,9 @@
|
|
|
42
37
|
"js-yaml": "^4.1.1"
|
|
43
38
|
},
|
|
44
39
|
"devDependencies": {
|
|
40
|
+
"@biomejs/biome": "^1.9.0",
|
|
41
|
+
"@types/bun": "latest",
|
|
45
42
|
"@types/js-yaml": "^4.0.9",
|
|
46
|
-
"
|
|
47
|
-
"typescript": "^5.9.3",
|
|
48
|
-
"vitest": "^4.0.18"
|
|
43
|
+
"typescript": "^5.9.3"
|
|
49
44
|
}
|
|
50
45
|
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Classification,
|
|
3
|
+
ExpertiseRecord,
|
|
4
|
+
Outcome,
|
|
5
|
+
} from "./schemas/record.ts";
|
|
6
|
+
import { getExpertisePath, readConfig } from "./utils/config.ts";
|
|
7
|
+
import {
|
|
8
|
+
appendRecord,
|
|
9
|
+
filterByClassification,
|
|
10
|
+
filterByFile,
|
|
11
|
+
filterByType,
|
|
12
|
+
findDuplicate,
|
|
13
|
+
readExpertiseFile,
|
|
14
|
+
resolveRecordId,
|
|
15
|
+
searchRecords,
|
|
16
|
+
writeExpertiseFile,
|
|
17
|
+
} from "./utils/expertise.ts";
|
|
18
|
+
import { withFileLock } from "./utils/lock.ts";
|
|
19
|
+
|
|
20
|
+
export interface RecordOptions {
|
|
21
|
+
force?: boolean;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RecordResult {
|
|
26
|
+
action: "created" | "updated" | "skipped";
|
|
27
|
+
record: ExpertiseRecord;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SearchOptions {
|
|
31
|
+
domain?: string;
|
|
32
|
+
type?: string;
|
|
33
|
+
tag?: string;
|
|
34
|
+
classification?: string;
|
|
35
|
+
file?: string;
|
|
36
|
+
cwd?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SearchResult {
|
|
40
|
+
domain: string;
|
|
41
|
+
records: ExpertiseRecord[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface QueryOptions {
|
|
45
|
+
type?: string;
|
|
46
|
+
classification?: string;
|
|
47
|
+
file?: string;
|
|
48
|
+
cwd?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface EditOptions {
|
|
52
|
+
cwd?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RecordUpdates {
|
|
56
|
+
classification?: Classification;
|
|
57
|
+
tags?: string[];
|
|
58
|
+
relates_to?: string[];
|
|
59
|
+
supersedes?: string[];
|
|
60
|
+
outcomes?: Outcome[];
|
|
61
|
+
// type-specific fields
|
|
62
|
+
content?: string;
|
|
63
|
+
name?: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
resolution?: string;
|
|
66
|
+
title?: string;
|
|
67
|
+
rationale?: string;
|
|
68
|
+
files?: string[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Record an expertise record in the given domain.
|
|
73
|
+
* Named record types (pattern, decision, reference, guide) are upserted on
|
|
74
|
+
* duplicate key; convention and failure duplicates are skipped unless force=true.
|
|
75
|
+
*/
|
|
76
|
+
export async function recordExpertise(
|
|
77
|
+
domain: string,
|
|
78
|
+
record: ExpertiseRecord,
|
|
79
|
+
options: RecordOptions = {},
|
|
80
|
+
): Promise<RecordResult> {
|
|
81
|
+
const { force = false, cwd } = options;
|
|
82
|
+
|
|
83
|
+
const config = await readConfig(cwd);
|
|
84
|
+
if (!config.domains.includes(domain)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const filePath = getExpertisePath(domain, cwd);
|
|
91
|
+
|
|
92
|
+
return withFileLock(filePath, async () => {
|
|
93
|
+
const existing = await readExpertiseFile(filePath);
|
|
94
|
+
const dup = findDuplicate(existing, record);
|
|
95
|
+
|
|
96
|
+
if (dup && !force) {
|
|
97
|
+
const isNamed =
|
|
98
|
+
record.type === "pattern" ||
|
|
99
|
+
record.type === "decision" ||
|
|
100
|
+
record.type === "reference" ||
|
|
101
|
+
record.type === "guide";
|
|
102
|
+
|
|
103
|
+
if (isNamed) {
|
|
104
|
+
existing[dup.index] = record;
|
|
105
|
+
await writeExpertiseFile(filePath, existing);
|
|
106
|
+
return { action: "updated" as const, record };
|
|
107
|
+
}
|
|
108
|
+
return { action: "skipped" as const, record: dup.record };
|
|
109
|
+
}
|
|
110
|
+
await appendRecord(filePath, record);
|
|
111
|
+
return { action: "created" as const, record };
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Search expertise records across domains using BM25 ranking.
|
|
117
|
+
* Returns domains with at least one matching record.
|
|
118
|
+
*/
|
|
119
|
+
export async function searchExpertise(
|
|
120
|
+
query: string,
|
|
121
|
+
options: SearchOptions = {},
|
|
122
|
+
): Promise<SearchResult[]> {
|
|
123
|
+
const { cwd } = options;
|
|
124
|
+
const config = await readConfig(cwd);
|
|
125
|
+
|
|
126
|
+
let domainsToSearch: string[];
|
|
127
|
+
if (options.domain) {
|
|
128
|
+
if (!config.domains.includes(options.domain)) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Domain "${options.domain}" not found in config. Available domains: ${config.domains.join(", ")}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
domainsToSearch = [options.domain];
|
|
134
|
+
} else {
|
|
135
|
+
domainsToSearch = config.domains;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const results: SearchResult[] = [];
|
|
139
|
+
|
|
140
|
+
for (const d of domainsToSearch) {
|
|
141
|
+
const filePath = getExpertisePath(d, cwd);
|
|
142
|
+
let records = await readExpertiseFile(filePath);
|
|
143
|
+
|
|
144
|
+
if (options.type) {
|
|
145
|
+
records = filterByType(records, options.type);
|
|
146
|
+
}
|
|
147
|
+
if (options.tag) {
|
|
148
|
+
const tagLower = options.tag.toLowerCase();
|
|
149
|
+
records = records.filter((r) =>
|
|
150
|
+
r.tags?.some((t) => t.toLowerCase() === tagLower),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (options.classification) {
|
|
154
|
+
records = filterByClassification(records, options.classification);
|
|
155
|
+
}
|
|
156
|
+
if (options.file) {
|
|
157
|
+
records = filterByFile(records, options.file);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const matches = searchRecords(records, query);
|
|
161
|
+
if (matches.length > 0) {
|
|
162
|
+
results.push({ domain: d, records: matches });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return results;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Query all records in a domain with optional filtering.
|
|
171
|
+
*/
|
|
172
|
+
export async function queryDomain(
|
|
173
|
+
domain: string,
|
|
174
|
+
options: QueryOptions = {},
|
|
175
|
+
): Promise<ExpertiseRecord[]> {
|
|
176
|
+
const { cwd } = options;
|
|
177
|
+
const config = await readConfig(cwd);
|
|
178
|
+
|
|
179
|
+
if (!config.domains.includes(domain)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const filePath = getExpertisePath(domain, cwd);
|
|
186
|
+
let records = await readExpertiseFile(filePath);
|
|
187
|
+
|
|
188
|
+
if (options.type) {
|
|
189
|
+
records = filterByType(records, options.type);
|
|
190
|
+
}
|
|
191
|
+
if (options.classification) {
|
|
192
|
+
records = filterByClassification(records, options.classification);
|
|
193
|
+
}
|
|
194
|
+
if (options.file) {
|
|
195
|
+
records = filterByFile(records, options.file);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return records;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Edit an existing record by ID in the given domain.
|
|
203
|
+
* Only provided fields in updates are modified; all other fields are preserved.
|
|
204
|
+
*/
|
|
205
|
+
export async function editRecord(
|
|
206
|
+
domain: string,
|
|
207
|
+
id: string,
|
|
208
|
+
updates: RecordUpdates,
|
|
209
|
+
options: EditOptions = {},
|
|
210
|
+
): Promise<ExpertiseRecord> {
|
|
211
|
+
const { cwd } = options;
|
|
212
|
+
const config = await readConfig(cwd);
|
|
213
|
+
|
|
214
|
+
if (!config.domains.includes(domain)) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const filePath = getExpertisePath(domain, cwd);
|
|
221
|
+
|
|
222
|
+
return withFileLock(filePath, async () => {
|
|
223
|
+
const records = await readExpertiseFile(filePath);
|
|
224
|
+
const resolved = resolveRecordId(records, id);
|
|
225
|
+
|
|
226
|
+
if (!resolved.ok) {
|
|
227
|
+
throw new Error(resolved.error);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const targetIndex = resolved.index;
|
|
231
|
+
const record = { ...records[targetIndex] };
|
|
232
|
+
|
|
233
|
+
// Apply common updates
|
|
234
|
+
if (updates.classification !== undefined) {
|
|
235
|
+
record.classification = updates.classification;
|
|
236
|
+
}
|
|
237
|
+
if (updates.tags !== undefined) {
|
|
238
|
+
record.tags = updates.tags;
|
|
239
|
+
}
|
|
240
|
+
if (updates.relates_to !== undefined) {
|
|
241
|
+
record.relates_to = updates.relates_to;
|
|
242
|
+
}
|
|
243
|
+
if (updates.supersedes !== undefined) {
|
|
244
|
+
record.supersedes = updates.supersedes;
|
|
245
|
+
}
|
|
246
|
+
if (updates.outcomes !== undefined) {
|
|
247
|
+
record.outcomes = updates.outcomes;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Apply type-specific updates
|
|
251
|
+
switch (record.type) {
|
|
252
|
+
case "convention":
|
|
253
|
+
if (updates.content !== undefined) {
|
|
254
|
+
record.content = updates.content;
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
case "pattern":
|
|
258
|
+
if (updates.name !== undefined) {
|
|
259
|
+
record.name = updates.name;
|
|
260
|
+
}
|
|
261
|
+
if (updates.description !== undefined) {
|
|
262
|
+
record.description = updates.description;
|
|
263
|
+
}
|
|
264
|
+
if (updates.files !== undefined) {
|
|
265
|
+
record.files = updates.files;
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
case "failure":
|
|
269
|
+
if (updates.description !== undefined) {
|
|
270
|
+
record.description = updates.description;
|
|
271
|
+
}
|
|
272
|
+
if (updates.resolution !== undefined) {
|
|
273
|
+
record.resolution = updates.resolution;
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
case "decision":
|
|
277
|
+
if (updates.title !== undefined) {
|
|
278
|
+
record.title = updates.title;
|
|
279
|
+
}
|
|
280
|
+
if (updates.rationale !== undefined) {
|
|
281
|
+
record.rationale = updates.rationale;
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
case "reference":
|
|
285
|
+
if (updates.name !== undefined) {
|
|
286
|
+
record.name = updates.name;
|
|
287
|
+
}
|
|
288
|
+
if (updates.description !== undefined) {
|
|
289
|
+
record.description = updates.description;
|
|
290
|
+
}
|
|
291
|
+
if (updates.files !== undefined) {
|
|
292
|
+
record.files = updates.files;
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
case "guide":
|
|
296
|
+
if (updates.name !== undefined) {
|
|
297
|
+
record.name = updates.name;
|
|
298
|
+
}
|
|
299
|
+
if (updates.description !== undefined) {
|
|
300
|
+
record.description = updates.description;
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
records[targetIndex] = record;
|
|
306
|
+
await writeExpertiseFile(filePath, records);
|
|
307
|
+
|
|
308
|
+
return record;
|
|
309
|
+
});
|
|
310
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { registerAddCommand } from "./commands/add.ts";
|
|
5
|
+
import { registerCompactCommand } from "./commands/compact.ts";
|
|
6
|
+
import { registerDeleteCommand } from "./commands/delete.ts";
|
|
7
|
+
import { registerDiffCommand } from "./commands/diff.ts";
|
|
8
|
+
import { registerDoctorCommand } from "./commands/doctor.ts";
|
|
9
|
+
import { registerEditCommand } from "./commands/edit.ts";
|
|
10
|
+
import { registerInitCommand } from "./commands/init.ts";
|
|
11
|
+
import { registerLearnCommand } from "./commands/learn.ts";
|
|
12
|
+
import { registerOnboardCommand } from "./commands/onboard.ts";
|
|
13
|
+
import { registerPrimeCommand } from "./commands/prime.ts";
|
|
14
|
+
import { registerPruneCommand } from "./commands/prune.ts";
|
|
15
|
+
import { registerQueryCommand } from "./commands/query.ts";
|
|
16
|
+
import { registerReadyCommand } from "./commands/ready.ts";
|
|
17
|
+
import { registerRecordCommand } from "./commands/record.ts";
|
|
18
|
+
import { registerSearchCommand } from "./commands/search.ts";
|
|
19
|
+
import { registerSetupCommand } from "./commands/setup.ts";
|
|
20
|
+
import { registerStatusCommand } from "./commands/status.ts";
|
|
21
|
+
import { registerSyncCommand } from "./commands/sync.ts";
|
|
22
|
+
import { registerUpdateCommand } from "./commands/update.ts";
|
|
23
|
+
import { registerValidateCommand } from "./commands/validate.ts";
|
|
24
|
+
|
|
25
|
+
const program = new Command();
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.name("mulch")
|
|
29
|
+
.description("Let your agents grow 🌱")
|
|
30
|
+
.version("0.6.0")
|
|
31
|
+
.option("--json", "output as structured JSON");
|
|
32
|
+
|
|
33
|
+
registerInitCommand(program);
|
|
34
|
+
registerAddCommand(program);
|
|
35
|
+
registerRecordCommand(program);
|
|
36
|
+
registerEditCommand(program);
|
|
37
|
+
registerQueryCommand(program);
|
|
38
|
+
registerSetupCommand(program);
|
|
39
|
+
registerPrimeCommand(program);
|
|
40
|
+
registerOnboardCommand(program);
|
|
41
|
+
registerStatusCommand(program);
|
|
42
|
+
registerValidateCommand(program);
|
|
43
|
+
registerPruneCommand(program);
|
|
44
|
+
registerSearchCommand(program);
|
|
45
|
+
registerDoctorCommand(program);
|
|
46
|
+
registerReadyCommand(program);
|
|
47
|
+
registerSyncCommand(program);
|
|
48
|
+
registerDeleteCommand(program);
|
|
49
|
+
registerLearnCommand(program);
|
|
50
|
+
registerCompactCommand(program);
|
|
51
|
+
registerDiffCommand(program);
|
|
52
|
+
registerUpdateCommand(program);
|
|
53
|
+
|
|
54
|
+
program.parse();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import type { Command } from "commander";
|
|
4
|
+
import {
|
|
5
|
+
getExpertisePath,
|
|
6
|
+
getMulchDir,
|
|
7
|
+
readConfig,
|
|
8
|
+
writeConfig,
|
|
9
|
+
} from "../utils/config.ts";
|
|
10
|
+
import { createExpertiseFile } from "../utils/expertise.ts";
|
|
11
|
+
import { outputJson, outputJsonError } from "../utils/json-output.ts";
|
|
12
|
+
|
|
13
|
+
export function registerAddCommand(program: Command): void {
|
|
14
|
+
program
|
|
15
|
+
.command("add")
|
|
16
|
+
.argument("<domain>", "expertise domain to add")
|
|
17
|
+
.description("Add a new expertise domain")
|
|
18
|
+
.action(async (domain: string) => {
|
|
19
|
+
const jsonMode = program.opts().json === true;
|
|
20
|
+
const mulchDir = getMulchDir();
|
|
21
|
+
|
|
22
|
+
if (!existsSync(mulchDir)) {
|
|
23
|
+
if (jsonMode) {
|
|
24
|
+
outputJsonError(
|
|
25
|
+
"add",
|
|
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
|
+
if (config.domains.includes(domain)) {
|
|
40
|
+
if (jsonMode) {
|
|
41
|
+
outputJsonError("add", `Domain "${domain}" already exists.`);
|
|
42
|
+
} else {
|
|
43
|
+
console.error(chalk.red(`Domain "${domain}" already exists.`));
|
|
44
|
+
}
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const expertisePath = getExpertisePath(domain);
|
|
50
|
+
await createExpertiseFile(expertisePath);
|
|
51
|
+
|
|
52
|
+
config.domains.push(domain);
|
|
53
|
+
await writeConfig(config);
|
|
54
|
+
|
|
55
|
+
if (jsonMode) {
|
|
56
|
+
outputJson({ success: true, command: "add", domain });
|
|
57
|
+
} else {
|
|
58
|
+
console.log(chalk.green(`Added domain "${domain}".`));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|