Skip to content

Commit db2feb5

Browse files
ronshapiroclaude
andcommitted
Split CLI entrypoint out (yargs breaks under jest)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent d8e2d11 commit db2feb5

5 files changed

Lines changed: 144 additions & 36 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"Rashi on Menachot 77a:1:1": {
3+
"hebrew": "<strong class=\"dibur-hamatchil\">עשרה היו לחמץ עשרון לחלה</strong> - די' חלות היו חמץ, ובמצה ג' מינין וכל אחד היה של י' חלות, נמצא במצה שלשים חלות של י' עשרונות, נמצאו שלש עשרונות ושליש עשרון לכל מין ומין, וג' חלות לעשרון, שמכל עשרון היה עושה שלשה חלות של מצה, דשלשים חלות לא היו אלא י' עשרון. ומה ששנינו עד כאן היא במדה מדברית, אבל במדה ירושלמית שהיו ה' סאין, שהן שלשים קבין, דסאה ששה קבין, ט\"ו קבין לחמץ וט\"ו למצה:",
4+
"english": "<strong class=\"dibur-hamatchil\">Ten were for leavened bread, a tenth for each loaf</strong> - That the ten loaves were leavened bread. And for matzah there were three kinds, and each kind consisted of ten loaves; thus for matzah there were thirty loaves, from ten tenths [of an ephah]. So there were three and a third tenths for each and every kind, and three loaves per tenth - for from each tenth he would make three loaves of matzah, since the thirty loaves came from only ten tenths. And what we have learned so far is according to the wilderness measure. But according to the Jerusalem measure, [the total dough] was five se'ah, which are thirty kav (a se'ah being six kav) - fifteen kav for the leavened bread and fifteen for the unleavened bread."
5+
},
6+
"Rashi on Menachot 77a:2:1": {
7+
"english": "<b>One and a half <i>kav</i></b>: He would allot this [amount] for [each] loaf, for from the fifteen <i>kav</i> he would make ten loaves."
8+
},
9+
"Rashi on Menachot 77a:2:2": {
10+
"hebrew": "<strong class=\"dibur-hamatchil\">ושתי חלות לקב</strong> - דמקב אחד היה עושה שתי חלות, שהרי מט\"ו קבין היה עושה שלשים חלות של מצה:",
11+
"english": "<b>And two loaves per <i>kav</i></b>: For from one <i>kav</i> he would make two loaves, since from fifteen <i>kav</i> he would make thirty loaves of matza."
12+
},
13+
"Rashi on Menachot 77a:3:1": {
14+
"english": "<strong>From where are these matters</strong> - that an ephah is three <i>se’a</i>."
15+
},
16+
"Rashi on Menachot 77a:3:2": {
17+
"english": "<strong class=\"dibur-hamatchil\">ONE MEASURE</strong> - The ephah and the bat are the same measure."
18+
},
19+
"Rashi on Menachot 77a:4:1": {
20+
"english": "The bat represents the tenth-part taken from a ḥomer, for a ḥomer is thirty se’a, and the verse states that the ephah and the bat are of the same measure."
21+
}
22+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"Rashi on Menachot 77a:1:1": {
3+
"dependsOn": [],
4+
"generatedAt": "2026-08-10T13:07:25.524Z",
5+
"model": "claude-haiku-4-5-20251001",
6+
"promptVersion": "v1",
7+
"sourceRefs": [
8+
"Rashi on Menachot 77a:1:1"
9+
],
10+
"sourceText": "<strong class=\"dibur-hamatchil\">עשרה היו לחמץ עשרון לחלה</strong> - די' חלות היו חמץ ובמצה ג' מינין וכל אחד היה של י' חלות נמצא במצה שלשים חלות של י' עשרונות נמצאו שלש עשרונות ושליש עשרון לכל מין ומין וג' חלות לעשרון שמכל עשרון היה עושה שלשה חלות של מצה דשלשים חלות לא היו אלא י' עשרון ומה ששנינו עד כאן היא במדה מדברית אבל במדה ירושלמית שהיו ה' סאין שהן שלשים קבין דסאה ששה קבין ט\"ו קבין לחמץ וט\"ו למצה:"
11+
},
12+
"Rashi on Menachot 77a:2:1": {
13+
"dependsOn": [],
14+
"generatedAt": "2026-08-10T13:08:28.026Z",
15+
"model": "claude-haiku-4-5-20251001",
16+
"promptVersion": "v1",
17+
"sourceRefs": [
18+
"Rashi on Menachot 77a:2:1"
19+
],
20+
"sourceText": "<strong class=\"dibur-hamatchil\">קב ומחצה</strong> - היה נותן לחלה דמט\"ו קבין היה עושה י' חלות:"
21+
},
22+
"Rashi on Menachot 77a:2:2": {
23+
"dependsOn": [],
24+
"generatedAt": "2026-08-10T13:10:01.795Z",
25+
"model": "claude-haiku-4-5-20251001",
26+
"promptVersion": "v1",
27+
"sourceRefs": [
28+
"Rashi on Menachot 77a:2:2"
29+
],
30+
"sourceText": "<strong class=\"dibur-hamatchil\">ושתי חלות לקב</strong> - דמקב אחד היה עושה שתי חלות שהרי מט\"ו קבין היה עושה שלשים חלות של מצה:"
31+
},
32+
"Rashi on Menachot 77a:3:1": {
33+
"dependsOn": [],
34+
"generatedAt": "2026-08-10T13:11:03.205Z",
35+
"model": "claude-haiku-4-5-20251001",
36+
"promptVersion": "v1",
37+
"sourceRefs": [
38+
"Rashi on Menachot 77a:3:1"
39+
],
40+
"sourceText": "<strong class=\"dibur-hamatchil\">מנהני מילי</strong> - דאיפה שלש סאין:"
41+
},
42+
"Rashi on Menachot 77a:3:2": {
43+
"dependsOn": [],
44+
"generatedAt": "2026-08-10T13:11:41.268Z",
45+
"model": "claude-sonnet-5",
46+
"promptVersion": "v1",
47+
"sourceRefs": [
48+
"Rashi on Menachot 77a:3:2"
49+
],
50+
"sourceText": "<strong class=\"dibur-hamatchil\">תוכן אחד</strong> - מדה אחת הן:"
51+
},
52+
"Rashi on Menachot 77a:4:1": {
53+
"dependsOn": [],
54+
"generatedAt": "2026-08-10T13:13:32.200Z",
55+
"model": "claude-haiku-4-5-20251001",
56+
"promptVersion": "v1",
57+
"sourceRefs": [
58+
"Rashi on Menachot 77a:4:1"
59+
],
60+
"sourceText": "<strong class=\"dibur-hamatchil\">לשאת <span class=\"parenthesized\">(את)</span> מעשר החומר הבת</strong> - והבת היא הוא נטילת מעשר על החומר דחומר הוא ל' סאין ואמר קרא דאיפה ובת תוכן אחד הוא:"
61+
}
62+
}

