feat(kb): split-view PDF jump-to-page for both ask and search modes
Search and ask modes now render results as a two-column layout: a left column with ranked hit cards (search) or Shira's answer + source picker (ask), and a right column with an iframe showing the source PDF scrolled to page_number. Clicking a hit (search) or source pill (ask) swaps the iframe's src, so users can verify a quote against the original PDF without leaving the KB tab. - search: renderSearchResults lays out results as panel cards on the left (with kind + title + section + "עמ׳ N" label); the top hit is pre-selected and its PDF loads on the right. Clicking any card re-highlights it and swaps the preview. Law-kind hits (Wikisource text) gracefully fall back to a chunk-text panel with a Wikisource link so the right pane never 404s on a text source. - ask: renderAskAnswer dedups /kb/ask's sources[] by source_id, collects all cited pages per source, and renders a picker row plus per-source page-jump buttons. First source's first page loads on initial render; buttons swap the iframe without re-running the query. Depends on shira-hermes commits e534709 + 1ca1cc0 (chunker page_number propagation + null-byte strip) — without them, every PDF collapses to page 1 in the DB and the jump links are cosmetic. Refs Task Master #1
This commit is contained in:
@@ -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_...
|
||||
+25
@@ -1,2 +1,27 @@
|
||||
*.zip
|
||||
.DS_Store
|
||||
|
||||
# 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
|
||||
|
||||
# Task files
|
||||
# tasks.json
|
||||
# tasks/
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"models": {
|
||||
"main": {
|
||||
"provider": "anthropic",
|
||||
"modelId": "claude-sonnet-4-20250514",
|
||||
"maxTokens": 64000,
|
||||
"temperature": 0.2
|
||||
},
|
||||
"research": {
|
||||
"provider": "perplexity",
|
||||
"modelId": "sonar",
|
||||
"maxTokens": 8700,
|
||||
"temperature": 0.1
|
||||
},
|
||||
"fallback": {
|
||||
"provider": "anthropic",
|
||||
"modelId": "claude-3-7-sonnet-20250219",
|
||||
"maxTokens": 120000,
|
||||
"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": "English",
|
||||
"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-04-24T15:47:33.139Z",
|
||||
"branchTagMapping": {},
|
||||
"migrationNoticeShown": false
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"master": {
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "feat: search-mode split-view with PDF + page jump",
|
||||
"description": "Render /kb/search results as a two-column split view: left column is the stack of ranked hit cards (kind, title, section, page label), right column is an iframe viewing the selected hit's PDF scrolled to page_number. Clicking any hit swaps the iframe. Text-only sources (law from Wikisource) fall back to a chunk/text panel with a Wikisource link so the right column never 404s.",
|
||||
"status": "in-progress",
|
||||
"priority": "high",
|
||||
"details": "Before this change renderSearchResults produced plain vertical text cards — users got the chunk body and had to open the Wikisource link (or track down the PDF manually) to verify context. With 5 of 5 PDFs now carrying accurate page_number (shira-hermes e534709 + 1ca1cc0), the search UI can finally deep-link to the right page. Mirrors the ask-mode split view (v0.1.7 uncommitted).",
|
||||
"testStrategy": "Search 'תקנה 37' → first hit should be a regulation/circular → right pane loads the PDF at page ~N where the section appears. Click a lower-ranked law hit → right pane swaps to a Wikisource link view. Search 'הגדרות' → hits span multiple sources → each click swaps iframe source.",
|
||||
"subtasks": [],
|
||||
"dependencies": [],
|
||||
"createdAt": "2026-04-24T15:47:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "fix(kb/chunker): page_number propagation through split + packing (shira-hermes)",
|
||||
"description": "Applied in shira-hermes commits e534709 + 1ca1cc0: (1) _split_oversized now re-derives page_number per piece from markers embedded in parent content + a virtual (offset 0, parent.page_number) anchor, (2) chunk_circular tracks absolute paragraph offsets in the original text so each packed sub-chunk reports its real starting page, (3) _as_dicts strips stray \\x00 bytes left behind when _split_oversized slices through a marker sentinel. Reference data: ספר הליקויים now 211 chunks × 210 pages (1-413) vs 209 × 1 before. Not work for this repo, but the search split-view in task #1 depends on it.",
|
||||
"status": "done",
|
||||
"priority": "high",
|
||||
"details": "Lives in espocrm-extensions/shira-hermes. Logged here for traceability because task #1 can't demonstrate correct page jumps without it.",
|
||||
"testStrategy": "Query DB: SELECT source_id, MIN/MAX(page_number), COUNT(DISTINCT page_number) FROM kb_chunk GROUP BY source_id — every PDF source should span multiple distinct pages.",
|
||||
"subtasks": [],
|
||||
"dependencies": [],
|
||||
"createdAt": "2026-04-24T15:47:00Z"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"created": "2026-04-24T15:47:00Z",
|
||||
"updated": "2026-04-24T15:47:00Z",
|
||||
"description": "KnowledgeBase extension tasks"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -113,7 +113,7 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
|
||||
}, {timeout: 240000}).then(res => {
|
||||
this.stopAskProgress();
|
||||
this.setLoading(false);
|
||||
this.renderAskAnswer(res.text || '');
|
||||
this.renderAskAnswer(res.text || '', res.sources || []);
|
||||
}).catch(err => {
|
||||
this.stopAskProgress();
|
||||
this.setLoading(false);
|
||||
@@ -172,50 +172,252 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
|
||||
return;
|
||||
}
|
||||
const kindHe = {law: 'חוק', regulation: 'תקנה', circular: 'חוזר'};
|
||||
const rows = hits.map(h => {
|
||||
const self = this;
|
||||
|
||||
const isPdfBacked = h => h && (h.kind === 'regulation' || h.kind === 'circular');
|
||||
|
||||
// Left column: stacked hit cards. Click → preview on the right.
|
||||
const bodyStyle =
|
||||
'direction:rtl;unicode-bidi:plaintext;white-space:pre-wrap;' +
|
||||
'font-family:"Segoe UI",Arial,sans-serif;line-height:1.6;text-align:right;' +
|
||||
'max-height:14em;overflow:hidden;';
|
||||
const listHtml = hits.map((h, i) => {
|
||||
const kind = kindHe[h.kind] || h.kind;
|
||||
const ident = h.identifier ? ' ' + this.escape(h.identifier) : '';
|
||||
const path = h.heading_path || h.section_ref || '';
|
||||
const pub = h.published_at ? ' · פורסם ' + h.published_at : '';
|
||||
// Content is rendered inside a white-space:pre-wrap block —
|
||||
// no need to convert \n to <br>; that would double-break.
|
||||
const content = this.escape(h.content || '');
|
||||
const srcLink = h.source_url
|
||||
? `<a href="${this.escape(h.source_url)}" target="_blank" rel="noopener">מקור</a>`
|
||||
: '';
|
||||
const bodyStyle =
|
||||
'direction:rtl;unicode-bidi:plaintext;white-space:pre-wrap;' +
|
||||
'font-family:"Segoe UI",Arial,sans-serif;line-height:1.7;text-align:right;';
|
||||
const pageLabel = (isPdfBacked(h) && h.page_number)
|
||||
? ` · <span class="label label-default">עמ׳ ${h.page_number}</span>` : '';
|
||||
const activeClass = i === 0 ? 'panel-primary' : 'panel-default';
|
||||
return `
|
||||
<div class="kb-hit panel panel-default" style="margin-bottom:10px;">
|
||||
<div class="panel-heading" style="direction:rtl;text-align:right;">
|
||||
<div class="kb-hit panel ${activeClass}" data-hit-idx="${i}"
|
||||
style="margin-bottom:8px;cursor:pointer;">
|
||||
<div class="panel-heading" style="direction:rtl;text-align:right;padding:6px 10px;">
|
||||
<strong>[${kind}${ident}]</strong> ${this.escape(h.title || '')}
|
||||
${path ? ' — <span class="text-muted">' + this.escape(path) + '</span>' : ''}
|
||||
<span class="text-muted small">${pub}</span>
|
||||
<span class="text-muted small">${pub}</span>${pageLabel}
|
||||
</div>
|
||||
<div class="panel-body" style="${bodyStyle}">${content}</div>
|
||||
${srcLink ? '<div class="panel-footer small">' + srcLink + '</div>' : ''}
|
||||
<div class="panel-body" style="${bodyStyle}">${this.escape(h.content || '')}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
$results.html(rows);
|
||||
|
||||
$results.html(`
|
||||
<div class="row" style="margin:0;">
|
||||
<div class="col-md-5" style="padding-right:0;max-height:86vh;overflow-y:auto;">
|
||||
${listHtml}
|
||||
</div>
|
||||
<div class="col-md-7 kb-search-preview" style="padding-left:0;">
|
||||
<div class="kb-preview-slot"></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Cache hits on the view so click handlers can reach them.
|
||||
this._searchHits = hits;
|
||||
this.showSearchPreview(0);
|
||||
|
||||
this.$el.find('.kb-hit').off('click').on('click', function () {
|
||||
const idx = parseInt($(this).data('hit-idx'), 10);
|
||||
if (Number.isNaN(idx)) return;
|
||||
self.$el.find('.kb-hit')
|
||||
.removeClass('panel-primary').addClass('panel-default');
|
||||
$(this).removeClass('panel-default').addClass('panel-primary');
|
||||
self.showSearchPreview(idx);
|
||||
});
|
||||
},
|
||||
|
||||
renderAskAnswer: function (text) {
|
||||
showSearchPreview: function (idx) {
|
||||
const hits = this._searchHits || [];
|
||||
const h = hits[idx];
|
||||
const $slot = this.$el.find('.kb-preview-slot');
|
||||
if (!h) { $slot.empty(); return; }
|
||||
|
||||
const isPdf = h.kind === 'regulation' || h.kind === 'circular';
|
||||
if (isPdf && h.source_id) {
|
||||
const page = h.page_number || 1;
|
||||
const url = '?entryPoint=KnowledgeBasePdf&sourceId='
|
||||
+ encodeURIComponent(h.source_id)
|
||||
+ '#page=' + encodeURIComponent(page);
|
||||
$slot.html(`
|
||||
<div style="direction:rtl;text-align:right;margin-bottom:6px;">
|
||||
<span class="text-muted small">מציג עמ׳ ${page} של המסמך</span>
|
||||
</div>
|
||||
<iframe class="kb-pdf-iframe"
|
||||
src="${url}"
|
||||
style="width:100%;height:82vh;border:1px solid #ddd;background:#fafafa;"
|
||||
title="מקור"></iframe>
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Text source (law from Wikisource) — show the full chunk content
|
||||
// and a link to the public source page.
|
||||
const bodyStyle =
|
||||
'direction:rtl;unicode-bidi:plaintext;white-space:pre-wrap;' +
|
||||
'font-family:"Segoe UI",Arial,sans-serif;line-height:1.7;text-align:right;';
|
||||
const srcLink = h.source_url
|
||||
? `<a href="${this.escape(h.source_url)}" target="_blank" rel="noopener">מקור ב-Wikisource</a>`
|
||||
: '';
|
||||
$slot.html(`
|
||||
<div class="panel panel-default" style="direction:rtl;text-align:right;">
|
||||
<div class="panel-heading">${this.escape(h.title || '')}</div>
|
||||
<div class="panel-body" style="${bodyStyle};max-height:80vh;overflow-y:auto;">
|
||||
${this.escape(h.content || '')}
|
||||
</div>
|
||||
${srcLink ? '<div class="panel-footer small">' + srcLink + '</div>' : ''}
|
||||
</div>
|
||||
`);
|
||||
},
|
||||
|
||||
renderAskAnswer: function (text, sources) {
|
||||
const $results = this.$el.find('.kb-results');
|
||||
sources = sources || [];
|
||||
|
||||
// Shira replies in markdown; render through EspoCRM's markdown
|
||||
// helper when available, otherwise fall back to escaped text.
|
||||
let html;
|
||||
let answerHtml;
|
||||
try {
|
||||
const helper = this.getHelper && this.getHelper();
|
||||
if (helper && typeof helper.transformMarkdownText === 'function') {
|
||||
html = helper.transformMarkdownText(text || '');
|
||||
if (html && html.toString) html = html.toString();
|
||||
answerHtml = helper.transformMarkdownText(text || '');
|
||||
if (answerHtml && answerHtml.toString) answerHtml = answerHtml.toString();
|
||||
}
|
||||
} catch (e) { /* fall through */ }
|
||||
if (!html) {
|
||||
html = '<div style="white-space:pre-wrap;">' + this.escape(text || '') + '</div>';
|
||||
if (!answerHtml) {
|
||||
answerHtml = '<div style="white-space:pre-wrap;">' + this.escape(text || '') + '</div>';
|
||||
}
|
||||
$results.html('<div class="kb-answer panel panel-info"><div class="panel-body">' + html + '</div></div>');
|
||||
|
||||
const kindHe = {law: 'חוק', regulation: 'תקנה', circular: 'חוזר'};
|
||||
|
||||
// Dedup sources by source_id (first occurrence = most relevant),
|
||||
// but remember every page that came up per source so users can
|
||||
// jump directly to the right page.
|
||||
const perSource = {};
|
||||
sources.forEach(s => {
|
||||
if (!s.source_id) return;
|
||||
const key = s.source_id;
|
||||
if (!perSource[key]) {
|
||||
perSource[key] = {
|
||||
source_id: s.source_id,
|
||||
title: s.title || '',
|
||||
kind: s.kind || '',
|
||||
identifier: s.identifier || '',
|
||||
pages: [],
|
||||
};
|
||||
}
|
||||
if (s.page_number && !perSource[key].pages.includes(s.page_number)) {
|
||||
perSource[key].pages.push(s.page_number);
|
||||
}
|
||||
});
|
||||
const sourceList = Object.values(perSource);
|
||||
|
||||
if (!sourceList.length) {
|
||||
// No PDFs to show — render just the answer.
|
||||
$results.html(
|
||||
'<div class="kb-answer panel panel-info">' +
|
||||
'<div class="panel-body" style="direction:rtl;text-align:right;">' +
|
||||
answerHtml + '</div></div>'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pick the initial PDF + page: the first source (highest-ranked)
|
||||
// and its first-cited page.
|
||||
const first = sourceList[0];
|
||||
const firstPage = first.pages[0] || 1;
|
||||
|
||||
// Source picker: radio-like buttons that swap the iframe's src.
|
||||
const pickerItems = sourceList.map((s, i) => {
|
||||
const pagesLabel = s.pages.length
|
||||
? ' · עמ׳ ' + s.pages.join(', ')
|
||||
: '';
|
||||
const k = kindHe[s.kind] || s.kind;
|
||||
const active = i === 0 ? 'btn-primary' : 'btn-default';
|
||||
return `<button type="button"
|
||||
class="btn btn-xs ${active} kb-pdf-pick"
|
||||
data-source-id="${s.source_id}"
|
||||
data-page="${s.pages[0] || 1}"
|
||||
style="margin:2px;">
|
||||
${this.escape(k)} — ${this.escape(s.title)}${pagesLabel}
|
||||
</button>`;
|
||||
}).join(' ');
|
||||
|
||||
// Extra jump links per page, for the active source.
|
||||
const pageLinks = first.pages.map(p =>
|
||||
`<button type="button" class="btn btn-xs btn-default kb-page-jump"
|
||||
data-source-id="${first.source_id}" data-page="${p}"
|
||||
style="margin:2px;">עמ׳ ${p}</button>`
|
||||
).join(' ');
|
||||
|
||||
const initialUrl = '?entryPoint=KnowledgeBasePdf&sourceId='
|
||||
+ encodeURIComponent(first.source_id)
|
||||
+ '#page=' + encodeURIComponent(firstPage);
|
||||
|
||||
$results.html(`
|
||||
<div class="row" style="margin:0;">
|
||||
<div class="col-md-6" style="padding-right:0;">
|
||||
<div class="kb-answer panel panel-info">
|
||||
<div class="panel-body" style="direction:rtl;text-align:right;max-height:80vh;overflow-y:auto;">
|
||||
${answerHtml}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default" style="direction:rtl;text-align:right;">
|
||||
<div class="panel-heading"><strong>📎 מקורות</strong></div>
|
||||
<div class="panel-body">
|
||||
<div style="margin-bottom:6px;">${pickerItems}</div>
|
||||
<div class="kb-page-links" style="font-size:0.9em;">
|
||||
<span class="text-muted">עמודים רלוונטיים:</span> ${pageLinks}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6" style="padding-left:0;">
|
||||
<iframe class="kb-pdf-iframe"
|
||||
src="${initialUrl}"
|
||||
style="width:100%;height:86vh;border:1px solid #ddd;background:#fafafa;"
|
||||
title="מקור"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const self = this;
|
||||
this.$el.find('.kb-pdf-pick').off('click').on('click', function () {
|
||||
const id = $(this).data('source-id');
|
||||
const page = $(this).data('page');
|
||||
self.loadPdfInIframe(id, page);
|
||||
self.$el.find('.kb-pdf-pick').removeClass('btn-primary').addClass('btn-default');
|
||||
$(this).removeClass('btn-default').addClass('btn-primary');
|
||||
// Refresh page links for the newly selected source.
|
||||
const src = sourceList.find(x => x.source_id === id);
|
||||
if (src && src.pages.length) {
|
||||
const links = src.pages.map(p =>
|
||||
`<button type="button" class="btn btn-xs btn-default kb-page-jump"
|
||||
data-source-id="${src.source_id}" data-page="${p}"
|
||||
style="margin:2px;">עמ׳ ${p}</button>`
|
||||
).join(' ');
|
||||
self.$el.find('.kb-page-links').html(
|
||||
'<span class="text-muted">עמודים רלוונטיים:</span> ' + links
|
||||
);
|
||||
self.bindPageJumps();
|
||||
}
|
||||
});
|
||||
this.bindPageJumps();
|
||||
},
|
||||
|
||||
bindPageJumps: function () {
|
||||
const self = this;
|
||||
this.$el.find('.kb-page-jump').off('click').on('click', function () {
|
||||
const id = $(this).data('source-id');
|
||||
const page = $(this).data('page');
|
||||
self.loadPdfInIframe(id, page);
|
||||
});
|
||||
},
|
||||
|
||||
loadPdfInIframe: function (sourceId, page) {
|
||||
const url = '?entryPoint=KnowledgeBasePdf&sourceId='
|
||||
+ encodeURIComponent(sourceId)
|
||||
+ '#page=' + encodeURIComponent(page || 1);
|
||||
this.$el.find('.kb-pdf-iframe').attr('src', url);
|
||||
},
|
||||
|
||||
renderSourcesList: function () {
|
||||
@@ -310,7 +512,7 @@ define('modules/knowledge-base/views/kb/index', ['view'], function (Dep) {
|
||||
setLoading: function (loading) {
|
||||
const $btn = this.$el.find('[data-action="submit"]');
|
||||
if (loading) {
|
||||
$btn.prop('disabled', true).text(this.translate('Loading', 'labels'));
|
||||
$btn.prop('disabled', true).text('טוען…');
|
||||
// Only overwrite results with "מחפש…" for search. For ask,
|
||||
// startAskProgress() renders a richer progress panel.
|
||||
if (this.mode !== 'ask') {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "KnowledgeBase",
|
||||
"module": "KnowledgeBase",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.8",
|
||||
"acceptableVersions": [
|
||||
">=8.0.0"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user