Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI Checks

on:
pull_request:
branches: [main] # Or your default branch

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Cache pip dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt # Adjust if you have dev requirements
# Install dev tools and type stubs if not in requirements.txt
pip install black flake8 mypy pytest pytest-cov types-tqdm types-PyYAML

- name: Run Linter and Formatter Check
run: |
flake8 src/
black --check src/

- name: Run MyPy (Type Checking)
run: |
mypy src/
49 changes: 49 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Publish Python Package to PyPI

on:
push:
tags:
- "v*.*.*" # Trigger on tags like v0.1.0, v1.2.3

jobs:
deploy:
runs-on: ubuntu-latest

# Optional: Use environments for protection rules
# environment:
# name: pypi
# url: https://pypi.org/p/build-influence # Replace with your package name

# Grant GITHUB_TOKEN permissions to authenticate with PyPI using OIDC (more secure, recommended)
permissions:
id-token: write # Required for trusted publishing

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install build dependencies
run: python -m pip install --upgrade build twine

- name: Build package
run: python -m build

# Preferred: Publish using Trusted Publishing (OIDC)
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# No API token needed here if PyPI project is configured for trusted publishing

# --- Alternative: Publish using API Token (keep only ONE publish step) ---
# Uncomment the step below and comment out the Trusted Publishing step above
# if you prefer using the API token secret.
# Ensure you have configured the PYPI_API_TOKEN secret in GitHub repo settings.
# - name: Publish package to PyPI using API Token
# uses: pypa/gh-action-pypi-publish@release/v1
# with:
# password: ${{ secrets.PYPI_API_TOKEN }}
# ---------------------------------------------------------------------
66 changes: 66 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Contributing to Build Influence

First off, thank you for considering contributing to Build Influence! We welcome any help, from reporting bugs and suggesting features to submitting code changes.

## How Can I Contribute?

### Reporting Bugs

- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/drorivry/build-influence/issues). (Replace `drorivry/build-influence` with your actual repository path if different).
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/drorivry/build-influence/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample or an executable test case** demonstrating the expected behavior that is not occurring.

### Suggesting Enhancements

- Open a new issue using the Feature Request template.
- Clearly describe the enhancement and the motivation for it.
- Explain why this enhancement would be useful.
- Provide code examples if applicable.

### Pull Requests

We actively welcome your pull requests:

1. Fork the repo and create your branch from `main`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes (`pytest tests/`).
5. Make sure your code lints (`flake8 src/ tests/`) and is formatted (`black src/ tests/`).
6. Ensure type checks pass (`mypy src/ tests/`).
7. Issue that pull request!

## Development Setup

1. Fork the repository on GitHub.
2. Clone your fork locally:
```bash
git clone [email protected]:YOUR_USERNAME/build-influence.git
cd build-influence
```
3. Create a virtual environment (recommended):
```bash
python3.12 -m venv venv
source venv/bin/activate # On Windows use `venv\Scripts\activate`
```
4. Install dependencies:
```bash
pip install -r requirements.txt
# Install development/testing tools and type stubs if they are not in requirements.txt
pip install black flake8 mypy pytest pytest-cov build twine types-tqdm types-PyYAML
```

## Coding Standards

- Please follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) style guide.
- Use `flake8` for linting and `black` for formatting. Our CI pipeline checks this, so please run `flake8 src/ tests/` and `black src/ tests/` before committing.
- Use type hints (`mypy`) for static analysis. Our CI pipeline checks this.
- Write tests using `pytest` for new functionality.

## Code of Conduct

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. (Consider adding a `CODE_OF_CONDUCT.md` file).

## Any questions?

Feel free to open an issue if you have questions about contributing.

Thank you for your contribution!
32 changes: 18 additions & 14 deletions src/build_influence/analysis/analyzer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import subprocess
from pathlib import Path
from typing import List, Dict, Any
from typing import List, Dict, Any, Optional
import litellm
import json
from tqdm import tqdm
Expand Down Expand Up @@ -128,7 +128,7 @@ def analyze(self) -> Dict[str, Any]:
# --------------------------------------------

