mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-07 21:44:02 -05:00
feat: add release updates section and Gemini thinking support
- Add comprehensive "Recent Major Features" section to README - Introduce new readme_updates Python script for automation - Enable Gemini thinking configuration with token budgets - Update CLI help text for Gemini thinking support - Add comprehensive test coverage for Gemini thinking - Create documentation for README update automation - Reorganize README navigation structure with changelog section
This commit is contained in:
57
README.md
57
README.md
@@ -47,6 +47,52 @@ It's all really exciting and powerful, but _it's not easy to integrate this func
|
||||
|
||||
Fabric organizes prompts by real-world task, allowing people to create, collect, and organize their most important AI solutions in a single place for use in their favorite tools. And if you're command-line focused, you can use Fabric itself as the interface!
|
||||
|
||||
## Updates
|
||||
|
||||
Dear Users,
|
||||
|
||||
We've been doing so many exciting things here at Fabric, I wanted to give a quick summary here to give you a sense of our development velocity!
|
||||
|
||||
Below are the **new features and capabilities** we've added (newest first):
|
||||
|
||||
### Recent Major Features
|
||||
|
||||
- [v1.4.287](https://github.com/danielmiessler/fabric/releases/tag/v1.4.287) (Aug 16, 2025) — **AI Reasoning**: Add Thinking to Gemini models and introduce `readme_updates` python script
|
||||
- [v1.4.286](https://github.com/danielmiessler/fabric/releases/tag/v1.4.286) (Aug 14, 2025) — **AI Reasoning**: Introduce Thinking Config Across Anthropic and OpenAI Providers
|
||||
- [v1.4.285](https://github.com/danielmiessler/fabric/releases/tag/v1.4.285) (Aug 13, 2025) — **Extended Context**: Enable One Million Token Context Beta Feature for Sonnet-4
|
||||
- [v1.4.284](https://github.com/danielmiessler/fabric/releases/tag/v1.4.284) (Aug 12, 2025) — **Easy Shell Completions Setup**: Introduce One-Liner Curl Install for Completions
|
||||
- [v1.4.283](https://github.com/danielmiessler/fabric/releases/tag/v1.4.283) (Aug 12, 2025) — **Model Management**: Add Vendor Selection Support for Models
|
||||
- [v1.4.282](https://github.com/danielmiessler/fabric/releases/tag/v1.4.282) (Aug 11, 2025) — **Enhanced Shell Completions**: Enhanced Shell Completions for Fabric CLI Binaries
|
||||
- [v1.4.281](https://github.com/danielmiessler/fabric/releases/tag/v1.4.281) (Aug 11, 2025) — **Gemini Search Tool**: Add Web Search Tool Support for Gemini Models
|
||||
- [v1.4.278](https://github.com/danielmiessler/fabric/releases/tag/v1.4.278) (Aug 9, 2025) — **Enhance YouTube Transcripts**: Enhance YouTube Support with Custom yt-dlp Arguments
|
||||
- [v1.4.277](https://github.com/danielmiessler/fabric/releases/tag/v1.4.277) (Aug 8, 2025) — **Desktop Notifications**: Add cross-platform desktop notifications to Fabric CLI
|
||||
- [v1.4.274](https://github.com/danielmiessler/fabric/releases/tag/v1.4.274) (Aug 7, 2025) — **Claude 4.1 Added**: Add Support for Claude Opus 4.1 Model
|
||||
- [v1.4.271](https://github.com/danielmiessler/fabric/releases/tag/v1.4.271) (Jul 28, 2025) — **AI Summarized Release Notes**: Enable AI summary updates for GitHub releases
|
||||
- [v1.4.268](https://github.com/danielmiessler/fabric/releases/tag/v1.4.268) (Jul 26, 2025) — **Gemmini TTS Voice Selection**: add Gemini TTS voice selection and listing functionality
|
||||
- [v1.4.267](https://github.com/danielmiessler/fabric/releases/tag/v1.4.267) (Jul 26, 2025) — **Text-to-Speech**: Update Gemini Plugin to New SDK with TTS Support
|
||||
- [v1.4.258](https://github.com/danielmiessler/fabric/releases/tag/v1.4.258) (Jul 17, 2025) — **Onboarding Improved**: Add startup check to initialize config and .env file automatically
|
||||
- [v1.4.257](https://github.com/danielmiessler/fabric/releases/tag/v1.4.257) (Jul 17, 2025) — **OpenAI Routing Control**: Introduce CLI Flag to Disable OpenAI Responses API
|
||||
- [v1.4.252](https://github.com/danielmiessler/fabric/releases/tag/v1.4.252) (Jul 16, 2025) — **Hide Thinking Block**: Optional Hiding of Model Thinking Process with Configurable Tags
|
||||
- [v1.4.246](https://github.com/danielmiessler/fabric/releases/tag/v1.4.246) (Jul 14, 2025) — **Automatic ChangeLog Updates**: Add AI-powered changelog generation with high-performance Go tool and comprehensive caching
|
||||
- [v1.4.245](https://github.com/danielmiessler/fabric/releases/tag/v1.4.245) (Jul 11, 2025) — **Together AI**: Together AI Support with OpenAI Fallback Mechanism Added
|
||||
- [v1.4.232](https://github.com/danielmiessler/fabric/releases/tag/v1.4.232) (Jul 6, 2025) — **Add Custom**: Add Custom Patterns Directory Support
|
||||
- [v1.4.231](https://github.com/danielmiessler/fabric/releases/tag/v1.4.231) (Jul 5, 2025) — **OAuth Auto-Auth**: OAuth Authentication Support for Anthropic (Use your Max Subscription)
|
||||
- [v1.4.230](https://github.com/danielmiessler/fabric/releases/tag/v1.4.230) (Jul 5, 2025) — **Model Management**: Add advanced image generation parameters for OpenAI models with four new CLI flags
|
||||
- [v1.4.227](https://github.com/danielmiessler/fabric/releases/tag/v1.4.227) (Jul 4, 2025) — **Add Image**: Add Image Generation Support to Fabric
|
||||
- [v1.4.226](https://github.com/danielmiessler/fabric/releases/tag/v1.4.226) (Jul 4, 2025) — **Web Search**: OpenAI Plugin Now Supports Web Search Functionality
|
||||
- [v1.4.225](https://github.com/danielmiessler/fabric/releases/tag/v1.4.225) (Jul 4, 2025) — **Web Search**: Runtime Web Search Control via Command-Line `--search` Flag
|
||||
- [v1.4.224](https://github.com/danielmiessler/fabric/releases/tag/v1.4.224) (Jul 1, 2025) — **Add code_review**: Add code_review pattern and updates in Pattern_Descriptions
|
||||
- [v1.4.222](https://github.com/danielmiessler/fabric/releases/tag/v1.4.222) (Jul 1, 2025) — **OpenAI Plugin**: OpenAI Plugin Migrates to New Responses API
|
||||
- [v1.4.218](https://github.com/danielmiessler/fabric/releases/tag/v1.4.218) (Jun 27, 2025) — **Model Management**: Add Support for OpenAI Search and Research Model Variants
|
||||
- [v1.4.217](https://github.com/danielmiessler/fabric/releases/tag/v1.4.217) (Jun 26, 2025) — **New YouTube**: New YouTube Transcript Endpoint Added to REST API
|
||||
- [v1.4.212](https://github.com/danielmiessler/fabric/releases/tag/v1.4.212) (Jun 23, 2025) — **Add Langdock**: Add Langdock AI and enhance generic OpenAI compatible support
|
||||
- [v1.4.211](https://github.com/danielmiessler/fabric/releases/tag/v1.4.211) (Jun 19, 2025) — **REST API**: REST API and Web UI Now Support Dynamic Pattern Variables
|
||||
- [v1.4.210](https://github.com/danielmiessler/fabric/releases/tag/v1.4.210) (Jun 18, 2025) — **Add Citations**: Add Citation Support to Perplexity Response
|
||||
- [v1.4.208](https://github.com/danielmiessler/fabric/releases/tag/v1.4.208) (Jun 17, 2025) — **Add Perplexity**: Add Perplexity AI Provider with Token Limits Support
|
||||
- [v1.4.203](https://github.com/danielmiessler/fabric/releases/tag/v1.4.203) (Jun 14, 2025) — **Add Amazon Bedrock**: Add support for Amazon Bedrock
|
||||
|
||||
These features represent our commitment to making Fabric the most powerful and flexible AI augmentation framework available!
|
||||
|
||||
## Intro videos
|
||||
|
||||
Keep in mind that many of these were recorded when Fabric was Python-based, so remember to use the current [install instructions](#installation) below.
|
||||
@@ -60,9 +106,11 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
|
||||
|
||||
- [`fabric`](#fabric)
|
||||
- [What and why](#what-and-why)
|
||||
- [Updates](#updates)
|
||||
- [Recent Major Features](#recent-major-features)
|
||||
- [Intro videos](#intro-videos)
|
||||
- [Navigation](#navigation)
|
||||
- [Updates](#updates)
|
||||
- [Changelog](#changelog)
|
||||
- [Philosophy](#philosophy)
|
||||
- [Breaking problems into components](#breaking-problems-into-components)
|
||||
- [Too many prompts](#too-many-prompts)
|
||||
@@ -112,7 +160,7 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
|
||||
|
||||
<br />
|
||||
|
||||
## Updates
|
||||
## Changelog
|
||||
|
||||
Fabric is evolving rapidly.
|
||||
|
||||
@@ -576,9 +624,8 @@ Application Options:
|
||||
--notification-command= Custom command to run for notifications (overrides built-in
|
||||
notifications)
|
||||
--yt-dlp-args= Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')
|
||||
--thinking= Set reasoning/thinking level (e.g., off, low, medium,
|
||||
high, or numeric tokens for Anthropic)
|
||||
|
||||
--thinking= Set reasoning/thinking level (e.g., off, low, medium, high, or
|
||||
numeric tokens for Anthropic or Google Gemini)
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
```
|
||||
|
||||
7
cmd/generate_changelog/incoming/1706.txt
Normal file
7
cmd/generate_changelog/incoming/1706.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
### PR [#1706](https://github.com/danielmiessler/Fabric/pull/1706) by [ksylvan](https://github.com/ksylvan): Gemini Thinking Support and README (New Features) automation
|
||||
|
||||
- Add comprehensive "Recent Major Features" section to README
|
||||
- Introduce new readme_updates Python script for automation
|
||||
- Enable Gemini thinking configuration with token budgets
|
||||
- Update CLI help text for Gemini thinking support
|
||||
- Add comprehensive test coverage for Gemini thinking
|
||||
@@ -95,7 +95,7 @@ type Flags struct {
|
||||
ListGeminiVoices bool `long:"list-gemini-voices" description:"List all available Gemini TTS voices"`
|
||||
Notification bool `long:"notification" yaml:"notification" description:"Send desktop notification when command completes"`
|
||||
NotificationCommand string `long:"notification-command" yaml:"notificationCommand" description:"Custom command to run for notifications (overrides built-in notifications)"`
|
||||
Thinking domain.ThinkingLevel `long:"thinking" yaml:"thinking" description:"Set reasoning/thinking level (e.g., off, low, medium, high, or numeric tokens for Anthropic)"`
|
||||
Thinking domain.ThinkingLevel `long:"thinking" yaml:"thinking" description:"Set reasoning/thinking level (e.g., off, low, medium, high, or numeric tokens for Anthropic or Google Gemini)"`
|
||||
}
|
||||
|
||||
var debug = false
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
@@ -170,6 +171,25 @@ func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func parseThinkingConfig(level domain.ThinkingLevel) (*genai.ThinkingConfig, bool) {
|
||||
lower := strings.ToLower(strings.TrimSpace(string(level)))
|
||||
switch domain.ThinkingLevel(lower) {
|
||||
case "", domain.ThinkingOff:
|
||||
return nil, false
|
||||
case domain.ThinkingLow, domain.ThinkingMedium, domain.ThinkingHigh:
|
||||
if budget, ok := domain.ThinkingBudgets[domain.ThinkingLevel(lower)]; ok {
|
||||
b := int32(budget)
|
||||
return &genai.ThinkingConfig{IncludeThoughts: true, ThinkingBudget: &b}, true
|
||||
}
|
||||
default:
|
||||
if tokens, err := strconv.ParseInt(lower, 10, 32); err == nil && tokens > 0 {
|
||||
t := int32(tokens)
|
||||
return &genai.ThinkingConfig{IncludeThoughts: true, ThinkingBudget: &t}, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// buildGenerateContentConfig constructs the generation config with optional tools.
|
||||
// When search is enabled it injects the Google Search tool. The optional search
|
||||
// location accepts either:
|
||||
@@ -201,6 +221,10 @@ func (o *Client) buildGenerateContentConfig(opts *domain.ChatOptions) (*genai.Ge
|
||||
}
|
||||
}
|
||||
|
||||
if tc, ok := parseThinkingConfig(opts.Thinking); ok {
|
||||
cfg.ThinkingConfig = tc
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,38 @@ func TestBuildGenerateContentConfig_LanguageCodeNormalization(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGenerateContentConfig_Thinking(t *testing.T) {
|
||||
client := &Client{}
|
||||
opts := &domain.ChatOptions{Thinking: domain.ThinkingLow}
|
||||
|
||||
cfg, err := client.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cfg.ThinkingConfig == nil || !cfg.ThinkingConfig.IncludeThoughts {
|
||||
t.Fatalf("expected thinking config with thoughts included")
|
||||
}
|
||||
if cfg.ThinkingConfig.ThinkingBudget == nil || *cfg.ThinkingConfig.ThinkingBudget != int32(domain.TokenBudgetLow) {
|
||||
t.Errorf("expected thinking budget %d, got %+v", domain.TokenBudgetLow, cfg.ThinkingConfig.ThinkingBudget)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGenerateContentConfig_ThinkingTokens(t *testing.T) {
|
||||
client := &Client{}
|
||||
opts := &domain.ChatOptions{Thinking: domain.ThinkingLevel("123")}
|
||||
|
||||
cfg, err := client.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cfg.ThinkingConfig == nil || cfg.ThinkingConfig.ThinkingBudget == nil {
|
||||
t.Fatalf("expected thinking config with budget")
|
||||
}
|
||||
if *cfg.ThinkingConfig.ThinkingBudget != 123 {
|
||||
t.Errorf("expected thinking budget 123, got %d", *cfg.ThinkingConfig.ThinkingBudget)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCitationFormatting(t *testing.T) {
|
||||
client := &Client{}
|
||||
response := &genai.GenerateContentResponse{
|
||||
|
||||
99
scripts/readme_updates/README.md
Normal file
99
scripts/readme_updates/README.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# README Update Scripts
|
||||
|
||||
This directory contains automation scripts for updating the main README.md file with release information from the changelog database.
|
||||
|
||||
## `update_readme_features.py`
|
||||
|
||||
A Python script that generates the "Recent Major Features" section for the README by extracting and filtering release information from the changelog SQLite database.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Generate the Recent Major Features section with default limit (20 releases)
|
||||
python scripts/readme_updates/update_readme_features.py
|
||||
|
||||
# Specify a custom limit
|
||||
python scripts/readme_updates/update_readme_features.py --limit 15
|
||||
|
||||
# Use a custom database path
|
||||
python scripts/readme_updates/update_readme_features.py --db /path/to/changelog.db
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Database Connection**: Connects to `cmd/generate_changelog/changelog.db` (or custom path)
|
||||
2. **Data Extraction**: Queries the `versions` table for release information
|
||||
3. **Feature Filtering**: Uses heuristics to identify feature/improvement releases
|
||||
4. **Markdown Generation**: Formats output to match README style
|
||||
|
||||
### Feature Detection Heuristics
|
||||
|
||||
The script uses keyword-based heuristics to filter releases:
|
||||
|
||||
#### Include Keywords (Features/Improvements)
|
||||
- new, feature, feat, add, introduce, enable, support
|
||||
- improve, enhance, performance, speed
|
||||
- option, flag, argument, parameter
|
||||
- integration, provider, search, tts, audio, model
|
||||
- cli, ui, web, oauth, sync, database
|
||||
- notifications, desktop, reasoning, thinking
|
||||
|
||||
#### Exclude Keywords (Non-Features)
|
||||
- fix, bug, hotfix
|
||||
- ci, cd, pipeline, chore
|
||||
- docs, readme, refactor, style, typo
|
||||
- test, bump, deps, dependency
|
||||
- merge, revert, format, lint, build
|
||||
- release, prepare, coverage, security
|
||||
|
||||
### Integration with README
|
||||
|
||||
To update the README with new release features:
|
||||
|
||||
```bash
|
||||
# Generate the features and save to a temporary file
|
||||
python scripts/readme_updates/update_readme_features.py --limit 20 > /tmp/recent_features.md
|
||||
|
||||
# Manually replace the "### Recent Major Features" section in README.md
|
||||
# with the generated content
|
||||
```
|
||||
|
||||
### Database Schema
|
||||
|
||||
The script expects the following SQLite table structure:
|
||||
|
||||
```sql
|
||||
CREATE TABLE versions (
|
||||
name TEXT PRIMARY KEY,
|
||||
date DATETIME,
|
||||
commit_sha TEXT,
|
||||
pr_numbers TEXT,
|
||||
ai_summary TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### Date Format Support
|
||||
|
||||
The script can parse various date formats:
|
||||
- ISO 8601 with timezone: `2025-08-14 14:11:04+00:00`
|
||||
- ISO 8601 basic: `2025-08-14T14:11:04`
|
||||
- Date only: `2025-08-14`
|
||||
- US format: `08/14/2025`
|
||||
|
||||
Output format is standardized to: `Aug 14, 2025`
|
||||
|
||||
### Maintenance Notes
|
||||
|
||||
- **AI Summary Format Changes**: If the format of AI summaries changes, update the `extract_title_desc()` and `split_summary()` functions
|
||||
- **Keyword Tuning**: Adjust `INCLUDE_RE` and `EXCLUDE_RE` patterns as needed
|
||||
- **Title Extraction**: The script attempts to extract concise titles from feature descriptions
|
||||
- **Description Length**: Descriptions are limited to 200 characters for readability
|
||||
|
||||
### Future Enhancements
|
||||
|
||||
Potential improvements for automated README updates:
|
||||
- Add section delimiter markers in README for automated replacement
|
||||
- Create a GitHub Action to run on new releases
|
||||
- Add support for categorizing features by type
|
||||
- Implement confidence scoring for feature detection
|
||||
281
scripts/readme_updates/update_readme_features.py
Executable file
281
scripts/readme_updates/update_readme_features.py
Executable file
@@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate the '### Recent Major Features' markdown section for README from the changelog SQLite DB.
|
||||
|
||||
- Connects to cmd/generate_changelog/changelog.db
|
||||
- Extracts version, date, and AI summaries from the 'versions' table
|
||||
- Heuristically filters for feature/improvement items (excludes CI/CD/docs/bug fixes)
|
||||
- Formats output to match README style:
|
||||
- [vX.Y.Z](https://github.com/danielmiessler/fabric/releases/tag/vX.Y.Z) (Aug 14, 2025) — **Feature Name**: Short description
|
||||
|
||||
Usage:
|
||||
python scripts/readme_updates/update_readme_features.py --limit 20
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import re
|
||||
import sys
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
# Heuristics for filtering feature-related lines
|
||||
EXCLUDE_RE = re.compile(
|
||||
r"(?i)\b(fix|bug|hotfix|ci|cd|pipeline|chore|docs|doc|readme|refactor|style|typo|"
|
||||
"test|tests|bump|deps|dependency|merge|revert|format|lint|build|release\b|prepare|"
|
||||
"codeowners|coverage|security)\b"
|
||||
)
|
||||
INCLUDE_RE = re.compile(
|
||||
r"(?i)\b(new|feature|feat|add|added|introduce|enable|support|improve|enhance|"
|
||||
"performance|speed|option|flag|argument|parameter|integration|provider|search|tts|"
|
||||
"audio|model|cli|ui|web|oauth|sync|database|notifications|desktop|reasoning|thinking)\b"
|
||||
)
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse command-line arguments."""
|
||||
p = argparse.ArgumentParser(
|
||||
description="Generate README 'Recent Major Features' markdown from changelog DB."
|
||||
)
|
||||
p.add_argument(
|
||||
"--limit", type=int, default=20, help="Maximum number of releases to include."
|
||||
)
|
||||
p.add_argument(
|
||||
"--db",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Optional path to changelog.db (defaults to repo cmd/generate_changelog/changelog.db)",
|
||||
)
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
"""Get the repository root directory."""
|
||||
# scripts/readme_updates/update_readme_features.py -> repo root is parent.parent.parent
|
||||
return Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
|
||||
def db_path(args) -> Path:
|
||||
"""Determine the database path."""
|
||||
if args.db:
|
||||
return Path(args.db).expanduser().resolve()
|
||||
return repo_root() / "cmd" / "generate_changelog" / "changelog.db"
|
||||
|
||||
|
||||
def connect(dbfile: Path):
|
||||
"""Connect to the SQLite database."""
|
||||
if not dbfile.exists():
|
||||
print(f"ERROR: changelog database not found: {dbfile}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return sqlite3.connect(str(dbfile))
|
||||
|
||||
|
||||
def normalize_version(name: str) -> str:
|
||||
"""Ensure version string starts with 'v'."""
|
||||
n = str(name).strip()
|
||||
return n if n.startswith("v") else f"v{n}"
|
||||
|
||||
|
||||
def parse_date(value) -> str:
|
||||
"""Parse various date formats and return formatted string."""
|
||||
if value is None:
|
||||
return "(Unknown date)"
|
||||
|
||||
# Handle the ISO format with timezone from the database
|
||||
s = str(value).strip()
|
||||
|
||||
# Try to parse the ISO format with timezone
|
||||
if "+" in s or "T" in s:
|
||||
# Remove timezone info and microseconds for simpler parsing
|
||||
s_clean = s.split("+")[0].split(".")[0]
|
||||
try:
|
||||
dt = datetime.strptime(s_clean, "%Y-%m-%d %H:%M:%S")
|
||||
return dt.strftime("%b %d, %Y").replace(" 0", " ")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Fallback formats
|
||||
fmts = [
|
||||
"%Y-%m-%d",
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%Y-%m-%dT%H:%M:%S",
|
||||
"%Y/%m/%d",
|
||||
"%m/%d/%Y",
|
||||
]
|
||||
|
||||
for fmt in fmts:
|
||||
try:
|
||||
dt = datetime.strptime(s, fmt)
|
||||
return dt.strftime("%b %d, %Y").replace(" 0", " ")
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# Return original if we can't parse it
|
||||
return f"({s})"
|
||||
|
||||
|
||||
def split_summary(text: str) -> List[str]:
|
||||
"""Split AI summary into individual lines/bullets."""
|
||||
if not text:
|
||||
return []
|
||||
|
||||
lines = []
|
||||
# Split by newlines first
|
||||
for line in text.split("\n"):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
# Remove markdown headers
|
||||
line = re.sub(r"^#+\s+", "", line)
|
||||
# Remove PR links and author info
|
||||
line = re.sub(
|
||||
r"^PR\s+\[#\d+\]\([^)]+\)\s+by\s+\[[^\]]+\]\([^)]+\):\s*", "", line
|
||||
)
|
||||
# Remove bullet points
|
||||
line = re.sub(r"^[-*•]\s+", "", line)
|
||||
if line:
|
||||
lines.append(line)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def is_feature_line(line: str) -> bool:
|
||||
"""Check if a line describes a feature/improvement (not a bug fix or CI/CD)."""
|
||||
line_lower = line.lower()
|
||||
|
||||
# Strong exclusions first
|
||||
if any(
|
||||
word in line_lower
|
||||
for word in ["chore:", "fix:", "docs:", "test:", "ci:", "build:", "refactor:"]
|
||||
):
|
||||
return False
|
||||
|
||||
if EXCLUDE_RE.search(line):
|
||||
return False
|
||||
|
||||
return bool(INCLUDE_RE.search(line))
|
||||
|
||||
|
||||
def extract_title_desc(line: str) -> Tuple[str, str]:
|
||||
"""Extract title and description from a feature line."""
|
||||
# Remove any markdown formatting
|
||||
line = re.sub(r"\*\*([^*]+)\*\*", r"\1", line)
|
||||
|
||||
# Look for colon separator first
|
||||
if ":" in line:
|
||||
parts = line.split(":", 1)
|
||||
if len(parts) == 2:
|
||||
title = parts[0].strip()
|
||||
desc = parts[1].strip()
|
||||
|
||||
# Clean up the title
|
||||
title = (
|
||||
title.replace("Introduce ", "")
|
||||
.replace("Enable ", "")
|
||||
.replace("Add ", "")
|
||||
)
|
||||
title = title.replace("Implement ", "").replace("Support ", "")
|
||||
|
||||
# Make title more concise
|
||||
if len(title) > 30:
|
||||
# Try to extract key words
|
||||
key_words = []
|
||||
for word in title.split():
|
||||
if word[0].isupper() or "-" in word or "_" in word:
|
||||
key_words.append(word)
|
||||
if key_words:
|
||||
title = " ".join(key_words[:3])
|
||||
|
||||
return (title, desc)
|
||||
|
||||
# Fallback: use first sentence as description
|
||||
sentences = re.split(r"[.!?]\s+", line)
|
||||
if sentences:
|
||||
desc = sentences[0].strip()
|
||||
# Extract a title from the description
|
||||
if "thinking" in desc.lower():
|
||||
return ("AI Reasoning", desc)
|
||||
elif "token" in desc.lower() and "context" in desc.lower():
|
||||
return ("Extended Context", desc)
|
||||
elif "curl" in desc.lower() or "install" in desc.lower():
|
||||
return ("Easy Setup", desc)
|
||||
elif "vendor" in desc.lower() or "model" in desc.lower():
|
||||
return ("Model Management", desc)
|
||||
elif "notification" in desc.lower():
|
||||
return ("Desktop Notifications", desc)
|
||||
elif "tts" in desc.lower() or "speech" in desc.lower():
|
||||
return ("Text-to-Speech", desc)
|
||||
elif "oauth" in desc.lower() or "auth" in desc.lower():
|
||||
return ("OAuth Auto-Auth", desc)
|
||||
elif "search" in desc.lower() and "web" in desc.lower():
|
||||
return ("Web Search", desc)
|
||||
else:
|
||||
# Generic title from first significant words
|
||||
words = desc.split()[:2]
|
||||
title = " ".join(words)
|
||||
return (title, desc)
|
||||
|
||||
return ("Feature", line)
|
||||
|
||||
|
||||
def pick_feature(ai_summary: str) -> Optional[Tuple[str, str]]:
|
||||
"""Pick the best feature line from the AI summary."""
|
||||
lines = split_summary(ai_summary)
|
||||
|
||||
# Look for the first feature line
|
||||
for line in lines:
|
||||
if is_feature_line(line):
|
||||
title, desc = extract_title_desc(line)
|
||||
# Clean up description - remove redundant info
|
||||
desc = desc[:200] if len(desc) > 200 else desc # Limit length
|
||||
return (title, desc)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_item(
|
||||
version: str, date_str: str, feature_title: str, feature_desc: str
|
||||
) -> str:
|
||||
"""Build a markdown list item for a release."""
|
||||
url = f"https://github.com/danielmiessler/fabric/releases/tag/{version}"
|
||||
return f"- [{version}]({url}) ({date_str}) — **{feature_title}**: {feature_desc}"
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
args = parse_args()
|
||||
dbfile = db_path(args)
|
||||
conn = connect(dbfile)
|
||||
cur = conn.cursor()
|
||||
|
||||
# Query the database
|
||||
cur.execute("SELECT name, date, ai_summary FROM versions ORDER BY date DESC")
|
||||
rows = cur.fetchall()
|
||||
|
||||
items = []
|
||||
for name, date, summary in rows:
|
||||
version = normalize_version(name)
|
||||
date_fmt = parse_date(date)
|
||||
feat = pick_feature(summary or "")
|
||||
|
||||
if not feat:
|
||||
continue
|
||||
|
||||
title, desc = feat
|
||||
items.append(build_item(version, date_fmt, title, desc))
|
||||
|
||||
if len(items) >= args.limit:
|
||||
break
|
||||
|
||||
conn.close()
|
||||
|
||||
# Output the markdown
|
||||
print("### Recent Major Features")
|
||||
print()
|
||||
for item in items:
|
||||
print(item)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user