strom-research 1.19.0-beta.8 → 1.20.0-beta.1

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 CHANGED
@@ -5,9 +5,21 @@
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.20.0
9
+
10
+ *Připravuje se: bety do 1.20.0-beta.1. Oddíl bude úplný s vydáním.*
11
+
12
+ - **Menu bez nikoho u terminálu to řekne hned.** Spuštěné tam, kde nemá kdo odpovědět (zavřený vstup, okno
13
+ bez terminálu), skončí jednou větou v jazyce výzkumu a tím, co udělat místo toho, už ne chybou programu; otázka,
14
+ jejíž vstup skončí dřív než odpověď, ukončí menu stejně.
15
+ - **`strom check` mluví jedním jazykem.** Pro člověka jsou všechny nálezy, součet i poznámky v jazyce výzkumu, pro
16
+ agenta celé anglicky, nikdy směs obou.
17
+ - **Když sandbox agenta odmítne git, je jmenováno správné místo.** Zpráva uvádí jako odepřenou historii výzkumu
18
+ (jeho složku `.git`), ne program gitu.
19
+
8
20
  ## 1.19.0
9
21
 
10
- *Připravuje se: bety do 1.19.0-beta.8. Oddíl bude úplný s vydáním.*
22
+ *22. 10. 2026 · patří k aplikaci Strom 3.15.0.*
11
23
 
12
24
  - **Na archiv, který zmlkl, se čeká, místo opakovaných dotazů.** Když archiv přestane odpovídat, strom ho nechá na
13
25
  pokoji hodinu — a když týž den mlčí dál, dvě a pak čtyři hodiny — a agent se dozví, do kdy: mezitím dělá
package/CHANGELOG.de.md CHANGED
@@ -5,9 +5,22 @@
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.20.0
9
+
10
+ *In Vorbereitung: die Betas bis 1.20.0-beta.1. Der Abschnitt ist mit dem Release vollständig.*
11
+
12
+ - **Das Menü ohne jemanden am Terminal sagt es sofort.** Dort gestartet, wo niemand antworten kann (geschlossene
13
+ Eingabe, ein Fenster ohne Terminal), endet es mit einem Satz in der Sprache der Forschung und dem, was
14
+ stattdessen zu tun ist, nicht mehr mit einem Fehler des Programms; eine Frage, deren Eingabe vor der Antwort endet,
15
+ beendet das Menü genauso.
16
+ - **`strom check` spricht eine Sprache.** Für einen Menschen stehen alle Befunde, die Summe und die Hinweise in der
17
+ Sprache der Forschung, für einen Agenten alles auf Englisch, nie beides gemischt.
18
+ - **Verweigert die Sandbox des Agenten git, wird der richtige Ort genannt.** Die Meldung nennt die Geschichte der
19
+ Forschung (ihren Ordner `.git`) als verweigerten Ort, nicht das Programm git.
20
+
8
21
  ## 1.19.0
9
22
 
10
- *In Vorbereitung: die Betas bis 1.19.0-beta.8. Der Abschnitt ist mit dem Release vollständig.*
23
+ *22.10.2026 · gehört zur Strom-App 3.15.0.*
11
24
 
12
25
  - **Auf ein verstummtes Archiv wird gewartet, statt es immer wieder zu fragen.** Antwortet ein Archiv nicht mehr,
13
26
  lässt strom es eine Stunde in Ruhe — zwei, dann vier, wenn es am selben Tag still bleibt — und der Agent erfährt, bis
package/CHANGELOG.md CHANGED
@@ -5,9 +5,21 @@ 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.20.0
9
+
10
+ *In preparation: the betas up to 1.20.0-beta.1. The section is complete with the release.*
11
+
12
+ - **The menu with nobody at a terminal says so at once.** Started where no one can answer (a closed input, a window
13
+ without a terminal), the menu ends with one sentence in the research's language and what to do instead,
14
+ no longer with an error of the program; a question whose input ends before an answer ends the menu the same way.
15
+ - **`strom check` speaks one language.** For a person every finding, the sum and the notes are in the research's
16
+ language; for an agent all of it is in English, never a mix of both.
17
+ - **When the agent's sandbox refuses git, the right place is named.** The message names the research's history (its
18
+ `.git` folder) as the place refused, not git's own program.
19
+
8
20
  ## 1.19.0