rsi_orchestrator/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,19 @@ billing.
2626
boundaries live in the repo rather than pre-assembling context itself, so the model decides how
2727
much it needs to read — then runs a bounded self-critique pass (generate → critique → at most
2828
one retry with feedback → give up) before writing to `precomputed/ai_additions/<Book Page>.json`
29-
and recording a generation record. Run with `npx ts-node rsi_orchestrator/rashi_tosafot_translation.ts <CanonicalBookName>`
30-
(e.g. `Zevachim`) — requires `cached_outputs/api_request_handler/` to be populated for that book
31-
first (`npx ts-node cache_all_api_requests.ts`).
29+
and recording a generation record. Requires `cached_outputs/api_request_handler/` to be
30+
populated for that book first (`npx ts-node cache_all_api_requests.ts`).
31+
- `rashi_tosafot_translation_cli.ts` — the CLI entrypoint for the above, kept in a separate file
32+
because `yargs` is ESM-only and breaks under jest; the core module stays importable by its test
33+
file this way. `--section`/`--limit` bound a run to one page / a handful of candidates.
3234
- `precomputed/rsi_state/triage_log.json` (created on first run) — tracks which issue numbers have
3335
already been triaged, so re-running doesn't re-comment on the same issue.
3436

3537
## Running manually
3638

3739
```sh
3840
npx ts-node rsi_orchestrator/triage_suggestions.ts
39-
npx ts-node rsi_orchestrator/rashi_tosafot_translation.ts Zevachim
41+
npx ts-node rsi_orchestrator/rashi_tosafot_translation_cli.ts Zevachim --section 2a --limit 2
4042
```
4143

