strom-research 1.16.0 → 1.17.0-beta.3
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.cs.md +11 -0
- package/CHANGELOG.de.md +12 -0
- package/CHANGELOG.md +12 -0
- package/assets/lang/cs.json +8 -0
- package/assets/lang/de.json +8 -0
- package/dist/brief/brief.js +12 -6
- package/dist/cli/commit.js +38 -28
- package/dist/cli/context.js +2 -1
- package/dist/cli/format.js +8 -2
- package/dist/cli/main.js +9 -2
- package/dist/cli/ui.js +8 -0
- package/dist/commands/analysis.js +21 -8
- package/dist/commands/archives.js +2 -2
- package/dist/commands/batch.js +2 -2
- package/dist/commands/checks.js +3 -3
- package/dist/commands/clips.js +4 -4
- package/dist/commands/connectors.js +35 -22
- package/dist/commands/grep.js +3 -3
- package/dist/commands/hooks.js +2 -2
- package/dist/commands/intake.js +20 -24
- package/dist/commands/media.js +9 -9
- package/dist/commands/output.js +6 -6
- package/dist/commands/pack.js +2 -2
- package/dist/commands/read.js +2 -2
- package/dist/commands/readings.js +2 -2
- package/dist/commands/session.js +4 -4
- package/dist/commands/setup.js +25 -1
- package/dist/commands/transcripts.js +4 -4
- package/dist/commands/web.js +2 -2
- package/dist/core/fetching.js +50 -3
- package/dist/core/hypolinks.js +81 -7
- package/dist/core/integrity.js +2 -1
- package/dist/core/live.js +14 -4
- package/dist/core/places.js +3 -2
- package/dist/core/queue.js +12 -4
- package/dist/core/readstats.js +3 -1
- package/dist/core/self.js +41 -4
- package/dist/core/tree.js +1 -1
- package/package.json +4 -1
package/CHANGELOG.cs.md
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou na
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (anglicky).
|
|
7
7
|
|
|
8
|
+
## 1.17.0
|
|
9
|
+
|
|
10
|
+
*Připravuje se: bety do 1.17.0-beta.3. Oddíl bude úplný s vydáním.*
|
|
11
|
+
|
|
12
|
+
- **Nejdřív, co by hypotézy spojovaly.** Úkoly doplnit, co by spojovaly verze otevřené hypotézy, jdou ve frontě
|
|
13
|
+
první — i ve stávajícím výzkumu po aktualizaci —, takže aplikace Strom brzy ukáže tyto vazby.
|
|
14
|
+
- **Přerušené stahování se ohlásí a uklidí.** Stahování, jehož proces skončil uprostřed, se ohlásí jako přerušené,
|
|
15
|
+
s tím, co se uložilo a co chybí; jeho pracovní složka se uklidí.
|
|
16
|
+
- **Nový materiál v jazyce výzkumu.** Co přišlo (zaregistrovaný materiál, co přibylo, pravděpodobné duplicity,
|
|
17
|
+
jména po sňatku, úkoly), se vypíše v jazyce výzkumu.
|
|
18
|
+
|
|
8
19
|
## 1.16.0
|
|
9
20
|
|
|
10
21
|
*17. 10. 2026 · patří k aplikaci Strom 3.13.0.*
|
package/CHANGELOG.de.md
CHANGED
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Installationszeilen stehen bei
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (auf Englisch).
|
|
7
7
|
|
|
8
|
+
## 1.17.0
|
|
9
|
+
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.17.0-beta.3. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
|
+
|
|
12
|
+
- **Zuerst, was Hypothesen verbinden würden.** Die Aufgaben, zu ergänzen, was die Varianten einer offenen Hypothese
|
|
13
|
+
verbinden würden, kommen in der Warteschlange zuerst — auch in einer bestehenden Forschung nach dem Update —, so
|
|
14
|
+
zeigt die Strom-App diese Verbindungen bald.
|
|
15
|
+
- **Ein unterbrochener Download wird gemeldet und aufgeräumt.** Ein Download, dessen Prozess mittendrin endete, wird
|
|
16
|
+
als unterbrochen gemeldet, mit dem, was gespeichert ist und was fehlt; sein Arbeitsordner wird entfernt.
|
|
17
|
+
- **Neues Material in der Sprache der Forschung.** Was hereinkam (registriertes Material, was hinzukam, wahrscheinliche
|
|
18
|
+
Duplikate, Namen nach der Heirat, Aufgaben), wird in der Sprache der Forschung gesagt.
|
|
19
|
+
|
|
8
20
|
## 1.16.0
|
|
9
21
|
|
|
10
22
|
*17.10.2026 · gehört zur Strom-App 3.13.0.*
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ English · [Čeština](CHANGELOG.cs.md) · [Deutsch](CHANGELOG.de.md)
|
|
|
5
5
|
The changes of each version in short. The full notes and the install lines are on
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases).
|
|
7
7
|
|
|
8
|
+
## 1.17.0
|
|
9
|
+
|
|
10
|
+
*In preparation: the betas up to 1.17.0-beta.3. The section is complete with the release.*
|
|
11
|
+
|
|
12
|
+
- **What hypotheses would connect, first.** The tasks to fill in what the variants of an open hypothesis would
|
|
13
|
+
connect go first in the queue — also in an existing research after the update — so the Strom app can show those
|
|
14
|
+
links soon.
|
|
15
|
+
- **An interrupted download is said and tidied.** A download whose process ended midway is reported as interrupted,
|
|
16
|
+
with what was saved and what is missing; its work folder is cleared away.
|
|
17
|
+
- **New material reported in the research's language.** What came in (registered material, what was added, likely
|
|
18
|
+
duplicates, married names, tasks) is said in the language of the research.
|
|
19
|
+
|
|
8
20
|
## 1.16.0
|
|
9
21
|
|
|
10
22
|
*2026-10-17 · goes with the Strom app 3.13.0.*
|
package/assets/lang/cs.json
CHANGED
|
@@ -1549,6 +1549,13 @@
|
|
|
1549
1549
|
"ui.intake.tree.matched": " – už ve výzkumu: {n}",
|
|
1550
1550
|
"ui.intake.tree.back": "Je to rodokmen tohoto výzkumu, který se vrací: strom sync \"{file}\" ukáže i to, co se v něm změnilo.",
|
|
1551
1551
|
"ui.intake.same": "Už tu bylo dřív: {n}.",
|
|
1552
|
+
"ui.intake.registered": "✓ Podklady zaevidované ve výzkumu: {n}.",
|
|
1553
|
+
"ui.intake.tree.unreadable": " · nečitelné řádky: {n}",
|
|
1554
|
+
"ui.intake.extended": "Doplněno k tomu, co výzkum má (vodítka, k ověření): {list}",
|
|
1555
|
+
"ui.intake.dupes": "Nejspíš už v rodokmenu: {list} (vypíše je úkol kontroly)",
|
|
1556
|
+
"ui.intake.married": "{id}: příjmení po sňatku, která uvádí a která se při převzetí vynechala: {n} → strom input names {id}",
|
|
1557
|
+
"ui.intake.tasks": "Úkoly k rozebrání podkladů: {n} → strom task next",
|
|
1558
|
+
"ui.intake.handed": "{task} dostal {inputs} a je zpět ve frontě – soubor se přečte jako jeho další krok (bez vlastního úkolu k rozebrání).",
|
|
1552
1559
|
"ui.intake.chat": "Projít to s agentem hned?",
|
|
1553
1560
|
"ui.intake.say": "Do výzkumu jsem přidal(a) nové podklady – pojďme je projít.",
|
|
1554
1561
|
"ui.research.kind": "Co zkoumat?",
|
|
@@ -1944,6 +1951,7 @@
|
|
|
1944
1951
|
"ui.person.leadsonly": "jen vodítka",
|
|
1945
1952
|
"ui.person.events": "údaje: {n}",
|
|
1946
1953
|
"ui.list.more": "… dalších {n}: {command}",
|
|
1954
|
+
"ui.list.past": "(stránka {asked} není: poslední je {pages}, zobrazená výše)",
|
|
1947
1955
|
"ui.error.archive": "Tento výzkum je Archiv Stromu: data přicházejí z aplikace Strom a nic se nezkoumá. Výzkum se zapíná v menu, první položka.",
|
|
1948
1956
|
"ui.error.archive.hint": "nebo v terminálu: strom mode research",
|
|
1949
1957
|
"ui.error.agent.missing": "Výzkum čeká na AI agenta ({name}), který na tomto počítači není: do té doby drží data z aplikace Strom jako Archiv Stromu. Agenta nainstalovat nebo vybrat jiného: menu, první položka.",
|
package/assets/lang/de.json
CHANGED
|
@@ -1549,6 +1549,13 @@
|
|
|
1549
1549
|
"ui.intake.tree.matched": " – schon in der Forschung: {n}",
|
|
1550
1550
|
"ui.intake.tree.back": "Es ist der Stammbaum dieser Forschung, der zurückkommt: strom sync \"{file}\" zeigt auch, was darin geändert wurde.",
|
|
1551
1551
|
"ui.intake.same": "Schon vorher hier: {n}.",
|
|
1552
|
+
"ui.intake.registered": "✓ Unterlagen in der Forschung erfasst: {n}.",
|
|
1553
|
+
"ui.intake.tree.unreadable": " · unlesbare Zeilen: {n}",
|
|
1554
|
+
"ui.intake.extended": "Ergänzt zu dem, was die Forschung hat (Hinweise, zu prüfen): {list}",
|
|
1555
|
+
"ui.intake.dupes": "Wahrscheinlich schon im Stammbaum: {list} (die Prüfaufgabe listet sie auf)",
|
|
1556
|
+
"ui.intake.married": "{id}: Ehenamen, die sie nennt und die bei der Übernahme ausgelassen wurden: {n} → strom input names {id}",
|
|
1557
|
+
"ui.intake.tasks": "Aufgaben zum Auswerten der Unterlagen: {n} → strom task next",
|
|
1558
|
+
"ui.intake.handed": "{task} hat {inputs} bekommen und ist wieder in der Warteschlange – die Datei wird als nächster Schritt gelesen (keine eigene Aufgabe zum Auswerten).",
|
|
1552
1559
|
"ui.intake.chat": "Jetzt mit dem Agenten durchgehen?",
|
|
1553
1560
|
"ui.intake.say": "Ich habe der Forschung neue Unterlagen hinzugefügt – gehen wir sie durch.",
|
|
1554
1561
|
"ui.research.kind": "Was soll erforscht werden?",
|
|
@@ -1944,6 +1951,7 @@
|
|
|
1944
1951
|
"ui.person.leadsonly": "nur Hinweise",
|
|
1945
1952
|
"ui.person.events": "Angaben: {n}",
|
|
1946
1953
|
"ui.list.more": "… {n} weitere: {command}",
|
|
1954
|
+
"ui.list.past": "(keine Seite {asked}: die letzte ist {pages}, oben gezeigt)",
|
|
1947
1955
|
"ui.error.archive": "Diese Forschung ist ein Strom-Archiv: die Daten kommen aus der Strom-App, und nichts wird erforscht. Die Forschung wird im Menü eingeschaltet, erster Punkt.",
|
|
1948
1956
|
"ui.error.archive.hint": "oder im Terminal: strom mode research",
|
|
1949
1957
|
"ui.error.agent.missing": "Die Forschung wartet auf ihren KI-Agenten ({name}), der auf diesem Computer nicht ist: bis dahin bewahrt sie die Daten aus der Strom-App als Strom-Archiv. Den Agenten installieren oder einen anderen wählen: das Menü, erster Punkt.",
|
package/dist/brief/brief.js
CHANGED
|
@@ -39,7 +39,7 @@ import { ENRICH_PAGES, STORY_SOURCES_ORIGIN } from "../core/stories.js";
|
|
|
39
39
|
import { sandboxOf, WITHOUT_READERS } from "../core/sandbox.js";
|
|
40
40
|
import { WEB_SOFT } from "../core/metrics.js";
|
|
41
41
|
import { indexBriefLines } from "../core/indexfirst.js";
|
|
42
|
-
import { activeFetches, endedFetches } from "../core/fetching.js";
|
|
42
|
+
import { activeFetches, endedFetches, withRegistered } from "../core/fetching.js";
|
|
43
43
|
/** The hosts of a connector, as its manifest names them (a wildcard without its star). */
|
|
44
44
|
function connectorHosts(shared, name) {
|
|
45
45
|
try {
|
|
@@ -628,13 +628,19 @@ function imagesLines(tree, b, route, shape, finder, noSharper = false, waiting)
|
|
|
628
628
|
: ""),
|
|
629
629
|
].filter(Boolean);
|
|
630
630
|
}
|
|
631
|
-
/** The fetches at work for the research (each book: how far), and those
|
|
632
|
-
function fetchingLines(
|
|
631
|
+
/** The fetches at work for the research (each book: how far), and those that ended lately with images left (in the background, or interrupted). */
|
|
632
|
+
function fetchingLines(tree) {
|
|
633
|
+
const root = tree.root;
|
|
633
634
|
const active = activeFetches(root);
|
|
635
|
+
// an interrupted one: what it registered as the research has it
|
|
636
|
+
const has = (rs) => new Set(tree.list("media").filter((m) => m.recordset === rs && !m.part && !m.retracted && m.image !== undefined).map((m) => m.image));
|
|
634
637
|
const out = active.map((m) => `- ${m.recordset}: ${m.registered}/${m.images.length} images registered (${m.background ? `in the background${m.task ? `, for ${m.task}` : ""}` : "in the foreground"}) — strom read ${m.recordset} --images ${runsArg(m.images)} reads them as they come; not fetched again`);
|
|
635
|
-
for (const e of endedFetches(root))
|
|
638
|
+
for (const e of endedFetches(root).map((x) => withRegistered(x, has(x.recordset))))
|
|
636
639
|
if (e.missed.length && !active.some((m) => m.recordset === e.recordset))
|
|
637
|
-
out.push(
|
|
640
|
+
out.push(e.interrupted
|
|
641
|
+
? // its process ended without a word (killed, the computer stopped): what it registered stays
|
|
642
|
+
`- ${e.recordset}: a fetch ${e.background ? "in the background" : "in the foreground"} was interrupted (${e.interrupted === "gone" ? "its process ended without a word" : "nothing heard of it for long"}) with images ${runs(e.missed)} not fetched — strom fetch ${e.connector} ${shellArg(e.book)} --images ${runsArg(e.missed)} --recordset ${e.recordset}${e.background ? " --background" : ""} if they are needed (what is registered is not fetched again)`
|
|
643
|
+
: `- ${e.recordset}: a fetch in the background ended with images ${runs(e.missed)} not fetched${e.stopped ? ` (stopped: ${e.stopped})` : ""} — strom fetch ${e.connector} ${shellArg(e.book)} --images ${runsArg(e.missed)} --recordset ${e.recordset} if they are needed`);
|
|
638
644
|
return out;
|
|
639
645
|
}
|
|
640
646
|
/** What the user put in the shared inbox, waiting to be registered — by folder (one download each). */
|
|
@@ -1209,7 +1215,7 @@ export function buildBrief(tree, opts) {
|
|
|
1209
1215
|
});
|
|
1210
1216
|
// 7c. images being fetched for the research now (strom fetch --background of an earlier session, or another strom),
|
|
1211
1217
|
// and what fetches in the background left unfetched lately: read as they come, never fetched twice
|
|
1212
|
-
const fetching = fetchingLines(tree
|
|
1218
|
+
const fetching = fetchingLines(tree);
|
|
1213
1219
|
if (fetching.length)
|
|
1214
1220
|
sections.push({
|
|
1215
1221
|
name: "fetching",
|
package/dist/cli/commit.js
CHANGED
|
@@ -4,6 +4,7 @@ import { hasErrors } from "../core/check.js";
|
|
|
4
4
|
import { guard } from "../core/guard.js";
|
|
5
5
|
import { snapshot, verifyFast } from "../core/integrity.js";
|
|
6
6
|
import { isArchive } from "../core/mode.js";
|
|
7
|
+
import { counted } from "./format.js";
|
|
7
8
|
export function commitMessage(summaries, command) {
|
|
8
9
|
const unique = [...new Set(summaries)];
|
|
9
10
|
const subject = unique.length === 1 ? unique[0] : `${command}: ${unique.length} changes`;
|
|
@@ -17,34 +18,43 @@ export function autoCommit(ctx, def) {
|
|
|
17
18
|
const ops = tree?.uncommitted() ?? [];
|
|
18
19
|
if (!tree || tree.dryRun || ops.length === 0)
|
|
19
20
|
return undefined;
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
written.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
21
|
+
// The check and the commit under one hold of the tree (a command that locks only while it writes reaches here
|
|
22
|
+
// without it): another strom in the middle of its commit — staged, its log not yet in HEAD — would otherwise be seen
|
|
23
|
+
// half done, its records as edited outside strom (found: a fetch in the foreground ending while one in the
|
|
24
|
+
// background committed its last batch).
|
|
25
|
+
const blocked = tree.withTreeLock(() => {
|
|
26
|
+
// Incremental gate: records were validated when written; here only what
|
|
27
|
+
// changed since the last commit is checked, so the cost stays flat.
|
|
28
|
+
const snap = snapshot(tree);
|
|
29
|
+
const findings = [...verifyFast(tree, snap).findings, ...guard(tree, snap)];
|
|
30
|
+
if (hasErrors(findings)) {
|
|
31
|
+
const errors = findings.filter((f) => f.level === "error");
|
|
32
|
+
return {
|
|
33
|
+
message: [
|
|
34
|
+
`error: written but NOT committed — ${counted(errors.length, "problem")} in the data:`,
|
|
35
|
+
...errors.slice(0, 5).map((f) => ` ${f.id ?? f.file ?? ""} ${f.message}`),
|
|
36
|
+
"→ strom check",
|
|
37
|
+
].join("\n"),
|
|
38
|
+
problems: errors.map((f) => ({ ...(f.id ? { id: f.id } : {}), ...(f.file ? { file: f.file } : {}), message: f.message })),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const message = commitMessage(ops.map((o) => o.summary), `strom ${def.path.join(" ")}`);
|
|
42
|
+
// Only the files this command wrote, plus the free-form folders the agent
|
|
43
|
+
// may edit — `git add` then never has to scan the whole data/ tree.
|
|
44
|
+
const written = new Set(["data/_counters.json"]);
|
|
45
|
+
for (const op of ops)
|
|
46
|
+
for (const f of op.files)
|
|
47
|
+
written.add(f.path);
|
|
48
|
+
for (const f of tree.opsFilesTouched())
|
|
49
|
+
written.add(f);
|
|
50
|
+
// an archive: no agent edits its free-form folders — a person's own file in notes/ is theirs, never swept into a
|
|
51
|
+
// commit of something else (found on Windows: strom mode committed a file of the user's) — nor by the switch itself
|
|
52
|
+
const free = isArchive(tree) || def.path[0] === "mode" ? ["inputs", "output"] : ["notes", "tools", "inputs", "output"];
|
|
53
|
+
tree.commit(message, [...written, ...free]);
|
|
54
|
+
return undefined;
|
|
55
|
+
});
|
|
56
|
+
if (blocked)
|
|
57
|
+
return blocked;
|
|
48
58
|
tree.markCommitted();
|
|
49
59
|
tree.settle();
|
|
50
60
|
return undefined;
|
package/dist/cli/context.js
CHANGED
|
@@ -19,6 +19,7 @@ import { isAgent } from "../core/which.js";
|
|
|
19
19
|
import { treeAgentHere } from "../core/state.js";
|
|
20
20
|
import { canShowDialog, systemDialog, WAIT_SECONDS } from "../core/dialog.js";
|
|
21
21
|
import { UI, ui } from "./ui.js";
|
|
22
|
+
import { DEFAULT_LIMIT } from "./format.js";
|
|
22
23
|
export class Context {
|
|
23
24
|
env;
|
|
24
25
|
cwd;
|
|
@@ -46,7 +47,7 @@ export class Context {
|
|
|
46
47
|
this.json = opts.json;
|
|
47
48
|
this.yes = opts.yes;
|
|
48
49
|
this.treeFlag = opts.tree;
|
|
49
|
-
this.limit = opts.limit ??
|
|
50
|
+
this.limit = opts.limit ?? DEFAULT_LIMIT;
|
|
50
51
|
this.page = opts.page ?? 1;
|
|
51
52
|
}
|
|
52
53
|
/** A context from parsed command-line values (global options and settings flags). */
|
package/dist/cli/format.js
CHANGED
|
@@ -16,10 +16,12 @@ export function table(rows, opts = {}) {
|
|
|
16
16
|
.trimEnd())
|
|
17
17
|
.join("\n");
|
|
18
18
|
}
|
|
19
|
+
/** The rows of a page when --limit is not given. */
|
|
20
|
+
export const DEFAULT_LIMIT = 50;
|
|
19
21
|
export function paginate(items, limit, page) {
|
|
20
22
|
const pages = Math.max(1, Math.ceil(items.length / limit));
|
|
21
23
|
const p = Math.min(Math.max(1, page), pages);
|
|
22
|
-
return { items: items.slice((p - 1) * limit, p * limit), total: items.length, page: p, pages, limit };
|
|
24
|
+
return { items: items.slice((p - 1) * limit, p * limit), total: items.length, page: p, pages, limit, ...(page > pages && items.length ? { asked: page } : {}) };
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* The most rows a page of a long listing gives, whatever --limit says: a row is at most ~170 bytes, a page under ~20 kB
|
|
@@ -28,10 +30,14 @@ export function paginate(items, limit, page) {
|
|
|
28
30
|
export const LIST_PAGE_MAX = 100;
|
|
29
31
|
/** Footer telling the caller how to see more — in the research's language where the listing is (lang), else English. */
|
|
30
32
|
export function moreLine(page, command, lang) {
|
|
33
|
+
// a page past the last: the last one shown, and said so (found on Windows: --page 99 gave the last page silently)
|
|
34
|
+
if (page.asked)
|
|
35
|
+
return lang ? ui(lang, "ui.list.past", { asked: String(page.asked), pages: String(page.pages) }) : `(no page ${page.asked}: the last is ${page.pages}, shown above)`;
|
|
31
36
|
if (page.page >= page.pages)
|
|
32
37
|
return "";
|
|
33
38
|
const left = page.total - page.page * page.limit;
|
|
34
|
-
|
|
39
|
+
// the next page of the same size: a --limit of its own goes along (found on Windows: --page 2 alone took 50 rows)
|
|
40
|
+
const next = `${command}${page.limit !== DEFAULT_LIMIT ? ` --limit ${page.limit}` : ""} --page ${page.page + 1}`;
|
|
35
41
|
return lang ? ui(lang, "ui.list.more", { n: String(left), command: next }) : `… ${left} more: ${next}`;
|
|
36
42
|
}
|
|
37
43
|
export function truncate(text, max) {
|
package/dist/cli/main.js
CHANGED
|
@@ -16,7 +16,7 @@ import { assertIntact } from "../core/integrity.js";
|
|
|
16
16
|
import { resetCache } from "../core/git.js";
|
|
17
17
|
import { newerTree, Tree, VERSION } from "../core/tree.js";
|
|
18
18
|
import { settleArchive } from "../core/mode.js";
|
|
19
|
-
import { settleHypothesisLinks } from "../core/hypolinks.js";
|
|
19
|
+
import { raiseHypothesisLinkTasks, settleHypothesisLinks } from "../core/hypolinks.js";
|
|
20
20
|
import { liveRunning, reviveLive, startLive } from "../core/live.js";
|
|
21
21
|
import { fireHooks, HOOK_INTERFACE } from "../core/hooks.js";
|
|
22
22
|
import { prependPath } from "../runners/runner.js";
|
|
@@ -314,7 +314,14 @@ export async function main(argv, io, env, cwd) {
|
|
|
314
314
|
catch {
|
|
315
315
|
// another computer's seal, a tree at work: its next bridge or switch settles it
|
|
316
316
|
}
|
|
317
|
-
// …and
|
|
317
|
+
// …and the tasks an older strom made to say those links come first in the queue (once), then older hypotheses get
|
|
318
|
+
// the links of their variants their claims say beyond doubt, the rest a task (once)
|
|
319
|
+
try {
|
|
320
|
+
raiseHypothesisLinkTasks(Tree.open(k.root, env));
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// another computer's seal, a tree at work: its bridge's start raises them
|
|
324
|
+
}
|
|
318
325
|
try {
|
|
319
326
|
settleHypothesisLinks(Tree.open(k.root, env));
|
|
320
327
|
}
|
package/dist/cli/ui.js
CHANGED
|
@@ -1462,6 +1462,13 @@ export const UI = {
|
|
|
1462
1462
|
"ui.intake.tree.matched": " – already in the research: {n}",
|
|
1463
1463
|
"ui.intake.tree.back": "It is this research's own family tree coming back: strom sync \"{file}\" shows what was changed in it too.",
|
|
1464
1464
|
"ui.intake.same": "Already here before: {n}.",
|
|
1465
|
+
"ui.intake.registered": "✓ Material registered in the research: {n}.",
|
|
1466
|
+
"ui.intake.tree.unreadable": " · unreadable lines: {n}",
|
|
1467
|
+
"ui.intake.extended": "Added to what the research has (leads, to be checked): {list}",
|
|
1468
|
+
"ui.intake.dupes": "Probably in the tree already: {list} (the review task lists them)",
|
|
1469
|
+
"ui.intake.married": "{id}: married names it gives, left out when it was imported: {n} → strom input names {id}",
|
|
1470
|
+
"ui.intake.tasks": "Tasks to go through the material: {n} → strom task next",
|
|
1471
|
+
"ui.intake.handed": "{task} got {inputs} and is back in the queue — the file is read as its next step (no task of its own to go through it).",
|
|
1465
1472
|
"ui.intake.chat": "Go through it with the agent now?",
|
|
1466
1473
|
"ui.intake.say": "I added new material to the research — let us go through it.",
|
|
1467
1474
|
"ui.research.kind": "What should be found out?",
|
|
@@ -1748,6 +1755,7 @@ export const UI = {
|
|
|
1748
1755
|
"ui.person.leadsonly": "leads only",
|
|
1749
1756
|
"ui.person.events": "{n} ev",
|
|
1750
1757
|
"ui.list.more": "… {n} more: {command}",
|
|
1758
|
+
"ui.list.past": "(no page {asked}: the last is {pages}, shown above)",
|
|
1751
1759
|
"ui.error.archive": "This research is a Strom archive: its data come from the Strom app and nothing is researched. Research is switched on in the menu, its first item.",
|
|
1752
1760
|
"ui.error.archive.hint": "or in a terminal: strom mode research",
|
|
1753
1761
|
"ui.error.agent.missing": "The research waits for its AI agent ({name}), which is not on this computer: until then it keeps the data from the Strom app as a Strom archive. Installing the agent or choosing another: the menu, its first item.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// search · searched · conflict · hypothesis · lesson · find · gaps
|
|
2
2
|
import { register } from "../cli/registry.js";
|
|
3
3
|
import { ui } from "../cli/ui.js";
|
|
4
|
-
import { lines, moreLine, paginate, table, truncate } from "../cli/format.js";
|
|
4
|
+
import { counted, lines, moreLine, paginate, table, truncate } from "../cli/format.js";
|
|
5
5
|
import { UsageError } from "../core/errors.js";
|
|
6
6
|
import { LESSON_DETAIL_MAX, LESSON_MAX, LESSON_SCOPES, RECORD_TYPES, SEARCH_METHODS, SEARCH_RESULTS, VARIANT_LINK_KINDS, } from "../core/model.js";
|
|
7
7
|
import { create, csvOpt, listOpt, normId, requireRecord, update } from "../core/records.js";
|
|
@@ -19,6 +19,7 @@ import { childLink, LINK_HOW, linkHints, linkText, personsLink, recordedLinks, r
|
|
|
19
19
|
import { yearsOption, yearsOverlap } from "../core/years.js";
|
|
20
20
|
import { searchedAs } from "../core/evidence.js";
|
|
21
21
|
import { refuseNegativeOverUnclear } from "./readings.js";
|
|
22
|
+
import { hypothesisPeople } from "../core/directions.js";
|
|
22
23
|
function written(tree) {
|
|
23
24
|
return lines(...tree.written.map((o) => o.summary));
|
|
24
25
|
}
|
|
@@ -72,6 +73,17 @@ function conflictConcerns(tree, id) {
|
|
|
72
73
|
(c.edit !== undefined && (ids.has(c.edit.event) || (c.edit.cites ?? []).some((ci) => ids.has(ci.source)))) ||
|
|
73
74
|
(c.parents !== undefined && [c.parents.child, c.parents.from, c.parents.to ?? "", ...c.parents.partners].some((s) => ids.has(s)));
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether a hypothesis is about a person or family (strom hypothesis list --about): named in its subject, among the
|
|
78
|
+
* people it is about (hypothesisPeople: its subject's, those its variants would connect or name), or in what its
|
|
79
|
+
* variants' links have named; a person also through the families they are a partner of.
|
|
80
|
+
*/
|
|
81
|
+
function hypothesisConcerns(tree, id) {
|
|
82
|
+
const ids = new Set([id, ...(typeOfId(id) === "person" ? familiesAsPartner(tree, id).map((f) => f.id) : [])]);
|
|
83
|
+
return (h) => h.subject.some((s) => ids.has(s)) ||
|
|
84
|
+
hypothesisPeople(tree, h).some((p) => ids.has(p)) ||
|
|
85
|
+
h.variants.some((v) => (v.linked ?? []).some((x) => ids.has(x)) || (v.links ?? []).some((l) => l.kind === "child" && l.family !== undefined && ids.has(l.family)));
|
|
86
|
+
}
|
|
75
87
|
/** The tasks that work on a conflict or hypothesis (--about X…/H…). */
|
|
76
88
|
function tasksAbout(tree, id) {
|
|
77
89
|
return tree.list("task").filter((t) => t.subject.includes(id));
|
|
@@ -380,7 +392,7 @@ register({
|
|
|
380
392
|
// a negative of an index is the index's: the book itself not searched in vain by it
|
|
381
393
|
const ofIndex = hits.filter((s) => s.result === "negative" && s.method === "index").length;
|
|
382
394
|
const text = hits.length
|
|
383
|
-
? lines(`${hits.length
|
|
395
|
+
? lines(`${counted(hits.length, "search", "searches")} for ${what}${years ? ` in ${years}` : ""} — ${negative} negative${ofIndex ? ` (${ofIndex} in an index only — not the book searched in vain)` : ""}, ${hits.length - negative} with results`, table(hits.map(searchLine)))
|
|
384
396
|
: `nothing searched yet for ${what}${years ? ` in ${years}` : ""}`;
|
|
385
397
|
return { text: lines(text, ...said), data: { searches: hits } };
|
|
386
398
|
},
|
|
@@ -753,21 +765,22 @@ register({
|
|
|
753
765
|
options: [
|
|
754
766
|
{ name: "all", type: "boolean", description: "include decided and abandoned" },
|
|
755
767
|
{ name: "unlinked", type: "boolean", description: "only those no variant of which says what it would connect (strom hypothesis link), but those whose every variant naming people says what the tree records already" },
|
|
768
|
+
{ name: "about", type: "string", value: "<P…|F…>", description: "only the hypotheses about this person or family: its subject, or the people and families its variants would connect" },
|
|
756
769
|
],
|
|
757
|
-
examples: ["strom hypothesis list", "strom hypothesis list --unlinked"],
|
|
770
|
+
examples: ["strom hypothesis list", "strom hypothesis list --unlinked", "strom hypothesis list --about P0001"],
|
|
758
771
|
run(ctx, { opts }) {
|
|
759
772
|
const tree = ctx.tree();
|
|
760
773
|
// a hypothesis whose variants say only what the tree records already has nothing to link
|
|
761
774
|
const linked = (h) => h.variants.some((v) => v.links?.length) || (h.variants.some((v) => recordedLinks(tree, h, v).length) && !h.variants.some((v) => toLink(tree, h, v)));
|
|
762
|
-
const
|
|
775
|
+
const about = typeof opts.about === "string" ? anyRefs(tree, [opts.about])[0] : undefined;
|
|
776
|
+
const concerns = about ? hypothesisConcerns(tree, about) : undefined;
|
|
777
|
+
const all = tree.list("hypothesis").filter((h) => (opts.all || h.state === "open") && !(opts.unlinked && linked(h)) && (!concerns || concerns(h)));
|
|
763
778
|
const links = (h) => h.variants.reduce((n, v) => n + (v.links?.length ?? 0), 0);
|
|
764
779
|
return {
|
|
765
780
|
text: all.length
|
|
766
781
|
? table(all.map((h) => [h.id, h.state, truncate(h.question, 60), h.variants.map((v) => v.label).join("/"), links(h) ? `${links(h)} link${links(h) === 1 ? "" : "s"}` : ""]))
|
|
767
|
-
: opts.unlinked
|
|
768
|
-
|
|
769
|
-
: "no open hypotheses",
|
|
770
|
-
data: { hypotheses: all },
|
|
782
|
+
: `no ${opts.all ? "" : "open "}${opts.unlinked ? "hypothesis without links" : about ? "hypothesis" : "hypotheses"}${about ? ` about ${about} → all of them: strom hypothesis list${opts.all ? "" : " --all"}` : ""}`,
|
|
783
|
+
data: { ...(about ? { about } : {}), hypotheses: all },
|
|
771
784
|
};
|
|
772
785
|
},
|
|
773
786
|
}, {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { register } from "../cli/registry.js";
|
|
8
8
|
import { ui } from "../cli/ui.js";
|
|
9
|
-
import { lines, secondsIn, shellArg, table } from "../cli/format.js";
|
|
9
|
+
import { counted, lines, secondsIn, shellArg, table } from "../cli/format.js";
|
|
10
10
|
import { EXIT, StromError, UsageError } from "../core/errors.js";
|
|
11
11
|
import { isAgent } from "../core/which.js";
|
|
12
12
|
import { canShowDialog, systemChoice, WAIT_SECONDS } from "../core/dialog.js";
|
|
@@ -558,7 +558,7 @@ register({
|
|
|
558
558
|
return { text: `${hosts.join(", ")}: kept as before — ${milder.map((p) => `${p.connector} ${p.from}`).join(", ")}`, data: { hosts, kept: kept.map((c) => c.name) } };
|
|
559
559
|
}
|
|
560
560
|
if (opts.accept) {
|
|
561
|
-
ctx.requireHuman(`Accept what ${connectors.map((c) => c.name).join(", ")} say
|
|
561
|
+
ctx.requireHuman(`Accept what ${connectors.map((c) => c.name).join(", ")} ${connectors.length === 1 ? "says" : "say"} of ${hosts.join(", ")} now (${connectors.map((c) => claimOf(c)).join(", ")}), its pace too?`, `strom allow archive ${target} --accept`, `archive:accept:${hosts.join(",")}`, ui(lang, "ui.archive.accept", { names: connectors.map((c) => `${c.manifest.title} (${levelWords(lang, claimOf(c))})`).join(", ") }));
|
|
562
562
|
acceptConnectors(ctx.env, connectors);
|
|
563
563
|
return { text: `${hosts.join(", ")}: the connectors' readings accepted — ${connectors.map((c) => `${c.name} ${claimOf(c)}`).join(", ")}`, data: { hosts, accepted: connectors.map((c) => c.name) } };
|
|
564
564
|
}
|
package/dist/commands/batch.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// creates; later lines use it as "@name".
|
|
9
9
|
import fs from "node:fs";
|
|
10
10
|
import { register } from "../cli/registry.js";
|
|
11
|
-
import { lines, table } from "../cli/format.js";
|
|
11
|
+
import { counted, lines, table } from "../cli/format.js";
|
|
12
12
|
import { AmbiguousError, StromError, UsageError } from "../core/errors.js";
|
|
13
13
|
/** A label: a word in any script ("anna", "šimon", "otec_2"), its accents composed or not. */
|
|
14
14
|
const LABEL = "[\\p{L}_][\\p{L}\\p{M}\\p{N}_-]*";
|
|
@@ -254,7 +254,7 @@ register({
|
|
|
254
254
|
throw errors[0];
|
|
255
255
|
if (errors.length)
|
|
256
256
|
throw manyWrong(errors, batch.length);
|
|
257
|
-
const text = lines(table(report.flatMap((r) => r.summaries.map((s, i) => [i === 0 ? `${r.line}` : "", s, i === 0 && r.label ? `#${r.label}` : ""]))), `${report.length
|
|
257
|
+
const text = lines(table(report.flatMap((r) => r.summaries.map((s, i) => [i === 0 ? `${r.line}` : "", s, i === 0 && r.label ? `#${r.label}` : ""]))), `${counted(report.length, "command")} as one change`);
|
|
258
258
|
return { text, data: { lines: report, labels: Object.fromEntries(labels) } };
|
|
259
259
|
},
|
|
260
260
|
});
|
package/dist/commands/checks.js
CHANGED
|
@@ -4,7 +4,7 @@ import { actsAsArchive } from "../core/state.js";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { register } from "../cli/registry.js";
|
|
7
|
-
import { lines, table } from "../cli/format.js";
|
|
7
|
+
import { counted, lines, table } from "../cli/format.js";
|
|
8
8
|
import { check } from "../core/check.js";
|
|
9
9
|
import { guard } from "../core/guard.js";
|
|
10
10
|
import { unprovenPeople } from "../core/review.js";
|
|
@@ -21,7 +21,7 @@ export function report(findings, okText, lang) {
|
|
|
21
21
|
const sum = { errors: errors.length, warnings: findings.length - errors.length };
|
|
22
22
|
const text = findings.length === 0
|
|
23
23
|
? okText
|
|
24
|
-
: lines(table(findings.map((f) => [f.level === "error" ? "ERROR" : "warn", f.code, f.id ?? f.file ?? "", f.message, f.hint ? `→ ${f.hint}` : ""])), "", lang ? ui(lang, "ui.check.found", sum) : `${sum.errors
|
|
24
|
+
: lines(table(findings.map((f) => [f.level === "error" ? "ERROR" : "warn", f.code, f.id ?? f.file ?? "", f.message, f.hint ? `→ ${f.hint}` : ""])), "", lang ? ui(lang, "ui.check.found", sum) : `${counted(sum.errors, "error")}, ${counted(sum.warnings, "warning")}`);
|
|
25
25
|
return { text, data: { ok: errors.length === 0, findings }, exitCode: errors.length ? 1 : 0 };
|
|
26
26
|
}
|
|
27
27
|
register({
|
|
@@ -127,7 +127,7 @@ register({
|
|
|
127
127
|
const after = verifyFast(tree).findings.filter((f) => f.level === "error");
|
|
128
128
|
const text = r.restored.length + r.quarantined.length === 0
|
|
129
129
|
? "nothing to repair — every record matches its seal"
|
|
130
|
-
: lines(...r.restored.map((f) => `restored ${f}`), ...r.quarantined.map((f) => `removed ${f}`), `sealed state: ${r.from.slice(0, 8)} · copies of replaced files: .strom/quarantine/`, after.length ? `still ${after.length
|
|
130
|
+
: lines(...r.restored.map((f) => `restored ${f}`), ...r.quarantined.map((f) => `removed ${f}`), `sealed state: ${r.from.slice(0, 8)} · copies of replaced files: .strom/quarantine/`, after.length ? `still ${counted(after.length, "problem")} → strom verify` : undefined);
|
|
131
131
|
return { text, data: { ...r, ok: after.length === 0 }, exitCode: after.length ? 1 : 0 };
|
|
132
132
|
},
|
|
133
133
|
}, {
|
package/dist/commands/clips.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { register } from "../cli/registry.js";
|
|
9
|
-
import { lines, truncate } from "../cli/format.js";
|
|
9
|
+
import { counted, lines, truncate } from "../cli/format.js";
|
|
10
10
|
import { UsageError } from "../core/errors.js";
|
|
11
11
|
import { normId, requireRecord, update } from "../core/records.js";
|
|
12
12
|
import { makeView } from "../core/views.js";
|
|
@@ -90,10 +90,10 @@ register({
|
|
|
90
90
|
throw new UsageError("--batch must be a positive number");
|
|
91
91
|
const groups = batches(todo, size);
|
|
92
92
|
if (!todo.length)
|
|
93
|
-
return { text: lines("nothing to do: every source on images has its clip", away.length ? `${away.length
|
|
93
|
+
return { text: lines("nothing to do: every source on images has its clip", away.length ? `${counted(away.length, "source")} on images not on this computer (or not JPEG/PNG)` : undefined), data: { sources: 0 } };
|
|
94
94
|
if (tree.dryRun)
|
|
95
95
|
return {
|
|
96
|
-
text: lines(`${todo.length
|
|
96
|
+
text: lines(`${counted(todo.length, "source")} to find on their images — ${counted(groups.length, "reader")} to find them, as many to check`, away.length ? `${away.length} more on images not on this computer (or not JPEG/PNG)` : undefined, "(dry run — no reader started)"),
|
|
97
97
|
data: { sources: todo.length, readers: groups.length, away: away.map((s) => s.id) },
|
|
98
98
|
};
|
|
99
99
|
const r0 = readers(ctx, tree, shared, "clips", "find", opts);
|
|
@@ -174,7 +174,7 @@ register({
|
|
|
174
174
|
// a search over many entries, not one entry: no clip, and said so
|
|
175
175
|
const many = [...new Set(locs.filter((l) => l.result === "many").map((l) => l.source))];
|
|
176
176
|
const missed = todo.filter((s) => !clipsOf.has(s.id));
|
|
177
|
-
const text = lines(`${clipsOf.size} of ${todo.length
|
|
177
|
+
const text = lines(`${clipsOf.size} of ${counted(todo.length, "source")} got their clip${model ? ` · ${model}` : ""}${r0.cost() ? ` · $${r0.cost().toFixed(2)}` : ""}`, many.length ? `not one entry but a search over many (no clip — a page of it would show nothing): ${many.join(", ")}` : undefined, rejected.length ? `the check turned down: ${rejected.map((v) => `${v.source} (${v.verdict}${v.why ? `: ${truncate(v.why, 40)}` : ""})`).join(", ")}` : undefined, missed.length ? `without a clip: ${missed.map((s) => s.id).slice(0, 20).join(", ")}${missed.length > 20 ? ` … (${missed.length})` : ""} — strom source edit S… --clip M…@x,y,w,h, or strom clips again` : undefined, away.length ? `on images not on this computer (or not JPEG/PNG): ${away.length}` : undefined, failed.length ? `readers that failed: ${failed.join(", ")}` : undefined, "the entries cut out for the Strom app: strom app (output/tree-strom.ged)");
|
|
178
178
|
return {
|
|
179
179
|
text,
|
|
180
180
|
data: {
|