Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7d901be29 | |||
| 3178904b28 | |||
| c1535e744f | |||
| 15602a976f | |||
| ffc60a6c0f | |||
| 53844ace9a | |||
| f8022279ad | |||
| 763638abd5 | |||
| 99bc442495 | |||
| 61fe16a0ea |
@@ -0,0 +1,12 @@
|
||||
# API Keys (Required to enable respective provider)
|
||||
ANTHROPIC_API_KEY="your_anthropic_api_key_here" # Required: Format: sk-ant-api03-...
|
||||
PERPLEXITY_API_KEY="your_perplexity_api_key_here" # Optional: Format: pplx-...
|
||||
OPENAI_API_KEY="your_openai_api_key_here" # Optional, for OpenAI models. Format: sk-proj-...
|
||||
GOOGLE_API_KEY="your_google_api_key_here" # Optional, for Google Gemini models.
|
||||
MISTRAL_API_KEY="your_mistral_key_here" # Optional, for Mistral AI models.
|
||||
XAI_API_KEY="YOUR_XAI_KEY_HERE" # Optional, for xAI AI models.
|
||||
GROQ_API_KEY="YOUR_GROQ_KEY_HERE" # Optional, for Groq models.
|
||||
OPENROUTER_API_KEY="YOUR_OPENROUTER_KEY_HERE" # Optional, for OpenRouter models.
|
||||
AZURE_OPENAI_API_KEY="your_azure_key_here" # Optional, for Azure OpenAI models (requires endpoint in .taskmaster/config.json).
|
||||
OLLAMA_API_KEY="your_ollama_api_key_here" # Optional: For remote Ollama servers that require authentication.
|
||||
GITHUB_API_KEY="your_github_api_key_here" # Optional: For GitHub import/export features. Format: ghp_... or github_pat_...
|
||||
+26
@@ -1,3 +1,29 @@
|
||||
*.bak
|
||||
*.tmp
|
||||
/legal-docx-v3-fixed/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
dev-debug.log
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
# Environment variables
|
||||
.env
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
# OS specific
|
||||
.DS_Store
|
||||
|
||||
# Task files
|
||||
# tasks.json
|
||||
# tasks/
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"models": {
|
||||
"main": {
|
||||
"provider": "claude-code",
|
||||
"modelId": "opus",
|
||||
"maxTokens": 32000,
|
||||
"temperature": 0.2
|
||||
},
|
||||
"research": {
|
||||
"provider": "claude-code",
|
||||
"modelId": "opus",
|
||||
"maxTokens": 32000,
|
||||
"temperature": 0.1
|
||||
},
|
||||
"fallback": {
|
||||
"provider": "claude-code",
|
||||
"modelId": "sonnet",
|
||||
"maxTokens": 64000,
|
||||
"temperature": 0.2
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"logLevel": "info",
|
||||
"debug": false,
|
||||
"defaultNumTasks": 10,
|
||||
"defaultSubtasks": 5,
|
||||
"defaultPriority": "medium",
|
||||
"projectName": "Task Master",
|
||||
"ollamaBaseURL": "http://localhost:11434/api",
|
||||
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
|
||||
"responseLanguage": "Hebrew",
|
||||
"enableCodebaseAnalysis": true,
|
||||
"enableProxy": false,
|
||||
"anonymousTelemetry": true,
|
||||
"userId": "1234567890"
|
||||
},
|
||||
"claudeCode": {},
|
||||
"codexCli": {},
|
||||
"grokCli": {
|
||||
"timeout": 120000,
|
||||
"workingDirectory": null,
|
||||
"defaultModel": "grok-4-latest"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"currentTag": "master",
|
||||
"lastSwitched": "2026-06-03T07:25:36.666Z",
|
||||
"branchTagMapping": {},
|
||||
"migrationNoticeShown": true
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"master": {
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "fix: subject-only direct-access report naming (tight hyphen) + case-folder upload",
|
||||
"description": "Report file uses subject-only canonical naming and is uploaded to the case folder correctly.",
|
||||
"details": "DirectAccessReportGenerator: em-dash -> tight hyphen; sanitize via LocalFilesystemClient::sanitizeFileName; save Document with skipNetworkUpload, relate to Case, explicit uploadDocument + DB realign to canonical stored name.",
|
||||
"testStrategy": "",
|
||||
"status": "done",
|
||||
"dependencies": [],
|
||||
"priority": "high",
|
||||
"subtasks": [],
|
||||
"updatedAt": "2026-06-03T08:19:08.135Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "תיקון אבטחה: הסרת מחיקת טבלאות ועמודות מ-AfterUninstall",
|
||||
"description": "הסרת הקריאות ל-dropTables ו-cleanCaseColumns מ-AfterUninstall.php כדי למנוע אובדן נתונים בלתי הפיך בעת הסרה/התקנה מחדש של ההרחבה.",
|
||||
"details": "## רקע\n\nהממצא F-002 בביקורת האבטחה (SECURITY_AUDIT_2026-04-25.md:137-168) מזהה שהסקריפט `scripts/AfterUninstall.php` מבצע פעולות הרסניות:\n\n1. **מחיקת טבלאות** (שורות 54-73): `DROP TABLE legal_aid` ו-`DROP TABLE direct_access_report`\n2. **מחיקת עמודות** (שורות 75-101): `ALTER TABLE case DROP COLUMN` על 5 עמודות (`c_legal_aid_type`, `c_aid_type`, `c_urgency_level`, `c_appointment_date`, `c_filing_deadline`)\n\nזה מפר את **כלל N2** מ-EXTENSION_DEVELOPMENT_RULES.md:\n> כאשר הרחבה מוסיפה שדות לישויות ליבה (Case, Account, Contact וכו'), הסקריפט AfterUninstall חייב להסיר רק את ההפניות מ-layouts אבל **להשאיר את עמודות הדאטאבייס**, כך שנתוני הלקוח ישרדו מחזור הסרה/התקנה מחדש.\n\n## שינויים נדרשים\n\n### 1. הסרת הפונקציה `dropTables()` (שורות 54-73)\nמחק את כל הפונקציה והקריאה אליה בשורה 31.\n\n### 2. הסרת הפונקציה `cleanCaseColumns()` (שורות 75-101)\nמחק את כל הפונקציה והקריאה אליה בשורה 34.\n\n### 3. הוספת פונקציית ניקוי layouts (אופציונלי)\nאם יש layouts ב-`custom/Espo/Custom/Resources/layouts/Case/*.json` שמפנים לשדות שנוספו, יש להסיר את ההפניות:\n\n```php\nprivate function removeLayoutReferences(Log $log): void\n{\n $layoutDir = 'custom/Espo/Custom/Resources/layouts/Case';\n if (!is_dir($layoutDir)) {\n return;\n }\n\n $fieldsToRemove = [\n 'cLegalAidType',\n 'cAidType',\n 'cUrgencyLevel',\n 'cAppointmentDate',\n 'cFilingDeadline',\n ];\n\n foreach (glob($layoutDir . '/*.json') as $file) {\n $content = file_get_contents($file);\n $layout = json_decode($content, true);\n if (!$layout) continue;\n\n $modified = $this->removeFieldsFromLayout($layout, $fieldsToRemove);\n if ($modified) {\n file_put_contents($file, json_encode($layout, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));\n $log->info(\"LegalAssistance: Removed field references from {$file}\");\n }\n }\n}\n\nprivate function removeFieldsFromLayout(array &$layout, array $fields): bool\n{\n $modified = false;\n foreach ($layout as $key => &$value) {\n if (is_array($value)) {\n if (isset($value['name']) && in_array($value['name'], $fields)) {\n unset($layout[$key]);\n $modified = true;\n } else {\n $modified = $this->removeFieldsFromLayout($value, $fields) || $modified;\n }\n }\n }\n $layout = array_values($layout); // Re-index\n return $modified;\n}\n```\n\n### 4. עדכון תיעוד הקוד בראש הקובץ\nשנה את התיאור ב-PHPDoc להסביר שהסקריפט מסיר רק:\n- רשומות DocumentTemplate\n- קבצי i18n\n- הפניות ב-layouts (אם רלוונטי)\n- וניקוי cache\n\n**לא** מוחק טבלאות או עמודות.\n\n## מבנה הקובץ לאחר התיקון\n\n```php\n<?php\n/************************************************************************\n * LegalAssistance Extension — AfterUninstall\n *\n * 1. Remove DocumentTemplate records created by this extension\n * 2. Remove custom i18n files\n * 3. (Optional) Remove layout references to extension fields\n * 4. Clear cache\n *\n * Note: Database tables and columns are intentionally preserved to\n * protect customer data during uninstall/reinstall cycles (rule N2).\n ************************************************************************/\n\nclass AfterUninstall\n{\n public function run(Container $container): void\n {\n // ... (שמירה על removeDocumentTemplates, removeCustomI18n, clearCache)\n // הסרה מלאה של dropTables ו-cleanCaseColumns\n }\n}\n```",
|
||||
"testStrategy": "## אסטרטגיית בדיקה\n\n### 1. בדיקת יחידה - סקריפט האנאינסטול\n- וודא שהקובץ `AfterUninstall.php` לא מכיל את המילים `DROP TABLE` או `DROP COLUMN`\n- וודא שאין קריאות לפונקציות `dropTables` או `cleanCaseColumns`\n\n### 2. בדיקת אינטגרציה בסביבת פיתוח\n1. **הכנה**: התקן את ההרחבה על instance ריק של EspoCRM\n2. **יצירת נתונים**: צור Case עם ערכים בשדות הרלוונטיים (אם קיימים)\n3. **הסרה**: הסר את ההרחבה דרך Administration → Extensions\n4. **אימות**: בדוק שהטבלאות `legal_aid` ו-`direct_access_report` עדיין קיימות (אם היו מלכתחילה)\n5. **אימות**: בדוק שהעמודות ב-Case עדיין קיימות (אם היו מלכתחילה)\n6. **התקנה מחדש**: התקן את ההרחבה שוב\n7. **אימות**: וודא שהנתונים שנשמרו בשלב 2 עדיין קיימים\n\n### 3. בדיקת לוגים\n- לאחר הסרה, בדוק את `data/logs/espo-YYYY-MM-DD.log`\n- וודא שאין שורות `Dropping table` או `Dropped column`\n- וודא שיש שורות `Removed DocumentTemplate` ו-`Cache cleared`\n\n### 4. בדיקת קוד סטטית\n```bash\n# בדוק שאין DROP statements\ngrep -n \"DROP TABLE\\|DROP COLUMN\" scripts/AfterUninstall.php\n# צפוי: אין תוצאות\n\n# בדוק שאין קריאות לפונקציות שהוסרו\ngrep -n \"dropTables\\|cleanCaseColumns\" scripts/AfterUninstall.php\n# צפוי: אין תוצאות\n```\n\n### 5. בדיקת רגרסיה\n- וודא שה-DocumentTemplate עדיין נמחק כראוי\n- וודא שקבצי i18n עדיין נמחקים כראוי\n- וודא ש-cache clearing עדיין עובד",
|
||||
"status": "pending",
|
||||
"dependencies": [
|
||||
1
|
||||
],
|
||||
"priority": "high",
|
||||
"subtasks": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "תיקון אבטחה: הוספת בדיקת ACL ברמת רשומה ב-DirectAccessReportGenerator",
|
||||
"description": "הוספת בדיקת הרשאות ברמת רשומה (record-level ACL) לכלי generate_direct_access_report למניעת IDOR — כרגע הכלי סומך רק על בדיקת scope-level ומאפשר למשתמש עם הרשאת קריאה לתיקים לצרף מסמכים לכל תיק במערכת.",
|
||||
"details": "## רקע\n\nהממצא F-001 בביקורת האבטחה (SECURITY_AUDIT_2026-04-25.md:61-134) מזהה פגיעות IDOR חמורה:\n\nהכלי `DirectAccessReportGenerator.php` מקבל `caseId` שרירותי ומייצר מסמך DOCX שמצורף לתיק ללא בדיקת הרשאות ברמת הרשומה. הבדיקה היחידה שמתבצעת היא ב-upstream (SmartAssistant ActionExecutor) ברמת scope בלבד (`acl->checkScope('Case', 'read')`).\n\n**וקטור התקיפה:** כל משתמש EspoCRM מאומת עם הרשאת קריאה לתיקים יכול:\n1. לקרוא ל-`POST /api/v1/SmartAssistant/action/executeTool` עם `caseId` של תיק שאינו שלו\n2. הכלי קורא ל-`getEntityById('Case', $caseId)` שעוקף ACL כברירת מחדל\n3. מסמך DOCX מזויף נוצר ומצורף לתיק הקורבן\n4. המסמך מכיל תוכן שנשלט על ידי התוקף אך נושא את שם עורך הדין המוקצה לתיק\n\n## שינויים נדרשים\n\n### 1. הזרקת AclManager לקונסטרקטור\n\n```php\n// files/custom/Espo/Modules/LegalAssistance/SmartAssistant/Tools/DirectAccessReportGenerator.php\n\nuse Espo\\Core\\Acl;\nuse Espo\\Core\\Exceptions\\Forbidden;\n\n// ...\n\npublic function __construct(\n private EntityManager $entityManager,\n private InjectableFactory $injectableFactory,\n private Log $log,\n private User $user,\n private Acl $acl // הוסף\n) {}\n```\n\n### 2. בדיקת הרשאות ברמת רשומה בתחילת execute()\n\nבתחילת המתודה `execute()`, מיד אחרי שליפת התיק (שורה 41-44):\n\n```php\npublic function execute(array $params, ?string $caseId, string $userId): array\n{\n if (!$caseId) {\n throw new Error('generate_direct_access_report requires a case context.');\n }\n\n $this->log->info(\"LegalAssistance: Generating direct access report for case {$caseId}\");\n\n $case = $this->entityManager->getEntityById('Case', $caseId);\n if (!$case) {\n throw new Error(\"Case {$caseId} not found.\");\n }\n\n // === תיקון אבטחה F-001: בדיקת ACL ברמת רשומה ===\n // נדרשת הרשאת עריכה כי אנחנו מצרפים מסמך לתיק\n if (!$this->acl->checkEntityEdit($case)) {\n $this->log->warning(\n \"LegalAssistance: ACL denied — user {$this->user->getId()} \" .\n \"attempted to attach document to case {$caseId}\"\n );\n throw new Forbidden(\"אין לך הרשאה לצרף מסמכים לתיק זה.\");\n }\n\n // ... המשך הקוד הקיים\n}\n```\n\n### 3. שיקולי מימוש\n\n- **למה `checkEntityEdit` ולא `checkEntityRead`?** — יצירת Document וקישורו לתיק היא פעולת כתיבה, לא קריאה. משתמש עם read-only על התיק לא צריך לוכל לשנות את רשימת המסמכים שלו.\n\n- **למה `$this->acl` ולא `AclManager`?** — ה-`Acl` service כבר מוזרק עם ה-User הנכון (המשתמש המבצע את הבקשה). שימוש ב-`AclManager->checkEntity($user, $entity, 'edit')` היה דורש לשלוף את ה-User לפי `$userId` — מיותר כש-DI כבר מספק לנו את זה.\n\n- **לוגים:** חשוב לתעד ניסיונות גישה שנדחו לצורך אבחון וניטור אבטחה.\n\n- **שגיאה בעברית:** ההודעה מוחזרת למשתמש דרך shira-hermes, אז עברית מתאימה לחוויית משתמש.\n\n### 4. קובץ יחיד לעריכה\n\n`files/custom/Espo/Modules/LegalAssistance/SmartAssistant/Tools/DirectAccessReportGenerator.php`\n\n**שורות לעריכה:**\n- שורות 12-19: הוספת `use` statements\n- שורות 26-31: הוספת `Acl` לקונסטרקטור\n- שורות 40-44: הוספת בדיקת ACL אחרי שליפת התיק",
|
||||
"testStrategy": "## אסטרטגיית בדיקה\n\n### 1. בדיקה ידנית — מניעת IDOR\n\n**הכנה:**\n1. צור שני משתמשים ב-EspoCRM:\n - `lawyer-a` — עם תיק משויך (נקרא לו `case-a`)\n - `lawyer-b` — ללא גישה ל-`case-a`\n\n2. וודא ש-`lawyer-b` יכול לקרוא תיקים (scope-level read) אבל לא את `case-a` (record-level)\n\n**בדיקה:**\n1. התחבר כ-`lawyer-b`\n2. קרא ל-API:\n ```bash\n curl -X POST https://espocrm.dev.marcus-law.co.il/api/v1/SmartAssistant/action/executeTool \\\n -H \"Content-Type: application/json\" \\\n -H \"X-Api-Key: <lawyer-b-api-key>\" \\\n -d '{\n \"tool\": \"generate_direct_access_report\",\n \"caseId\": \"<case-a-id>\",\n \"params\": {\"aidType\": \"יעוץ והדרכה\"}\n }'\n ```\n3. **תוצאה צפויה:** שגיאת 403 Forbidden עם הודעה \"אין לך הרשאה לצרף מסמכים לתיק זה.\"\n4. וודא שלא נוצר Document חדש ב-`case-a`\n\n### 2. בדיקת regression — פעולה תקינה\n\n1. התחבר כ-`lawyer-a` (בעל התיק)\n2. קרא לאותו API עם `caseId` של התיק שלו\n3. **תוצאה צפויה:** הדוח נוצר בהצלחה ומצורף לתיק\n\n### 3. בדיקת לוגים\n\n1. לאחר ניסיון IDOR שנדחה, בדוק בלוג:\n ```bash\n docker exec <espocrm-container> tail -20 data/logs/espo-$(date +%Y-%m-%d).log | grep \"ACL denied\"\n ```\n2. **תוצאה צפויה:** שורת לוג עם WARNING המציינת את user ID וה-case ID\n\n### 4. בדיקת אינטגרציה עם shira-hermes\n\n1. דרך ממשק Shira (צ'אט), בקש מעוזר המשפט ליצור דוח לתיק שאין לך גישה אליו\n2. **תוצאה צפויה:** הודעת שגיאה ידידותית בעברית מוחזרת למשתמש\n\n### 5. בדיקת unit (אופציונלי)\n\nאם קיימת תשתית PHPUnit בפרויקט:\n\n```php\npublic function testExecuteThrowsForbiddenWhenUserCannotEditCase(): void\n{\n // Mock Acl to return false for checkEntityEdit\n $acl = $this->createMock(Acl::class);\n $acl->method('checkEntityEdit')->willReturn(false);\n \n $generator = new DirectAccessReportGenerator(\n $this->entityManager,\n $this->injectableFactory,\n $this->log,\n $this->user,\n $acl\n );\n \n $this->expectException(Forbidden::class);\n $generator->execute(['aidType' => 'test'], 'some-case-id', 'user-id');\n}\n```",
|
||||
"status": "pending",
|
||||
"dependencies": [
|
||||
1
|
||||
],
|
||||
"priority": "high",
|
||||
"subtasks": []
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "feat: P7 — שיוך דוח גישה ישירה לתיקיית CaseFiles",
|
||||
"description": "הוספת ענף CaseFiles ליצירת המסמכים: אם CaseFilesCore מותקן (soft-detect), המסמך שנוצר משויך לתיקיית 'מסמכים' של התיק (folderId) כדי שיופיע בפאנל CaseDocs. ההתנהגות הקיימת (קישור לתיק + העלאה ל-NetworkStorage אם פעיל) נשמרת — ללא רגרסיה.",
|
||||
"details": "אומת על dev 21/06/2026 (P7_OK): php -l נקי; קישור דרך Case.documents נראה דרך Document.cases (אותה טבלת קישור), folderId נקבע לתיקיית 'מסמכים'. ענף זהה לנתב P5/P6 המאומת.",
|
||||
"testStrategy": "dev: יצירת מסמך עם CaseFilesCore מותקן → folderId=מסמכים, נראה בפאנל.",
|
||||
"status": "done",
|
||||
"dependencies": [],
|
||||
"priority": "medium",
|
||||
"subtasks": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"version": "1.0.0",
|
||||
"lastModified": "2026-06-03T08:19:08.136Z",
|
||||
"taskCount": 1,
|
||||
"completedCount": 1,
|
||||
"tags": [
|
||||
"master"
|
||||
],
|
||||
"created": "2026-06-20T12:07:53.783Z",
|
||||
"description": "Tasks for master context",
|
||||
"updated": "2026-06-21"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<context>
|
||||
# Overview
|
||||
[Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.]
|
||||
|
||||
# Core Features
|
||||
[List and describe the main features of your product. For each feature, include:
|
||||
- What it does
|
||||
- Why it's important
|
||||
- How it works at a high level]
|
||||
|
||||
# User Experience
|
||||
[Describe the user journey and experience. Include:
|
||||
- User personas
|
||||
- Key user flows
|
||||
- UI/UX considerations]
|
||||
</context>
|
||||
<PRD>
|
||||
# Technical Architecture
|
||||
[Outline the technical implementation details:
|
||||
- System components
|
||||
- Data models
|
||||
- APIs and integrations
|
||||
- Infrastructure requirements]
|
||||
|
||||
# Development Roadmap
|
||||
[Break down the development process into phases:
|
||||
- MVP requirements
|
||||
- Future enhancements
|
||||
- Do not think about timelines whatsoever -- all that matters is scope and detailing exactly what needs to be build in each phase so it can later be cut up into tasks]
|
||||
|
||||
# Logical Dependency Chain
|
||||
[Define the logical order of development:
|
||||
- Which features need to be built first (foundation)
|
||||
- Getting as quickly as possible to something usable/visible front end that works
|
||||
- Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches]
|
||||
|
||||
# Risks and Mitigations
|
||||
[Identify potential risks and how they'll be addressed:
|
||||
- Technical challenges
|
||||
- Figuring out the MVP that we can build upon
|
||||
- Resource constraints]
|
||||
|
||||
# Appendix
|
||||
[Include any additional information:
|
||||
- Research findings
|
||||
- Technical specifications]
|
||||
</PRD>
|
||||
@@ -0,0 +1,511 @@
|
||||
<rpg-method>
|
||||
# Repository Planning Graph (RPG) Method - PRD Template
|
||||
|
||||
This template teaches you (AI or human) how to create structured, dependency-aware PRDs using the RPG methodology from Microsoft Research. The key insight: separate WHAT (functional) from HOW (structural), then connect them with explicit dependencies.
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Dual-Semantics**: Think functional (capabilities) AND structural (code organization) separately, then map them
|
||||
2. **Explicit Dependencies**: Never assume - always state what depends on what
|
||||
3. **Topological Order**: Build foundation first, then layers on top
|
||||
4. **Progressive Refinement**: Start broad, refine iteratively
|
||||
|
||||
## How to Use This Template
|
||||
|
||||
- Follow the instructions in each `<instruction>` block
|
||||
- Look at `<example>` blocks to see good vs bad patterns
|
||||
- Fill in the content sections with your project details
|
||||
- The AI reading this will learn the RPG method by following along
|
||||
- Task Master will parse the resulting PRD into dependency-aware tasks
|
||||
|
||||
## Recommended Tools for Creating PRDs
|
||||
|
||||
When using this template to **create** a PRD (not parse it), use **code-context-aware AI assistants** for best results:
|
||||
|
||||
**Why?** The AI needs to understand your existing codebase to make good architectural decisions about modules, dependencies, and integration points.
|
||||
|
||||
**Recommended tools:**
|
||||
- **Claude Code** (claude-code CLI) - Best for structured reasoning and large contexts
|
||||
- **Cursor/Windsurf** - IDE integration with full codebase context
|
||||
- **Gemini CLI** (gemini-cli) - Massive context window for large codebases
|
||||
- **Codex/Grok CLI** - Strong code generation with context awareness
|
||||
|
||||
**Note:** Once your PRD is created, `task-master parse-prd` works with any configured AI model - it just needs to read the PRD text itself, not your codebase.
|
||||
</rpg-method>
|
||||
|
||||
---
|
||||
|
||||
<overview>
|
||||
<instruction>
|
||||
Start with the problem, not the solution. Be specific about:
|
||||
- What pain point exists?
|
||||
- Who experiences it?
|
||||
- Why existing solutions don't work?
|
||||
- What success looks like (measurable outcomes)?
|
||||
|
||||
Keep this section focused - don't jump into implementation details yet.
|
||||
</instruction>
|
||||
|
||||
## Problem Statement
|
||||
[Describe the core problem. Be concrete about user pain points.]
|
||||
|
||||
## Target Users
|
||||
[Define personas, their workflows, and what they're trying to achieve.]
|
||||
|
||||
## Success Metrics
|
||||
[Quantifiable outcomes. Examples: "80% task completion via autopilot", "< 5% manual intervention rate"]
|
||||
|
||||
</overview>
|
||||
|
||||
---
|
||||
|
||||
<functional-decomposition>
|
||||
<instruction>
|
||||
Now think about CAPABILITIES (what the system DOES), not code structure yet.
|
||||
|
||||
Step 1: Identify high-level capability domains
|
||||
- Think: "What major things does this system do?"
|
||||
- Examples: Data Management, Core Processing, Presentation Layer
|
||||
|
||||
Step 2: For each capability, enumerate specific features
|
||||
- Use explore-exploit strategy:
|
||||
* Exploit: What features are REQUIRED for core value?
|
||||
* Explore: What features make this domain COMPLETE?
|
||||
|
||||
Step 3: For each feature, define:
|
||||
- Description: What it does in one sentence
|
||||
- Inputs: What data/context it needs
|
||||
- Outputs: What it produces/returns
|
||||
- Behavior: Key logic or transformations
|
||||
|
||||
<example type="good">
|
||||
Capability: Data Validation
|
||||
Feature: Schema validation
|
||||
- Description: Validate JSON payloads against defined schemas
|
||||
- Inputs: JSON object, schema definition
|
||||
- Outputs: Validation result (pass/fail) + error details
|
||||
- Behavior: Iterate fields, check types, enforce constraints
|
||||
|
||||
Feature: Business rule validation
|
||||
- Description: Apply domain-specific validation rules
|
||||
- Inputs: Validated data object, rule set
|
||||
- Outputs: Boolean + list of violated rules
|
||||
- Behavior: Execute rules sequentially, short-circuit on failure
|
||||
</example>
|
||||
|
||||
<example type="bad">
|
||||
Capability: validation.js
|
||||
(Problem: This is a FILE, not a CAPABILITY. Mixing structure into functional thinking.)
|
||||
|
||||
Capability: Validation
|
||||
Feature: Make sure data is good
|
||||
(Problem: Too vague. No inputs/outputs. Not actionable.)
|
||||
</example>
|
||||
</instruction>
|
||||
|
||||
## Capability Tree
|
||||
|
||||
### Capability: [Name]
|
||||
[Brief description of what this capability domain covers]
|
||||
|
||||
#### Feature: [Name]
|
||||
- **Description**: [One sentence]
|
||||
- **Inputs**: [What it needs]
|
||||
- **Outputs**: [What it produces]
|
||||
- **Behavior**: [Key logic]
|
||||
|
||||
#### Feature: [Name]
|
||||
- **Description**:
|
||||
- **Inputs**:
|
||||
- **Outputs**:
|
||||
- **Behavior**:
|
||||
|
||||
### Capability: [Name]
|
||||
...
|
||||
|
||||
</functional-decomposition>
|
||||
|
||||
---
|
||||
|
||||
<structural-decomposition>
|
||||
<instruction>
|
||||
NOW think about code organization. Map capabilities to actual file/folder structure.
|
||||
|
||||
Rules:
|
||||
1. Each capability maps to a module (folder or file)
|
||||
2. Features within a capability map to functions/classes
|
||||
3. Use clear module boundaries - each module has ONE responsibility
|
||||
4. Define what each module exports (public interface)
|
||||
|
||||
The goal: Create a clear mapping between "what it does" (functional) and "where it lives" (structural).
|
||||
|
||||
<example type="good">
|
||||
Capability: Data Validation
|
||||
→ Maps to: src/validation/
|
||||
├── schema-validator.js (Schema validation feature)
|
||||
├── rule-validator.js (Business rule validation feature)
|
||||
└── index.js (Public exports)
|
||||
|
||||
Exports:
|
||||
- validateSchema(data, schema)
|
||||
- validateRules(data, rules)
|
||||
</example>
|
||||
|
||||
<example type="bad">
|
||||
Capability: Data Validation
|
||||
→ Maps to: src/utils.js
|
||||
(Problem: "utils" is not a clear module boundary. Where do I find validation logic?)
|
||||
|
||||
Capability: Data Validation
|
||||
→ Maps to: src/validation/everything.js
|
||||
(Problem: One giant file. Features should map to separate files for maintainability.)
|
||||
</example>
|
||||
</instruction>
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
project-root/
|
||||
├── src/
|
||||
│ ├── [module-name]/ # Maps to: [Capability Name]
|
||||
│ │ ├── [file].js # Maps to: [Feature Name]
|
||||
│ │ └── index.js # Public exports
|
||||
│ └── [module-name]/
|
||||
├── tests/
|
||||
└── docs/
|
||||
```
|
||||
|
||||
## Module Definitions
|
||||
|
||||
### Module: [Name]
|
||||
- **Maps to capability**: [Capability from functional decomposition]
|
||||
- **Responsibility**: [Single clear purpose]
|
||||
- **File structure**:
|
||||
```
|
||||
module-name/
|
||||
├── feature1.js
|
||||
├── feature2.js
|
||||
└── index.js
|
||||
```
|
||||
- **Exports**:
|
||||
- `functionName()` - [what it does]
|
||||
- `ClassName` - [what it does]
|
||||
|
||||
</structural-decomposition>
|
||||
|
||||
---
|
||||
|
||||
<dependency-graph>
|
||||
<instruction>
|
||||
This is THE CRITICAL SECTION for Task Master parsing.
|
||||
|
||||
Define explicit dependencies between modules. This creates the topological order for task execution.
|
||||
|
||||
Rules:
|
||||
1. List modules in dependency order (foundation first)
|
||||
2. For each module, state what it depends on
|
||||
3. Foundation modules should have NO dependencies
|
||||
4. Every non-foundation module should depend on at least one other module
|
||||
5. Think: "What must EXIST before I can build this module?"
|
||||
|
||||
<example type="good">
|
||||
Foundation Layer (no dependencies):
|
||||
- error-handling: No dependencies
|
||||
- config-manager: No dependencies
|
||||
- base-types: No dependencies
|
||||
|
||||
Data Layer:
|
||||
- schema-validator: Depends on [base-types, error-handling]
|
||||
- data-ingestion: Depends on [schema-validator, config-manager]
|
||||
|
||||
Core Layer:
|
||||
- algorithm-engine: Depends on [base-types, error-handling]
|
||||
- pipeline-orchestrator: Depends on [algorithm-engine, data-ingestion]
|
||||
</example>
|
||||
|
||||
<example type="bad">
|
||||
- validation: Depends on API
|
||||
- API: Depends on validation
|
||||
(Problem: Circular dependency. This will cause build/runtime issues.)
|
||||
|
||||
- user-auth: Depends on everything
|
||||
(Problem: Too many dependencies. Should be more focused.)
|
||||
</example>
|
||||
</instruction>
|
||||
|
||||
## Dependency Chain
|
||||
|
||||
### Foundation Layer (Phase 0)
|
||||
No dependencies - these are built first.
|
||||
|
||||
- **[Module Name]**: [What it provides]
|
||||
- **[Module Name]**: [What it provides]
|
||||
|
||||
### [Layer Name] (Phase 1)
|
||||
- **[Module Name]**: Depends on [[module-from-phase-0], [module-from-phase-0]]
|
||||
- **[Module Name]**: Depends on [[module-from-phase-0]]
|
||||
|
||||
### [Layer Name] (Phase 2)
|
||||
- **[Module Name]**: Depends on [[module-from-phase-1], [module-from-foundation]]
|
||||
|
||||
[Continue building up layers...]
|
||||
|
||||
</dependency-graph>
|
||||
|
||||
---
|
||||
|
||||
<implementation-roadmap>
|
||||
<instruction>
|
||||
Turn the dependency graph into concrete development phases.
|
||||
|
||||
Each phase should:
|
||||
1. Have clear entry criteria (what must exist before starting)
|
||||
2. Contain tasks that can be parallelized (no inter-dependencies within phase)
|
||||
3. Have clear exit criteria (how do we know phase is complete?)
|
||||
4. Build toward something USABLE (not just infrastructure)
|
||||
|
||||
Phase ordering follows topological sort of dependency graph.
|
||||
|
||||
<example type="good">
|
||||
Phase 0: Foundation
|
||||
Entry: Clean repository
|
||||
Tasks:
|
||||
- Implement error handling utilities
|
||||
- Create base type definitions
|
||||
- Setup configuration system
|
||||
Exit: Other modules can import foundation without errors
|
||||
|
||||
Phase 1: Data Layer
|
||||
Entry: Phase 0 complete
|
||||
Tasks:
|
||||
- Implement schema validator (uses: base types, error handling)
|
||||
- Build data ingestion pipeline (uses: validator, config)
|
||||
Exit: End-to-end data flow from input to validated output
|
||||
</example>
|
||||
|
||||
<example type="bad">
|
||||
Phase 1: Build Everything
|
||||
Tasks:
|
||||
- API
|
||||
- Database
|
||||
- UI
|
||||
- Tests
|
||||
(Problem: No clear focus. Too broad. Dependencies not considered.)
|
||||
</example>
|
||||
</instruction>
|
||||
|
||||
## Development Phases
|
||||
|
||||
### Phase 0: [Foundation Name]
|
||||
**Goal**: [What foundational capability this establishes]
|
||||
|
||||
**Entry Criteria**: [What must be true before starting]
|
||||
|
||||
**Tasks**:
|
||||
- [ ] [Task name] (depends on: [none or list])
|
||||
- Acceptance criteria: [How we know it's done]
|
||||
- Test strategy: [What tests prove it works]
|
||||
|
||||
- [ ] [Task name] (depends on: [none or list])
|
||||
|
||||
**Exit Criteria**: [Observable outcome that proves phase complete]
|
||||
|
||||
**Delivers**: [What can users/developers do after this phase?]
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: [Layer Name]
|
||||
**Goal**:
|
||||
|
||||
**Entry Criteria**: Phase 0 complete
|
||||
|
||||
**Tasks**:
|
||||
- [ ] [Task name] (depends on: [[tasks-from-phase-0]])
|
||||
- [ ] [Task name] (depends on: [[tasks-from-phase-0]])
|
||||
|
||||
**Exit Criteria**:
|
||||
|
||||
**Delivers**:
|
||||
|
||||
---
|
||||
|
||||
[Continue with more phases...]
|
||||
|
||||
</implementation-roadmap>
|
||||
|
||||
---
|
||||
|
||||
<test-strategy>
|
||||
<instruction>
|
||||
Define how testing will be integrated throughout development (TDD approach).
|
||||
|
||||
Specify:
|
||||
1. Test pyramid ratios (unit vs integration vs e2e)
|
||||
2. Coverage requirements
|
||||
3. Critical test scenarios
|
||||
4. Test generation guidelines for Surgical Test Generator
|
||||
|
||||
This section guides the AI when generating tests during the RED phase of TDD.
|
||||
|
||||
<example type="good">
|
||||
Critical Test Scenarios for Data Validation module:
|
||||
- Happy path: Valid data passes all checks
|
||||
- Edge cases: Empty strings, null values, boundary numbers
|
||||
- Error cases: Invalid types, missing required fields
|
||||
- Integration: Validator works with ingestion pipeline
|
||||
</example>
|
||||
</instruction>
|
||||
|
||||
## Test Pyramid
|
||||
|
||||
```
|
||||
/\
|
||||
/E2E\ ← [X]% (End-to-end, slow, comprehensive)
|
||||
/------\
|
||||
/Integration\ ← [Y]% (Module interactions)
|
||||
/------------\
|
||||
/ Unit Tests \ ← [Z]% (Fast, isolated, deterministic)
|
||||
/----------------\
|
||||
```
|
||||
|
||||
## Coverage Requirements
|
||||
- Line coverage: [X]% minimum
|
||||
- Branch coverage: [X]% minimum
|
||||
- Function coverage: [X]% minimum
|
||||
- Statement coverage: [X]% minimum
|
||||
|
||||
## Critical Test Scenarios
|
||||
|
||||
### [Module/Feature Name]
|
||||
**Happy path**:
|
||||
- [Scenario description]
|
||||
- Expected: [What should happen]
|
||||
|
||||
**Edge cases**:
|
||||
- [Scenario description]
|
||||
- Expected: [What should happen]
|
||||
|
||||
**Error cases**:
|
||||
- [Scenario description]
|
||||
- Expected: [How system handles failure]
|
||||
|
||||
**Integration points**:
|
||||
- [What interactions to test]
|
||||
- Expected: [End-to-end behavior]
|
||||
|
||||
## Test Generation Guidelines
|
||||
[Specific instructions for Surgical Test Generator about what to focus on, what patterns to follow, project-specific test conventions]
|
||||
|
||||
</test-strategy>
|
||||
|
||||
---
|
||||
|
||||
<architecture>
|
||||
<instruction>
|
||||
Describe technical architecture, data models, and key design decisions.
|
||||
|
||||
Keep this section AFTER functional/structural decomposition - implementation details come after understanding structure.
|
||||
</instruction>
|
||||
|
||||
## System Components
|
||||
[Major architectural pieces and their responsibilities]
|
||||
|
||||
## Data Models
|
||||
[Core data structures, schemas, database design]
|
||||
|
||||
## Technology Stack
|
||||
[Languages, frameworks, key libraries]
|
||||
|
||||
**Decision: [Technology/Pattern]**
|
||||
- **Rationale**: [Why chosen]
|
||||
- **Trade-offs**: [What we're giving up]
|
||||
- **Alternatives considered**: [What else we looked at]
|
||||
|
||||
</architecture>
|
||||
|
||||
---
|
||||
|
||||
<risks>
|
||||
<instruction>
|
||||
Identify risks that could derail development and how to mitigate them.
|
||||
|
||||
Categories:
|
||||
- Technical risks (complexity, unknowns)
|
||||
- Dependency risks (blocking issues)
|
||||
- Scope risks (creep, underestimation)
|
||||
</instruction>
|
||||
|
||||
## Technical Risks
|
||||
**Risk**: [Description]
|
||||
- **Impact**: [High/Medium/Low - effect on project]
|
||||
- **Likelihood**: [High/Medium/Low]
|
||||
- **Mitigation**: [How to address]
|
||||
- **Fallback**: [Plan B if mitigation fails]
|
||||
|
||||
## Dependency Risks
|
||||
[External dependencies, blocking issues]
|
||||
|
||||
## Scope Risks
|
||||
[Scope creep, underestimation, unclear requirements]
|
||||
|
||||
</risks>
|
||||
|
||||
---
|
||||
|
||||
<appendix>
|
||||
## References
|
||||
[Papers, documentation, similar systems]
|
||||
|
||||
## Glossary
|
||||
[Domain-specific terms]
|
||||
|
||||
## Open Questions
|
||||
[Things to resolve during development]
|
||||
</appendix>
|
||||
|
||||
---
|
||||
|
||||
<task-master-integration>
|
||||
# How Task Master Uses This PRD
|
||||
|
||||
When you run `task-master parse-prd <file>.txt`, the parser:
|
||||
|
||||
1. **Extracts capabilities** → Main tasks
|
||||
- Each `### Capability:` becomes a top-level task
|
||||
|
||||
2. **Extracts features** → Subtasks
|
||||
- Each `#### Feature:` becomes a subtask under its capability
|
||||
|
||||
3. **Parses dependencies** → Task dependencies
|
||||
- `Depends on: [X, Y]` sets task.dependencies = ["X", "Y"]
|
||||
|
||||
4. **Orders by phases** → Task priorities
|
||||
- Phase 0 tasks = highest priority
|
||||
- Phase N tasks = lower priority, properly sequenced
|
||||
|
||||
5. **Uses test strategy** → Test generation context
|
||||
- Feeds test scenarios to Surgical Test Generator during implementation
|
||||
|
||||
**Result**: A dependency-aware task graph that can be executed in topological order.
|
||||
|
||||
## Why RPG Structure Matters
|
||||
|
||||
Traditional flat PRDs lead to:
|
||||
- ❌ Unclear task dependencies
|
||||
- ❌ Arbitrary task ordering
|
||||
- ❌ Circular dependencies discovered late
|
||||
- ❌ Poorly scoped tasks
|
||||
|
||||
RPG-structured PRDs provide:
|
||||
- ✅ Explicit dependency chains
|
||||
- ✅ Topological execution order
|
||||
- ✅ Clear module boundaries
|
||||
- ✅ Validated task graph before implementation
|
||||
|
||||
## Tips for Best Results
|
||||
|
||||
1. **Spend time on dependency graph** - This is the most valuable section for Task Master
|
||||
2. **Keep features atomic** - Each feature should be independently testable
|
||||
3. **Progressive refinement** - Start broad, use `task-master expand` to break down complex tasks
|
||||
4. **Use research mode** - `task-master parse-prd --research` leverages AI for better task generation
|
||||
</task-master-integration>
|
||||
@@ -0,0 +1,34 @@
|
||||
# LegalAssistance — Architecture (developer reference)
|
||||
|
||||
> Internal developer doc. Not shipped to clients. Customer overview: `README.md`. Security/dead-code: `_AUDIT/LegalAssistance/findings.md`.
|
||||
|
||||
**Module:** `LegalAssistance` · **Load order:** 25 · **Version:** 2.0.10 · **Author:** Marcus-Law · **Requires:** EspoCRM ≥ 8.0.0
|
||||
|
||||
## What it is
|
||||
Generates the Israeli legal-aid "**דוח גישה ישירה**" (direct-access report) as a DOCX, straight from JSON params (no intermediate entity), and uploads it to the case folder. It is reached as a **SmartAssistant agent tool** — Shira runs an 8-step Hebrew interview to collect the data, then calls the tool. Since Phase 3, the legal tools live in **shira-hermes**, not ai-gateway; this extension is now minimal.
|
||||
|
||||
## File-by-file
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `files/.../SmartAssistant/Tools/DirectAccessReportGenerator.php` | **Core:** JSON params → DOCX via PHPWord `TemplateProcessor` (checkbox mapping) → upload to case folder via NetworkStorageIntegration (local fallback). **IDOR via caseId — S1; param→DOCX injection — S3; temp file — S4.** |
|
||||
| `files/.../Resources/module.json` | Module order 25. |
|
||||
| `files/.../Resources/metadata/app/smartAssistant.json` | Registers the `generate_direct_access_report` tool handler. |
|
||||
| `files/.../Resources/metadata/entityDefs/Case.json` | **Empty `{}`** (no Case extension in v2.0.10) — D5. |
|
||||
| `files/.../Resources/i18n/{en_US,he_IL,fa_IR}/Case.json` | Light "LegalAssistance" tab labels. (`Global.json` empty — D3.) |
|
||||
| `plugins/legal-assistance/tools.json` | Shira tool schema for `generate_direct_access_report` (50+ params). |
|
||||
| `plugins/legal-assistance/prompts.json` | Hebrew `casePromptAppend` — the 8-step interview flow. |
|
||||
| `templates/direct-access-report.docx` | The DOCX template (39 KB binary). |
|
||||
| `scripts/AfterInstall.php` | Creates the DocumentTemplate, clears cache, ensures i18n dirs. **ai-gateway deploy removed (Phase 3) — only a doc-comment remains.** |
|
||||
| `scripts/AfterUninstall.php` | ⚠️ **Drops phantom v1.x tables + Case columns + i18n — destructive + dead (S2/D1).** |
|
||||
|
||||
### Dead/reference clutter (not packaged, but in the repo — D2/D4)
|
||||
`docs/examples/entity-reference-legalaid.md` (59 KB, removed architecture), `field_mapping.md`, `legal-docx-v3-fixed/` (Node.js reference), `.env.example`, `טופס`, old zips v1.0.0–1.3.2.
|
||||
|
||||
## Dependencies
|
||||
- **NetworkStorageIntegration (strong, graceful):** report upload to the case folder; falls back to local attachment storage if disabled.
|
||||
- **SmartAssistant (hard, runtime):** the tool is only reachable via SmartAssistant's `executeTool` — it inherits SmartAssistant's IDOR (S1).
|
||||
- **shira-hermes (soft):** Shira loads the `plugins/` tool + prompt to drive the interview.
|
||||
|
||||
## Post-install gotchas
|
||||
- Requires SmartAssistant + Shira configured; NetworkStorageIntegration recommended for case-folder filing.
|
||||
- Fix the destructive `AfterUninstall` (S2) before any external release — it can wipe legal-aid data.
|
||||
@@ -0,0 +1,39 @@
|
||||
# LegalAssistance — סיוע משפטי (דוח גישה ישירה)
|
||||
|
||||
**גרסה:** 2.0.10 | **מחבר:** Marcus-Law | **EspoCRM:** >= 8.0.0
|
||||
|
||||
## תיאור
|
||||
|
||||
יצירת **דוח גישה ישירה** (סיוע משפטי) כקובץ Word, ישירות מתוך שיחה עם העוזר **שירה**. שירה מנהל ראיון מובנה
|
||||
(8 שלבים) לאיסוף הנתונים, ואז המערכת מייצרת את הדוח מתבנית DOCX וצורפת אותו לתיקיית התיק.
|
||||
|
||||
## תלויות
|
||||
|
||||
- **SmartAssistant + שירה (shira-hermes)** — נדרש; הכלי נקרא דרך מנגנון הכלים של שירה.
|
||||
- **NetworkStorageIntegration** — מומלץ; שמירת הדוח בתיקיית התיק (יש fallback לאחסון מקומי).
|
||||
|
||||
## התקנה
|
||||
|
||||
1. ודא ש‑SmartAssistant ושירה מוגדרים.
|
||||
2. הורד את `LegalAssistance` והתקן דרך **ניהול → הרחבות**.
|
||||
3. בצע Rebuild ורענון קשיח.
|
||||
|
||||
## מה מתווסף למערכת
|
||||
|
||||
- כלי **"יצירת דוח גישה ישירה"** הזמין לעוזר שירה במצב תיק.
|
||||
- תבנית מסמך דוח גישה ישירה.
|
||||
|
||||
## שימוש
|
||||
|
||||
במצב תיק, בקש משירה ליצור דוח גישה ישירה. שירה יאסוף את הפרטים בשלבים, ובסיום ייצור את ה‑DOCX וישמור אותו בתיק.
|
||||
|
||||
## טיפול בעיות נפוצות
|
||||
|
||||
| תסמין | סיבה | פתרון |
|
||||
|---|---|---|
|
||||
| הדוח לא נוצר | שירה/SmartAssistant לא מוגדרים | ודא שהאינטגרציה פעילה. |
|
||||
| הדוח לא נשמר בתיק | NetworkStorageIntegration לא מותקן | יישמר מקומית; להתקנת NSI לשמירה בתיקיית התיק. |
|
||||
|
||||
---
|
||||
|
||||
> תיעוד טכני למפתחים: `ARCHITECTURE.md`.
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
VERSION=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])")
|
||||
MODULE=$(python3 -c "import json; m=json.load(open('manifest.json')); print(m.get('module', m['name']))")
|
||||
ZIPNAME="${MODULE}-${VERSION}.zip"
|
||||
|
||||
if [[ -f README.md ]]; then
|
||||
sed -i "s/\*\*גרסה:\*\* [^ |]*/\*\*גרסה:\*\* ${VERSION}/" README.md
|
||||
fi
|
||||
|
||||
echo "Building $ZIPNAME..."
|
||||
rm -f "$ZIPNAME"
|
||||
zip -r "$ZIPNAME" manifest.json files/ scripts/ \
|
||||
-x "*.DS_Store" "*__MACOSX*" "*.zip" 2>/dev/null || \
|
||||
zip -r "$ZIPNAME" manifest.json files/ \
|
||||
-x "*.DS_Store" "*__MACOSX*" "*.zip"
|
||||
|
||||
echo "✓ Built: $ZIPNAME ($(du -h "$ZIPNAME" | cut -f1))"
|
||||
+86
-8
@@ -14,6 +14,8 @@ use Espo\Core\InjectableFactory;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\NetworkStorageIntegration\Services\NetworkDocumentService;
|
||||
use Espo\Modules\NetworkStorageIntegration\Classes\LocalFilesystemClient;
|
||||
use PhpOffice\PhpWord\TemplateProcessor;
|
||||
|
||||
class DirectAccessReportGenerator
|
||||
@@ -87,9 +89,14 @@ class DirectAccessReportGenerator
|
||||
// Add checkbox mappings
|
||||
$this->addCheckboxData($data, $params);
|
||||
|
||||
// Find template and generate DOCX
|
||||
// Find template and generate DOCX. Subject-only, tight hyphen (rule N1).
|
||||
$templatePath = $this->resolveTemplatePath();
|
||||
$documentName = "דוח גישה ישירה — {$contactName}";
|
||||
$documentName = "דוח גישה ישירה-{$contactName}";
|
||||
$baseName = LocalFilesystemClient::sanitizeFileName($documentName);
|
||||
if ($baseName === '') {
|
||||
$baseName = 'דוח גישה ישירה';
|
||||
}
|
||||
$fileName = $baseName . '.docx';
|
||||
$outputPath = self::TEMP_DIR . uniqid('report_') . '.docx';
|
||||
|
||||
if (!is_dir(self::TEMP_DIR)) {
|
||||
@@ -110,7 +117,7 @@ class DirectAccessReportGenerator
|
||||
// Create Attachment
|
||||
$attachment = $this->entityManager->getNewEntity('Attachment');
|
||||
$attachment->set([
|
||||
'name' => $documentName . '.docx',
|
||||
'name' => $fileName,
|
||||
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'role' => 'Attachment',
|
||||
'size' => strlen($content),
|
||||
@@ -124,17 +131,76 @@ class DirectAccessReportGenerator
|
||||
$document->set([
|
||||
'name' => $documentName,
|
||||
'fileId' => $attachment->getId(),
|
||||
'fileName' => $documentName . '.docx',
|
||||
'fileName' => $fileName,
|
||||
'assignedUserId' => $userId,
|
||||
'type' => 'דוח',
|
||||
]);
|
||||
$this->entityManager->saveEntity($document);
|
||||
// Suppress the NSI upload hook (fires before the Case link below); we
|
||||
// upload explicitly with the Case so it lands in the case folder.
|
||||
$this->entityManager->saveEntity($document, ['skipNetworkUpload' => true]);
|
||||
|
||||
// Link Document to Case (via Case side of relationship)
|
||||
$this->entityManager->getRDBRepository('Case')
|
||||
->getRelation($case, 'documents')
|
||||
->relate($document);
|
||||
|
||||
// CaseFiles backend: place the Document in the case "מסמכים" folder so it
|
||||
// appears in the CaseDocs panel tree (soft dependency, no hard requirement).
|
||||
$caseFilesServiceClass = 'Espo\\Modules\\CaseFilesCore\\Services\\CaseFolderService';
|
||||
if (class_exists($caseFilesServiceClass)) {
|
||||
try {
|
||||
$cfFolder = $this->injectableFactory->create($caseFilesServiceClass)
|
||||
->getOrCreateSubfolder('Case', $caseId, 'מסמכים');
|
||||
$document->set('folderId', $cfFolder->getId());
|
||||
$this->entityManager->saveEntity($document, ['skipNetworkUpload' => true, 'silent' => true]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->log->warning('LegalAssistance: CaseFiles folder placement failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Upload to the case folder; align DB names to the canonical stored name.
|
||||
try {
|
||||
$nds = $this->injectableFactory->create(NetworkDocumentService::class);
|
||||
if ($nds->isEnabled()) {
|
||||
$result = $nds->uploadDocument($document, $attachment, 'Case', $caseId);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
$storedName = $result['fileName'] ?? $fileName;
|
||||
$storedBase = pathinfo($storedName, PATHINFO_FILENAME) ?: $documentName;
|
||||
|
||||
$document->set([
|
||||
'name' => $storedBase,
|
||||
'fileName' => $storedName,
|
||||
'storageType' => 'network',
|
||||
'networkStoragePath' => $result['path'] ?? null,
|
||||
'networkStorageFileName' => $storedName,
|
||||
'networkStorageSize' => $result['size'] ?? strlen($content),
|
||||
'networkStorageModifiedAt' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$this->entityManager->saveEntity($document, [
|
||||
'skipNetworkUpload' => true,
|
||||
'silent' => true,
|
||||
]);
|
||||
|
||||
$attachment->set('name', $storedName);
|
||||
$this->entityManager->saveEntity($attachment, ['silent' => true]);
|
||||
|
||||
$sourcePath = 'data/upload/' . $attachment->getSourceId();
|
||||
if (file_exists($sourcePath)) {
|
||||
@unlink($sourcePath);
|
||||
}
|
||||
|
||||
$documentName = $storedBase;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Document is safely persisted in Espo; the network copy is best-effort.
|
||||
$this->log->warning(
|
||||
"LegalAssistance: failed to copy report to network storage for Case {$caseId}: " .
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
$this->log->info("LegalAssistance: Report generated — Document {$document->getId()} attached to Case {$caseId}");
|
||||
|
||||
return [
|
||||
@@ -150,6 +216,8 @@ class DirectAccessReportGenerator
|
||||
$legalAnalysis = $params['legalAnalysis'] ?? [];
|
||||
if (is_string($legalAnalysis)) {
|
||||
$legalAnalysis = json_decode($legalAnalysis, true) ?? [];
|
||||
} elseif (is_object($legalAnalysis)) {
|
||||
$legalAnalysis = (array) $legalAnalysis;
|
||||
}
|
||||
|
||||
// Fallback: if AI sent legalAnalysis fields flat (not nested), pick them up
|
||||
@@ -214,8 +282,8 @@ class DirectAccessReportGenerator
|
||||
|
||||
private function addCheckboxData(array &$data, array $params): void
|
||||
{
|
||||
$CHK = '☑';
|
||||
$UNCHK = '☐';
|
||||
$CHK = "☑\u{00A0}";
|
||||
$UNCHK = "☐\u{00A0}";
|
||||
|
||||
$urgency = $params['urgencyLevel'] ?? '';
|
||||
$data['chk_urgency_regular'] = ($urgency === 'רגיל') ? $CHK : $UNCHK;
|
||||
@@ -372,6 +440,16 @@ class DirectAccessReportGenerator
|
||||
}
|
||||
|
||||
$processor->saveAs($outputPath);
|
||||
|
||||
// Fix space preservation: PHPWord strips whitespace from <w:t> elements
|
||||
// that lack xml:space="preserve", causing words to stick together in RTL docs.
|
||||
$zip = new \ZipArchive();
|
||||
if ($zip->open($outputPath) === true) {
|
||||
$xml = $zip->getFromName('word/document.xml');
|
||||
$xml = preg_replace('/<w:t(?=[ >\/])(?![^>]*xml:space)/', '<w:t xml:space="preserve"', $xml);
|
||||
$zip->addFromString('word/document.xml', $xml);
|
||||
$zip->close();
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeDate(?string $value): ?string
|
||||
@@ -390,7 +468,7 @@ class DirectAccessReportGenerator
|
||||
{
|
||||
if (!$date) return '';
|
||||
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $m)) {
|
||||
return "{$m[3]}/{$m[2]}/{$m[1]}";
|
||||
return "{$m[3]}.{$m[2]}.{$m[1]}";
|
||||
}
|
||||
return $date;
|
||||
}
|
||||
|
||||
+8
-4
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"name": "LegalAssistance",
|
||||
"version": "2.0.3",
|
||||
"acceptableVersions": [">=8.0.0"],
|
||||
"php": [">=8.1"],
|
||||
"releaseDate": "2026-04-09",
|
||||
"version": "2.1.0",
|
||||
"acceptableVersions": [
|
||||
">=8.0.0"
|
||||
],
|
||||
"php": [
|
||||
">=8.1"
|
||||
],
|
||||
"releaseDate": "2026-06-21",
|
||||
"author": "Marcus-Law",
|
||||
"description": "סיוע משפטי — יצירת דוח גישה ישירה (JSON ישירות ל-DOCX), אינטגרציה עם שירה",
|
||||
"scripts": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,9 +2,11 @@
|
||||
/************************************************************************
|
||||
* LegalAssistance Extension — AfterInstall
|
||||
*
|
||||
* 1. Deploy plugin files to ai-gateway plugins/ directory
|
||||
* 2. Create DocumentTemplate for the direct access report
|
||||
* 3. Clear cache
|
||||
* 1. Create DocumentTemplate for the direct access report
|
||||
* 2. Clear cache
|
||||
*
|
||||
* Note: ai-gateway plugin deployment removed — legal tools are now
|
||||
* built into shira-hermes (api/services/legal_tools.py) since Phase 3.
|
||||
************************************************************************/
|
||||
|
||||
use Espo\Core\Container;
|
||||
@@ -28,13 +30,10 @@ class AfterInstall
|
||||
$config = $container->getByClass(Config::class);
|
||||
$this->ensureI18nDirectories($config, $log);
|
||||
|
||||
// 1. Deploy ai-gateway plugin files
|
||||
$this->deployPluginFiles($log);
|
||||
|
||||
// 2. Create DocumentTemplate
|
||||
// 1. Create DocumentTemplate
|
||||
$this->createDocumentTemplate($entityManager, $log);
|
||||
|
||||
// 3. Clear cache
|
||||
// 2. Clear cache
|
||||
try {
|
||||
$dataManager = $container->getByClass(DataManager::class);
|
||||
$dataManager->clearCache();
|
||||
@@ -67,52 +66,6 @@ class AfterInstall
|
||||
}
|
||||
}
|
||||
|
||||
private function deployPluginFiles(Log $log): void
|
||||
{
|
||||
// Source: extension's plugins/ directory (relative to EspoCRM root after install)
|
||||
$sourceDir = 'custom/Espo/Modules/LegalAssistance/../../../../../../plugins/legal-assistance';
|
||||
|
||||
// Try common ai-gateway locations
|
||||
$gatewayPaths = [
|
||||
'/home/chaim/ai-gateway/plugins/legal-assistance',
|
||||
'/opt/ai-gateway/plugins/legal-assistance',
|
||||
];
|
||||
|
||||
// Find actual source files within the extension package
|
||||
$extPluginDir = __DIR__ . '/../plugins/legal-assistance';
|
||||
|
||||
if (!is_dir($extPluginDir)) {
|
||||
$log->warning("LegalAssistance: Plugin source directory not found at {$extPluginDir}");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($gatewayPaths as $targetDir) {
|
||||
$parentDir = dirname($targetDir);
|
||||
if (!is_dir($parentDir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_dir($targetDir)) {
|
||||
mkdir($targetDir, 0755, true);
|
||||
}
|
||||
|
||||
$files = ['tools.json', 'prompts.json'];
|
||||
foreach ($files as $file) {
|
||||
$src = $extPluginDir . '/' . $file;
|
||||
$dst = $targetDir . '/' . $file;
|
||||
if (file_exists($src)) {
|
||||
copy($src, $dst);
|
||||
$log->info("LegalAssistance: Deployed {$file} to {$targetDir}");
|
||||
}
|
||||
}
|
||||
|
||||
$log->info("LegalAssistance: Plugin files deployed to {$targetDir}");
|
||||
return;
|
||||
}
|
||||
|
||||
$log->warning('LegalAssistance: Could not find ai-gateway plugins directory. Deploy manually.');
|
||||
}
|
||||
|
||||
private function createDocumentTemplate(EntityManager $entityManager, Log $log): void
|
||||
{
|
||||
// Check if template already exists
|
||||
|
||||
+12
-34
@@ -2,11 +2,13 @@
|
||||
/************************************************************************
|
||||
* LegalAssistance Extension — AfterUninstall
|
||||
*
|
||||
* 1. Remove plugin files from ai-gateway
|
||||
* 2. Drop LegalAid and DirectAccessReport DB tables (with warning log)
|
||||
* 3. Remove LegalAssistance-specific columns from Case table
|
||||
* 4. Remove DocumentTemplate records created by this extension
|
||||
* 5. Clear cache
|
||||
* 1. Drop LegalAid and DirectAccessReport DB tables (with warning log)
|
||||
* 2. Remove LegalAssistance-specific columns from Case table
|
||||
* 3. Remove DocumentTemplate records created by this extension
|
||||
* 4. Clear cache
|
||||
*
|
||||
* Note: ai-gateway plugin cleanup removed — legal tools are now
|
||||
* built into shira-hermes since Phase 3.
|
||||
************************************************************************/
|
||||
|
||||
use Espo\Core\Container;
|
||||
@@ -25,22 +27,19 @@ class AfterUninstall
|
||||
|
||||
$log->info('LegalAssistance: Running AfterUninstall...');
|
||||
|
||||
// 1. Remove ai-gateway plugin files
|
||||
$this->removePluginFiles($log);
|
||||
|
||||
// 2. Drop tables created by this extension
|
||||
// 1. Drop tables created by this extension
|
||||
$this->dropTables($entityManager, $log);
|
||||
|
||||
// 3. Remove LegalAssistance-specific columns from Case
|
||||
// 2. Remove LegalAssistance-specific columns from Case
|
||||
$this->cleanCaseColumns($entityManager, $log);
|
||||
|
||||
// 4. Remove DocumentTemplate records
|
||||
// 3. Remove DocumentTemplate records
|
||||
$this->removeDocumentTemplates($entityManager, $log);
|
||||
|
||||
// 5. Remove custom i18n files
|
||||
// 4. Remove custom i18n files
|
||||
$this->removeCustomI18n($log);
|
||||
|
||||
// 6. Clear cache
|
||||
// 5. Clear cache
|
||||
try {
|
||||
$dataManager = $container->getByClass(DataManager::class);
|
||||
$dataManager->clearCache();
|
||||
@@ -52,27 +51,6 @@ class AfterUninstall
|
||||
$log->info('LegalAssistance: AfterUninstall completed.');
|
||||
}
|
||||
|
||||
private function removePluginFiles(Log $log): void
|
||||
{
|
||||
$gatewayPaths = [
|
||||
'/home/chaim/ai-gateway/plugins/legal-assistance',
|
||||
'/opt/ai-gateway/plugins/legal-assistance',
|
||||
];
|
||||
|
||||
foreach ($gatewayPaths as $dir) {
|
||||
if (is_dir($dir)) {
|
||||
$files = glob($dir . '/*');
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
rmdir($dir);
|
||||
$log->info("LegalAssistance: Removed plugin files from {$dir}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function dropTables(EntityManager $entityManager, Log $log): void
|
||||
{
|
||||
$pdo = $entityManager->getPDO();
|
||||
|
||||
Reference in New Issue
Block a user