strom-research 1.13.2-beta.3 → 1.13.2-beta.5
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/assets/lang/cs.json +3 -0
- package/assets/lang/de.json +3 -0
- package/assets/method/core.md +22 -2
- package/assets/method/reading.md +27 -12
- package/assets/method/recording.md +7 -3
- package/dist/agents/files.js +8 -1
- package/dist/agents/images.js +5 -0
- package/dist/agents/profiles.js +7 -0
- package/dist/agents/scanreader.js +96 -0
- package/dist/brief/brief.js +400 -56
- package/dist/brief/sheet.js +86 -18
- package/dist/cli/execute.js +159 -6
- package/dist/cli/main.js +7 -4
- package/dist/cli/ui.js +3 -0
- package/dist/commands/analysis.js +5 -2
- package/dist/commands/batch.js +1 -1
- package/dist/commands/clips.js +1 -1
- package/dist/commands/connectors.js +29 -14
- package/dist/commands/media.js +21 -14
- package/dist/commands/people.js +4 -4
- package/dist/commands/read.js +8 -3
- package/dist/commands/readings.js +11 -4
- package/dist/commands/research.js +5 -3
- package/dist/commands/session.js +42 -9
- package/dist/commands/show.js +1 -17
- package/dist/commands/sources.js +1 -1
- package/dist/core/assets.js +47 -10
- package/dist/core/people.js +5 -2
- package/dist/core/reader.js +2 -1
- package/dist/core/records.js +17 -2
- package/dist/core/tree.js +1 -1
- package/dist/core/views.js +4 -2
- package/dist/runners/claude.js +3 -0
- package/package.json +1 -1
package/assets/lang/cs.json
CHANGED
|
@@ -1641,12 +1641,15 @@
|
|
|
1641
1641
|
"ui.arg.year": "rok",
|
|
1642
1642
|
"ui.error.arg.extra": "nečekaný argument „{arg}“",
|
|
1643
1643
|
"ui.error.arg.extra.hint": "strom help {cmd} — hodnotu s mezerami dát do uvozovek",
|
|
1644
|
+
"ui.error.arg.extra.fix.hint": "správně: {fix}",
|
|
1645
|
+
"ui.error.arg.extra.several.hint": "více záznamů: {line}",
|
|
1644
1646
|
"ui.error.option.is-arg": "{opt} se u {cmd} píše jako argument, ne jako volba",
|
|
1645
1647
|
"ui.error.option.is-arg.hint": "{cmd} <{arg}>",
|
|
1646
1648
|
"ui.error.option.unknown": "neznámá volba {opt} u {cmd}",
|
|
1647
1649
|
"ui.error.option.unknown.hint": "volby: {options} · strom help {path}",
|
|
1648
1650
|
"ui.error.option.unknown.none": "neznámá volba {opt} u {cmd}",
|
|
1649
1651
|
"ui.error.option.unknown.none.hint": "{help}",
|
|
1652
|
+
"ui.error.option.unknown.status.hint": "jistota (--status) patří jen k údaji (E…), nikdy ke jménu ani k rodině: strom cite E… S… --status probable|proven, strom event edit E… --status …",
|
|
1650
1653
|
"ui.error.option.no-value": "{opt} se píše bez hodnoty",
|
|
1651
1654
|
"ui.error.option.needs-value": "{opt} potřebuje hodnotu {value}",
|
|
1652
1655
|
"ui.error.option.needs-value.hint": "strom help {path}",
|
package/assets/lang/de.json
CHANGED
|
@@ -1641,12 +1641,15 @@
|
|
|
1641
1641
|
"ui.arg.year": "Jahr",
|
|
1642
1642
|
"ui.error.arg.extra": "unerwartetes Argument „{arg}“",
|
|
1643
1643
|
"ui.error.arg.extra.hint": "strom help {cmd} — Werte mit Leerzeichen in Anführungszeichen",
|
|
1644
|
+
"ui.error.arg.extra.fix.hint": "richtig: {fix}",
|
|
1645
|
+
"ui.error.arg.extra.several.hint": "mehrere Datensätze: {line}",
|
|
1644
1646
|
"ui.error.option.is-arg": "{opt} ist bei {cmd} ein Argument, keine Option",
|
|
1645
1647
|
"ui.error.option.is-arg.hint": "{cmd} <{arg}>",
|
|
1646
1648
|
"ui.error.option.unknown": "unbekannte Option {opt} für {cmd}",
|
|
1647
1649
|
"ui.error.option.unknown.hint": "Optionen: {options} · strom help {path}",
|
|
1648
1650
|
"ui.error.option.unknown.none": "unbekannte Option {opt} für {cmd}",
|
|
1649
1651
|
"ui.error.option.unknown.none.hint": "{help}",
|
|
1652
|
+
"ui.error.option.unknown.status.hint": "eine Sicherheit (--status) gehört nur zu einer Angabe (E…), nie zu einem Namen oder einer Familie: strom cite E… S… --status probable|proven, strom event edit E… --status …",
|
|
1650
1653
|
"ui.error.option.no-value": "{opt} hat keinen Wert",
|
|
1651
1654
|
"ui.error.option.needs-value": "{opt} braucht einen Wert {value}",
|
|
1652
1655
|
"ui.error.option.needs-value.hint": "strom help {path}",
|
package/assets/method/core.md
CHANGED
|
@@ -4,11 +4,14 @@ You work to the Genealogical Proof Standard: search thoroughly, cite every
|
|
|
4
4
|
fact, analyse and correlate the evidence, resolve conflicts, and write the
|
|
5
5
|
conclusion so the next researcher can follow it.
|
|
6
6
|
|
|
7
|
+
<!-- for link verify enrich intake locate narrate -->
|
|
7
8
|
- **Certainty is explicit.** `proven` — you read the original record yourself
|
|
8
9
|
and it states the fact directly. `probable` — good evidence, but indirect or a
|
|
9
10
|
single reading of hard handwriting. `possible` — weak evidence. `lead` — a
|
|
10
11
|
family story, a family tree, an index or a guess: something to check, never a
|
|
11
12
|
fact. Family trees and memories are leads, always.
|
|
13
|
+
<!-- end -->
|
|
14
|
+
<!-- for link verify enrich intake locate -->
|
|
12
15
|
- **A namesake is not your person.** Identify by year, place, house, occupation,
|
|
13
16
|
spouse and parents — the mother's maiden name decides most cases. Two entries
|
|
14
17
|
that give one name different parents (another mother) are **two people** —
|
|
@@ -20,25 +23,36 @@ conclusion so the next researcher can follow it.
|
|
|
20
23
|
- **Negative results are results.** Every search is recorded (`strom search add
|
|
21
24
|
… --result negative`), with exactly what was covered. The most expensive
|
|
22
25
|
mistake is to search the same book twice.
|
|
26
|
+
<!-- end -->
|
|
23
27
|
- **Check the premise first.** Before opening anything, read the brief's
|
|
24
28
|
*Already known* — the searches of the task's books, people and surnames;
|
|
25
29
|
`strom searched <where>` only for a book, place or years it does not list.
|
|
30
|
+
Work that answers another open task as well closes it with yours:
|
|
31
|
+
`strom session close --done "…" --next "…" --also-done T…`.
|
|
32
|
+
<!-- for link verify enrich intake -->
|
|
26
33
|
- **Extract everything the first time** you open a record: names, ages, house
|
|
27
34
|
numbers, occupations, godparents, witnesses, midwife, remarks in the margin.
|
|
28
35
|
Opening the same page again later costs more than writing it down now.
|
|
36
|
+
<!-- end -->
|
|
37
|
+
<!-- for link verify enrich intake locate -->
|
|
29
38
|
- **Nothing is deleted.** A wrong fact is retracted with a reason; a changed
|
|
30
39
|
fact is edited with a reason. Contradictions become conflicts, competing
|
|
31
40
|
explanations become hypotheses — never silently pick one.
|
|
41
|
+
<!-- end -->
|
|
32
42
|
- **Write as you go.** Record each finding as soon as you have it. Your context
|
|
33
43
|
can be cut or summarised at any moment, and a session working alone is
|
|
34
44
|
stopped at its time limit (the brief says when; near the end strom's output
|
|
35
45
|
counts down): what is only in the conversation is lost, what is in strom is
|
|
36
|
-
not.
|
|
46
|
+
not.
|
|
47
|
+
<!-- for link verify enrich intake -->
|
|
48
|
+
Going through many images, record each stretch when it is done (every
|
|
37
49
|
ten images, each district or year): the finds, and `strom search add …
|
|
38
50
|
--pages` for what was searched in vain — never all of it at the end.
|
|
39
51
|
A fact of a person goes on the person (`event add`); a note or the diary is
|
|
40
52
|
for what is not a fact yet. What you held back while who it was stayed a
|
|
41
53
|
hypothesis goes on the person when you decide it.
|
|
54
|
+
<!-- end -->
|
|
55
|
+
<!-- for link verify enrich intake locate -->
|
|
42
56
|
- **Lessons belong where they apply.** A quirk of a register (its calibration,
|
|
43
57
|
two years per page, a hand that writes 7 like 1) goes to `strom lesson add
|
|
44
58
|
--on B…`, so whoever opens that book next sees it.
|
|
@@ -50,6 +64,8 @@ conclusion so the next researcher can follow it.
|
|
|
50
64
|
queue holds back every other task about people off the tree
|
|
51
65
|
(`strom task list --off-tree`); they come back by themselves once the link
|
|
52
66
|
is recorded.
|
|
67
|
+
<!-- end -->
|
|
68
|
+
<!-- for link verify enrich intake -->
|
|
53
69
|
- **Say what a hypothesis would connect.** A hypothesis about a connection —
|
|
54
70
|
whose child, the same person, a couple, siblings — gets each variant's links
|
|
55
71
|
when you record or argue it: `strom hypothesis link H… B --child P… --of F…`
|
|
@@ -63,11 +79,15 @@ conclusion so the next researcher can follow it.
|
|
|
63
79
|
(`strom hypothesis variant H… "<claim>"`). Deciding one whose variants have
|
|
64
80
|
links names the variant it is decided for:
|
|
65
81
|
`strom hypothesis decide H… --variant B --decision "…"`.
|
|
82
|
+
<!-- end -->
|
|
66
83
|
- **Stay within the task.** New questions become new tasks (`strom task add`,
|
|
67
|
-
with where and done-when), not detours.
|
|
84
|
+
with where and done-when), not detours.
|
|
85
|
+
<!-- for link verify enrich intake locate -->
|
|
86
|
+
A new task whose images are not here
|
|
68
87
|
gets them now: through the archive's connector (built first when it has
|
|
69
88
|
none), or — where the archive allows only that — it waits at once for what
|
|
70
89
|
the user should download (`strom task wait T… --images B…:<numbers> --on
|
|
71
90
|
"…"`); a later session would only find that out.
|
|
91
|
+
<!-- end -->
|
|
72
92
|
- **Hand over cleanly.** Close with a summary of what was proven, what was
|
|
73
93
|
searched in vain, and the next cheapest step.
|
package/assets/method/reading.md
CHANGED
|
@@ -13,7 +13,11 @@ about 80 views write down what you found before you open more.
|
|
|
13
13
|
|
|
14
14
|
- **Look only through views**: `strom media view B0001:57` (record set and
|
|
15
15
|
image number) makes a file in `.strom/views/` — open that file. Whole images
|
|
16
|
-
come reduced:
|
|
16
|
+
(and `--grid`) come reduced: for finding the entry, never for reading it.
|
|
17
|
+
**Transcribe only from a crop** at full resolution (`--crop`, or `--half`
|
|
18
|
+
where the entry fills the page). A whole view unclear, or the entry not where
|
|
19
|
+
expected: `--half both` before calling it not found — a negative search only
|
|
20
|
+
after the pages were seen at full resolution.
|
|
17
21
|
- **The views of a scan, or of a batch, in one call**, then open all the files
|
|
18
22
|
it lists together — not one call and one look per view:
|
|
19
23
|
`strom media view B0001:57-60 --half both` (the two pages of each double
|
|
@@ -26,11 +30,13 @@ about 80 views write down what you found before you open more.
|
|
|
26
30
|
width, height as fractions) or `--half left|right` for one page of a spread.
|
|
27
31
|
A crop comes at full resolution (small ones enlarged). `--contrast` for faded
|
|
28
32
|
ink, `--rotate 90` for sideways pages.
|
|
33
|
+
<!-- for link verify enrich part -->
|
|
29
34
|
- **Too small to read?** When a crop says it is enlarged, the scan has no more
|
|
30
35
|
detail there. A connector that can fetch a part of an image sharper says so
|
|
31
36
|
in that line: `strom fetch <connector> --recordset B0001 --images 57 --crop
|
|
32
37
|
…` (one request). Then view the same crop again: it comes from the sharper
|
|
33
38
|
part by itself.
|
|
39
|
+
<!-- end -->
|
|
34
40
|
- **Browsing a book is a reader's job**: `strom read B0001 --images 40-69
|
|
35
41
|
--question "…"` — readers in batches of ten, each with the full question.
|
|
36
42
|
Their reports stay in notes/readings/; `strom read` ends with what they
|
|
@@ -44,14 +50,14 @@ about 80 views write down what you found before you open more.
|
|
|
44
50
|
It waits for its readers (often ten minutes or more): run it in the
|
|
45
51
|
foreground and let it finish — your session ends with your turn, and
|
|
46
52
|
whatever is left running in the background ends with it.
|
|
47
|
-
(
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
(A scan-reader subagent where your agent has one, strom-scan-reader, or your
|
|
54
|
+
own subagents — on your own model, never a faster one — read too, in batches
|
|
55
|
+
of at most twelve with the whole question; what they report is lost with
|
|
56
|
+
your session unless you write it down: record each report as it comes back,
|
|
57
|
+
before you send the next readers.) Only the entries
|
|
50
58
|
that will be cited need your own eyes, at full resolution.
|
|
51
59
|
- Old handwriting is decoded, not copied: never a weaker model for handwriting,
|
|
52
60
|
never a guess. "Illegible" is a valid and valuable answer.
|
|
53
|
-
- **Extract everything the first time**: names, ages, house numbers,
|
|
54
|
-
occupations, godparents, witnesses, midwife, remarks in the margin.
|
|
55
61
|
- **Report image by image, as you go**: the image and page, what was found (or
|
|
56
62
|
nothing), what was illegible and where, the hand, how sure each name is.
|
|
57
63
|
- **Cite the image, and where the entry is on it**: `strom source add …
|
|
@@ -61,12 +67,16 @@ about 80 views write down what you found before you open more.
|
|
|
61
67
|
numbering jumps) were not searched: say so in `--note` (strom read puts the
|
|
62
68
|
gaps its readers saw there), or leave them out of the range
|
|
63
69
|
(`--pages 40-52,55-69`).
|
|
70
|
+
<!-- for verify enrich intake -->
|
|
64
71
|
- Page ↔ image: `strom recordset calibrate B0001 --point 57=112` (measured on
|
|
65
72
|
the image, never guessed); then `strom media view B0001 --page 112` works.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
<!-- end -->
|
|
74
|
+
<!-- for link verify enrich connector -->
|
|
75
|
+
- **No images here yet:** the book's connector fetches them through strom
|
|
76
|
+
(the brief's record set gives the exact `strom fetch`) — only the images you
|
|
77
|
+
need, never a whole book "just in case".
|
|
78
|
+
<!-- end -->
|
|
79
|
+
<!-- for link verify enrich no-connector -->
|
|
70
80
|
- **The archive has no connector yet: build one — now, you.** The user does
|
|
71
81
|
not know connectors exist and will not ask for one. Tell them in a sentence
|
|
72
82
|
("for this archive I am preparing a downloader; it will fetch only the images
|
|
@@ -74,10 +84,14 @@ about 80 views write down what you found before you open more.
|
|
|
74
84
|
follow its DISCOVERY.md (the portal's terms and robots.txt first, official
|
|
75
85
|
exports preferred), test it with `strom connector test`, then fetch. It is
|
|
76
86
|
built once per archive and serves every later task.
|
|
87
|
+
<!-- end -->
|
|
77
88
|
- You never download from an archive yourself (curl, a script, your browser
|
|
78
|
-
tools) — only through a connector, paced by strom.
|
|
89
|
+
tools) — only through a connector, paced by strom.
|
|
90
|
+
<!-- for link verify enrich no-connector by-hand -->
|
|
91
|
+
The user saves images by
|
|
79
92
|
hand only where the archive does not allow automation (the connector then
|
|
80
|
-
finds books and gives links only)
|
|
93
|
+
finds books and gives links only), where its connector only finds books, or
|
|
94
|
+
where a check stops it. Ask with
|
|
81
95
|
`strom task wait T… --images B0001:40-69 --on "…"`: strom makes the folder
|
|
82
96
|
of the inbox they go into, shows it to the user with the book's link, and
|
|
83
97
|
checks the numbers of what arrives. Write `--on` for the user, in their
|
|
@@ -88,3 +102,4 @@ about 80 views write down what you found before you open more.
|
|
|
88
102
|
the entry and save that view too (40a.jpg); a full-resolution scan can be
|
|
89
103
|
ordered from the archive. Then take the next task; when the images are
|
|
90
104
|
registered the task comes back by itself.
|
|
105
|
+
<!-- end -->
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Record a found entry in one batch: write the lines to a file in notes/ and run
|
|
4
4
|
`strom batch --file notes/<file>` — all or nothing: when a line fails nothing is
|
|
5
|
-
written and every failing line is said at once; fix them
|
|
6
|
-
|
|
5
|
+
written and every failing line is said at once; fix them in the file with your
|
|
6
|
+
file-editing tool, not with sed or another shell command (the tree's rules let
|
|
7
|
+
the shell run strom, not edit files), run it again. `#name` labels what a line
|
|
8
|
+
creates, `@name` uses it later; a found search comes after the source it found
|
|
9
|
+
(`--found @s`).
|
|
7
10
|
|
|
8
11
|
source add "Baptism of Jan Novák 1885" --kind baptism --recordset B0001 --clip B0001:57@0.05,0.40,0.45,0.18 --locator "pag. 112, entry 2" --language la --information primary --transcript @notes/entry.txt #s
|
|
9
12
|
event add P0001 CHR --date "25 JUN 1885" --place "Týnec" --house 13 --cite @s --quote "baptizatus est" --with "godparent:Marie Dvořáková" --with "midwife:Anna Nová" --with "officiant:P. Josef Kříž" --status proven
|
|
@@ -22,7 +25,8 @@ written and every failing line is said at once; fix them, run it again.
|
|
|
22
25
|
- `--transcript` is the entry's words as they stand in it — its own language,
|
|
23
26
|
spelling and abbreviations, line by line, unread letters `[?]`; the Strom app
|
|
24
27
|
shows them next to the entry's image. Every entry read gets them.
|
|
25
|
-
- A fact already in the tree gets the citation (`cite E
|
|
28
|
+
- A fact already in the tree gets the citation (`cite E…`, `--status` is a
|
|
29
|
+
fact's only — a name or a family cited takes none), not a second fact —
|
|
26
30
|
and what the record adds to it: `event edit E… --age husband:27 --age wife:17
|
|
27
31
|
--house 21 --with "witness:…" --with "officiant:…"` (filling in needs no reason).
|
|
28
32
|
- The names a record gives go on the person: `name add` (a maiden name completes
|
package/dist/agents/files.js
CHANGED
|
@@ -16,6 +16,7 @@ import { agentAppUrl, appSite } from "../core/stromapp.js";
|
|
|
16
16
|
import { configDir } from "../core/paths.js";
|
|
17
17
|
import { agentBrowser } from "../core/connector.js";
|
|
18
18
|
import { CHROME_ALLOW, CHROME_DENY, chromeDomain } from "../core/browser.js";
|
|
19
|
+
import { claudeScanReader, opencodeScanReader, SCAN_READER } from "./scanreader.js";
|
|
19
20
|
export const MARKER = "<!-- strom: generated above this line (strom agents sync); your own notes below are kept -->";
|
|
20
21
|
function agentsMd(tree) {
|
|
21
22
|
const lang = langName(tree.config.lang);
|
|
@@ -313,6 +314,8 @@ export function opencodeConfig(tree) {
|
|
|
313
314
|
description: "strom run: the agent working alone, nobody to ask — what the rules do not allow is refused",
|
|
314
315
|
permission: refused(permission),
|
|
315
316
|
},
|
|
317
|
+
// reading scans: the shell for strom and the file reader, nothing else
|
|
318
|
+
[SCAN_READER]: opencodeScanReader(tree, permission, refused),
|
|
316
319
|
},
|
|
317
320
|
};
|
|
318
321
|
}
|
|
@@ -343,6 +346,7 @@ function dropAgentFiles(tree) {
|
|
|
343
346
|
["AGENTS.md", agentsMd(tree)],
|
|
344
347
|
["CLAUDE.md", claudeMd(tree)],
|
|
345
348
|
[path.join(".claude", "settings.json"), JSON.stringify(claudeSettings(tree), null, 2) + "\n"],
|
|
349
|
+
[CLAUDE_SCAN_READER, claudeScanReader(tree)],
|
|
346
350
|
["opencode.json", JSON.stringify(opencodeConfig(tree), null, 2) + "\n"],
|
|
347
351
|
[path.join(".grok", "config.toml"), grokConfig(tree)],
|
|
348
352
|
[path.join(".grok", "rules", "strom.md"), GROK_RULES],
|
|
@@ -370,7 +374,9 @@ function dropAgentFiles(tree) {
|
|
|
370
374
|
}
|
|
371
375
|
return gone;
|
|
372
376
|
}
|
|
373
|
-
|
|
377
|
+
/** Claude Code's subagent for reading scans, in the tree. */
|
|
378
|
+
export const CLAUDE_SCAN_READER = path.join(".claude", "agents", `${SCAN_READER}.md`);
|
|
379
|
+
export const AGENT_FILES = ["AGENTS.md", "CLAUDE.md", path.join(".claude", "settings.json"), CLAUDE_SCAN_READER, "opencode.json", path.join(".grok", "config.toml"), path.join(".grok", "rules", "strom.md")];
|
|
374
380
|
export function syncAgentFiles(tree) {
|
|
375
381
|
// an archive: no agent works on it, and nothing of one shows in its folder (Milan's decision, 2026-10-03) — strom's own
|
|
376
382
|
// files of the agents go (what a person wrote in them stays); research switched on writes them again
|
|
@@ -396,6 +402,7 @@ export function syncAgentFiles(tree) {
|
|
|
396
402
|
write("AGENTS.md", agentsMd(tree));
|
|
397
403
|
write("CLAUDE.md", claudeMd(tree));
|
|
398
404
|
write(path.join(".claude", "settings.json"), JSON.stringify(claudeSettings(tree), null, 2) + "\n");
|
|
405
|
+
write(CLAUDE_SCAN_READER, claudeScanReader(tree));
|
|
399
406
|
write("opencode.json", JSON.stringify(opencodeConfig(tree), null, 2) + "\n");
|
|
400
407
|
write(path.join(".grok", "config.toml"), grokConfig(tree));
|
|
401
408
|
write(path.join(".grok", "rules", "strom.md"), GROK_RULES);
|
package/dist/agents/images.js
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
export const IMAGE_MAX = 1568;
|
|
11
11
|
/** What the newer models take in whole, and Claude Code, Codex and OpenCode pass on unshrunk. */
|
|
12
12
|
export const IMAGE_MAX_LARGE = 2000;
|
|
13
|
+
/**
|
|
14
|
+
* The long side of a whole image shown to find an entry on it (strom media view without --half, --crop or --split):
|
|
15
|
+
* the entry is found as surely as at 2000 px for half the tokens; it is read from a crop or a half at the model's size.
|
|
16
|
+
*/
|
|
17
|
+
export const OVERVIEW_MAX = 1400;
|
|
13
18
|
/** A Claude model name or alias ("opus", "claude-opus-4-7", "claude-sonnet-5-5[1m]", "us.anthropic.claude-…"): its family and version. */
|
|
14
19
|
export function claudeModel(model) {
|
|
15
20
|
const m = /(opus|sonnet|haiku|fable|mythos)(?:[-.]?(\d{1,2})(?:[-.](\d{1,2})(?!\d))?(?!\d))?/.exec(model.toLowerCase());
|
package/dist/agents/profiles.js
CHANGED
|
@@ -35,6 +35,10 @@ was), then open the next. Every image and view stays in your context to the end:
|
|
|
35
35
|
views, write down what you found and go on. A name or place the next search depends on:
|
|
36
36
|
read it again on a crop at full resolution (\`strom media view … --crop\`)
|
|
37
37
|
before you record it.
|
|
38
|
+
OpenCode: this tree's subagent strom-scan-reader (strom and the file reader
|
|
39
|
+
only) reads on your model, or on the one the user set for handwriting: hand
|
|
40
|
+
it a batch with the whole question, and record what it returns at once (a
|
|
41
|
+
negative result as a search by reader).
|
|
38
42
|
`;
|
|
39
43
|
/**
|
|
40
44
|
* The agents strom can start working alone without the user's personal add-ons (agent.addons): Claude Code
|
|
@@ -63,6 +67,9 @@ comes back to you. Choose the model by the kind of work:
|
|
|
63
67
|
| mechanical: downloads, renaming, counting, a plain grep | \`${m.cheap ?? "haiku"}\` |
|
|
64
68
|
| judgement: identity, conflicts, which task next, writing to strom | nobody — you |
|
|
65
69
|
|
|
70
|
+
Scans are read by this tree's subagent type \`strom-scan-reader\` (strom and the
|
|
71
|
+
file reader only, so it starts small); the model above still holds.
|
|
72
|
+
|
|
66
73
|
${DELEGATION_RULES}
|
|
67
74
|
- Send independent batches in ONE message so they run in parallel.
|
|
68
75
|
- Do not read a subagent's transcript or output file — its images would come
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// The scan reader: a subagent of its own kind for reading scans, in the agents that let a tree define one (Claude Code:
|
|
2
|
+
// .claude/agents/ and --agents; OpenCode: an agent of mode subagent in opencode.json). It has the shell (for strom) and
|
|
3
|
+
// the file reader (for its views) and nothing else — no subagents of its own, no web, no editing — and a short prompt,
|
|
4
|
+
// so it starts small (a subagent of the agent's general kind starts with every tool and instruction the agent has).
|
|
5
|
+
// Its model is the user's model.vision where they set one, else the model of the agent that starts it: never a
|
|
6
|
+
// cheaper one picked by strom. Codex, Antigravity and Grok Build get none: Codex's agent roles cannot narrow its tools
|
|
7
|
+
// and Antigravity has no such setting; Grok Build reads scans itself (profiles.ts), as the tree tells it.
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { Settings } from "../core/config.js";
|
|
10
|
+
import { writeFileAtomic } from "../core/json.js";
|
|
11
|
+
export const SCAN_READER = "strom-scan-reader";
|
|
12
|
+
export const SCAN_READER_DESCRIPTION = "Reads scans of old records for this family research (registers, indexes, land books, any old handwriting): give it the images (B0001:57-66, M0012) and the whole question — what counts as a find, which years and names. It opens the views through strom, reads them and returns what it read, image by image. It never writes to the research.";
|
|
13
|
+
export const SCAN_READER_PROMPT = `You read scans of old records for a family research kept with strom. You are given images
|
|
14
|
+
(B0001:57-66, M0012…) and the whole question: what counts as a find, which years and names. You read
|
|
15
|
+
and report; you never write to the research and decide nothing.
|
|
16
|
+
|
|
17
|
+
Views: run strom by its command — \`strom media view B0001:57-60 --half both\` (at most 12 images and
|
|
18
|
+
24 views a call); an entry to read closely: \`--grid\` to see where it is, then \`--crop x,y,w,h\` at
|
|
19
|
+
full resolution; \`--contrast\` for faded ink. Open ALL the views a call lists in ONE message — every
|
|
20
|
+
Read call together, never one view at a time — then read them, then the next call. Every view stays
|
|
21
|
+
in your context: after about 80 views, stop and report what you have. \`strom read …\` hands a range
|
|
22
|
+
to strom's own readers: only when the question asks for it (in the foreground; let it finish).
|
|
23
|
+
|
|
24
|
+
Reading: transcribe as written — the record's language, spelling and abbreviations, not modernised;
|
|
25
|
+
[?] for each uncertain letter or word; never complete a name or date from what the question expects.
|
|
26
|
+
"Illegible" beats a guess; "nothing" is a valid result.
|
|
27
|
+
|
|
28
|
+
Return, image by image (image and page): found / nothing / unclear; each entry that answers the
|
|
29
|
+
question word for word, where it stands and its columns (date, house, names, parents, godparents,
|
|
30
|
+
witnesses, remarks); what was illegible and where; the hand; how sure you are of each name. Say
|
|
31
|
+
plainly which images you did not open or read whole — never report an image you did not read as
|
|
32
|
+
searched.
|
|
33
|
+
`;
|
|
34
|
+
/** The model the user chose for reading handwriting (model.vision: flag, env, tree, user config) — none: the starting agent's. */
|
|
35
|
+
export function scanReaderModel(tree, agent) {
|
|
36
|
+
const r = new Settings(tree.env, {}).resolve("model.vision", tree.config, agent);
|
|
37
|
+
return r ? String(r.value) : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Claude Code's tools for it: the shell (on Windows its PowerShell too, where the tree's rules allow strom as well) and the file reader. */
|
|
40
|
+
export function scanReaderTools(platform = process.platform) {
|
|
41
|
+
return platform === "win32" ? ["Bash", "PowerShell", "Read"] : ["Bash", "Read"];
|
|
42
|
+
}
|
|
43
|
+
/** .claude/agents/strom-scan-reader.md: a conversation (the terminal, the desktop app) finds it in the tree. */
|
|
44
|
+
export function claudeScanReader(tree) {
|
|
45
|
+
return `---
|
|
46
|
+
name: ${SCAN_READER}
|
|
47
|
+
description: ${JSON.stringify(SCAN_READER_DESCRIPTION)}
|
|
48
|
+
tools: ${scanReaderTools().join(", ")}
|
|
49
|
+
model: ${JSON.stringify(scanReaderModel(tree, "claude") ?? "inherit")}
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
${SCAN_READER_PROMPT}`;
|
|
53
|
+
}
|
|
54
|
+
/** The same for --agents: a run is given it on the command line, whatever Claude Code makes of the folder's files. */
|
|
55
|
+
export function claudeAgentsJson(tree) {
|
|
56
|
+
return {
|
|
57
|
+
[SCAN_READER]: {
|
|
58
|
+
description: SCAN_READER_DESCRIPTION,
|
|
59
|
+
prompt: SCAN_READER_PROMPT,
|
|
60
|
+
tools: scanReaderTools(),
|
|
61
|
+
model: scanReaderModel(tree, "claude") ?? "inherit",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** The file a headless run of Claude Code is given (--agents): in .strom/, written afresh for each run. */
|
|
66
|
+
export function claudeAgentsFile(tree) {
|
|
67
|
+
const file = path.join(tree.root, ".strom", "claude-agents.json");
|
|
68
|
+
writeFileAtomic(file, JSON.stringify(claudeAgentsJson(tree), null, 2) + "\n");
|
|
69
|
+
return file;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* OpenCode's agent of mode subagent: the tree's rules with everything but reading and strom refused — no editing, no
|
|
73
|
+
* web, no subagents, no questions (nothing that would ask: a run nobody watches would end at it).
|
|
74
|
+
*/
|
|
75
|
+
export function opencodeScanReader(tree, permission, refused) {
|
|
76
|
+
const model = scanReaderModel(tree, "opencode");
|
|
77
|
+
const bash = permission.bash;
|
|
78
|
+
const only = {
|
|
79
|
+
...permission,
|
|
80
|
+
bash: { ...bash, "*": "deny" },
|
|
81
|
+
edit: "deny",
|
|
82
|
+
webfetch: "deny",
|
|
83
|
+
websearch: "deny",
|
|
84
|
+
task: "deny",
|
|
85
|
+
question: "deny",
|
|
86
|
+
skill: "deny",
|
|
87
|
+
todowrite: "deny",
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
mode: "subagent",
|
|
91
|
+
description: SCAN_READER_DESCRIPTION,
|
|
92
|
+
prompt: SCAN_READER_PROMPT,
|
|
93
|
+
...(model ? { model } : {}),
|
|
94
|
+
permission: refused(only),
|
|
95
|
+
};
|
|
96
|
+
}
|