9
21
 
10
- *In preparation: the betas up to 1.19.0-beta.8. The section is complete with the release.*
22
+ *2026-10-22 · goes with the Strom app 3.15.0.*
11
23
 
12
24
  - **An archive that went silent is waited for, not asked again and again.** When an archive stops answering, strom
13
25
  leaves it alone for an hour — two, then four when it stays silent the same day — and the agent hears until when:
@@ -2413,6 +2413,10 @@
2413
2413
  "ui.error.record.none": "{id} v rodokmenu není",
2414
2414
  "ui.error.record.merged": "{id} je sloučené do {into}",
2415
2415
  "ui.error.setting.missing": "chybí nastavení: {keys}",
2416
+ "ui.error.menu.no-terminal": "menu je pro člověka u terminálu – tady žádný není",
2417
+ "ui.error.menu.no-terminal.hint": "otevřít terminál a spustit v něm strom",
2418
+ "ui.error.input.ended": "vstup skončil dřív, než přišla odpověď – není tu koho se zeptat",
2419
+ "ui.error.input.ended.hint": "spustit v terminálu",
2416
2420
  "ui.error.conflict.needs-reasoning": "chybí --reasoning",
2417
2421
  "ui.error.conflict.needs-reasoning.hint": "strom conflict resolve {id} … --reasoning \"<proč>\"",
2418
2422
  "ui.error.sync.not-sync": "{input} není synchronizace",
@@ -2413,6 +2413,10 @@
2413
2413
  "ui.error.record.none": "{id} gibt es im Stammbaum nicht",
2414
2414
  "ui.error.record.merged": "{id} ist in {into} zusammengeführt",
2415
2415
  "ui.error.setting.missing": "es fehlt die Einstellung: {keys}",
2416
+ "ui.error.menu.no-terminal": "das Menü ist für einen Menschen am Terminal – hier ist keiner",
2417
+ "ui.error.menu.no-terminal.hint": "ein Terminal öffnen und strom darin starten",
2418
+ "ui.error.input.ended": "die Eingabe endete vor einer Antwort – hier ist niemand zu fragen",
2419
+ "ui.error.input.ended.hint": "im Terminal starten",
2416
2420
  "ui.error.conflict.needs-reasoning": "--reasoning fehlt",
2417
2421
  "ui.error.conflict.needs-reasoning.hint": "strom conflict resolve {id} … --reasoning \"<warum>\"",
2418
2422
  "ui.error.sync.not-sync": "{input} ist kein Abgleich",
@@ -8,7 +8,7 @@ import { Settings } from "../core/config.js";
8
8
  import { configDir, displayPath, expandHome, ISOLATED_HOME } from "../core/paths.js";
9
9
  import { backupsDir } from "../core/backup.js";
10
10
  import { insideProgram } from "../core/self.js";
11
- import { Cancelled, NeedsConsentError, NeedsInputError, StromError, UsageError } from "../core/errors.js";
11
+ import { Cancelled, InputEnded, NeedsConsentError, NeedsInputError, StromError, UsageError } from "../core/errors.js";
12
12
  import { migrate } from "../core/migrate.js";
13
13
  import { Tree, findTreeUpwards, isTreeDir, knownTreesIn } from "../core/tree.js";
14
14
  import { SCHEMA_VERSION } from "../core/model.js";
@@ -101,8 +101,12 @@ export class Context {
101
101
  }
