Includes: - docs/: architecture, block-schema, migration-plan, product-specification - scripts/: bidi_table, decompose-decisions, extract-claims, seed-knowledge, etc. - skill-legal-decision/: SKILL.md + references + block-schema - skill-legal-assistant/: SKILL.md - skill-legal-docx/: SKILL.md + references - .claude/commands/: bidi-table skill - .taskmaster/: task config + PRDs - .gitignore: exclude legacy/, kiryat-yearim/, node_modules/, memory/ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
262 lines
9.8 KiB
JavaScript
262 lines
9.8 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* create-legal-doc.js — תבנית בסיסית למסמך משפטי בעברית
|
||
*
|
||
* שימוש: העתק לתיקיית העבודה, ערוך את CONTENT, הרץ עם node.
|
||
*
|
||
* הסקריפט כולל את כל הגדרות ה-RTL הנדרשות ב-5 רמות:
|
||
* 1. Document defaults (styles)
|
||
* 2. Section properties (bidi: true)
|
||
* 3. Per-paragraph (bidirectional + alignment)
|
||
* 4. Per-run (rightToLeft + font)
|
||
* 5. Numbering (alignment: START, not RIGHT!)
|
||
*
|
||
* שינוי פונט: החלף "David" ב-"FrankRuehl" או "Miriam"
|
||
* שינוי שוליים: שנה MARGIN_CM
|
||
*
|
||
* v3.0 — תיקון באג: AlignmentType.START במקום RIGHT במספור
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const { Document, Packer, Paragraph, TextRun, Header, Footer,
|
||
AlignmentType, HeadingLevel, PageNumber, LevelFormat,
|
||
Table, TableRow, TableCell, WidthType, BorderStyle,
|
||
LineRuleType } = require('docx');
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// CONFIGURATION — שנה כאן
|
||
// ═══════════════════════════════════════════════
|
||
const FONT = "David";
|
||
const FONT_SIZE = 24; // half-points (24 = 12pt)
|
||
const HEADING1_SIZE = 32; // 16pt
|
||
const HEADING2_SIZE = 28; // 14pt
|
||
const MARGIN_CM = 2.5;
|
||
const MARGIN_DXA = Math.round(MARGIN_CM / 2.54 * 1440);
|
||
const OUTPUT_FILE = "legal-document.docx";
|
||
|
||
const HEADER_TEXT = ""; // שם המשרד — השאר ריק אם לא צריך
|
||
const FOOTER_CONFIDENTIAL = "";
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// HELPERS — פונקציות עזר
|
||
// ═══════════════════════════════════════════════
|
||
|
||
// TextRun עם RTL
|
||
const rtlRun = (text, opts = {}) => new TextRun({
|
||
text,
|
||
...opts,
|
||
font: opts.font || FONT,
|
||
size: opts.size || FONT_SIZE,
|
||
rightToLeft: true, // ← חובה! תמיד אחרון — לא ניתן לדריסה
|
||
});
|
||
|
||
// Paragraph עם RTL
|
||
const rtlPara = (children, opts = {}) => new Paragraph({
|
||
bidirectional: true, // ← חובה!
|
||
alignment: opts.alignment || AlignmentType.BOTH, // ✅ BOTH, לא JUSTIFIED
|
||
spacing: opts.spacing,
|
||
children: Array.isArray(children) ? children : [children],
|
||
...(opts.heading ? { heading: opts.heading } : {}),
|
||
...(opts.numbering ? { numbering: opts.numbering } : {}),
|
||
});
|
||
|
||
// כותרת ראשית
|
||
const heading1 = (text) => rtlPara(
|
||
rtlRun(text, { bold: true, size: HEADING1_SIZE }),
|
||
{ heading: HeadingLevel.HEADING_1, alignment: AlignmentType.CENTER }
|
||
);
|
||
|
||
// כותרת משנה
|
||
const heading2 = (text) => rtlPara(
|
||
rtlRun(text, { bold: true, size: HEADING2_SIZE }),
|
||
{ heading: HeadingLevel.HEADING_2, alignment: AlignmentType.START } // ✅ START
|
||
);
|
||
|
||
// רווח
|
||
const spacer = (after = 200) => rtlPara(rtlRun(""), { spacing: { after } });
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// CONTENT — ערוך כאן את תוכן המסמך
|
||
// ═══════════════════════════════════════════════
|
||
|
||
const CONTENT = [
|
||
// כותרת
|
||
heading1("הסכם שירותים משפטיים"),
|
||
|
||
// תאריך
|
||
rtlPara(rtlRun("נערך ונחתם בתל אביב ביום ___________"), { spacing: { after: 200 } }),
|
||
|
||
// צדדים
|
||
heading2("בין הצדדים"),
|
||
|
||
rtlPara([
|
||
rtlRun("מצד אחד: ", { bold: true }),
|
||
rtlRun('[שם], ח.פ./ת.ז. [מספר], מ[כתובת] (להלן: "'),
|
||
rtlRun("המזמין", { bold: true }),
|
||
rtlRun('")'),
|
||
], { spacing: { after: 120 } }),
|
||
|
||
rtlPara([
|
||
rtlRun("מצד שני: ", { bold: true }),
|
||
rtlRun('[שם], ח.פ./ת.ז. [מספר], מ[כתובת] (להלן: "'),
|
||
rtlRun("הספק", { bold: true }),
|
||
rtlRun('")'),
|
||
], { spacing: { after: 200 } }),
|
||
|
||
// הואילים
|
||
heading2("הואיל:"),
|
||
rtlPara(rtlRun("1. והואיל ו[תנאי ראשון];"), { spacing: { after: 120 } }),
|
||
rtlPara(rtlRun("2. והואיל ו[תנאי שני];"), { spacing: { after: 120 } }),
|
||
rtlPara(rtlRun("3. והואיל והצדדים מעוניינים להסדיר את תנאי ההתקשרות ביניהם;"),
|
||
{ spacing: { after: 200 } }),
|
||
|
||
// מעבר
|
||
rtlPara(rtlRun("לפיכך הוסכם, הותנה והוצהר בין הצדדים כדלקמן:", { bold: true }),
|
||
{ alignment: AlignmentType.CENTER, spacing: { before: 200, after: 200 } }),
|
||
|
||
// סעיפים
|
||
heading2("1. היקף השירותים"),
|
||
rtlPara(rtlRun("1.1 [תוכן הסעיף]"), { spacing: { after: 120 } }),
|
||
rtlPara(rtlRun("1.2 [תוכן הסעיף]"), { spacing: { after: 120 } }),
|
||
|
||
heading2("2. התמורה"),
|
||
rtlPara(rtlRun("2.1 [תוכן הסעיף]"), { spacing: { after: 120 } }),
|
||
|
||
// חתימות
|
||
rtlPara(rtlRun("ולראיה באו הצדדים על החתום:", { bold: true }),
|
||
{ alignment: AlignmentType.CENTER, spacing: { before: 600, after: 400 } }),
|
||
|
||
// טבלת חתימות
|
||
new Table({
|
||
visuallyRightToLeft: true, // ✅ קריטי!
|
||
width: { size: 9072, type: WidthType.DXA },
|
||
columnWidths: [4536, 4536],
|
||
rows: [
|
||
new TableRow({
|
||
children: [
|
||
new TableCell({
|
||
borders: {
|
||
top: { style: BorderStyle.NONE },
|
||
bottom: { style: BorderStyle.NONE },
|
||
left: { style: BorderStyle.NONE },
|
||
right: { style: BorderStyle.NONE }
|
||
},
|
||
children: [
|
||
rtlPara(rtlRun("________________________"), { alignment: AlignmentType.CENTER }),
|
||
rtlPara(rtlRun("המזמין"), { alignment: AlignmentType.CENTER })
|
||
]
|
||
}),
|
||
new TableCell({
|
||
borders: {
|
||
top: { style: BorderStyle.NONE },
|
||
bottom: { style: BorderStyle.NONE },
|
||
left: { style: BorderStyle.NONE },
|
||
right: { style: BorderStyle.NONE }
|
||
},
|
||
children: [
|
||
rtlPara(rtlRun("________________________"), { alignment: AlignmentType.CENTER }),
|
||
rtlPara(rtlRun("הספק"), { alignment: AlignmentType.CENTER })
|
||
]
|
||
})
|
||
]
|
||
})
|
||
]
|
||
})
|
||
];
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// DOCUMENT GENERATION — לא לשנות (אלא אם יודע מה עושים)
|
||
// ═══════════════════════════════════════════════
|
||
|
||
const doc = new Document({
|
||
// רמה 1: Document defaults
|
||
styles: {
|
||
default: {
|
||
document: {
|
||
run: { font: FONT, size: FONT_SIZE, rightToLeft: true },
|
||
paragraph: { bidirectional: true, alignment: AlignmentType.BOTH }
|
||
}
|
||
},
|
||
paragraphStyles: [
|
||
{
|
||
id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal",
|
||
quickFormat: true,
|
||
run: { size: HEADING1_SIZE, bold: true, font: FONT, rightToLeft: true },
|
||
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0,
|
||
bidirectional: true, alignment: AlignmentType.CENTER }
|
||
},
|
||
{
|
||
id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal",
|
||
quickFormat: true,
|
||
run: { size: HEADING2_SIZE, bold: true, font: FONT, rightToLeft: true },
|
||
paragraph: { spacing: { before: 200, after: 200 }, outlineLevel: 1,
|
||
bidirectional: true, alignment: AlignmentType.START } // ✅ START
|
||
},
|
||
]
|
||
},
|
||
// מספור סעיפים
|
||
numbering: {
|
||
config: [{
|
||
reference: "legal-clauses",
|
||
levels: [
|
||
{
|
||
level: 0,
|
||
format: LevelFormat.DECIMAL,
|
||
text: "%1.",
|
||
alignment: AlignmentType.START, // ✅ START — לא RIGHT!
|
||
suffix: "tab",
|
||
style: { paragraph: { indent: { left: 720, hanging: 360 } } }
|
||
},
|
||
{
|
||
level: 1,
|
||
format: LevelFormat.DECIMAL,
|
||
text: "%1.%2",
|
||
alignment: AlignmentType.START, // ✅ START — לא RIGHT!
|
||
suffix: "tab",
|
||
style: { paragraph: { indent: { left: 1440, hanging: 500 } } }
|
||
},
|
||
]
|
||
}]
|
||
},
|
||
sections: [{
|
||
// רמה 2: Section properties
|
||
properties: {
|
||
page: {
|
||
size: { width: 11906, height: 16838 }, // A4
|
||
margin: { top: MARGIN_DXA, right: MARGIN_DXA, bottom: MARGIN_DXA, left: MARGIN_DXA }
|
||
},
|
||
bidi: true, // ← חובה!
|
||
},
|
||
headers: HEADER_TEXT ? {
|
||
default: new Header({
|
||
children: [rtlPara(rtlRun(HEADER_TEXT, { bold: true, size: 20 }),
|
||
{ alignment: AlignmentType.CENTER })]
|
||
})
|
||
} : undefined,
|
||
footers: {
|
||
default: new Footer({
|
||
children: [new Paragraph({
|
||
bidirectional: true,
|
||
alignment: AlignmentType.CENTER,
|
||
children: [
|
||
rtlRun("עמוד ", { size: 18 }),
|
||
new TextRun({ children: [PageNumber.CURRENT], font: FONT, size: 18 }),
|
||
...(FOOTER_CONFIDENTIAL ? [rtlRun(` | ${FOOTER_CONFIDENTIAL}`, { size: 18 })] : []),
|
||
]
|
||
})]
|
||
})
|
||
},
|
||
children: CONTENT
|
||
}]
|
||
});
|
||
|
||
Packer.toBuffer(doc).then(buffer => {
|
||
fs.writeFileSync(OUTPUT_FILE, buffer);
|
||
console.log(`✅ ${OUTPUT_FILE} created successfully`);
|
||
console.log(` Font: ${FONT} ${FONT_SIZE/2}pt`);
|
||
console.log(` Margins: ${MARGIN_CM}cm (${MARGIN_DXA} DXA)`);
|
||
console.log(` Size: ${(buffer.length / 1024).toFixed(1)} KB`);
|
||
console.log(` RTL: bidi + bidirectional + rightToLeft ✓`);
|
||
console.log(` Alignment: START/END (not LEFT/RIGHT) ✓`);
|
||
});
|