strom-research 1.25.1 → 1.25.2
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 +28 -0
- package/CHANGELOG.de.md +37 -0
- package/CHANGELOG.md +30 -0
- package/CHANGELOG.pl.md +32 -0
- package/assets/lang/cs.json +7 -0
- package/assets/lang/de.json +7 -0
- package/assets/lang/pl.json +7 -0
- package/assets/plugins/gates/claude-usage/gate.json +1 -1
- package/assets/plugins/gates/claude-usage/gate.ts +174 -24
- package/assets/terms/streets.json +214 -20
- package/dist/brief/places.js +40 -5
- package/dist/cli/ui.js +1 -0
- package/dist/commands/browse.js +11 -1
- package/dist/commands/history.js +4 -2
- package/dist/commands/placeid.js +100 -23
- package/dist/commands/session.js +10 -2
- package/dist/commands/sources.js +5 -13
- package/dist/commands/start.js +5 -2
- package/dist/commands/sync.js +2 -2
- package/dist/core/claudedir.js +59 -3
- package/dist/core/gate.js +3 -3
- package/dist/core/people.js +1 -1
- package/dist/core/phrases.js +6 -0
- package/dist/core/placeid.js +733 -79
- package/dist/core/placesview.js +1 -0
- package/dist/core/schema.js +3 -0
- package/dist/core/session.js +5 -0
- package/dist/core/streetfix.js +24 -1
- package/dist/core/streets.js +64 -5
- package/dist/core/sync.js +24 -13
- package/dist/core/tree.js +1 -1
- package/dist/runners/runner.js +3 -3
- package/package.json +1 -1
package/dist/core/placesview.js
CHANGED
|
@@ -60,6 +60,7 @@ function view(k, p) {
|
|
|
60
60
|
...(why ? { why } : {}),
|
|
61
61
|
...(loc?.source ? { source: loc.source } : {}),
|
|
62
62
|
...(loc?.verified ? { verified: loc.verified } : {}),
|
|
63
|
+
...(loc?.verifiedSource ? { verifiedSource: loc.verifiedSource } : {}),
|
|
63
64
|
...(loc?.checkWhy ? { checkWhy: loc.checkWhy } : {}),
|
|
64
65
|
...(!p.coords && p.unlocated && loc?.needs ? { needs: loc.needs } : {}),
|
|
65
66
|
...(q && (state === "proposed" || state === "conflict") ? { waiting: p.id } : {}),
|
package/dist/core/schema.js
CHANGED
|
@@ -77,7 +77,10 @@ export const SCHEMAS = {
|
|
|
77
77
|
why: S({ max: 1000 }),
|
|
78
78
|
source: { t: "object", of: { title: S({ req: true, max: 300 }), url: S({ max: 2000 }) } },
|
|
79
79
|
verified: S(),
|
|
80
|
+
verifiedWhy: S({ max: 1000 }),
|
|
81
|
+
verifiedSource: { t: "object", of: { title: S({ req: true, max: 300 }), url: S({ max: 2000 }) } },
|
|
80
82
|
checkWhy: S({ max: 1000 }),
|
|
83
|
+
checked: S(),
|
|
81
84
|
needs: S({ max: 300 }),
|
|
82
85
|
context: { t: "number", int: true, min: 0 },
|
|
83
86
|
kept: { t: "object", of: { lat: { t: "number", req: true, min: -90, max: 90 }, lon: { t: "number", req: true, min: -180, max: 180 } } },
|
package/dist/core/session.js
CHANGED
|
@@ -22,6 +22,7 @@ import { forgetLeftAlone } from "./leftalone.js";
|
|
|
22
22
|
import { Settings } from "./config.js";
|
|
23
23
|
import { forGaps, regionOfTask, takeRegion } from "./pastevents.js";
|
|
24
24
|
import { placeOfTask, takePlace } from "./placehistory.js";
|
|
25
|
+
import { PLACES_IDENTIFY_ORIGIN, placesSessionCounts } from "./placeid.js";
|
|
25
26
|
function currentFile(tree, env = tree.env) {
|
|
26
27
|
const w = env.STROM_WORKER;
|
|
27
28
|
return path.join(tree.root, ".strom", isWorkerId(w) ? `session-${w}.json` : "session.json");
|
|
@@ -236,6 +237,10 @@ export function closeSession(tree, s, input) {
|
|
|
236
237
|
tree.put(back, { op: "task.continue", targets: [task.id, s.id], summary: `${task.id} back to the queue` });
|
|
237
238
|
}
|
|
238
239
|
}
|
|
240
|
+
// a session on the places: what it did counted by strom (identified, verified, asked …) before the agent's words
|
|
241
|
+
const counts = task?.origin === PLACES_IDENTIFY_ORIGIN ? placesSessionCounts(tree, s.id) : "";
|
|
242
|
+
if (counts)
|
|
243
|
+
input = { ...input, summary: input.summary.trim() ? `${counts} — ${input.summary.trim()}` : counts };
|
|
239
244
|
const closed = {
|
|
240
245
|
...s,
|
|
241
246
|
state: input.interrupted ? "interrupted" : "closed",
|
package/dist/core/streetfix.js
CHANGED
|
@@ -44,7 +44,7 @@ export function streetPlaceFixes(tree) {
|
|
|
44
44
|
towns.set(b.key, t);
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
|
-
const place = streets.townOf(s.town);
|
|
47
|
+
const place = streets.townOf(s.town, s.sentence);
|
|
48
48
|
const had = e.house?.trim();
|
|
49
49
|
// the house: the street and the number where the fact has none, the street before a number alone
|
|
50
50
|
const house = houseWith(s.house, had);
|
|
@@ -56,6 +56,29 @@ export function streetPlaceFixes(tree) {
|
|
|
56
56
|
out.fixes.push({ owner: o.id, event: e.id, before: placeHouse(e.place, e.house), after: placeHouse(place, house), ...(record ? { record } : {}) });
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
// what an older fix left of a sentence in the place ("v Lhotici v" of "v Lhotici v čp. 46", before prepositions were
|
|
60
|
+
// known): the place as the rules split it now, the fact's house kept — only where the fact is as that fix left it
|
|
61
|
+
const ownerOf = new Map();
|
|
62
|
+
for (const o of owners(tree)) {
|
|
63
|
+
if (o.retracted || o.mergedInto)
|
|
64
|
+
continue;
|
|
65
|
+
for (const e of o.events)
|
|
66
|
+
if (!e.retracted && e.status !== "retracted")
|
|
67
|
+
ownerOf.set(e.id, { owner: o, place: e.place, house: e.house });
|
|
68
|
+
}
|
|
69
|
+
const taken = new Set(out.fixes.map((f) => f.event));
|
|
70
|
+
for (const it of readStreetsFile(tree).runs.flatMap((r) => r.items)) {
|
|
71
|
+
const now = ownerOf.get(it.event);
|
|
72
|
+
if (!now || taken.has(it.event) || !same(now.place, now.house, it.after))
|
|
73
|
+
continue;
|
|
74
|
+
const s = streets.split(it.before.place);
|
|
75
|
+
const place = s ? streets.townOf(s.town, s.sentence) : undefined;
|
|
76
|
+
if (!place || place === it.after.place)
|
|
77
|
+
continue;
|
|
78
|
+
const record = recordOf(place);
|
|
79
|
+
out.fixes.push({ owner: now.owner.id, event: it.event, before: it.after, after: placeHouse(place, it.after.house), ...(record ? { record } : {}) });
|
|
80
|
+
taken.add(it.event);
|
|
81
|
+
}
|
|
59
82
|
out.towns = [...towns]
|
|
60
83
|
.map(([key, t]) => ({ town: t.town, key, fixes: t.fixes, places: [...t.places].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([p]) => p) }))
|
|
61
84
|
.sort((a, b) => b.fixes.length - a.fixes.length || a.town.localeCompare(b.town));
|
package/dist/core/streets.js
CHANGED
|
@@ -36,6 +36,7 @@ export function streetTerms() {
|
|
|
36
36
|
last: new Set((f.last ?? []).map((w) => foldText(w))),
|
|
37
37
|
house: (f.house ?? []).map(words).filter((w) => w.length).sort((a, b) => b.length - a.length),
|
|
38
38
|
numbered: new Set((f.numbered ?? []).map((w) => foldText(w))),
|
|
39
|
+
prepositions: new Set(Object.values(f.prepositions ?? {}).flat().map((w) => foldText(w))),
|
|
39
40
|
};
|
|
40
41
|
return terms;
|
|
41
42
|
}
|
|
@@ -50,6 +51,10 @@ function markerAt(toks, i) {
|
|
|
50
51
|
return 0;
|
|
51
52
|
}
|
|
52
53
|
const isNumber = (t) => !!t && NUMBER.test(t.fold);
|
|
54
|
+
/** A house that says it is a house already ("č. p. 12", "Nr. 3", "nr domu 12"): a marker of a house number at its beginning. */
|
|
55
|
+
export function hasHouseMark(text) {
|
|
56
|
+
return markerAt(tokens(text.normalize("NFC")), 0) > 0;
|
|
57
|
+
}
|
|
53
58
|
/** A house of numbers alone ("12", "13/2"). */
|
|
54
59
|
export function numbersOnly(text) {
|
|
55
60
|
const toks = tokens(text.normalize("NFC"));
|
|
@@ -119,6 +124,7 @@ export function splitStreet(place, known) {
|
|
|
119
124
|
const keep = [];
|
|
120
125
|
const house = [];
|
|
121
126
|
let by = "word";
|
|
127
|
+
let sentence = false;
|
|
122
128
|
for (const [i, part] of parts.entries()) {
|
|
123
129
|
const toks = tokens(part);
|
|
124
130
|
if (!toks.length)
|
|
@@ -150,7 +156,12 @@ export function splitStreet(place, known) {
|
|
|
150
156
|
}
|
|
151
157
|
// a house number with its marker at the end of a part ("Lhota č. p. 12", "Lhota, Horní Lhota Nr. 3"): the number
|
|
152
158
|
if (tail && tail.at < tail.number && tail.at > 0) {
|
|
153
|
-
|
|
159
|
+
const words = sentenceWords(part, toks.slice(0, tail.at));
|
|
160
|
+
if (!words.town)
|
|
161
|
+
continue;
|
|
162
|
+
if (words.sentence)
|
|
163
|
+
sentence = true;
|
|
164
|
+
keep.push(words.town);
|
|
154
165
|
house.push(part.slice(toks[tail.number].start).trim());
|
|
155
166
|
continue;
|
|
156
167
|
}
|
|
@@ -158,7 +169,23 @@ export function splitStreet(place, known) {
|
|
|
158
169
|
}
|
|
159
170
|
if (!house.length || !keep.length || !keep[0])
|
|
160
171
|
return undefined;
|
|
161
|
-
return { town: keep.join(", "), house: house.join(" "), by };
|
|
172
|
+
return { town: keep.join(", "), house: house.join(" "), by, ...(sentence ? { sentence: true } : {}) };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The words before a house number without the sentence they stood in: a preposition at the end taken off ("v Lhotici v"
|
|
176
|
+
* of "v Lhotici v čp. 46"), and one in lower case at the beginning ("v Lhotici" → "Lhotici"; "U Lípy", a name, stays).
|
|
177
|
+
*/
|
|
178
|
+
function sentenceWords(part, toks) {
|
|
179
|
+
const prep = streetTerms().prepositions;
|
|
180
|
+
let a = 0;
|
|
181
|
+
let b = toks.length;
|
|
182
|
+
while (b > a + 1 && prep.has(toks[b - 1].fold))
|
|
183
|
+
b--;
|
|
184
|
+
const first = toks[a];
|
|
185
|
+
const word = part.slice(first.start, first.end);
|
|
186
|
+
if (b > a + 1 && prep.has(first.fold) && word === word.toLowerCase() && word !== word.toUpperCase())
|
|
187
|
+
a++;
|
|
188
|
+
return { town: part.slice(toks[a].start, toks[b - 1].end).replace(/[\s,;:–-]+$/u, "").trim(), sentence: a > 0 || b < toks.length };
|
|
162
189
|
}
|
|
163
190
|
/**
|
|
164
191
|
* A number alone at the end of the place's first part, after words, with no marker ("Lhota 16", "Lhota 16, okres
|
|
@@ -272,6 +299,11 @@ export function streetsOf(tree) {
|
|
|
272
299
|
}
|
|
273
300
|
for (const t of known.keys())
|
|
274
301
|
partOf(t);
|
|
302
|
+
// the municipalities' names as the research writes them, by their name folded (the facts' spelling first)
|
|
303
|
+
const named = new Map();
|
|
304
|
+
for (const f of spellings.keys())
|
|
305
|
+
if (!f.includes(","))
|
|
306
|
+
named.set(f, f);
|
|
275
307
|
for (const r of tree.list("place")) {
|
|
276
308
|
if (r.retracted)
|
|
277
309
|
continue;
|
|
@@ -280,6 +312,9 @@ export function streetsOf(tree) {
|
|
|
280
312
|
partOf(n.name.split(",")[0]);
|
|
281
313
|
else
|
|
282
314
|
towns.add(foldText(n.name.split(",")[0]));
|
|
315
|
+
const f = foldText(n.name);
|
|
316
|
+
if (!f.includes(",") && !named.has(f))
|
|
317
|
+
named.set(f, n.name.normalize("NFC").trim());
|
|
283
318
|
}
|
|
284
319
|
}
|
|
285
320
|
const bare = (place) => {
|
|
@@ -295,7 +330,7 @@ export function streetsOf(tree) {
|
|
|
295
330
|
return { ...b, key, ...(a ? { houses: a.houses } : {}) };
|
|
296
331
|
};
|
|
297
332
|
const most = (m) => [...m].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0][0];
|
|
298
|
-
const townOf = (town) => {
|
|
333
|
+
const townOf = (town, sentence) => {
|
|
299
334
|
const f = foldText(town);
|
|
300
335
|
const exact = spellings.get(f);
|
|
301
336
|
if (exact)
|
|
@@ -304,6 +339,14 @@ export function streetsOf(tree) {
|
|
|
304
339
|
const forms = byFirst.get(f);
|
|
305
340
|
if (forms?.size === 1)
|
|
306
341
|
return most(spellings.get([...forms][0]));
|
|
342
|
+
if (sentence && !named.has(f)) {
|
|
343
|
+
const like = [...named.keys()].filter((k) => otherCase(f, k));
|
|
344
|
+
if (like.length === 1) {
|
|
345
|
+
const k = like[0];
|
|
346
|
+
const m = spellings.get(k);
|
|
347
|
+
return m ? most(m) : named.get(k);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
307
350
|
}
|
|
308
351
|
return town;
|
|
309
352
|
};
|
|
@@ -313,12 +356,28 @@ export function streetsOf(tree) {
|
|
|
313
356
|
townOf,
|
|
314
357
|
placeOf: (place) => {
|
|
315
358
|
const s = split(place);
|
|
316
|
-
return s ? townOf(s.town) : place;
|
|
359
|
+
return s ? townOf(s.town, s.sentence) : place;
|
|
317
360
|
},
|
|
318
361
|
};
|
|
319
362
|
memo.set(tree, { version: tree.version, answers: answersKey, streets });
|
|
320
363
|
return streets;
|
|
321
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* A municipality's name in a sentence in another case than the research writes it (folded): the same words but the
|
|
367
|
+
* ending of the last — the word as written ending in at most three letters of its own, the research's in at most one,
|
|
368
|
+
* three letters alike before ("lhotici" of "lhotice", "lhote" of "lhota", "lhotowie" of "lhotow"; never "lhote" of
|
|
369
|
+
* "lhotka", another municipality).
|
|
370
|
+
*/
|
|
371
|
+
function otherCase(written, research) {
|
|
372
|
+
if (written === research)
|
|
373
|
+
return false;
|
|
374
|
+
let p = 0;
|
|
375
|
+
while (p < written.length && p < research.length && written[p] === research[p])
|
|
376
|
+
p++;
|
|
377
|
+
const a = written.slice(p);
|
|
378
|
+
const b = research.slice(p);
|
|
379
|
+
return p >= 3 && !/\s/u.test(written.slice(0, p).slice(-1)) && a.length <= 3 && b.length <= 1 && !/[\s,]/u.test(a + b);
|
|
380
|
+
}
|
|
322
381
|
/**
|
|
323
382
|
* Checks a place written now (strom event add|edit --place, person add --born-place|--died-place, family add
|
|
324
383
|
* --married-place): a street or a house number in it is refused with what to write instead — the municipality as the
|
|
@@ -331,7 +390,7 @@ export function checkStreetPlace(tree, place, house, flag = "--place") {
|
|
|
331
390
|
const s = streets.split(place);
|
|
332
391
|
if (!s)
|
|
333
392
|
return;
|
|
334
|
-
const town = streets.townOf(s.town);
|
|
393
|
+
const town = streets.townOf(s.town, s.sentence);
|
|
335
394
|
const given = house?.trim();
|
|
336
395
|
const both = houseWith(s.house, given);
|
|
337
396
|
const set = both ?? s.house;
|
package/dist/core/sync.js
CHANGED
|
@@ -33,6 +33,7 @@ import { appAge, appDate } from "./appshapes.js";
|
|
|
33
33
|
import { dateYears } from "./gdate.js";
|
|
34
34
|
import { create, update } from "./records.js";
|
|
35
35
|
import { decodeText, foldText, safeFolderName } from "./text.js";
|
|
36
|
+
import { hasHouseMark } from "./streets.js";
|
|
36
37
|
import { roleWord } from "./roles.js";
|
|
37
38
|
import { labels } from "../gedcom/labels.js";
|
|
38
39
|
import { eventName, humanDate, humanDay } from "../cli/human.js";
|
|
@@ -3684,7 +3685,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
3684
3685
|
toCheck(owner, gr);
|
|
3685
3686
|
}
|
|
3686
3687
|
else {
|
|
3687
|
-
const title = editTitle(c.name ?? owner, eventName(c.fact.kind, tree.lang, c.fact.label), factWords(tree, incoming, mine), factWords(tree, incoming, c.fact));
|
|
3688
|
+
const title = editTitle(tree.lang, c.name ?? owner, eventName(c.fact.kind, tree.lang, c.fact.label), factWords(tree, incoming, mine), factWords(tree, incoming, c.fact));
|
|
3688
3689
|
conflictOfEdit(tree, applied, owner, c.fact.kind, title, [
|
|
3689
3690
|
{ ...(mine.citations[0] ? { source: mine.citations[0].source } : {}), value: describe(mine), note: `the research: ${mine.id}`, text: factWords(tree, incoming, mine) },
|
|
3690
3691
|
{ source: source.id, value: claimValue(c.fact), note: "the user's edit", text: factWords(tree, incoming, c.fact) },
|
|
@@ -3698,7 +3699,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
3698
3699
|
break;
|
|
3699
3700
|
// picked without the state it was given: a record's fact is not overwritten
|
|
3700
3701
|
if (c.action === "conflict" || (c.action === "pick" && recordBacked(tree, mine))) {
|
|
3701
|
-
const title = editTitle(c.name ?? owner, eventName(c.fact.kind, tree.lang, c.fact.label), factWords(tree, incoming, mine), factWords(tree, incoming, c.fact));
|
|
3702
|
+
const title = editTitle(tree.lang, c.name ?? owner, eventName(c.fact.kind, tree.lang, c.fact.label), factWords(tree, incoming, mine), factWords(tree, incoming, c.fact));
|
|
3702
3703
|
conflictOfEdit(tree, applied, owner, c.fact.kind, title, [
|
|
3703
3704
|
{ ...(mine.citations[0] ? { source: mine.citations[0].source } : {}), value: describe(mine), note: `the research: ${mine.id}`, text: factWords(tree, incoming, mine) },
|
|
3704
3705
|
{ source: source.id, value: describe(c.fact), note: "the user's edit", text: factWords(tree, incoming, c.fact) },
|
|
@@ -3748,7 +3749,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
3748
3749
|
const at = c.wasPart ? before.findIndex((x) => x.role === c.wasPart.role && (c.wasPart.person ? x.person === c.wasPart.person : !x.person && nameKey(x.name ?? "") === nameKey(c.wasPart.name ?? ""))) : -1;
|
|
3749
3750
|
const kindOfRec = owner.startsWith("F") ? "family" : "person";
|
|
3750
3751
|
if (c.action === "conflict") {
|
|
3751
|
-
const title = editTitle(c.name ?? owner, eventName(mine.kind, tree.lang, mine.label), partWords(c.wasPart, incoming, tree.lang), partWords(c.part, incoming, tree.lang));
|
|
3752
|
+
const title = editTitle(tree.lang, c.name ?? owner, eventName(mine.kind, tree.lang, mine.label), partWords(c.wasPart, incoming, tree.lang), partWords(c.part, incoming, tree.lang));
|
|
3752
3753
|
conflictOfEdit(tree, applied, owner, mine.kind, title, [
|
|
3753
3754
|
{ ...(mine.citations[0] ? { source: mine.citations[0].source } : {}), value: `${c.wasPart.role} ${c.wasPart.name ?? c.wasPart.person ?? "?"}`, note: `the research: ${mine.id}`, text: partWords(c.wasPart, incoming, tree.lang) },
|
|
3754
3755
|
{ source: source.id, value: partText(c.part, incoming), note: "the user's edit", text: partWords(c.part, incoming, tree.lang) },
|
|
@@ -3905,7 +3906,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
3905
3906
|
const day = humanDay(p.retracted.at, tree.lang);
|
|
3906
3907
|
const ours = ui(tree.lang, "ui.conflict.removed.ours", { day, sync: c.removedBy ?? "—" });
|
|
3907
3908
|
const theirs = ui(tree.lang, "ui.conflict.removed.theirs");
|
|
3908
|
-
const { open } = conflictOfEdit(tree, applied, p.id, "PERSON", editTitle(name, ui(tree.lang, "ui.conflict.removed"), ours, theirs), [
|
|
3909
|
+
const { open } = conflictOfEdit(tree, applied, p.id, "PERSON", editTitle(tree.lang, name, ui(tree.lang, "ui.conflict.removed"), ours, theirs), [
|
|
3909
3910
|
{ value: `removed${c.removedBy ? ` ${c.removedBy}` : ""}`, note: "the research: removed by an earlier send", text: ours },
|
|
3910
3911
|
{ source: source.id, value: "edited", note: "the user's edit", text: theirs },
|
|
3911
3912
|
]);
|
|
@@ -3924,7 +3925,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
3924
3925
|
const ours = ui(tree.lang, by === "app" ? "ui.conflict.removing.ours" : `ui.conflict.removing.${by}`);
|
|
3925
3926
|
const theirs = ui(tree.lang, "ui.conflict.removing.theirs");
|
|
3926
3927
|
const where = by === "app" ? "in another copy" : by === "research" ? "in the research" : "in another copy and in the research";
|
|
3927
|
-
const { open } = conflictOfEdit(tree, applied, p.id, "PERSON", editTitle(name, ui(tree.lang, "ui.conflict.removed"), ours, theirs), [
|
|
3928
|
+
const { open } = conflictOfEdit(tree, applied, p.id, "PERSON", editTitle(tree.lang, name, ui(tree.lang, "ui.conflict.removed"), ours, theirs), [
|
|
3928
3929
|
{ value: "edited", note: `the research: edited since ${where}`, text: ours },
|
|
3929
3930
|
{ source: source.id, value: "removed", note: "the user's edit", text: theirs },
|
|
3930
3931
|
]);
|
|
@@ -4009,7 +4010,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
4009
4010
|
else {
|
|
4010
4011
|
// a record gives the name: the user decides; their name kept as another form meanwhile (sent again, it
|
|
4011
4012
|
// asks nothing new)
|
|
4012
|
-
const title = editTitle(c.wasName ?? owner, ui(tree.lang, "ui.conflict.name"), (c.wasName ?? "").replace(/\//g, "").replace(/\s+/g, " ").trim() || "—", c.text.replace(/\//g, "").replace(/\s+/g, " ").trim());
|
|
4013
|
+
const title = editTitle(tree.lang, c.wasName ?? owner, ui(tree.lang, "ui.conflict.name"), (c.wasName ?? "").replace(/\//g, "").replace(/\s+/g, " ").trim() || "—", c.text.replace(/\//g, "").replace(/\s+/g, " ").trim());
|
|
4013
4014
|
const { was } = conflictOfEdit(tree, applied, owner, "NAME", title, [
|
|
4014
4015
|
{ ...(shown.citations?.[0] ? { source: shown.citations[0].source } : {}), value: c.wasName ?? "", note: "the research: the name the person is shown by", text: (c.wasName ?? "").replace(/\//g, "").replace(/\s+/g, " ").trim() || "—" },
|
|
4015
4016
|
{ source: source.id, value: c.text, note: "the user's edit", text: c.text.replace(/\//g, "").replace(/\s+/g, " ").trim() },
|
|
@@ -4034,7 +4035,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
4034
4035
|
if (c.action === "conflict") {
|
|
4035
4036
|
const who = gedcomName(primaryName(p)).replace(/\//g, "").replace(/\s+/g, " ").trim();
|
|
4036
4037
|
const said = (n) => (n ? formatName(n) : ui(tree.lang, "ui.conflict.primary.none"));
|
|
4037
|
-
conflictOfEdit(tree, applied, owner, "NAME", editTitle(who, ui(tree.lang, "ui.conflict.primary"), said(was), said(target)), [
|
|
4038
|
+
conflictOfEdit(tree, applied, owner, "NAME", editTitle(tree.lang, who, ui(tree.lang, "ui.conflict.primary"), said(was), said(target)), [
|
|
4038
4039
|
{ value: was ? gedcomName(was) : "", note: "the research: the name it shows the person by", text: said(was) },
|
|
4039
4040
|
{ source: source.id, value: target ? gedcomName(target) : "", note: "the user's edit", text: said(target) },
|
|
4040
4041
|
], undefined, undefined, true);
|
|
@@ -4053,7 +4054,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
4053
4054
|
// a record gives the name: the user decides (strom conflict resolve --take user|research)
|
|
4054
4055
|
const said = (v) => v || "—";
|
|
4055
4056
|
const what = ui(tree.lang, c.title.part === "before" ? "ui.conflict.titleBefore" : "ui.conflict.titleAfter");
|
|
4056
|
-
conflictOfEdit(tree, applied, owner, titleTag(c.title.part), editTitle(formatName(shown), what, said(c.title.was), said(now)), [
|
|
4057
|
+
conflictOfEdit(tree, applied, owner, titleTag(c.title.part), editTitle(tree.lang, formatName(shown), what, said(c.title.was), said(now)), [
|
|
4057
4058
|
{ ...(shown.citations?.[0] ? { source: shown.citations[0].source } : {}), value: c.title.was, note: "the research: the title of the name the person is shown by", text: said(c.title.was) },
|
|
4058
4059
|
{ source: source.id, value: now, note: "the user's edit", text: said(now) },
|
|
4059
4060
|
]);
|
|
@@ -4092,7 +4093,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
4092
4093
|
const p = tree.get(owner);
|
|
4093
4094
|
if (c.action === "conflict") {
|
|
4094
4095
|
const who = gedcomName(primaryName(p)).replace(/\//g, "").replace(/\s+/g, " ").trim();
|
|
4095
|
-
conflictOfEdit(tree, applied, owner, "SEX", editTitle(who, ui(tree.lang, "ui.conflict.sex"), sexWord(p.sex, tree.lang), sexWord(c.text, tree.lang)), [
|
|
4096
|
+
conflictOfEdit(tree, applied, owner, "SEX", editTitle(tree.lang, who, ui(tree.lang, "ui.conflict.sex"), sexWord(p.sex, tree.lang), sexWord(c.text, tree.lang)), [
|
|
4096
4097
|
{ value: p.sex, note: "the research: the sex its records give", text: sexWord(p.sex, tree.lang) },
|
|
4097
4098
|
{ source: source.id, value: c.text, note: "the user's edit", text: sexWord(c.text, tree.lang) },
|
|
4098
4099
|
]);
|
|
@@ -4220,7 +4221,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
4220
4221
|
const partners = (c.partners ?? []).map((k) => who(k)).filter((k) => !!k);
|
|
4221
4222
|
// the family of the user's parents: the research's the file named, or the one this send made — else made when taken
|
|
4222
4223
|
const to = c.family ?? families.get(famKey(partners));
|
|
4223
|
-
conflictOfEdit(tree, applied, child, "FAMC", editTitle(name, ui(tree.lang, "ui.conflict.parents"), ours, theirs), [
|
|
4224
|
+
conflictOfEdit(tree, applied, child, "FAMC", editTitle(tree.lang, name, ui(tree.lang, "ui.conflict.parents"), ours, theirs), [
|
|
4224
4225
|
{ value: `${born.id}: ${ours}`, note: "the research: the parents it gives", text: ours },
|
|
4225
4226
|
{ source: source.id, value: theirs, note: "the user's edit", text: theirs },
|
|
4226
4227
|
], undefined, partners.length ? { child, from: born.id, partners, ...(to ? { to } : {}) } : undefined);
|
|
@@ -4400,15 +4401,25 @@ function nameOfView(p, view) {
|
|
|
4400
4401
|
const key = view.startsWith("married|") ? view.slice("married|".length) : undefined;
|
|
4401
4402
|
return key === undefined ? undefined : p.names.find((n) => n !== first && nameKey(gedcomName(n)) === key);
|
|
4402
4403
|
}
|
|
4403
|
-
function editTitle(who, what, ours, theirs) {
|
|
4404
|
-
return
|
|
4404
|
+
function editTitle(lang, who, what, ours, theirs) {
|
|
4405
|
+
return ui(lang, "ui.conflict.title", { who: who.replace(/\//g, "").replace(/\s+/g, " ").trim(), what: `${what.charAt(0).toUpperCase()}${what.slice(1)}`, ours, theirs });
|
|
4406
|
+
}
|
|
4407
|
+
/**
|
|
4408
|
+
* A fact's house as a person reads it, in the research's language: "čp. 12" of "12" — a house that says it is one
|
|
4409
|
+
* already ("nr domu 12", "Nr. 3") as it is, never "nr domu nr domu 12".
|
|
4410
|
+
*/
|
|
4411
|
+
export function houseWords(lang, house) {
|
|
4412
|
+
const prefix = foldText(ui(lang, "ui.sync.house", { x: "" }).trim());
|
|
4413
|
+
if (hasHouseMark(house) || (prefix && foldText(house).startsWith(prefix)))
|
|
4414
|
+
return house;
|
|
4415
|
+
return ui(lang, "ui.sync.house", { x: house });
|
|
4405
4416
|
}
|
|
4406
4417
|
/** A fact as a conflict's title says it, in the research's language: "1904, Praha, čp. 12, věk 45 let". */
|
|
4407
4418
|
function factWords(tree, incoming, f) {
|
|
4408
4419
|
const lang = tree.lang;
|
|
4409
4420
|
const given = (k) => (k.startsWith("x:") ? undefined : tree.get(k)?.names[0]?.given) ?? (incoming.persons.get(k)?.names[0]?.split("/")[0]?.trim() || k);
|
|
4410
4421
|
const ages = Object.entries(f.ages ?? {}).map(([who, a]) => `${given(who)} ${humanAge(a, lang)}`);
|
|
4411
|
-
return [f.value, humanDate(f.date, lang), f.place, f.house &&
|
|
4422
|
+
return [f.value, humanDate(f.date, lang), f.place, f.house && houseWords(lang, f.house), f.age && humanAge(f.age, lang), ...ages, f.cause && ui(lang, "ui.sync.cause", { x: f.cause })].filter(Boolean).join(", ") || "—";
|
|
4412
4423
|
}
|
|
4413
4424
|
/** Who the record names, as a conflict's title says it: "kmotr Jan Novák". */
|
|
4414
4425
|
function partWords(p, snap, lang) {
|
package/dist/core/tree.js
CHANGED
|
@@ -31,7 +31,7 @@ export const TREE_FILE = "strom.json";
|
|
|
31
31
|
export const SEAL_FOREIGN = "seal.foreign";
|
|
32
32
|
// between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
|
|
33
33
|
// at a release the release's own (package.json)
|
|
34
|
-
export const VERSION = "1.25.
|
|
34
|
+
export const VERSION = "1.25.2";
|
|
35
35
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
36
36
|
.strom/
|
|
37
37
|
/media/
|
package/dist/runners/runner.js
CHANGED
|
@@ -4,7 +4,7 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { findAgent } from "../core/which.js";
|
|
7
|
-
import {
|
|
7
|
+
import { forClaude, isClaudeCommand } from "../core/claudedir.js";
|
|
8
8
|
/** A number the agent said, else nothing. */
|
|
9
9
|
export function usageNumber(n) {
|
|
10
10
|
return typeof n === "number" && Number.isFinite(n) ? n : undefined;
|
|
@@ -104,10 +104,10 @@ function cmdQuote(arg) {
|
|
|
104
104
|
/**
|
|
105
105
|
* Start an agent CLI found on PATH. npm installs agents on Windows as .cmd
|
|
106
106
|
* launchers, which Node can only start through cmd.exe. Claude Code gets its folder (agent.claudeConfigDir) when the
|
|
107
|
-
* environment says none: every `claude` strom starts goes through here.
|
|
107
|
+
* environment says none, and none when it says the default one (forClaude): every `claude` strom starts goes through here.
|
|
108
108
|
*/
|
|
109
109
|
export function spawnAgent(command, args, given) {
|
|
110
|
-
const opts = isClaudeCommand(command) ? { ...given, env:
|
|
110
|
+
const opts = isClaudeCommand(command) ? { ...given, env: forClaude(given.env) } : given;
|
|
111
111
|
const found = findAgent(command, opts.env) ?? command;
|
|
112
112
|
if (process.platform === "win32" && /\.(cmd|bat)$/i.test(found)) {
|
|
113
113
|
const line = [cmdQuote(found), ...args.map(cmdQuote)].join(" ");
|