@littlebigbrain/create 0.2.0 → 0.3.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 +3 -1
- package/lib/create.mjs +26 -4
- package/package.json +1 -1
- package/templates/_shared/testing.ts +17 -4
- package/templates/link/README.md +203 -0
- package/templates/link/candidates.ts +432 -0
- package/templates/link/decide.test.ts +368 -0
- package/templates/link/decide.ts +340 -0
- package/templates/link/fixture.ts +727 -0
- package/templates/link/grade-run.ts +53 -0
- package/templates/link/grade.test.ts +113 -0
- package/templates/link/grade.ts +335 -0
- package/templates/link/items.ts +333 -0
- package/templates/link/names.test.ts +260 -0
- package/templates/link/names.ts +590 -0
- package/templates/link/reads.ts +302 -0
- package/templates/link/samples/gold.json +108 -0
- package/templates/link/samples/records.json +48 -0
- package/templates/link/samples.ts +64 -0
- package/templates/link/settings.ts +162 -0
- package/templates/link/setup.ts +18 -0
- package/templates/link/source-turn.ts +894 -0
- package/templates/link/sources/records.ts +429 -0
- package/templates/link/things-turn.ts +984 -0
- package/templates/link/things.test.ts +204 -0
- package/templates/link/things.ts +426 -0
- package/templates/link/try.ts +28 -0
- package/templates/link/worker.ts +24 -0
- package/templates/link/workflow.test.ts +484 -0
- package/templates/link/workflow.ts +202 -0
- package/templates/versions.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ npm test
|
|
|
20
20
|
| `ontology-fit` | Proposes the ontology changes the texts of a class support, as review items |
|
|
21
21
|
| `documents-map` | Maps the links of `documents-link` into the graph's own ontology, as fact proposals |
|
|
22
22
|
| `review` | Writes the fact proposals at or above a gate as facts and gives the others to a person |
|
|
23
|
+
| `link` | Connects the mentions of one thing across the records of a graph into one thing, and proposes records of one thing as an identity |
|
|
23
24
|
|
|
24
25
|
A template id is the id of its starter workflow, with dashes for dots:
|
|
25
26
|
`documents-link` is the code of `documents.link`.
|
|
@@ -31,7 +32,8 @@ A template id is the id of its starter workflow, with dashes for dots:
|
|
|
31
32
|
| `src/workflow.ts` | The workflow, its settings, the triggered workflow that sends it the instances (`trigger()`) and the ontology it writes (`ontology()`) |
|
|
32
33
|
| `src/*.ts` | The template's helpers and `samples.ts`, which puts the samples on a graph |
|
|
33
34
|
| `src/workflow.test.ts`, `src/testing.ts` | The workflow on its samples, with mock models and no server |
|
|
34
|
-
| `src/worker.ts`, `src/setup.ts`, `src/try.ts` | `npm start`, `npm run setup` and `npm run try` |
|
|
35
|
+
| `src/worker.ts`, `src/setup.ts`, `src/try.ts` | `npm start`, `npm run setup` and `npm run try`; a template that has its own (`link`) keeps them |
|
|
36
|
+
| `src/grade-run.ts` | `npm run grade`, in a template that grades a run against a gold file (`link`) |
|
|
35
37
|
| `samples/` | The samples and their answer key |
|
|
36
38
|
|
|
37
39
|
`npm run try` creates a graph (`LBB_GRAPH`, default `try-<template>`), puts
|
package/lib/create.mjs
CHANGED
|
@@ -21,6 +21,7 @@ export const TEMPLATES = [
|
|
|
21
21
|
"review",
|
|
22
22
|
"values-extract",
|
|
23
23
|
"ontology-fit",
|
|
24
|
+
"link",
|
|
24
25
|
];
|
|
25
26
|
|
|
26
27
|
const PACKAGE = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -119,15 +120,23 @@ export async function create({ template, dir, client, root = PACKAGE }) {
|
|
|
119
120
|
const src = join(dir, "src");
|
|
120
121
|
await mkdir(src, { recursive: true });
|
|
121
122
|
|
|
122
|
-
// The template's code, its test and the test support, all under src
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
// The template's code, its test and the test support, all under src/, the
|
|
124
|
+
// template's folders (`sources/`) as they are.
|
|
125
|
+
const code = await templateCode(from);
|
|
126
|
+
for (const file of code) {
|
|
125
127
|
const text = await readFile(join(from, file), "utf8");
|
|
126
|
-
|
|
128
|
+
const up = "../".repeat(file.split("/").length - 1);
|
|
129
|
+
await mkdir(dirname(join(src, file)), { recursive: true });
|
|
130
|
+
await writeFile(
|
|
131
|
+
join(src, file),
|
|
132
|
+
text.replaceAll(`../${up}_shared/testing.js`, `${up || "./"}testing.js`),
|
|
133
|
+
);
|
|
127
134
|
}
|
|
128
135
|
await cp(join(templates, "_shared", "testing.ts"), join(src, "testing.ts"));
|
|
129
136
|
await cp(join(from, "samples"), join(dir, "samples"), { recursive: true });
|
|
137
|
+
// A template's own commands win: a workflow of several parts sets itself up.
|
|
130
138
|
for (const file of ["worker.ts", "setup.ts", "try.ts"]) {
|
|
139
|
+
if (code.includes(file)) continue;
|
|
131
140
|
const text = await readFile(join(root, "project", file), "utf8");
|
|
132
141
|
await writeFile(join(src, file), text.replaceAll("__TEMPLATE__", template));
|
|
133
142
|
}
|
|
@@ -144,6 +153,7 @@ export async function create({ template, dir, client, root = PACKAGE }) {
|
|
|
144
153
|
try: "tsx src/try.ts",
|
|
145
154
|
test: "tsx --test src/workflow.test.ts",
|
|
146
155
|
typecheck: "tsc --noEmit",
|
|
156
|
+
...(code.includes("grade-run.ts") ? { grade: "tsx src/grade-run.ts" } : {}),
|
|
147
157
|
},
|
|
148
158
|
dependencies: { "@littlebigbrain/client": client ?? `^${clientVersion(templates)}` },
|
|
149
159
|
devDependencies: { "@types/node": "^22", tsx: "^4", typescript: "^5.6" },
|
|
@@ -209,6 +219,18 @@ ${projectReadme(readme).trim()}
|
|
|
209
219
|
return listFiles(dir);
|
|
210
220
|
}
|
|
211
221
|
|
|
222
|
+
/**
|
|
223
|
+
* The TypeScript files of a template folder, relative, in its folders too;
|
|
224
|
+
* the samples are copied on their own.
|
|
225
|
+
* @param {string} from
|
|
226
|
+
* @returns {Promise<string[]>}
|
|
227
|
+
*/
|
|
228
|
+
async function templateCode(from) {
|
|
229
|
+
return (await listFiles(from)).filter(
|
|
230
|
+
(file) => file.endsWith(".ts") && !file.startsWith("samples/"),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
212
234
|
/** Every file under `dir`, relative and sorted. @param {string} dir @returns {Promise<string[]>} */
|
|
213
235
|
async function listFiles(dir, prefix = "") {
|
|
214
236
|
/** @type {string[]} */
|
package/package.json
CHANGED
|
@@ -405,7 +405,14 @@ export async function runTurn<S>(
|
|
|
405
405
|
definition: WorkflowDefinition<S, WorkflowTriggerMessage>,
|
|
406
406
|
state: unknown,
|
|
407
407
|
message: WorkflowTriggerMessage,
|
|
408
|
-
options: {
|
|
408
|
+
options: {
|
|
409
|
+
prefix?: string;
|
|
410
|
+
saved?: Turn;
|
|
411
|
+
crashAfter?: number;
|
|
412
|
+
/** The engine identity that runs the turn (default `starter.test`), and the turn's number. */
|
|
413
|
+
workflowId?: string;
|
|
414
|
+
turn?: number;
|
|
415
|
+
} = {},
|
|
409
416
|
): Promise<{ result: TurnResult<S>; turn: Turn }> {
|
|
410
417
|
const prefix = options.prefix ?? mockId(JSON.stringify(message));
|
|
411
418
|
const turn: Turn = { message, keys: [], outputs: [], bytes: 0, progress: [] };
|
|
@@ -413,9 +420,9 @@ export async function runTurn<S>(
|
|
|
413
420
|
let ran = 0;
|
|
414
421
|
const ctx = {
|
|
415
422
|
signal: new AbortController().signal,
|
|
416
|
-
workflowId: "starter.test",
|
|
423
|
+
workflowId: options.workflowId ?? "starter.test",
|
|
417
424
|
messageId: "starter-test-1",
|
|
418
|
-
turn: 1,
|
|
425
|
+
turn: options.turn ?? 1,
|
|
419
426
|
async step<T>(
|
|
420
427
|
key: string,
|
|
421
428
|
effect: (s: {
|
|
@@ -480,17 +487,23 @@ export async function runTurn<S>(
|
|
|
480
487
|
/**
|
|
481
488
|
* Run a message and every continuation it leads to, from the empty state the
|
|
482
489
|
* server creates the instance with. Returns the final state and the turns.
|
|
490
|
+
* With `workflowId` and `turn`, the turns run as that identity, numbered
|
|
491
|
+
* from `turn` on.
|
|
483
492
|
*/
|
|
484
493
|
export async function runMessage<S>(
|
|
485
494
|
definition: WorkflowDefinition<S, WorkflowTriggerMessage>,
|
|
486
495
|
message: WorkflowTriggerMessage,
|
|
487
496
|
state: unknown = {},
|
|
497
|
+
options: { workflowId?: string; turn?: number } = {},
|
|
488
498
|
) {
|
|
489
499
|
const turns: Turn[] = [];
|
|
490
500
|
let next: WorkflowTriggerMessage | undefined = message;
|
|
491
501
|
while (next) {
|
|
492
502
|
if (turns.length > 100) throw new Error("more than 100 turns");
|
|
493
|
-
const ran: { result: TurnResult<S>; turn: Turn } = await runTurn(definition, state, next
|
|
503
|
+
const ran: { result: TurnResult<S>; turn: Turn } = await runTurn(definition, state, next, {
|
|
504
|
+
...(options.workflowId ? { workflowId: options.workflowId } : {}),
|
|
505
|
+
...(options.turn !== undefined ? { turn: options.turn + turns.length } : {}),
|
|
506
|
+
});
|
|
494
507
|
turns.push(ran.turn);
|
|
495
508
|
state = ran.result.state;
|
|
496
509
|
next = ran.result.continuation?.message;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# link
|
|
2
|
+
|
|
3
|
+
Connects the mentions of one thing across the records of a graph. A
|
|
4
|
+
contact, a lead, a deal and a note can each name one person in a different
|
|
5
|
+
way: `Mira Tannenholt-Ruiz`, `Mira Ruiz`, `M. Tannenholt`, `Mimi T.`. The
|
|
6
|
+
workflow groups those mentions into one `LinkedThing`. The links are
|
|
7
|
+
references across the graph, and later checks compare what each source says
|
|
8
|
+
about the thing.
|
|
9
|
+
|
|
10
|
+
This is phase L1 of the design
|
|
11
|
+
[general linking](../../../docs/architecture/general-linking.md): identities
|
|
12
|
+
on records. Documents (pages) come in phase L2.
|
|
13
|
+
|
|
14
|
+
## What it writes
|
|
15
|
+
|
|
16
|
+
| Term | Kind | Holds |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `Mention` | class | `span`, `role` (`self` or `refers`), `kind`, `field`, `offset`, `length`, `block_key` (several), `window` (the sentence of a span of text); the links and constraints `linked_to`, `must_link` and `cannot_link` |
|
|
19
|
+
| `LinkedThing` | class | its name (the anchor's name), `kind`, `anchor_key`, `alias` (several: the other names its members use) |
|
|
20
|
+
| `MENTIONED_IN` | relation, mention to record | the record that holds the mention |
|
|
21
|
+
| `SAME_THING_AS` | relation, mention to mention | a decided or accepted pair, with its confidence and its evidence (the sentence and the record's name) |
|
|
22
|
+
| `REFERS_TO` | relation, mention to `LinkedThing` | the thing the mention is in |
|
|
23
|
+
| `HAS_RECORD` | relation, `LinkedThing` to record | the records whose names and keys are in the thing |
|
|
24
|
+
| `MERGED_INTO` | relation, `LinkedThing` to `LinkedThing` | where an old thing is now: a thing whose anchor changed, or that merged |
|
|
25
|
+
| `SAME_AS` | relation, record to record | two records of one thing: a person writes it when they accept an identity |
|
|
26
|
+
|
|
27
|
+
A record's own names and keys are `self` mentions. Each record anchors a
|
|
28
|
+
thing. A span in a text field that names something is a `refers` mention.
|
|
29
|
+
|
|
30
|
+
## How it links
|
|
31
|
+
|
|
32
|
+
1. **Mentions.** A `name` or `key:*` field gives one `self` mention per
|
|
33
|
+
value. A `text` field gives `refers` mentions: first the gazetteer pass,
|
|
34
|
+
which keeps each span whose name or key a mention of the graph has; then
|
|
35
|
+
the spans written like a name or a key (`M. Tannenholt`, an email).
|
|
36
|
+
2. **Block keys.** Each mention gets normal forms of its name or key as
|
|
37
|
+
graph facts (`block_key`): the sorted name, the initials forms, the name
|
|
38
|
+
parts and their sound, an email, a web address, a code. A key that more
|
|
39
|
+
than 200 mentions hold is a stop key and finds nothing.
|
|
40
|
+
3. **Candidates.** The mentions that share a key, and the things of the
|
|
41
|
+
records within two hops (the deal that a note is about). At most 30, the
|
|
42
|
+
ones with a strong key first.
|
|
43
|
+
4. **Decision.** See the next section.
|
|
44
|
+
5. **Things.** The things identity groups the decided pairs. A person's
|
|
45
|
+
decisions are constraints. Two records are never one thing until a person
|
|
46
|
+
accepts their identity.
|
|
47
|
+
|
|
48
|
+
The thing's IRI is a hash of the graph and its anchor's key. The anchor is a
|
|
49
|
+
record's name first, then any other mention. So the same inputs give the
|
|
50
|
+
same IRIs. A thing whose anchor changes keeps one statement, `MERGED_INTO`
|
|
51
|
+
the thing that holds its members now.
|
|
52
|
+
|
|
53
|
+
## Model reads of record values
|
|
54
|
+
|
|
55
|
+
`modelsReadRecords` is off by default (general linking 12, decision 1).
|
|
56
|
+
|
|
57
|
+
- **Off.** No model reads a record value. A strong key (an email, a web
|
|
58
|
+
address, a phone number, a handle, a code) links at confidence 1. Every
|
|
59
|
+
other candidate goes to a person, grouped by thing.
|
|
60
|
+
- **On.** The decision model reads the names, keys and context fields of
|
|
61
|
+
the records. It answers a kind question and a names question per mention,
|
|
62
|
+
and a same-record question per pair of records. A link at or above `gate`
|
|
63
|
+
(0.8) is written; a link below it goes to a person. The reasoning model
|
|
64
|
+
reads only the text fields that opt in: `{ "role": "text", "extract": true }`
|
|
65
|
+
(decision 8).
|
|
66
|
+
|
|
67
|
+
Every model call has an effect id made from its request. The same question
|
|
68
|
+
gets its first answer from the effect cache, at no cost.
|
|
69
|
+
|
|
70
|
+
## Review items
|
|
71
|
+
|
|
72
|
+
| Item | One per | Accept writes |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Link (`starter:<name>:link:…`) | Thing | The kept `SAME_THING_AS` rows. A row left out keeps its mention apart from the thing. |
|
|
75
|
+
| No link (`starter:<name>:nolink:…`) | Thing of the best candidate | The kept rows: the model chose no thing, and a person disagrees. |
|
|
76
|
+
| Identity (`starter:<name>:identity:…`) | Group of 2 to 10 records | `SAME_AS` between the records, and `SAME_THING_AS` between their names. |
|
|
77
|
+
|
|
78
|
+
A person always accepts an identity (decision 2). The `identityGate` (0.99)
|
|
79
|
+
only orders and groups the identity items (`mapping.identity.above_gate`).
|
|
80
|
+
Each item has the origin `starter:<name>` with the label `link`, so the
|
|
81
|
+
inbox labels and patterns of the producer `link` read it.
|
|
82
|
+
|
|
83
|
+
A person's decisions feed back. A kept row is a must-link. A row left out,
|
|
84
|
+
or a dismissed item, is a cannot-link. No model answer joins what a
|
|
85
|
+
cannot-link keeps apart.
|
|
86
|
+
|
|
87
|
+
## Settings
|
|
88
|
+
|
|
89
|
+
| Setting | Default | Meaning |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `name` | `link` | The named workflows are `<name>-<class>` and `<name>-reviews`; the things identity is `<name>-things` |
|
|
92
|
+
| `fields` | the samples' CRM | The role of each field of each class (below) |
|
|
93
|
+
| `gate` | 0.8 | A link at or above it is written, with model reads on |
|
|
94
|
+
| `identityGate` | 0.99 | Orders and groups the identity items |
|
|
95
|
+
| `modelsReadRecords` | `false` | Whether models may read record values |
|
|
96
|
+
| `shards` | 4 | Source shards of each named workflow |
|
|
97
|
+
| `sourcesPerTurn` | 16 | Records per message |
|
|
98
|
+
| `textPerTurn` | 8,000 | Characters of text per turn; the rest continues in a new turn |
|
|
99
|
+
|
|
100
|
+
| Field role | Gives |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `name` | One `self` mention per value |
|
|
103
|
+
| `name:given`, `name:family` | One `self` mention of the two joined (`first_name+last_name`) |
|
|
104
|
+
| `key:email`, `key:phone`, `key:web`, `key:handle`, `key:code` | One `self` mention per value, with a strong key |
|
|
105
|
+
| `text`, `{ "role": "text", "extract": true }` | `refers` mentions found in the text |
|
|
106
|
+
| `context` | No mention; read for the decisions |
|
|
107
|
+
|
|
108
|
+
A field named `name` (or `label`) is the record's display name, which the
|
|
109
|
+
published graph holds as `rdfs:label`; the workflow watches it as `label`.
|
|
110
|
+
Any other field is a property of the record.
|
|
111
|
+
|
|
112
|
+
## Run it
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npm create @littlebigbrain -- --template link my-links
|
|
116
|
+
cd my-links && npm install
|
|
117
|
+
npm test # the workflow on the samples, with mock models
|
|
118
|
+
LBB_GRAPH=try-link npm run try # the samples on a graph of your stack
|
|
119
|
+
LBB_GRAPH=try-link npm start # serve it
|
|
120
|
+
LBB_GRAPH=try-link npm run grade -- --gold samples/gold.json
|
|
121
|
+
LBB_GRAPH=crm npm run setup && npm start # your graph
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`npm run setup` creates the classes and properties, one named workflow per
|
|
125
|
+
class of the settings, the named workflow that watches accepted items, and
|
|
126
|
+
the things identity. It is safe to repeat. The workflow is for a records
|
|
127
|
+
graph. Do not run it on a graph where `documents.link` runs: both write
|
|
128
|
+
`MENTIONED_IN` and `REFERS_TO`.
|
|
129
|
+
|
|
130
|
+
## Grade a run
|
|
131
|
+
|
|
132
|
+
`npm run grade` (`src/grade-run.ts`) prints the metrics of general linking
|
|
133
|
+
9.2: link precision at the gate and its 95% Wilson interval, pairwise
|
|
134
|
+
precision and recall over things, cluster purity and completeness
|
|
135
|
+
(B-cubed), the review share, and identity precision and recall.
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
LBB_URL=... LBB_API_KEY=... LBB_GRAPH=crm npm run grade -- --gold .lbb-datasets/link-eval/gold.json --save run.json
|
|
139
|
+
npm run grade -- --gold .lbb-datasets/link-eval/gold.json --run run.json
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The gold file (`lbb.link.gold.v1`) lists the groups. Each group is one real
|
|
143
|
+
thing, with the mentions that name it:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"format": "lbb.link.gold.v1",
|
|
148
|
+
"complete": false,
|
|
149
|
+
"records": { "contact-1": "https://littlebigbrain.com/e/…" },
|
|
150
|
+
"groups": [
|
|
151
|
+
{ "id": "mira", "kind": "person", "mentions": [
|
|
152
|
+
{ "record": "contact-1", "field": "name", "span": "Mira Tannenholt-Ruiz" },
|
|
153
|
+
{ "record": "https://littlebigbrain.com/e/…", "field": "body", "span": "Mimi T.", "occurrence": 0 }
|
|
154
|
+
] }
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
A mention names its record (an IRI, or a name of `records`), its field
|
|
160
|
+
(`first_name+last_name` for a joined name), its span as the field writes it,
|
|
161
|
+
and which equal span of the field it is (`occurrence`, default 0). With
|
|
162
|
+
`complete: true`, a mention that the gold does not list is a thing of its
|
|
163
|
+
own. Without it, a link to such a mention counts apart as unjudged. Keep a
|
|
164
|
+
gold file of real records out of git, under `.lbb-datasets/`.
|
|
165
|
+
|
|
166
|
+
## The samples
|
|
167
|
+
|
|
168
|
+
`samples/records.json` is an invented CRM: 7 organizations, 12 contacts, 5
|
|
169
|
+
leads, 7 deals and 11 notes. Every name, company and address is invented.
|
|
170
|
+
The names vary: a nickname (`Bob` for `Robert`), initials (`M. Tannenholt`,
|
|
171
|
+
`F. Okafor`), accents and transliterations (`Jürgen`, `Juergen`, `Jurgen`;
|
|
172
|
+
`Søren`, `Soren`; `Zoë`, `Zoe`), name order (`Wren, Tobias`), a typo
|
|
173
|
+
(`Ramaswamey`), a person known only by an email, a surname that two people
|
|
174
|
+
share (`Okafor`), two people of one name at two organizations (`Alex
|
|
175
|
+
Brandt`), and company domains. `samples/gold.json` holds the gold groups.
|
|
176
|
+
|
|
177
|
+
## How the template differs from the design
|
|
178
|
+
|
|
179
|
+
- A named workflow takes one watch trigger in this release, so each class is
|
|
180
|
+
a named workflow of its own. All of them run the workflow type `link`.
|
|
181
|
+
- The candidates by the name index and by meaning wait for work tokens that
|
|
182
|
+
reach those routes (N3). The keys and the neighbours find the candidates.
|
|
183
|
+
- A source turn sends its decided pairs in messages of at most 24,000
|
|
184
|
+
bytes; a larger message goes in parts (`.1`, `.2`, …) until blobs (G11).
|
|
185
|
+
- The things identity is an engine identity that `npm run setup` creates.
|
|
186
|
+
It reads the review items it filed to learn a person's decisions.
|
|
187
|
+
|
|
188
|
+
## Files
|
|
189
|
+
|
|
190
|
+
| File | Holds |
|
|
191
|
+
| --- | --- |
|
|
192
|
+
| `workflow.ts` | The workflow, the named workflows (`workflows()`), the ontology, the ports (`linkPorts()`) and `setup()` |
|
|
193
|
+
| `settings.ts` | The settings and the names of the parts, and the decided message |
|
|
194
|
+
| `names.ts` | Normal forms and block keys |
|
|
195
|
+
| `candidates.ts` | The candidate reads, the scoring and the bounds |
|
|
196
|
+
| `decide.ts` | The questions, the packing, the link rule and the rules without models |
|
|
197
|
+
| `things.ts` | The grouping with constraints, anchors, IRIs, merges and splits |
|
|
198
|
+
| `sources/records.ts` | Field roles, and the mentions of record fields |
|
|
199
|
+
| `source-turn.ts`, `things-turn.ts` | The turns of a source shard and of the things identity |
|
|
200
|
+
| `items.ts`, `reads.ts` | The review items, and the reads of the graph |
|
|
201
|
+
| `grade.ts`, `grade-run.ts` | The grading, and its command |
|
|
202
|
+
| `fixture.ts` | The test support: the graph in memory, mock models and ports, a reviewer that follows a gold file |
|
|
203
|
+
| `setup.ts`, `worker.ts`, `try.ts`, `samples.ts` | The commands |
|