open-classify 0.9.3 → 1.0.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/CHANGELOG.md +89 -0
- package/README.md +86 -137
- package/bin/open-classify.mjs +265 -682
- package/dist/src/classifiers.js +25 -18
- package/dist/src/config.d.ts +2 -2
- package/dist/src/config.js +18 -16
- package/docs/adding-a-classifier.md +15 -14
- package/docs/manifests.md +1 -1
- package/package.json +2 -3
- package/templates/scaffold/open-classify/README.md +46 -0
- package/templates/scaffold/open-classify/classifiers/README.md +22 -0
- package/templates/scaffold/open-classify/config.json +12 -0
- package/open-classify.config.example.json +0 -26
- /package/{downstream-models.json → templates/scaffold/open-classify/downstream-models.json} +0 -0
- /package/templates/{context_shift → stock/context_shift}/manifest.json +0 -0
- /package/templates/{context_shift → stock/context_shift}/prompt.md +0 -0
- /package/templates/{conversation_digest → stock/conversation_digest}/manifest.json +0 -0
- /package/templates/{conversation_digest → stock/conversation_digest}/prompt.md +0 -0
- /package/templates/{memory_retrieval_queries → stock/memory_retrieval_queries}/manifest.json +0 -0
- /package/templates/{memory_retrieval_queries → stock/memory_retrieval_queries}/prompt.md +0 -0
- /package/templates/{tools → stock/tools}/manifest.json +0 -0
- /package/templates/{tools → stock/tools}/prompt.md +0 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
A focused rewrite of the consumer-facing surface. The runtime is unchanged; what's new is how a consumer installs, configures, and customizes Open Classify.
|
|
6
|
+
|
|
7
|
+
### Scaffold layout
|
|
8
|
+
|
|
9
|
+
The scaffold is now a single directory at the project root:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
your-project/
|
|
13
|
+
└── open-classify/
|
|
14
|
+
├── config.json
|
|
15
|
+
├── downstream-models.json
|
|
16
|
+
├── README.md
|
|
17
|
+
└── classifiers/
|
|
18
|
+
└── README.md
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Previously, `init` wrote three things at the project root (`open-classify.config.json`, `downstream-models.json`, `classifiers/`) plus four `_<name>/` template folders inside `classifiers/`. The 1.0 scaffold keeps everything together in one folder, leaves `classifiers/` empty by default, and uses the new `eject` command for stock-classifier customization.
|
|
22
|
+
|
|
23
|
+
### CLI
|
|
24
|
+
|
|
25
|
+
Subcommands:
|
|
26
|
+
|
|
27
|
+
- `init` — copy `open-classify/` into the current project. Re-run safe.
|
|
28
|
+
- `eject <name>` — copy a stock classifier (`tools`, `memory_retrieval_queries`, `conversation_digest`, `context_shift`) into `open-classify/classifiers/<name>/` so you can edit it.
|
|
29
|
+
- `doctor` — verify install, config, Ollama, classifiers.
|
|
30
|
+
- `try <message>` — one-shot smoke test.
|
|
31
|
+
|
|
32
|
+
Flags trimmed to `--yes`, `--force`, `--dry-run` across the board.
|
|
33
|
+
|
|
34
|
+
Removed:
|
|
35
|
+
|
|
36
|
+
- `uninstall` subcommand. `rm -rf open-classify/ && npm uninstall open-classify` is the documented path. Bundling it into a CLI created the npx "needs to install" prompt — the cure was worse than the disease.
|
|
37
|
+
- `init --minimal`, `init --no-install`, `init --package-manager`, `init --classifier-dir`. None of these earned their slot in the help text.
|
|
38
|
+
- `init`'s auto-install. Install the package first (`npm install open-classify`), then run `init`. Predictable; matches every other tool in the ecosystem.
|
|
39
|
+
|
|
40
|
+
### Config schema
|
|
41
|
+
|
|
42
|
+
- Default path: `open-classify/config.json` (was `open-classify.config.json` at project root).
|
|
43
|
+
- `classifiers.stock` is now a `string[]` of names to enable (was a `Record<string, boolean>` map).
|
|
44
|
+
- All paths in the config (`catalog`, `classifiers.dirs`) resolve relative to the config file's directory — so the scaffold works regardless of where your server starts from.
|
|
45
|
+
|
|
46
|
+
### Stock classifier customization
|
|
47
|
+
|
|
48
|
+
The `_<name>/` template-rename pattern is gone. Customizing a stock classifier is now an explicit `eject`:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
npx open-classify eject tools
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Copies the stock files into `open-classify/classifiers/tools/`. The runtime transparently prefers your local copy over the package version — a user classifier with the same name as a stock classifier always wins. To revert, delete the folder.
|
|
55
|
+
|
|
56
|
+
### Package contents
|
|
57
|
+
|
|
58
|
+
Templates split into:
|
|
59
|
+
|
|
60
|
+
- `templates/scaffold/open-classify/` — copied wholesale by `init`
|
|
61
|
+
- `templates/stock/<name>/` — copied one at a time by `eject <name>`
|
|
62
|
+
|
|
63
|
+
Every scaffolded file is a real file in the package now, not an inlined JS string constant. The scaffolded `config.json`, README, and stock classifiers are all maintainable as their native file types.
|
|
64
|
+
|
|
65
|
+
### Removed from the package
|
|
66
|
+
|
|
67
|
+
- Root `downstream-models.json` (moved into `templates/scaffold/open-classify/`)
|
|
68
|
+
- Root `open-classify.config.example.json` (the scaffold IS the example)
|
|
69
|
+
|
|
70
|
+
### Migration from 0.9.x
|
|
71
|
+
|
|
72
|
+
If you were using a 0.9.x install:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
# Move existing files into the new layout
|
|
76
|
+
mkdir -p open-classify/classifiers
|
|
77
|
+
mv open-classify.config.json open-classify/config.json
|
|
78
|
+
mv downstream-models.json open-classify/downstream-models.json
|
|
79
|
+
mv classifiers/* open-classify/classifiers/ 2>/dev/null || true
|
|
80
|
+
rmdir classifiers 2>/dev/null || true
|
|
81
|
+
|
|
82
|
+
# Edit open-classify/config.json: change classifiers.stock from { "tools": true, ... }
|
|
83
|
+
# to ["tools", ...] — only list the names that were set to true.
|
|
84
|
+
|
|
85
|
+
# Update to 1.0
|
|
86
|
+
npm install open-classify@latest
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If you had renamed any `_<name>/` template folders to activate them (e.g. `_tools/` → `tools/`), they'll still work in the new layout — local classifiers always win over stock by name.
|
package/README.md
CHANGED
|
@@ -19,7 +19,6 @@ normalize + trim classifier context
|
|
|
19
19
|
├─► preflight ─────────────► final_reply? / ack_reply?
|
|
20
20
|
├─► model_tier ────────────► model_tier?
|
|
21
21
|
├─► model_specialization ──► model_specialization?
|
|
22
|
-
├─► tools ─────────────────► tools?
|
|
23
22
|
├─► prompt_injection ─────► risk_level?
|
|
24
23
|
└─► your own classifiers ──► any JSON-Schema-validated payload
|
|
25
24
|
(all run in parallel, capped by maxConcurrency)
|
|
@@ -49,15 +48,21 @@ Prerequisites: Node 18+, [Ollama](https://ollama.com), and the default classifie
|
|
|
49
48
|
ollama pull gemma4:e4b-it-q4_K_M
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
**1.
|
|
51
|
+
**1. Install**
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npm install open-classify
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**2. Scaffold**
|
|
53
58
|
|
|
54
59
|
```sh
|
|
55
60
|
npx open-classify init
|
|
56
61
|
```
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
This creates a single `open-classify/` directory in your project root with the config, model catalog, and a place for your own classifiers. Verify the setup at any time with `npx open-classify doctor`.
|
|
59
64
|
|
|
60
|
-
**
|
|
65
|
+
**3. Use it**
|
|
61
66
|
|
|
62
67
|
```ts
|
|
63
68
|
import { createClassifier } from "open-classify";
|
|
@@ -73,29 +78,51 @@ else if (result.action === "block") handleBlock(result.block_reason); // inj
|
|
|
73
78
|
else callDownstream(result.model_id, result.tools, result.reply?.text); // route the real request
|
|
74
79
|
```
|
|
75
80
|
|
|
76
|
-
`createClassifier()`
|
|
81
|
+
`createClassifier()` finds `open-classify/config.json` in the working directory — no other wiring required.
|
|
82
|
+
|
|
83
|
+
## Removal
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
rm -rf open-classify/
|
|
87
|
+
npm uninstall open-classify
|
|
88
|
+
```
|
|
77
89
|
|
|
78
|
-
|
|
90
|
+
That's it. The scaffold lives in one folder; removing it leaves no trace.
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
## Optional stock classifiers
|
|
81
93
|
|
|
82
|
-
|
|
94
|
+
Open Classify ships four optional stock classifiers: `tools`, `memory_retrieval_queries`, `conversation_digest`, `context_shift`. They're off by default. Enable one in `open-classify/config.json`:
|
|
83
95
|
|
|
84
96
|
```json
|
|
85
|
-
{
|
|
97
|
+
{
|
|
98
|
+
"classifiers": {
|
|
99
|
+
"dirs": ["classifiers"],
|
|
100
|
+
"stock": ["tools"]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
|
|
105
|
+
The package-owned prompt is used, and `npm update open-classify` keeps it current.
|
|
89
106
|
|
|
90
|
-
|
|
107
|
+
When you want to take a stock classifier over and edit its prompt:
|
|
91
108
|
|
|
92
109
|
```sh
|
|
93
|
-
|
|
110
|
+
npx open-classify eject tools
|
|
94
111
|
```
|
|
95
112
|
|
|
96
|
-
The
|
|
113
|
+
That copies the stock files into `open-classify/classifiers/tools/`. The runtime transparently prefers your local copy over the package version, so this works whether or not `tools` is also listed in `classifiers.stock` — local always wins on name. To revert: delete the folder. If `tools` is still listed in `classifiers.stock`, the package version takes over; otherwise the classifier stops running.
|
|
97
114
|
|
|
98
|
-
|
|
115
|
+
## Writing your own classifier
|
|
116
|
+
|
|
117
|
+
Drop a folder into `open-classify/classifiers/` with two files:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
open-classify/classifiers/topic_tags/
|
|
121
|
+
├── manifest.json
|
|
122
|
+
└── prompt.md
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The folder name must match the manifest's `name`. The runtime picks it up on the next start. See [docs/adding-a-classifier.md](docs/adding-a-classifier.md) for the full reference.
|
|
99
126
|
|
|
100
127
|
### Classifying assistant output
|
|
101
128
|
|
|
@@ -150,17 +177,14 @@ Example result:
|
|
|
150
177
|
"model_tier": { "model_tier": "frontier_strong", "reason": "...", "certainty": 0.88 },
|
|
151
178
|
"model_specialization": { "model_specialization": "coding", "reason": "...", "certainty": 0.75 },
|
|
152
179
|
"tools": { "tools": ["workspace"], "reason": "...", "certainty": 0.88 },
|
|
153
|
-
"prompt_injection": { "risk_level": "normal", "reason": "...", "certainty": 0.97 }
|
|
154
|
-
"memory_retrieval_queries": { "queries": ["user code review preferences"], "reason": "...", "certainty": 0.75 }
|
|
180
|
+
"prompt_injection": { "risk_level": "normal", "reason": "...", "certainty": 0.97 }
|
|
155
181
|
}
|
|
156
182
|
}
|
|
157
183
|
```
|
|
158
184
|
|
|
159
185
|
## Classifier model
|
|
160
186
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Open Classify ships four mandatory base classifiers and four optional stock classifiers. The mandatory base classifiers always load from the package, can't be turned off, and are updated by `npm update open-classify`. Optional stock classifiers also live in the package, but are enabled by `open-classify.config.json`.
|
|
187
|
+
Open Classify ships four mandatory base classifiers that always run, plus four optional stock classifiers you can enable or eject.
|
|
164
188
|
|
|
165
189
|
| Name | dispatch_order | Reserved fields | Bundled as | What the aggregator does with it |
|
|
166
190
|
|---|---|---|---|---|
|
|
@@ -173,76 +197,7 @@ Open Classify ships four mandatory base classifiers and four optional stock clas
|
|
|
173
197
|
| `conversation_digest` | 70 | — | optional stock | Passes through |
|
|
174
198
|
| `context_shift` | 80 | — | optional stock | Passes through |
|
|
175
199
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
```json
|
|
179
|
-
{
|
|
180
|
-
"classifiers": {
|
|
181
|
-
"stock": {
|
|
182
|
-
"tools": true,
|
|
183
|
-
"memory_retrieval_queries": false,
|
|
184
|
-
"conversation_digest": false,
|
|
185
|
-
"context_shift": false
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
For copied/custom classifiers in `classifiers/`, the directory-naming convention still applies: `_<name>/` is inactive; `<name>/` runs. Root project files are user-owned, so `init` skips existing config/classifier files unless you pass `--force`.
|
|
192
|
-
|
|
193
|
-
> Need to customize `preflight`'s prompt or any other mandatory built-in? Use a custom `RunClassifier` (see [Bring your own backend](#bring-your-own-backend)) to intercept it, or fork the package.
|
|
194
|
-
|
|
195
|
-
## Adding your own classifier
|
|
196
|
-
|
|
197
|
-
The two files are:
|
|
198
|
-
|
|
199
|
-
```
|
|
200
|
-
classifiers/topic_tags/
|
|
201
|
-
├── manifest.json
|
|
202
|
-
└── prompt.md
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
`manifest.json` declares the output shape and a fallback for when the classifier errors:
|
|
206
|
-
|
|
207
|
-
```json
|
|
208
|
-
{
|
|
209
|
-
"name": "topic_tags",
|
|
210
|
-
"version": "1.0.0",
|
|
211
|
-
"purpose": "Tag the message with a small set of topic labels for analytics.",
|
|
212
|
-
"dispatch_order": 70,
|
|
213
|
-
"output_schema": {
|
|
214
|
-
"required": ["tags"],
|
|
215
|
-
"properties": {
|
|
216
|
-
"tags": {
|
|
217
|
-
"type": "array", "maxItems": 5,
|
|
218
|
-
"items": { "type": "string", "minLength": 1, "maxLength": 40 }
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
"fallback": {
|
|
223
|
-
"reason": "Classifier failed; no tags generated.",
|
|
224
|
-
"certainty": "no_signal",
|
|
225
|
-
"tags": []
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
`prompt.md` is the classification rule in plain language. No need to write JSON examples — the runtime synthesizes one from your schema — and no need to paste enum values for reserved fields:
|
|
231
|
-
|
|
232
|
-
```markdown
|
|
233
|
-
You are the topic_tags classifier.
|
|
234
|
-
|
|
235
|
-
`tags` are short single-word topic labels (lowercase, no spaces). Use at most five.
|
|
236
|
-
Return an empty array when no clear topic applies.
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Consume:
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
const tags = result.classifier_outputs.topic_tags?.tags ?? [];
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Rules: `name` must match the directory name; reserved-field names can't appear in `output_schema.properties` (declare them under `reserved_fields` instead); `fallback` only needs `reason` and `certainty`; name collisions throw at startup. See [docs/adding-a-classifier.md](docs/adding-a-classifier.md) for the full reference.
|
|
200
|
+
To customize a mandatory built-in, use a custom `RunClassifier` (see [Bring your own backend](#bring-your-own-backend)).
|
|
246
201
|
|
|
247
202
|
## Using reserved fields in your own classifier
|
|
248
203
|
|
|
@@ -263,52 +218,9 @@ The runtime injects canonical sub-schemas and prompt fragments for each declared
|
|
|
263
218
|
|
|
264
219
|
The available reserved fields are: `final_reply`, `ack_reply`, `model_tier`, `model_specialization`, `tools`, `risk_level`. The `tools` field additionally requires an `allowed_tools` array on the manifest listing the tool ids the classifier may pick from.
|
|
265
220
|
|
|
266
|
-
## Model catalog
|
|
267
|
-
|
|
268
|
-
Classifiers never emit model ids. They emit constraints; your catalog maps constraints to concrete models.
|
|
269
|
-
|
|
270
|
-
```json
|
|
271
|
-
{
|
|
272
|
-
"models": [
|
|
273
|
-
{
|
|
274
|
-
"id": "gpt-5.5",
|
|
275
|
-
"provider": "openai",
|
|
276
|
-
"runtime": "api",
|
|
277
|
-
"specializations": [
|
|
278
|
-
"chat",
|
|
279
|
-
"writing",
|
|
280
|
-
"reasoning",
|
|
281
|
-
"planning",
|
|
282
|
-
"coding",
|
|
283
|
-
"tool_use"
|
|
284
|
-
],
|
|
285
|
-
"tier": "frontier_strong",
|
|
286
|
-
"params_in_billions": null,
|
|
287
|
-
"context_window": 1050000,
|
|
288
|
-
"max_output_tokens": 128000,
|
|
289
|
-
"input_tokens_cpm": 5,
|
|
290
|
-
"cached_tokens_cpm": 0.5,
|
|
291
|
-
"output_tokens_cpm": 30
|
|
292
|
-
}
|
|
293
|
-
],
|
|
294
|
-
"default": "gpt-5.5",
|
|
295
|
-
"pricing_unit": "USD per 1M tokens"
|
|
296
|
-
}
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
The resolver picks the cheapest model matching `model_specialization` and `model_tier`, relaxing constraints in order when nothing fits. See [docs/resolver.md](docs/resolver.md) for ranking details.
|
|
300
|
-
|
|
301
|
-
## Input contract
|
|
302
|
-
|
|
303
|
-
`classify({ messages })` — that's the whole input.
|
|
304
|
-
|
|
305
|
-
- `messages` is chronological, oldest to newest, and must end with the user message you want classified.
|
|
306
|
-
- Open Classify keeps whole messages only, drops oldest first to fit a 5,000-char budget, and caps history at 20 messages.
|
|
307
|
-
- Unknown fields are rejected, not passed through.
|
|
308
|
-
|
|
309
221
|
## Configuration
|
|
310
222
|
|
|
311
|
-
`
|
|
223
|
+
`open-classify/config.json` supports:
|
|
312
224
|
|
|
313
225
|
| Field | What it controls |
|
|
314
226
|
|---|---|
|
|
@@ -317,9 +229,17 @@ The resolver picks the cheapest model matching `model_specialization` and `model
|
|
|
317
229
|
| `runner.defaultModel` | Classifier model used when there is no per-classifier override. |
|
|
318
230
|
| `runner.options` | Ollama generation options: `temperature`, `top_p`, `seed`, `num_ctx`. |
|
|
319
231
|
| `runner.models` | Per-classifier model overrides. Flat map keyed by classifier name. |
|
|
320
|
-
| `catalog` | Path to the downstream model catalog
|
|
321
|
-
| `classifiers.dirs` | Directories of user-owned classifiers to
|
|
322
|
-
| `classifiers.stock` |
|
|
232
|
+
| `catalog` | Path to the downstream model catalog, relative to `open-classify/`. |
|
|
233
|
+
| `classifiers.dirs` | Directories of user-owned classifiers, relative to `open-classify/`. |
|
|
234
|
+
| `classifiers.stock` | Array of stock classifiers to enable. Members of `tools`, `memory_retrieval_queries`, `conversation_digest`, `context_shift`. |
|
|
235
|
+
|
|
236
|
+
## Input contract
|
|
237
|
+
|
|
238
|
+
`classify({ messages })` — that's the whole input.
|
|
239
|
+
|
|
240
|
+
- `messages` is chronological, oldest to newest, and must end with the user message you want classified.
|
|
241
|
+
- Open Classify keeps whole messages only, drops oldest first to fit a 5,000-char budget, and caps history at 20 messages.
|
|
242
|
+
- Unknown fields are rejected, not passed through.
|
|
323
243
|
|
|
324
244
|
## Bring your own backend
|
|
325
245
|
|
|
@@ -345,7 +265,35 @@ const runClassifier: RunClassifier = async (name, input, signal) => {
|
|
|
345
265
|
const { classify, inspect } = createClassifier({ runClassifier });
|
|
346
266
|
```
|
|
347
267
|
|
|
348
|
-
For the lowest-level entry points, `classifyOpenClassifyInput(input, { runClassifier, catalog })` and `inspectOpenClassifyInput(input, { runClassifier })` skip the factory entirely.
|
|
268
|
+
For the lowest-level entry points, `classifyOpenClassifyInput(input, { runClassifier, catalog, registry })` and `inspectOpenClassifyInput(input, { runClassifier, registry })` skip the factory entirely.
|
|
269
|
+
|
|
270
|
+
## Model catalog
|
|
271
|
+
|
|
272
|
+
Classifiers never emit model ids. They emit constraints; your catalog (`open-classify/downstream-models.json`) maps constraints to concrete models.
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"models": [
|
|
277
|
+
{
|
|
278
|
+
"id": "gpt-5.5",
|
|
279
|
+
"provider": "openai",
|
|
280
|
+
"runtime": "api",
|
|
281
|
+
"specializations": ["chat", "writing", "reasoning", "planning", "coding", "tool_use"],
|
|
282
|
+
"tier": "frontier_strong",
|
|
283
|
+
"params_in_billions": null,
|
|
284
|
+
"context_window": 1050000,
|
|
285
|
+
"max_output_tokens": 128000,
|
|
286
|
+
"input_tokens_cpm": 5,
|
|
287
|
+
"cached_tokens_cpm": 0.5,
|
|
288
|
+
"output_tokens_cpm": 30
|
|
289
|
+
}
|
|
290
|
+
],
|
|
291
|
+
"default": "gpt-5.5",
|
|
292
|
+
"pricing_unit": "USD per 1M tokens"
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The resolver picks the cheapest model matching `model_specialization` and `model_tier`, relaxing constraints in order when nothing fits. See [docs/resolver.md](docs/resolver.md) for ranking details.
|
|
349
297
|
|
|
350
298
|
## Further reading
|
|
351
299
|
|
|
@@ -353,6 +301,7 @@ For the lowest-level entry points, `classifyOpenClassifyInput(input, { runClassi
|
|
|
353
301
|
- [docs/manifests.md](docs/manifests.md) — manifest reference
|
|
354
302
|
- [docs/resolver.md](docs/resolver.md) — aggregation and model resolution
|
|
355
303
|
- [docs/adding-a-classifier.md](docs/adding-a-classifier.md) — author guide
|
|
304
|
+
- [CHANGELOG.md](CHANGELOG.md) — release notes
|
|
356
305
|
|
|
357
306
|
## Contributing
|
|
358
307
|
|