# --- Final Result ---
final_analysis_result = {
final_analysis_result: Dict[str, Any] = {
**interim_result,
"high_level_features": high_level_features,
}
Expand Down Expand Up @@ -185,7 +185,7 @@ def _extract_metadata(self) -> Dict[str, Any]:
def _build_file_tree(self) -> List[Dict[str, Any]]:
"""Scan directory structure and build a list of files to consider."""
logger.debug("Building file tree...")
file_tree = []
file_tree: List[Dict[str, Any]] = []
potential_files_count = 0

try:
Expand All @@ -196,7 +196,7 @@ def _build_file_tree(self) -> List[Dict[str, Any]]:

try:
relative_path = item.relative_to(self.repo_path)
file_info = {
file_info: Dict[str, Any] = {
"path": str(relative_path),
"absolute_path": str(item),
"size": item.stat().st_size,
Expand Down Expand Up @@ -269,7 +269,7 @@ def _analyze_file_content_with_ai(
Generic helper to analyze file content using LiteLLM with a specific
prompt.
"""
insights = {"error": None}
insights: Dict[str, Any] = {"error": None}
file_name = file_path.name

try:
Expand Down Expand Up @@ -406,15 +406,17 @@ def _analyze_code_file_with_ai(self, file_path: Path) -> Dict[str, Any]:
# logger.debug(
# f"Attempting code key extraction from: {raw_insights!r}"
# )
purpose = raw_insights.get("purpose")
purpose: Optional[str] = raw_insights.get("purpose")
# logger.debug(f"Code Purpose extracted: {purpose!r}")
elements = raw_insights.get("key_elements", [])
elements: Optional[List[str]] = raw_insights.get("key_elements", [])
# logger.debug(f"Code Elements extracted: {elements!r}")
dependencies = raw_insights.get("dependencies", [])
dependencies: Optional[List[str]] = raw_insights.get("dependencies", [])
# logger.debug(
# f"Code Dependencies extracted: {dependencies!r}"
# )
aspects = raw_insights.get("interesting_aspects", [])
aspects: Optional[List[str]] = raw_insights.get(
"interesting_aspects", []
)
# logger.debug(f"Code Aspects extracted: {aspects!r}")
# --- End Detailed Logging ---

Expand Down Expand Up @@ -456,10 +458,12 @@ def _analyze_doc_file_with_ai(self, file_path: Path) -> Dict[str, Any]:
return raw_insights # Return error dict as is
elif isinstance(raw_insights, dict):
try:
summary = raw_insights.get("summary")
features = raw_insights.get("features", [])
setup_steps = raw_insights.get("setup_steps", [])
usage_examples = raw_insights.get("usage_examples", [])
summary: Optional[str] = raw_insights.get("summary")
features: Optional[List[str]] = raw_insights.get("features", [])
setup_steps: Optional[List[str]] = raw_insights.get("setup_steps", [])
usage_examples: Optional[List[str]] = raw_insights.get(
"usage_examples", []
)

return {
"summary": summary,
Expand Down Expand Up @@ -506,7 +510,7 @@ def _analyze_doc_file_with_ai(self, file_path: Path) -> Dict[str, Any]:
# Print first few files with AI insights if available
for i, file_info in enumerate(result["file_tree"][:5]):
print(
f"\n File {i+1}: {file_info['path']} "
f"\n File {i + 1}: {file_info['path']} "
f"({file_info['type']}, {file_info['size']}b)"
)
insights = None
Expand Down
8 changes: 4 additions & 4 deletions src/build_influence/generation/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ def _build_prompt(
**Instructions:**
1. Synthesize the provided information.
2. Write a compelling '{content_type}' post in well-formatted Markdown.
3. Ensure the tone is appropriate for the target audience and platform
3. Ensure the tone is appropriate for the target audience and platform
(general technical audience for Markdown).
4. If generating a 'deepdive', elaborate on technical aspects. If
4. If generating a 'deepdive', elaborate on technical aspects. If
'announcement', focus on highlights and purpose.
5. Make sure the output is only the Markdown content, without any preamble
5. Make sure the output is only the Markdown content, without any preamble
or explanation.

**Generated Markdown Post:**
""".strip()

Expand Down
4 changes: 2 additions & 2 deletions src/build_influence/generation/linkedin_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ def _build_prompt(
2. Focus on the value proposition, achievements, or key learnings.
3. Maintain a professional and engaging tone suitable for LinkedIn.
4. Use appropriate formatting (bullet points, maybe bolding key terms).
5. If '{content_type}' is 'announcement', highlight the launch/update
professionally. If 'deepdive', focus on technical achievements or
5. If '{content_type}' is 'announcement', highlight the launch/update
professionally. If 'deepdive', focus on technical achievements or
learnings.
6. Consider adding relevant professional hashtags (e.g., #SoftwareDevelopment,
#Tech, #ProjectManagement).
Expand Down
7 changes: 5 additions & 2 deletions src/build_influence/publication/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Publication module

from typing import Type

from .base_publisher import BasePublisher, PublicationContent, PublishResult
from .linkedin_publisher import LinkedInPublisher
from .devto_publisher import DevToPublisher
Expand All @@ -17,14 +19,15 @@

def get_publisher(platform_name: str) -> BasePublisher | None:
"""Factory function to get a publisher instance for the platform name."""
publisher_class = _publisher_map.get(platform_name.lower())
publisher_class: Type[BasePublisher] | None = _publisher_map.get(
platform_name.lower()
)
if publisher_class:
return publisher_class()
return None


__all__ = [
"BasePublisher",
"PublicationContent",
"PublishResult",
"LinkedInPublisher",
Expand Down
2 changes: 1 addition & 1 deletion src/build_influence/publication/base_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class BasePublisher(ABC):
platform_name: str = "Base"

@abstractmethod
async def publish(self, content: PublicationContent, config: dict) -> PublishResult:
def publish(self, content: PublicationContent, config: dict) -> PublishResult:
"""
Publishes the given content to the specific platform.

Expand Down
2 changes: 1 addition & 1 deletion src/build_influence/publication/linkedin_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class LinkedInPublisher(BasePublisher):

platform_name = "LinkedIn"

async def publish(self, content: PublicationContent, config: dict) -> PublishResult:
def publish(self, content: PublicationContent, config: dict) -> PublishResult:
"""Publishes content to LinkedIn. Placeholder implementation."""
print(f"Publishing to {self.platform_name}:")
print(f"Title: {content.title}")
Expand Down