Add docs, scripts, skills, commands, and taskmaster config to repo
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>
This commit is contained in:
261
skill-legal-docx/scripts/create-legal-doc.js
Normal file
261
skill-legal-docx/scripts/create-legal-doc.js
Normal file
@@ -0,0 +1,261 @@
|
||||
#!/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) ✓`);
|
||||
});
|
||||
Reference in New Issue
Block a user