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
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
|
|
@@ -71,7 +82,7 @@ Everything is git-tracked. Clone a repo and your agents immediately have the pro
|
|
|
71
82
|
| `mulch record <domain> --type <type>` | Record an expertise record (`--tags`, `--force`, `--relates-to`, `--supersedes`, `--batch`, `--stdin`, `--dry-run`, `--evidence-bead`) |
|
|
72
83
|
| `mulch edit <domain> <id>` | Edit an existing record by ID or 1-based index |
|
|
73
84
|
| `mulch delete <domain> <id>` | Delete a record by ID or 1-based index |
|
|
74
|
-
| `mulch query [domain]` | Query expertise (use `--all` for all domains, `--classification`, `--file` filters) |
|
|
85
|
+
| `mulch query [domain]` | Query expertise (use `--all` for all domains, `--classification`, `--file`, `--outcome-status`, `--sort-by-score` filters) |
|
|
75
86
|
| `mulch prime [domains...]` | Output AI-optimized expertise context (`--budget`, `--no-limit`, `--context`, `--files`, `--exclude-domain`, `--format`, `--export`) |
|
|
76
87
|
| `mulch search [query]` | Search records across domains with BM25 ranking (`--domain`, `--type`, `--tag`, `--classification`, `--file`, `--sort-by-score` filters) |
|
|
77
88
|
| `mulch compact [domain]` | Analyze compaction candidates or apply a compaction (`--analyze`, `--auto`, `--apply`, `--dry-run`, `--min-group`, `--max-records`) |
|
|
@@ -197,9 +208,18 @@ The `--apply`, default (non-dry-run), and `--fix` variants acquire locks and are
|
|
|
197
208
|
|
|
198
209
|
## Programmatic API
|
|
199
210
|
|
|
200
|
-
Mulch exports
|
|
211
|
+
Mulch exports both low-level utilities and a high-level programmatic API:
|
|
201
212
|
|
|
202
213
|
```typescript
|
|
214
|
+
// High-level API — recommended for most use cases
|
|
215
|
+
import {
|
|
216
|
+
recordExpertise, // Record a new expertise entry (with dedup and locking)
|
|
217
|
+
searchExpertise, // Search records across domains
|
|
218
|
+
queryDomain, // Query all records for a domain
|
|
219
|
+
editRecord, // Edit an existing record by ID
|
|
220
|
+
} from "mulch-cli";
|
|
221
|
+
|
|
222
|
+
// Low-level utilities
|
|
203
223
|
import {
|
|
204
224
|
readConfig,
|
|
205
225
|
getExpertisePath,
|
|
@@ -213,7 +233,7 @@ import {
|
|
|
213
233
|
} from "mulch-cli";
|
|
214
234
|
```
|
|
215
235
|
|
|
216
|
-
Types (`ExpertiseRecord`, `MulchConfig`, `RecordType`, `Classification`, `ScoredRecord`, `Outcome`, etc.) are also exported.
|
|
236
|
+
Types (`ExpertiseRecord`, `MulchConfig`, `RecordType`, `Classification`, `ScoredRecord`, `Outcome`, `RecordOptions`, `RecordResult`, `SearchOptions`, `SearchResult`, `QueryOptions`, `EditOptions`, `RecordUpdates`, etc.) are also exported.
|
|
217
237
|
|
|
218
238
|
## Contributing
|
|
219
239
|
|
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
|
+
}
|