mfcli 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mfcli/.env.example +72 -0
- mfcli/__init__.py +0 -0
- mfcli/agents/__init__.py +0 -0
- mfcli/agents/controller/__init__.py +0 -0
- mfcli/agents/controller/agent.py +19 -0
- mfcli/agents/controller/config.yaml +27 -0
- mfcli/agents/controller/tools.py +42 -0
- mfcli/agents/tools/general.py +118 -0
- mfcli/alembic/env.py +61 -0
- mfcli/alembic/script.py.mako +28 -0
- mfcli/alembic/versions/6ccc0c7c397c_added_fields_to_pdf_parts_model.py +39 -0
- mfcli/alembic/versions/769019ef4870_added_gemini_file_path_to_pdf_part_model.py +33 -0
- mfcli/alembic/versions/7a2e3a779fdc_added_functional_block_and_component_.py +54 -0
- mfcli/alembic/versions/7d5adb2a47a7_added_pdf_parts_model.py +41 -0
- mfcli/alembic/versions/7fcb7d6a5836_init.py +167 -0
- mfcli/alembic/versions/e0f2b5765c72_added_cascade_delete_for_models_that_.py +32 -0
- mfcli/alembic.ini +147 -0
- mfcli/cli/__init__.py +0 -0
- mfcli/cli/dependencies.py +59 -0
- mfcli/cli/main.py +200 -0
- mfcli/client/__init__.py +0 -0
- mfcli/client/chroma_db.py +184 -0
- mfcli/client/docling.py +44 -0
- mfcli/client/gemini.py +252 -0
- mfcli/client/llama_parse.py +38 -0
- mfcli/client/vector_db.py +93 -0
- mfcli/constants/__init__.py +0 -0
- mfcli/constants/base_enum.py +18 -0
- mfcli/constants/directory_names.py +1 -0
- mfcli/constants/file_types.py +189 -0
- mfcli/constants/gemini.py +1 -0
- mfcli/constants/openai.py +6 -0
- mfcli/constants/pipeline_run_status.py +3 -0
- mfcli/crud/__init__.py +0 -0
- mfcli/crud/file.py +42 -0
- mfcli/crud/functional_blocks.py +26 -0
- mfcli/crud/netlist.py +18 -0
- mfcli/crud/pipeline_run.py +17 -0
- mfcli/crud/project.py +144 -0
- mfcli/digikey/__init__.py +0 -0
- mfcli/digikey/digikey.py +105 -0
- mfcli/main.py +5 -0
- mfcli/mcp/__init__.py +0 -0
- mfcli/mcp/configs/cline_mcp_settings.json +11 -0
- mfcli/mcp/configs/mfcli.mcp.json +7 -0
- mfcli/mcp/mcp_instance.py +6 -0
- mfcli/mcp/server.py +37 -0
- mfcli/mcp/state_manager.py +51 -0
- mfcli/mcp/tools/__init__.py +0 -0
- mfcli/mcp/tools/query_knowledgebase.py +108 -0
- mfcli/models/__init__.py +10 -0
- mfcli/models/base.py +10 -0
- mfcli/models/bom.py +71 -0
- mfcli/models/datasheet.py +10 -0
- mfcli/models/debug_setup.py +64 -0
- mfcli/models/file.py +43 -0
- mfcli/models/file_docket.py +94 -0
- mfcli/models/file_metadata.py +19 -0
- mfcli/models/functional_blocks.py +94 -0
- mfcli/models/llm_response.py +5 -0
- mfcli/models/mcu.py +97 -0
- mfcli/models/mcu_errata.py +26 -0
- mfcli/models/netlist.py +59 -0
- mfcli/models/pdf_parts.py +25 -0
- mfcli/models/pipeline_run.py +34 -0
- mfcli/models/project.py +27 -0
- mfcli/models/project_metadata.py +15 -0
- mfcli/pipeline/__init__.py +0 -0
- mfcli/pipeline/analysis/__init__.py +0 -0
- mfcli/pipeline/analysis/bom_netlist_mapper.py +28 -0
- mfcli/pipeline/analysis/generators/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/bom/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/bom/bom.py +74 -0
- mfcli/pipeline/analysis/generators/debug_setup/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/debug_setup/debug_setup.py +71 -0
- mfcli/pipeline/analysis/generators/debug_setup/instructions.py +150 -0
- mfcli/pipeline/analysis/generators/functional_blocks/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/functional_blocks/functional_blocks.py +93 -0
- mfcli/pipeline/analysis/generators/functional_blocks/instructions.py +34 -0
- mfcli/pipeline/analysis/generators/functional_blocks/validator.py +94 -0
- mfcli/pipeline/analysis/generators/generator.py +258 -0
- mfcli/pipeline/analysis/generators/generator_base.py +18 -0
- mfcli/pipeline/analysis/generators/mcu/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/mcu/instructions.py +156 -0
- mfcli/pipeline/analysis/generators/mcu/mcu.py +84 -0
- mfcli/pipeline/analysis/generators/mcu_errata/__init__.py +1 -0
- mfcli/pipeline/analysis/generators/mcu_errata/instructions.py +77 -0
- mfcli/pipeline/analysis/generators/mcu_errata/mcu_errata.py +95 -0
- mfcli/pipeline/analysis/generators/summary/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/summary/summary.py +47 -0
- mfcli/pipeline/classifier.py +93 -0
- mfcli/pipeline/data_enricher.py +15 -0
- mfcli/pipeline/extractor.py +34 -0
- mfcli/pipeline/extractors/__init__.py +0 -0
- mfcli/pipeline/extractors/pdf.py +12 -0
- mfcli/pipeline/parser.py +120 -0
- mfcli/pipeline/parsers/__init__.py +0 -0
- mfcli/pipeline/parsers/netlist/__init__.py +0 -0
- mfcli/pipeline/parsers/netlist/edif.py +93 -0
- mfcli/pipeline/parsers/netlist/kicad_legacy_net.py +326 -0
- mfcli/pipeline/parsers/netlist/kicad_spice.py +135 -0
- mfcli/pipeline/parsers/netlist/pads.py +185 -0
- mfcli/pipeline/parsers/netlist/protel.py +166 -0
- mfcli/pipeline/parsers/netlist/protel_detector.py +29 -0
- mfcli/pipeline/pipeline.py +470 -0
- mfcli/pipeline/preprocessors/__init__.py +0 -0
- mfcli/pipeline/preprocessors/user_guide.py +127 -0
- mfcli/pipeline/run_context.py +32 -0
- mfcli/pipeline/schema_mapper.py +89 -0
- mfcli/pipeline/sub_classifier.py +115 -0
- mfcli/utils/__init__.py +0 -0
- mfcli/utils/cline_rules.py +256 -0
- mfcli/utils/config.py +33 -0
- mfcli/utils/configurator.py +324 -0
- mfcli/utils/data_cleaner.py +114 -0
- mfcli/utils/datasheet_vectorizer.py +283 -0
- mfcli/utils/directory_manager.py +116 -0
- mfcli/utils/file_upload.py +298 -0
- mfcli/utils/files.py +16 -0
- mfcli/utils/http_requests.py +54 -0
- mfcli/utils/kb_lister.py +89 -0
- mfcli/utils/kb_remover.py +173 -0
- mfcli/utils/logger.py +28 -0
- mfcli/utils/mcp_configurator.py +394 -0
- mfcli/utils/migrations.py +18 -0
- mfcli/utils/orm.py +43 -0
- mfcli/utils/pdf_splitter.py +63 -0
- mfcli/utils/pre_uninstall.py +167 -0
- mfcli/utils/query_service.py +22 -0
- mfcli/utils/system_check.py +306 -0
- mfcli/utils/tools.py +98 -0
- mfcli/utils/vectorizer.py +28 -0
- mfcli-0.2.1.dist-info/METADATA +956 -0
- mfcli-0.2.1.dist-info/RECORD +138 -0
- mfcli-0.2.1.dist-info/WHEEL +5 -0
- mfcli-0.2.1.dist-info/entry_points.txt +4 -0
- mfcli-0.2.1.dist-info/licenses/LICENSE +21 -0
- mfcli-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Type, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from sqlmodel import SQLModel
|
|
5
|
+
|
|
6
|
+
from mfcli.agents.tools.general import format_instructions
|
|
7
|
+
from mfcli.client.gemini import Gemini
|
|
8
|
+
from mfcli.constants.file_types import FileSubtypes
|
|
9
|
+
from mfcli.models.bom import BOM, BOMSchema
|
|
10
|
+
from mfcli.models.netlist import Netlist
|
|
11
|
+
from mfcli.utils.logger import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
SubtypeSchemas: dict[FileSubtypes, Type[BaseModel]] = {
|
|
16
|
+
FileSubtypes.BOM: BOMSchema
|
|
17
|
+
}
|
|
18
|
+
SubtypeModels: dict[FileSubtypes, Type[SQLModel]] = {
|
|
19
|
+
FileSubtypes.BOM: BOM,
|
|
20
|
+
FileSubtypes.KICAD_LEGACY_NET: Netlist,
|
|
21
|
+
FileSubtypes.PADS_PCB_ASCII: Netlist,
|
|
22
|
+
FileSubtypes.KICAD_SPICE: Netlist,
|
|
23
|
+
FileSubtypes.PROTEL_ALTIUM: Netlist
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SchemaMapping(BaseModel):
|
|
28
|
+
input_field: Optional[str] = Field(None, description="A field found in the sample file")
|
|
29
|
+
mapped_field: str = Field(..., description="The field found in the backend to be mapped to")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SchemaMappings(BaseModel):
|
|
33
|
+
fields: list[SchemaMapping] = Field(default_factory=list, description="List of schema mappings")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
schema_mapper_instructions = format_instructions(
|
|
37
|
+
"""
|
|
38
|
+
You are responsible for mapping fields found in a file to a backend schema.
|
|
39
|
+
You will be given the schema for a file subtype, like Bill of Materials (BOM).
|
|
40
|
+
You will map whatever fields you see in the file, to fields in the backend.
|
|
41
|
+
For example, a column "Designator" found in a BOM file would map to "reference" in the backend.
|
|
42
|
+
|
|
43
|
+
You must respond **only** with valid JSON that exactly matches the `SchemaMappings` model:
|
|
44
|
+
|
|
45
|
+
- The top-level object must have a key `"fields"` containing a list.
|
|
46
|
+
- Each item in the list must be an object with:
|
|
47
|
+
- `"input_field"` (optional string) — the field name from the file header.
|
|
48
|
+
- `"mapped_field"` (required string) — the corresponding backend field name.
|
|
49
|
+
|
|
50
|
+
Do **not** include any markdown, text, or code fences. Respond **only** with JSON.
|
|
51
|
+
|
|
52
|
+
Example of valid response:
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
"fields": [
|
|
56
|
+
{"input_field": "RefDes", "mapped_field": "reference"},
|
|
57
|
+
{"input_field": "Value", "mapped_field": "value"},
|
|
58
|
+
{"input_field": "Qty", "mapped_field": "quantity"},
|
|
59
|
+
{"input_field": "Description", "mapped_field": "description"}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
"""
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def map_schema(gemini: Gemini, subtype: int, text: str) -> SchemaMappings | None:
|
|
67
|
+
if not SubtypeSchemas.get(subtype):
|
|
68
|
+
logger.debug(f"No subtype mapping required for subtype: {subtype}")
|
|
69
|
+
return
|
|
70
|
+
schema = str(SubtypeSchemas[subtype].model_json_schema())
|
|
71
|
+
prompt = format_instructions(
|
|
72
|
+
f"""
|
|
73
|
+
{schema_mapper_instructions}
|
|
74
|
+
|
|
75
|
+
Here is the backend schema for this filetype:
|
|
76
|
+
|
|
77
|
+
{schema}
|
|
78
|
+
|
|
79
|
+
Here is the file contents:
|
|
80
|
+
|
|
81
|
+
{text}
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
)
|
|
85
|
+
return await gemini.generate(
|
|
86
|
+
prompt=prompt,
|
|
87
|
+
instructions=schema_mapper_instructions,
|
|
88
|
+
response_model=SchemaMappings
|
|
89
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from google.genai.types import File as GeminiFile
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from mfcli.agents.tools.general import format_instructions
|
|
7
|
+
from mfcli.client.gemini import Gemini
|
|
8
|
+
from mfcli.constants.file_types import (
|
|
9
|
+
FileTypes,
|
|
10
|
+
FileSubtypes,
|
|
11
|
+
FILE_SUBTYPE_UNKNOWN,
|
|
12
|
+
PDFSubtypeDescriptions,
|
|
13
|
+
OtherFileTypeDescriptions,
|
|
14
|
+
PDFFileSubtypeNames,
|
|
15
|
+
OtherFileSubtypeNames
|
|
16
|
+
)
|
|
17
|
+
from mfcli.models.file import File
|
|
18
|
+
from mfcli.pipeline.parsers.netlist.kicad_legacy_net import is_kicad_legacy_netlist
|
|
19
|
+
from mfcli.pipeline.parsers.netlist.protel_detector import is_protel_netlist
|
|
20
|
+
from mfcli.utils.files import is_text_mime_type
|
|
21
|
+
from mfcli.utils.logger import get_logger
|
|
22
|
+
|
|
23
|
+
sub_classifier_instructions = format_instructions(
|
|
24
|
+
"""
|
|
25
|
+
You are the sub-classifier agent for an engineering document processing pipeline.
|
|
26
|
+
You will receive the first 50 lines of text from a file.
|
|
27
|
+
You will examine the content of the file, and determine the sub-type of this file.
|
|
28
|
+
You will be given the sub-types and sub-type descriptions.
|
|
29
|
+
If you are not able to determine the sub-type you will respond with "UNKNOWN".
|
|
30
|
+
|
|
31
|
+
Here are the valid sub-types and a description of each:
|
|
32
|
+
|
|
33
|
+
{}
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
logger = get_logger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PDFSubtypeClassifierResponse(BaseModel):
|
|
42
|
+
type: PDFFileSubtypeNames
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OtherFileSubtypeClassifierResponse(BaseModel):
|
|
46
|
+
type: OtherFileSubtypeNames
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
FileClass = Literal['pdf', 'other']
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FileSubtypeAnalyzer:
|
|
53
|
+
def __init__(self, gemini: Gemini):
|
|
54
|
+
self._gemini = gemini
|
|
55
|
+
|
|
56
|
+
async def _get_subtype_from_gemini(
|
|
57
|
+
self,
|
|
58
|
+
prompt: str,
|
|
59
|
+
instructions: str,
|
|
60
|
+
gemini_file: GeminiFile | None = None,
|
|
61
|
+
file_class: FileClass = 'other'
|
|
62
|
+
) -> str:
|
|
63
|
+
model = OtherFileSubtypeClassifierResponse if file_class == 'other' else PDFSubtypeClassifierResponse
|
|
64
|
+
files = [gemini_file] if gemini_file else None
|
|
65
|
+
response = await self._gemini.generate(
|
|
66
|
+
prompt=prompt,
|
|
67
|
+
instructions=instructions,
|
|
68
|
+
response_model=model,
|
|
69
|
+
files=files
|
|
70
|
+
)
|
|
71
|
+
return response.type
|
|
72
|
+
|
|
73
|
+
async def _get_subtype(
|
|
74
|
+
self,
|
|
75
|
+
prompt: str,
|
|
76
|
+
file: File,
|
|
77
|
+
gemini_file: GeminiFile | None = None,
|
|
78
|
+
file_class: FileClass = 'other'
|
|
79
|
+
) -> None:
|
|
80
|
+
logger.debug(f"Fetching subtype for file: {file.name}")
|
|
81
|
+
relevant_subtype_descriptions = PDFSubtypeDescriptions if file.type == FileTypes.PDF else OtherFileTypeDescriptions
|
|
82
|
+
logger.debug(f"Relevant subtypes: {relevant_subtype_descriptions.keys()}")
|
|
83
|
+
instructions = sub_classifier_instructions.format(relevant_subtype_descriptions)
|
|
84
|
+
subtype = await self._get_subtype_from_gemini(prompt, instructions, gemini_file, file_class)
|
|
85
|
+
logger.debug(f"Subtype discovered: {subtype}")
|
|
86
|
+
if subtype == FILE_SUBTYPE_UNKNOWN:
|
|
87
|
+
raise RuntimeError(f"Could not determine the file subtype for file: {file.name}")
|
|
88
|
+
if not subtype in relevant_subtype_descriptions:
|
|
89
|
+
raise RuntimeError(f"LLM responded with invalid subtype: {subtype}")
|
|
90
|
+
file.sub_type = FileSubtypes.get(subtype)
|
|
91
|
+
|
|
92
|
+
async def analyze_pdf(
|
|
93
|
+
self,
|
|
94
|
+
file: File,
|
|
95
|
+
gemini_file: GeminiFile
|
|
96
|
+
) -> None:
|
|
97
|
+
prompt = "Determine this PDF file subtype"
|
|
98
|
+
await self._get_subtype(prompt, file, gemini_file, 'pdf')
|
|
99
|
+
|
|
100
|
+
async def analyze_file(
|
|
101
|
+
self,
|
|
102
|
+
file: File,
|
|
103
|
+
text: str
|
|
104
|
+
) -> None:
|
|
105
|
+
# Handle text MIME types
|
|
106
|
+
if text and is_text_mime_type(file.mime_type):
|
|
107
|
+
if file.type == FileTypes.NET:
|
|
108
|
+
if is_kicad_legacy_netlist(text):
|
|
109
|
+
file.sub_type = FileSubtypes.KICAD_LEGACY_NET.value
|
|
110
|
+
elif is_protel_netlist(text):
|
|
111
|
+
file.sub_type = FileSubtypes.PROTEL_ALTIUM.value
|
|
112
|
+
|
|
113
|
+
# If subtype cannot be parsed, use LLM to determine subtype
|
|
114
|
+
if not file.sub_type:
|
|
115
|
+
await self._get_subtype(text[0:500], file)
|
mfcli/utils/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Utility for creating and managing Cline workspace rules files."""
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from mfcli.utils.tools import get_git_root
|
|
5
|
+
from mfcli.utils.logger import get_logger
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_cline_rules_content() -> str:
|
|
11
|
+
"""Get the content for the multifactor.md Cline rules file."""
|
|
12
|
+
return """# Multifactor Hardware Project Guidelines
|
|
13
|
+
|
|
14
|
+
This project uses the **mfcli** (Multifactor CLI) tool for hardware engineering document processing and analysis.
|
|
15
|
+
|
|
16
|
+
## MCP Server Integration
|
|
17
|
+
|
|
18
|
+
This project has access to the **mfcli-mcp** Model Context Protocol server, which provides AI-powered access to the project's hardware documentation knowledge base.
|
|
19
|
+
|
|
20
|
+
### When to Use the MCP Server
|
|
21
|
+
|
|
22
|
+
**ALWAYS query the mfcli MCP server** when working on tasks that involve:
|
|
23
|
+
- Hardware specifications and datasheets
|
|
24
|
+
- MCU (microcontroller) information
|
|
25
|
+
- Component details and part numbers
|
|
26
|
+
- Schematic analysis
|
|
27
|
+
- BOM (Bill of Materials) data
|
|
28
|
+
- Debug setup configurations
|
|
29
|
+
- Functional block diagrams
|
|
30
|
+
- Pin configurations
|
|
31
|
+
- Power management specifications
|
|
32
|
+
- Any hardware-related context
|
|
33
|
+
|
|
34
|
+
### How to Use the MCP Server
|
|
35
|
+
|
|
36
|
+
Use the `query_local_rag` tool to search the project's knowledge base:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Query the local RAG for "<your search query>" in project "<project_name>"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Examples:**
|
|
43
|
+
- "Query the local RAG for 'MSPM0L130x voltage specifications'"
|
|
44
|
+
- "Search the knowledge base for 'debug interface pinout'"
|
|
45
|
+
- "Find information about 'power supply requirements'"
|
|
46
|
+
|
|
47
|
+
The MCP server will return:
|
|
48
|
+
- Relevant document chunks from processed files
|
|
49
|
+
- Metadata (file names, document types)
|
|
50
|
+
- Similarity scores (lower = more relevant)
|
|
51
|
+
|
|
52
|
+
**Note:** After the first query, the project name is remembered, so you only need to specify it once per session.
|
|
53
|
+
|
|
54
|
+
## Hardware Cheat Sheets
|
|
55
|
+
|
|
56
|
+
The project includes a **`hw_cheat_sheets/`** folder within the `multifactor/` directory that contains AI-generated JSON summaries of key hardware information:
|
|
57
|
+
|
|
58
|
+
### Available Cheat Sheets
|
|
59
|
+
|
|
60
|
+
1. **MCU Datasheets** (`mcu_*.json`)
|
|
61
|
+
- Register maps
|
|
62
|
+
- Peripheral descriptions
|
|
63
|
+
- Technical specifications
|
|
64
|
+
- Pin configurations
|
|
65
|
+
- Memory maps
|
|
66
|
+
|
|
67
|
+
2. **MCU Errata** (`errata_*.json`)
|
|
68
|
+
- Known hardware issues
|
|
69
|
+
- Workarounds and fixes
|
|
70
|
+
- Affected chip revisions
|
|
71
|
+
- Severity levels
|
|
72
|
+
|
|
73
|
+
3. **Debug Setup** (`debug_setup_*.json`)
|
|
74
|
+
- Debug interface configurations
|
|
75
|
+
- Pin assignments for debugging
|
|
76
|
+
- Programming instructions
|
|
77
|
+
- Tool requirements
|
|
78
|
+
|
|
79
|
+
4. **Functional Blocks** (`functional_blocks_*.json`)
|
|
80
|
+
- System architecture
|
|
81
|
+
- Block diagrams
|
|
82
|
+
- Component interconnections
|
|
83
|
+
- Signal flow
|
|
84
|
+
|
|
85
|
+
### Using Cheat Sheets
|
|
86
|
+
|
|
87
|
+
**When to use:**
|
|
88
|
+
- Quick reference for common specifications
|
|
89
|
+
- Understanding system architecture
|
|
90
|
+
- Getting started with a new MCU
|
|
91
|
+
- Identifying debug configurations
|
|
92
|
+
|
|
93
|
+
**How to access:**
|
|
94
|
+
1. List available cheat sheets: `ls multifactor/hw_cheat_sheets/`
|
|
95
|
+
2. Read specific cheat sheet: Read the JSON file directly
|
|
96
|
+
3. The JSON format makes it easy to extract specific information programmatically
|
|
97
|
+
|
|
98
|
+
**Example workflow:**
|
|
99
|
+
```
|
|
100
|
+
1. Check hw_cheat_sheets/ for a quick overview of the MCU
|
|
101
|
+
2. Use query_local_rag for detailed information from datasheets
|
|
102
|
+
3. Combine both sources for comprehensive understanding
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Project Structure
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
<project_root>/
|
|
109
|
+
└── multifactor/ # Main project folder
|
|
110
|
+
├── config.json # Project configuration
|
|
111
|
+
├── file_docket.json # File tracking metadata
|
|
112
|
+
├── context/ # Input files for processing
|
|
113
|
+
├── hw_cheat_sheets/ # AI-generated hardware summaries (JSON)
|
|
114
|
+
├── generated_files/ # Generated BOMs and outputs
|
|
115
|
+
├── data_sheets/ # Downloaded component datasheets
|
|
116
|
+
└── pdf_parts/ # Extracted PDF segments
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Best Practices
|
|
120
|
+
|
|
121
|
+
### 1. Always Check Context First
|
|
122
|
+
- Review hw_cheat_sheets/ for quick reference
|
|
123
|
+
- Query the MCP server for detailed information
|
|
124
|
+
- This ensures accurate, project-specific responses
|
|
125
|
+
|
|
126
|
+
### 2. Be Specific in Queries
|
|
127
|
+
- Use part numbers when available
|
|
128
|
+
- Include relevant keywords (e.g., "voltage", "pinout", "timing")
|
|
129
|
+
- Reference specific sections when needed
|
|
130
|
+
|
|
131
|
+
### 3. Combine Multiple Sources
|
|
132
|
+
- Cheat sheets for overview
|
|
133
|
+
- MCP queries for detailed specs
|
|
134
|
+
- Cross-reference between documents
|
|
135
|
+
|
|
136
|
+
### 4. Project Awareness
|
|
137
|
+
- All hardware files should be placed in `multifactor/context/` for processing
|
|
138
|
+
- Run `mfcli run` to process new or modified files
|
|
139
|
+
- The knowledge base stays in sync with processed documents
|
|
140
|
+
|
|
141
|
+
## Common Tasks
|
|
142
|
+
|
|
143
|
+
### Getting MCU Specifications
|
|
144
|
+
1. Check `hw_cheat_sheets/mcu_*.json` for quick specs
|
|
145
|
+
2. Query MCP: "Find voltage and temperature specifications for [MCU_NAME]"
|
|
146
|
+
3. Cross-reference with datasheets in `data_sheets/`
|
|
147
|
+
|
|
148
|
+
### Understanding Debug Setup
|
|
149
|
+
1. Read `hw_cheat_sheets/debug_setup_*.json`
|
|
150
|
+
2. Query MCP: "What are the debug interface pin assignments?"
|
|
151
|
+
3. Look for programming instructions
|
|
152
|
+
|
|
153
|
+
### Analyzing Schematics
|
|
154
|
+
1. Query MCP: "What components are used in [section]?"
|
|
155
|
+
2. Check `generated_files/` for extracted BOMs
|
|
156
|
+
3. Review component datasheets in `data_sheets/`
|
|
157
|
+
|
|
158
|
+
### Finding Errata Information
|
|
159
|
+
1. Check `hw_cheat_sheets/errata_*.json` for known issues
|
|
160
|
+
2. Query MCP: "Are there any errata related to [feature]?"
|
|
161
|
+
3. Review workarounds and affected revisions
|
|
162
|
+
|
|
163
|
+
## Important Notes
|
|
164
|
+
|
|
165
|
+
- **Data Freshness**: The MCP knowledge base reflects files processed by mfcli. Run `mfcli run` after adding/updating files.
|
|
166
|
+
- **Local Processing**: All data is stored locally - no external data transmission.
|
|
167
|
+
- **File Changes**: Modified files need to be reprocessed. The system detects changes via MD5 checksums.
|
|
168
|
+
|
|
169
|
+
## Commands Reference
|
|
170
|
+
|
|
171
|
+
- `mfcli init` - Initialize project
|
|
172
|
+
- `mfcli run` - Process files and update knowledge base
|
|
173
|
+
- `mfcli add <file>` - Add specific file to knowledge base
|
|
174
|
+
- `mfcli ls` - List vectorized files
|
|
175
|
+
- `mfcli doctor` - Check system health
|
|
176
|
+
- `mfcli setup-mcp` - Configure MCP server
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
**Remember**: Always leverage the MCP server and hw_cheat_sheets/ for hardware-related tasks. This ensures responses are grounded in the actual project documentation rather than general knowledge.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def create_cline_rules_file(root_dir: str | Path) -> bool:
|
|
185
|
+
"""
|
|
186
|
+
Create .clinerules/multifactor.md workspace rules file if it doesn't exist.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
root_dir: Root directory of the project (will look for git root)
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if file was created or updated, False if it already exists
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
root_path = Path(root_dir)
|
|
196
|
+
|
|
197
|
+
# Determine the base directory - use git root if available
|
|
198
|
+
git_root = get_git_root(root_path)
|
|
199
|
+
base_dir = git_root if git_root else root_path
|
|
200
|
+
|
|
201
|
+
# Create .clinerules directory at the base
|
|
202
|
+
clinerules_dir = base_dir / ".clinerules"
|
|
203
|
+
clinerules_dir.mkdir(exist_ok=True, parents=True)
|
|
204
|
+
|
|
205
|
+
# Path to multifactor.md
|
|
206
|
+
multifactor_rules_file = clinerules_dir / "multifactor.md"
|
|
207
|
+
|
|
208
|
+
# Check if file already exists
|
|
209
|
+
if multifactor_rules_file.exists():
|
|
210
|
+
logger.info(f"Cline rules file already exists: {multifactor_rules_file}")
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
# Create the file with content
|
|
214
|
+
with open(multifactor_rules_file, 'w', encoding='utf-8') as f:
|
|
215
|
+
f.write(get_cline_rules_content())
|
|
216
|
+
|
|
217
|
+
logger.info(f"Created Cline workspace rules file: {multifactor_rules_file}")
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"Error creating Cline rules file: {e}")
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def update_cline_rules_file(root_dir: str | Path) -> bool:
|
|
226
|
+
"""
|
|
227
|
+
Update existing .clinerules/multifactor.md file with latest content.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
root_dir: Root directory of the project
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
True if file was updated, False otherwise
|
|
234
|
+
"""
|
|
235
|
+
try:
|
|
236
|
+
root_path = Path(root_dir)
|
|
237
|
+
|
|
238
|
+
# Determine the base directory
|
|
239
|
+
git_root = get_git_root(root_path)
|
|
240
|
+
base_dir = git_root if git_root else root_path
|
|
241
|
+
|
|
242
|
+
multifactor_rules_file = base_dir / ".clinerules" / "multifactor.md"
|
|
243
|
+
|
|
244
|
+
if not multifactor_rules_file.exists():
|
|
245
|
+
return create_cline_rules_file(root_dir)
|
|
246
|
+
|
|
247
|
+
# Update with latest content
|
|
248
|
+
with open(multifactor_rules_file, 'w', encoding='utf-8') as f:
|
|
249
|
+
f.write(get_cline_rules_content())
|
|
250
|
+
|
|
251
|
+
logger.info(f"Updated Cline workspace rules file: {multifactor_rules_file}")
|
|
252
|
+
return True
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
logger.error(f"Error updating Cline rules file: {e}")
|
|
256
|
+
return False
|
mfcli/utils/config.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
5
|
+
|
|
6
|
+
from mfcli.constants.openai import OPENAI_DEFAULT_EMBEDDING_MODEL, OPENAI_DEFAULT_EMBEDDING_DIMENSIONS
|
|
7
|
+
from mfcli.utils.directory_manager import app_dirs
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Settings(BaseSettings):
|
|
11
|
+
digikey_client_id: str
|
|
12
|
+
digikey_client_secret: str
|
|
13
|
+
openai_api_key: str
|
|
14
|
+
google_api_key: str
|
|
15
|
+
log_level: str = Field(default="INFO")
|
|
16
|
+
use_docling: bool = Field(default=True)
|
|
17
|
+
chunk_tokens: int = Field(default=2000)
|
|
18
|
+
chunk_size: int = Field(default=500)
|
|
19
|
+
chunk_overlap: int = Field(default=50)
|
|
20
|
+
embedding_model: str = Field(default=OPENAI_DEFAULT_EMBEDDING_MODEL)
|
|
21
|
+
embedding_dimensions: int = Field(default=OPENAI_DEFAULT_EMBEDDING_DIMENSIONS)
|
|
22
|
+
|
|
23
|
+
model_config = SettingsConfigDict(
|
|
24
|
+
env_file=str(app_dirs.env_file_path),
|
|
25
|
+
extra="allow",
|
|
26
|
+
env_file_encoding="utf-8",
|
|
27
|
+
case_sensitive=False
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@lru_cache
|
|
32
|
+
def get_config() -> Settings:
|
|
33
|
+
return Settings()
|