4244
Requires the `gh` CLI authenticated with access to `ronshapiro/talmud.page` (already true on this

rsi_orchestrator/rashi_tosafot_translation.ts

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as fs from "fs";
22
import {Amud, ApiComment} from "../apiTypes";
3-
import {Book, books} from "../books";
3+
import {Book} from "../books";
44
import {cachedOutputFilePath} from "../cached_outputs";
55
import {readUtf8} from "../files";
6-
import {Edit, writeAiEdit} from "../precomputed/ai_edits";
6+
import {Edit} from "../precomputed/ai_edits";
77
import {readGenerationRecord, upsertGenerationRecord} from "../precomputed/rsi_state/generation_record";
88
import {checkTextStaleness, DEFAULT_STALENESS_THRESHOLDS} from "../precomputed/rsi_state/staleness";
99
import {toFlatArray} from "../sefariaTextType";
@@ -198,7 +198,7 @@ async function critiqueViaClaude(
198198
return parseJsonResponse<CritiqueVerdict>(result.text);
199199
}
200200

201-
async function generateAndRecord(
201+
export async function generateAndRecord(
202202
candidate: TranslationCandidate,
203203
): Promise<GeneratedEdit | undefined> {
204204
return generateWithSelfCritique(candidate, {
@@ -207,7 +207,7 @@ async function generateAndRecord(
207207
});
208208
}
209209

210-
function recordGenerationForCandidate(
210+
export function recordGenerationForCandidate(
211211
candidate: TranslationCandidate, generated: GeneratedEdit,
212212
): void {
213213
upsertGenerationRecord(TASK_TYPE, candidate.page, candidate.ref, {
@@ -219,31 +219,3 @@ function recordGenerationForCandidate(
219219
dependsOn: [],
220220
});
221221
}
222-
223-
async function main(): Promise<void> {
224-
const bookName = process.argv[2];
225-
if (!bookName || !books.byCanonicalName[bookName]) {
226-
console.error("Usage: ts-node rashi_tosafot_translation.ts <CanonicalBookName> [limit]");
227-
process.exitCode = 1;
228-
return;
229-
}
230-
const book = books.byCanonicalName[bookName];
231-
const limit = process.argv[3] ? parseInt(process.argv[3], 10) : undefined;
232-
await translateRashiTosafotComments({
233-
listCandidates: () => {
234-
const candidates = listCandidatesForBook(book);
235-
return limit ? candidates.slice(0, limit) : candidates;
236-
},
237-
isFresh: isFreshTranslation,
238-
generate: generateAndRecord,
239-
writeEdit: (candidate, edit) => writeAiEdit(candidate.page, candidate.ref, edit),
240-
recordGeneration: recordGenerationForCandidate,
241-
});
242-
}
243-
244-
if (require.main === module) {
245-
main().catch(e => {
246-
console.error(e);
247-
process.exitCode = 1;
248-
});
249-
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as yargs from "yargs";
2+
import {hideBin} from "yargs/helpers";
3+
import {books} from "../books";
4+
import {writeAiEdit} from "../precomputed/ai_edits";
5+
import {
6+
generateAndRecord,
7+
isFreshTranslation,
8+
listCandidatesForBook,
9+
recordGenerationForCandidate,
10+
translateRashiTosafotComments,
11+
} from "./rashi_tosafot_translation";
12+
13+
/**
14+
* CLI entrypoint, kept separate from rashi_tosafot_translation.ts because `yargs` ships ESM-only
15+
* and breaks under jest — this file is never imported by a test, so it's fine for it to depend on
16+
* yargs; the core module (which the test file does import) stays jest-safe.
17+
*/
18+
async function main(): Promise<void> {
19+
const FLAGS = yargs(hideBin(process.argv))
20+
.options({
21+
section: {type: "string", describe: 'e.g. "77" (matches 77a/77b) or "77a" (exact)'},
22+
limit: {type: "number", describe: "cap the number of candidates processed"},
23+
})
24+
.parseSync();
25+
const bookName = FLAGS._[0] as string | undefined;
26+
if (!bookName || !books.byCanonicalName[bookName]) {
27+
console.error(
28+
"Usage: ts-node rashi_tosafot_translation_cli.ts <CanonicalBookName> "
29+
+ "[--section 77] [--limit 2]");
30+
process.exitCode = 1;
31+
return;
32+
}
33+
const book = books.byCanonicalName[bookName];
34+
await translateRashiTosafotComments({
35+
listCandidates: () => {
36+
let candidates = listCandidatesForBook(book);
37+
if (FLAGS.section) candidates = candidates.filter(c => c.section.startsWith(FLAGS.section!));
38+
return FLAGS.limit ? candidates.slice(0, FLAGS.limit) : candidates;
39+
},
40+
isFresh: isFreshTranslation,
41+
generate: generateAndRecord,
42+
writeEdit: (candidate, edit) => writeAiEdit(candidate.page, candidate.ref, edit),
43+
recordGeneration: recordGenerationForCandidate,
44+
});
45+
}
46+
47+
main().catch(e => {
48+
console.error(e);
49+
process.exitCode = 1;
50+
});

0 commit comments

Comments
 (0)