102
102
  else {
103
103
  const rl = readline.createInterface({ input: this.io.stdin ?? process.stdin, output: process.stdout });
104
+ // the input ending before an answer (stdin closed, used up) settles the question too — never a question left
105
+ // open; a moment later, so Ctrl-C at a terminal (it closes the line first) stays the person's Ctrl-C
106
+ const ended = new Promise((_, reject) => rl.once("close", () => setImmediate(() => reject(new InputEnded()))));
107
+ ended.catch(() => undefined);
104
108
  try {
105
- answer = await rl.question(prompt);
109
+ answer = await Promise.race([rl.question(prompt), ended]);
106
110
  }
107
111
  catch (err) {
108
112
  // Ctrl-C at the question (Node: "Aborted with Ctrl+C", found on Mac as the menu's "that did not work")
package/dist/cli/main.js CHANGED
@@ -70,7 +70,8 @@ function systemError(e, agent) {
70
70
  case "EACCES":
71
71
  case "EPERM":
72
72
  case "EROFS":
73
- return noPermission(e.path, agent);
73
+ // git not let to start under an agent: said as the research's folder (underAgent)
74
+ return noPermission((agent && e.gitFolder) || e.path, agent);
74
75
  case "EISDIR":
75
76
  return new UsageError(`a folder was given where a file is expected${where}`, { code: "fs.folder", ...(e.path ? { params: { path: e.path } } : {}) });
76
77
  case "ENOTDIR":
@@ -109,20 +110,23 @@ const REFUSALS = new Set(["fs.no-permission", "git.unwritable", "init.unwritable
109
110
  * Codex's sandbox ran strom with none of its marks) is named to the person as the likely cause — not certain, so
110
111
  * the other causes after it.
111
112
  */
112
- function underAgent(e, env) {
113
+ function underAgent(e, env, gitFolder) {
113
114
  if (!REFUSALS.has(e.code ?? ""))
114
115
  return e;
116
+ // git not let to start under an agent: the research's folder is where (found on Windows: "…\Git\cmd\git.exe"),
117
+ // what the agent's sandbox is to let strom use; with no agent the program refused stays said (an antivirus)
118
+ const where = gitFolder ?? e.params?.where;
115
119
  if (isAgent(env))
116
- return noPermission(e.params?.where, env);
120
+ return noPermission(where, env);
117
121
  const above = agentInAncestry(env);
118
122
  if (above)
119
- return noPermission(e.params?.where, false, process.platform, above);
120
- return ancestryUnread(env) ? noPermission(e.params?.where, false, process.platform, "?") : e;
123
+ return noPermission(where, false, process.platform, above);
124
+ return ancestryUnread(env) ? noPermission(where, false, process.platform, "?") : e;
121
125
  }
122
126
  function printError(io, json, err, debug, lang, env) {
123
127
  const agent = isAgent(env) && env;
124
128
  const sys = err instanceof StromError ? err : (systemError(err, agent) ?? err);
125
- const e = sys instanceof StromError ? underAgent(sys, env) : sys;
129
+ const e = sys instanceof StromError ? underAgent(sys, env, err?.gitFolder) : sys;
126
130
  if (e instanceof StromError) {
127
131
  if (json)
128
132
  io.stdout(toJson(e.toJSON()));
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  import { agentHere } from "../core/state.js";
5
5
  import { ui } from "./ui.js";
6
6
  import { Tree } from "../core/tree.js";
7
- import { Cancelled } from "../core/errors.js";
7
+ import { Cancelled, InputEnded } from "../core/errors.js";
8
8
  import { findPersons, label } from "../core/people.js";
9
9
  import { agentsHere } from "../core/apps.js";
10
10
  /** The test (or a script) gave all its answers: nothing more is asked. */
@@ -144,6 +144,9 @@ export async function guarded(ctx, lang, act) {
144
144
  // Ctrl-C at its question: stopped by the person, nothing changed — no failure, the menu goes on
145
145
  if (err instanceof Cancelled)
146
146
  return void ctx.io.stdout(ui(lang, "ui.cancelled") + "\n");
147
+ // nobody left to ask: the menu ends, said once
148
+ if (err instanceof InputEnded)
149
+ throw err;
147
150
  ctx.io.stdout(ui(lang, "ui.menu.failed", { reason: err.message }) + "\n");
148
151
  await pause(ctx, lang);
149
152
  }
package/dist/cli/ui.js CHANGED
@@ -2279,6 +2279,10 @@ export const UI = {
2279
2279
  "ui.error.record.none": "{id} is not in the family tree",
2280
2280
  "ui.error.record.merged": "{id} was merged into {into}",
2281
2281
  "ui.error.setting.missing": "missing setting: {keys}",
2282
+ "ui.error.menu.no-terminal": "the menu is for a person at a terminal — none here",
2283
+ "ui.error.menu.no-terminal.hint": "open a terminal and run strom there",
2284
+ "ui.error.input.ended": "the input ended before an answer — nobody to ask here",
2285
+ "ui.error.input.ended.hint": "run it at a terminal",
2282
2286
  "ui.error.conflict.needs-reasoning": "--reasoning is required",
2283
2287
  "ui.error.conflict.needs-reasoning.hint": "strom conflict resolve {id} … --reasoning \"<why>\"",
2284
2288
  "ui.error.sync.not-sync": "{input} is not a sync",
@@ -62,10 +62,13 @@ register({
62
62
  tree: true,
63
63
  run(ctx) {
64
64
  const tree = ctx.tree();
65
- // its verdict and sum in the research's language: the person follows it in the agent's session too; a warning
66
- // a person may meet, in it where a person runs it
67
- const checked = report([...verifyFull(tree).findings, ...check(tree), ...guard(tree), ...unwritable(tree, isAgent(ctx.env))], ui(tree.lang, "ui.check.ok"), tree.lang, !isAgent(ctx.env));
68
- // much kept beside the research that can go (an older strom never tidied it): a sentence, in the research's language —
65
+ // one language from the first line to the last: a person reads it in the research's language (the findings a
66
+ // person may meet too); an agent reads it all in English, as it reads strom's errors — its findings say what to
67
+ // run (found on Mac: English findings above a Czech sum)
68
+ const agent = isAgent(ctx.env);
69
+ const lang = agent ? "en" : tree.lang;
70
+ const checked = report([...verifyFull(tree).findings, ...check(tree), ...guard(tree), ...unwritable(tree, agent)], ui(lang, "ui.check.ok"), lang, !agent);
71
+ // much kept beside the research that can go (an older strom never tidied it): a sentence, in that language —
69
72
  // none where .strom cannot be listed (found on Windows: a folder denied to strom ended check with "no permission"
70
73
  // instead of its finding; unwritable says it)
71
74
  let disk;
@@ -76,14 +79,14 @@ register({
76
79
  disk = undefined;
77
80
  }
78
81
  const out = disk && disk.frees >= TIDY_SAID
79
- ? { ...checked, text: lines(checked.text, ui(tree.lang, "ui.tidy.hint", { strom: mb(disk.size.strom, tree.lang), size: mb(disk.frees, tree.lang) })), data: { ...checked.data, disk: { strom: disk.size.strom, frees: disk.frees } } }
82
+ ? { ...checked, text: lines(checked.text, ui(lang, "ui.tidy.hint", { strom: mb(disk.size.strom, lang), size: mb(disk.frees, lang) })), data: { ...checked.data, disk: { strom: disk.size.strom, frees: disk.frees } } }
80
83
  : checked;
81
84
  // not an error — research to do: who rests on no record of their own
82
85
  const unproven = unprovenPeople(tree).filter((u) => !u.living).length;
83
86
  // an archive: what to research is no matter of it
84
87
  if (!unproven || actsAsArchive(tree))
85
88
  return out;
86
- return { ...out, text: lines(out.text, ui(tree.lang, "ui.check.unproven", { n: unproven })), data: { ...out.data, unproven } };
89
+ return { ...out, text: lines(out.text, ui(lang, "ui.check.unproven", { n: unproven })), data: { ...out.data, unproven } };
87
90
  },
88
91
  }, {
89
92
  path: ["verify"],
@@ -8,7 +8,7 @@ import { prependPath, spawnAgent } from "../runners/runner.js";
8
8
  import { register } from "../cli/registry.js";
9
9
  import { lines, numberIn, truncate } from "../cli/format.js";
10
10
  import { ui } from "../cli/ui.js";
11
- import { StromError, UsageError } from "../core/errors.js";
11
+ import { EXIT, StromError, UsageError } from "../core/errors.js";
12
12
  import { PROFILES } from "../agents/profiles.js";
13
13
  import { conversationArgs, conversationEnv } from "../agents/launch.js";
14
14
  import { globalTargets, installGlobal, isInstalled, uninstallGlobal } from "../agents/global.js";
@@ -579,6 +579,10 @@ register({
579
579
  run: async (ctx) => {
580
580
  if (isAgent(ctx.env))
581
581
  throw new UsageError("the menu is for a person at a terminal", { hint: "strom (orientation) — or strom --json" });
582
+ // nobody at a terminal to answer (stdin closed or not one, STROM_NONINTERACTIVE, --yes/--json): said at once —
583
+ // never a menu waiting for an answer that cannot come
584
+ if (!ctx.interactive)
585
+ throw new StromError("the menu is for a person at a terminal — none here", { exitCode: EXIT.needsInput, code: "menu.no-terminal", hint: "strom (orientation) — or strom --json" });
582
586
  const { runMenu } = await import("../cli/menu.js");
583
587
  const { main } = await import("../cli/main.js");
584
588
  await runMenu(ctx, (argv, quiet) => main(argv, quiet ? { ...ctx.io, stdout: () => undefined } : ctx.io, ctx.env, ctx.cwd));
@@ -72,6 +72,17 @@ export class Cancelled extends StromError {
72
72
  this.name = "Cancelled";
73
73
  }
74
74
  }
75
+ /**
76
+ * The input ended before an answer (stdin closed, a file or a pipe used up): nobody left to ask — said, never a
77
+ * process that hangs on its question (found on Mac: the menu with stdin closed ended in Node's "unsettled top-level
78
+ * await", exit 13).
79
+ */
80
+ export class InputEnded extends StromError {
81
+ constructor() {
82
+ super("the input ended before an answer — nobody to ask here", { exitCode: EXIT.needsInput, code: "input.ended", hint: "run it at a terminal" });
83
+ this.name = "InputEnded";
84
+ }
85
+ }
75
86
  /** A name matched more than one record. Never pick one silently. */
76
87
  export class AmbiguousError extends StromError {
77
88
  candidates;
package/dist/core/git.js CHANGED
@@ -72,6 +72,9 @@ export function runGit(cwd, args, input, extraEnv = {}) {
72
72
  const where = fs.existsSync(dotGit) ? dotGit : cwd;
73
73
  if (writeRefusal(where))
74
74
  throw gitUnwritable(where, "");
75
+ // git itself not let to start (found on Windows: an agent's sandbox, git.exe refused): the research's folder
76
+ // kept with it — under an agent that is what its sandbox is to let strom use (cli/main.ts)
77
+ throw Object.assign(r.error, { gitFolder: where });
75
78
  }
76
79
  throw gitTimedOut(args, r.error) ?? r.error;
77
80
  }
package/dist/core/tree.js CHANGED
@@ -27,7 +27,7 @@ import { lastBackup } from "./backup.js";
27
27
  export const TREE_FILE = "strom.json";
28
28
  // between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
29
29
  // at a release the release's own (package.json)
30
- export const VERSION = "1.19.0-beta.8";
30
+ export const VERSION = "1.20.0-beta.1";
31
31
  const GITIGNORE = `# Strom Research — local state and large files are not versioned
32
32
  .strom/
33
33
  /media/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strom-research",
3
- "version": "1.19.0-beta.8",
3
+ "version": "1.20.0-beta.1",
4
4
  "description": "Genealogical research toolkit for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {