Commit Graph

125 Commits

Author SHA1 Message Date
francwa 1d50b63af2 Merge branch 'dev/sprint-cleanup'
Multi-week sprint: ISO 639-2/B language unification, release parser
unification + data-driven tokenizer, removal of fossil services
(movies/tv_shows/subtitles), subtitle services split into a package,
MediaInfo split, test suite expansion (990 passing).

See CHANGELOG.md [Unreleased] for the user-facing summary.
2026-05-17 23:42:05 +02:00
francwa 891ba502a2 chore: apply pre-commit auto-fixes (trim trailing whitespace, EOF) 2026-05-17 23:41:54 +02:00
francwa e07c9ec77b chore: sprint cleanup — language unification, parser unification, fossils removal
Several weeks of work accumulated without being committed. Grouped here for
clarity; see CHANGELOG.md [Unreleased] for the user-facing summary.

Highlights
----------

P1 #2 — ISO 639-2/B canonical migration
- New Language VO + LanguageRegistry (alfred/domain/shared/knowledge/).
- iso_languages.yaml as single source of truth for language codes.
- SubtitleKnowledgeBase now delegates lookup to LanguageRegistry; subtitles.yaml
  only declares subtitle-specific tokens (vostfr, vf, vff, …).
- SubtitlePreferences default → ["fre", "eng"]; subtitle filenames written as
  {iso639_2b}.srt (legacy fr.srt still read via alias).
- Scanner: dropped _LANG_KEYWORDS / _SDH_TOKENS / _FORCED_TOKENS /
  SUBTITLE_EXTENSIONS hardcoded dicts.
- Fixed: 'hi' token no longer marks SDH (conflicted with Hindi alias).
- Added settings.min_movie_size_bytes (was a module constant).

P1 #3 — Release parser unification + data-driven tokenizer
- parse_release() is now the single source of truth for release-name parsing.
- alfred/knowledge/release/separators.yaml declares the token separators used
  by the tokenizer (., space, [, ], (, ), _). New conventions can be added
  without code changes.
- Tokenizer now splits on any configured separator instead of name.split('.').
  Releases like 'The Father (2020) [1080p] [WEBRip] [5.1] [YTS.MX]' parse via
  the direct path without sanitization fallback.
- Site-tag extraction always runs first; well-formedness only rejects truly
  forbidden chars.
- _parse_season_episode() extended with NxNN / NxNNxNN alt forms.
- Removed dead helpers: _sanitize, _normalize.

Domain cleanup
- Deleted fossil services with zero production callers:
    alfred/domain/movies/services.py
    alfred/domain/tv_shows/services.py
    alfred/domain/subtitles/services.py (replaced by subtitles/services/ package)
    alfred/domain/subtitles/repositories.py
- Split monolithic subtitle services into a package (identifier, matcher,
  placer, pattern_detector, utils) + dedicated knowledge/ package.
- MediaInfo split into dedicated package (alfred/domain/shared/media/:
  audio, video, subtitle, info, matching).

Persistence cleanup
- Removed dead JSON repositories (movie/subtitle/tvshow_repository.py).

Tests
- Major expansion of the test suite organized to mirror the source tree.
- Removed obsolete *_edge_cases test files superseded by structured tests.
- Suite: 990 passed, 8 skipped.

Misc
- .gitignore: exclude env_backup/ and *.bak.
- Adjustments across agent/llm, app.py, application/filesystem, and
  infrastructure/filesystem to align with the new domain layout.
2026-05-17 23:38:00 +02:00
francwa ba6f016d49 feat: generic MetadataStore + read_release_metadata + query_library
- Extract MetadataStore from SubtitleMetadataStore (alfred/infrastructure/metadata/).
  Generic load/save + typed update helpers (update_parse, update_probe, update_tmdb)
  for the per-release .alfred/metadata.yaml.
- SubtitleMetadataStore becomes a thin facade — owns subtitle_history shape,
  delegates I/O to MetadataStore.
- Agent._execute_tool_call auto-persists successful analyze_release / probe_media /
  find_media_imdb_id results to the release's .alfred file. find_media_imdb_id
  follows release_focus when it has no path argument.
- New tools:
  · read_release_metadata(release_path) — cacheable, key=release_path.
    Returns the .alfred content or has_metadata=false.
  · query_library(name) — substring scan across configured library roots.
- Both new tools added to CORE_TOOLS (always visible).
2026-05-15 11:02:25 +02:00
francwa 3c7c6695f2 feat(memory): Phase 1 — STM ToolResultsCache + ReleaseFocus + cache flag in YAML specs
Adds two STM components and a transparent cache hook in the agent loop so
read-only tools don't re-do work the agent already did in this session.

New STM components:
  - ToolResultsCache  — {tool_name: {key: result}}, session-scoped.
    to_dict() exposes only the key inventory (not payloads) to keep the
    prompt cheap.
  - ReleaseFocus      — current_release_path + working_set list, updated
    automatically when a path-keyed inspector runs.

YAML spec layer:
  - New optional 'cache: { key: <param_name> }' block in ToolSpec.
  - Validated at load time: cache.key must be a declared parameter.
  - Surfaced on Tool dataclass as cache_key: str | None.

Agent._execute_tool_call:
  - Pre-exec cache lookup; hit short-circuits and adds _from_cache=true.
  - Post-exec: stores successful results, updates release_focus for
    path-keyed tools, refreshes episodic.last_search_results when
    find_torrent's hit served the response (so get_torrent_by_index
    keeps pointing at the right list).

Cacheable tools (5): analyze_release, probe_media, list_folder,
find_media_imdb_id, find_torrent.
2026-05-15 10:44:14 +02:00
francwa 2db3198ef2 feat(agent): migrate all remaining tools to YAML specs (21/21 covered)
Adds YAML specs for the 14 tools that were still description-from-docstring:

  filesystem:
    - set_path_for_folder, list_folder, analyze_release, probe_media,
      move_media, manage_subtitles, create_seed_links, learn
  api:
    - find_media_imdb_id, find_torrent, get_torrent_by_index,
      add_torrent_to_qbittorrent, add_torrent_by_index
  language:
    - set_language

Each spec follows the established shape (summary / description /
when_to_use / when_not_to_use / next_steps / parameters with
why_needed + example / returns) and the Python function docstring is
slimmed to a one-line pointer.

Registry now reports: 21 tools, 21 with YAML spec, 0 doc-only.
2026-05-14 21:18:43 +02:00
francwa 23a9dd7990 refactor(memory): rename workflow.target -> params, type -> name
The Workflow STM component stored an active workflow as
{type, target, stage, started_at}. Now that start_workflow takes a
workflow_name and a params dict, those keys match what they actually
hold:

  type   -> name    (the YAML workflow name, e.g. media.organize_media)
  target -> params  (the dict passed to start_workflow)

ShortTermMemory.start_workflow parameters renamed accordingly. All
consumers (prompt builder workflow scope + STM context, start/end
workflow tools) updated.
2026-05-14 21:11:23 +02:00
francwa 74a52ba6a3 feat(agent): workflow-scoped tool catalog + start/end_workflow meta-tools
Introduce a scope-aware agent so the LLM never sees the full 21-tool
catalog at once. The system prompt now describes either:
  - idle mode: core noyau (5 tools: set_language, set_path_for_folder,
    list_folder, start_workflow, end_workflow) + a list of available
    workflows with their goals;
  - active mode: the noyau plus the tools declared by the active
    workflow's YAML, with the step plan inlined into the prompt.

Pieces:
- alfred/agent/tools/workflow.py: start_workflow / end_workflow tools
  (with YAML specs under tools/specs/) that drive memory.stm.workflow.
- alfred/agent/prompt.py: CORE_TOOLS constant, visible_tool_names(),
  filtered build_tools_spec() / _format_tools_description(), and a new
  _format_workflow_scope() section in the system prompt.
- alfred/agent/agent.py: WorkflowLoader wired into Agent, defensive
  out-of-scope check in _execute_tool_call.
- alfred/agent/registry.py: registers the two new meta-tools (21 total,
  7 with YAML spec).
- workflows/media.organize_media.yaml: tools/steps list refreshed to
  match the current resolver split (analyze_release, probe_media,
  resolve_*_destination, move_to_destination).
2026-05-14 21:07:36 +02:00
francwa 97adfbda45 refactor(workflows): adopt media.* naming convention
Rename workflow files and their 'name' field with a 'media.' domain
prefix to anticipate future multi-domain expansion (mail.*, calendar.*, ...).

- organize_media -> media.organize_media
- manage_subtitles -> media.manage_subtitles

WorkflowLoader picks them up unchanged (uses data['name']).
2026-05-14 20:55:35 +02:00
francwa 239fce9e4e chore(agent): remove dead parameters.py
The ParameterSchema / REQUIRED_PARAMETERS / get_missing_required_parameters
machinery in alfred/agent/parameters.py was used in early prototypes for
the prompt-required-params check but has been unwired from production for
several refactors. The new YAML tool-spec layer (alfred/agent/tools/specs/)
covers the same need (rich, LLM-facing parameter descriptions) without
the parallel registration plumbing.

Tests in tests/test_config_edge_cases.py still reference the deleted
module — left untouched per the project policy of treating test sync
as a dedicated end-of-week task.
2026-05-14 18:06:34 +02:00
francwa 99c95af64e feat(agent): YAML tool specs as the LLM-facing semantic layer
Introduce a first-class semantic layer for tool descriptions, separated
from Python signatures (which stay the source of truth for types and
required-ness).

New
- alfred/agent/tools/spec.py — ToolSpec / ParameterSpec / ReturnsSpec
  dataclasses with strict YAML validation (ToolSpecError on malformed
  or inconsistent specs). compile_description() builds the rich text
  passed to the LLM as Tool.description, with sections for summary,
  description, when_to_use, when_not_to_use, next_steps, and returns.
  compile_parameter_description() injects the 'why_needed' field next
  to each parameter so the LLM sees the *intent* of each argument.
- alfred/agent/tools/spec_loader.py — discovers tools/specs/*.yaml,
  enforces filename ↔ spec.name match, rejects duplicates.
- alfred/agent/tools/specs/ — one YAML per tool:
    * resolve_season_destination.yaml
    * resolve_episode_destination.yaml
    * resolve_movie_destination.yaml
    * resolve_series_destination.yaml
    * move_to_destination.yaml

Refactor
- alfred/agent/registry.py
    * _create_tool_from_function now takes an optional ToolSpec.
      When provided, the long description + per-parameter descriptions
      come from the spec; types and required-ness still come from the
      Python signature.
    * Cross-validates spec.parameters against the function signature —
      crashes on missing or extra entries.
    * make_tools() loads all specs at startup and hands the right one
      to each tool. Tools without a spec fall back to the old
      docstring-only behaviour, so the 14 not-yet-migrated tools keep
      working unchanged.
    * Adds 'array' and 'object' to the Python→JSON type mapping and
      handles Optional[X] / X | None annotations.

- alfred/agent/tools/filesystem.py
    * Drops the '_tool' suffix on the 4 resolve_* wrappers (option 1:
      alias the use-case imports as _resolve_*). Tool names exposed to
      the LLM now match the underlying use case verbatim.
    * Wrapper docstrings shrink to a one-liner pointing to the YAML
      spec — no more duplicated when_to_use/Args/Returns in Python.

Verified
- make_tools() loads 19 tools (5 with YAML spec, 14 doc-only).
- Compiled descriptions render cleanly with all sections.
2026-05-14 18:06:27 +02:00
francwa b5025bb5f8 refactor(resolve_destination): factor shared series-folder resolution + DTO base
- New _Clarification sentinel and _resolve_series_folder() helper —
  the three TV use cases now share one matching/clarification path
  instead of triplicating the same if/elif/else block.
- New _ResolvedDestinationBase carrying status/question/options/error/
  message plus a _base_dict() helper; the four concrete DTOs only
  declare their own ok-state fields and a slim to_dict().
- No behaviour change: same outputs for ok/needs_clarification/error
  cases (verified by import + DTO smoke tests).
2026-05-14 16:09:33 +02:00
francwa e45465d52d feat: split resolve_destination, persona-driven prompts, qBittorrent relocation
Destination resolution
- Replace the single ResolveDestinationUseCase with four dedicated
  functions, one per release type:
    resolve_season_destination    (pack season, folder move)
    resolve_episode_destination   (single episode, file move)
    resolve_movie_destination     (movie, file move)
    resolve_series_destination    (multi-season pack, folder move)
- Each returns a dedicated DTO carrying only the fields relevant to
  that release type — no more polymorphic ResolvedDestination with
  half the fields unused depending on the case.
- Looser series folder matching: exact computed-name match is reused
  silently; any deviation (different group, multiple candidates) now
  prompts the user with all options including the computed name.

Agent tools
- Four new tools wrapping the use cases above; old resolve_destination
  removed from the registry.
- New move_to_destination tool: create_folder + move, chained — used
  after a resolve_* call to perform the actual relocation.
- Low-level filesystem_operations module (create_folder, move via mv)
  for instant same-FS renames (ZFS).

Prompt & persona
- New PromptBuilder (alfred/agent/prompt.py) replacing prompts.py:
  identity + personality block, situational expressions, memory
  schema, episodic/STM/config context, tool catalogue.
- Per-user expression system: knowledge/users/common.yaml +
  {username}.yaml are merged at runtime; one phrase per situation
  (greeting/success/error/...) is sampled into the system prompt.

qBittorrent integration
- Credentials now come from settings (qbittorrent_url/username/password)
  instead of hardcoded defaults.
- New client methods: find_by_name, set_location, recheck — the trio
  needed to update a torrent's save path and re-verify after a move.
- Host→container path translation settings (qbittorrent_host_path /
  qbittorrent_container_path) for docker-mounted setups.

Subtitles
- Identifier: strip parenthesized qualifiers (simplified, brazil…) at
  tokenization; new _tokenize_suffix used for the episode_subfolder
  pattern so episode-stem tokens no longer pollute language detection.
- Placer: extract _build_dest_name so it can be reused by the new
  dry_run path in ManageSubtitlesUseCase.
- Knowledge: add yue, ell, ind, msa, rus, vie, heb, tam, tel, tha,
  hin, ukr; add 'fre' to fra; add 'simplified'/'traditional' to zho.

Misc
- LTM workspace: add 'trash' folder slot.
- Default LLM provider switched to deepseek.
- testing/debug_release.py: CLI to parse a release, hit TMDB, and
  dry-run the destination resolution end-to-end.
2026-05-14 05:01:59 +02:00
francwa 1723b9fa53 feat: release parser, media type detection, ffprobe integration
Replace the old domain/media release parser with a full rewrite under
domain/release/:
- ParsedRelease with media_type ("movie" | "tv_show" | "tv_complete" |
  "documentary" | "concert" | "other" | "unknown"), site_tag, parse_path,
  languages, audio_codec, audio_channels, bit_depth, hdr_format, edition
- Well-formedness check + sanitize pipeline (_is_well_formed, _sanitize,
  _strip_site_tag) before token-level parsing
- Multi-token sequence matching for audio (DTS-HD.MA, TrueHD.Atmos…),
  HDR (DV.HDR10…) and editions (DIRECTORS.CUT…)
- Knowledge YAML: file_extensions, release_format, languages, audio,
  video, editions, sites/c411

New infrastructure:
- ffprobe.py — single-pass probe returning MediaInfo (video, audio
  tracks, subtitle tracks)
- find_video.py — locate first video file in a release folder

New application helpers:
- detect_media_type — filesystem-based type refinement
- enrich_from_probe — fill missing ParsedRelease fields from MediaInfo

New agent tools:
- analyze_release — parse + detect type + ffprobe in one call
- probe_media — standalone ffprobe for a specific file

New domain value object:
- MediaInfo + AudioTrack + SubtitleTrack (domain/shared/media_info.py)

Testing CLIs:
- recognize_folders_in_downloads.py — full pipeline with colored output
- probe_video.py — display MediaInfo for a video file
2026-05-12 16:14:20 +02:00
francwa 249c5de76a feat: major architectural refactor
- Refactor memory system (episodic/STM/LTM with components)
- Implement complete subtitle domain (scanner, matcher, placer)
- Add YAML workflow infrastructure
- Externalize knowledge base (patterns, release groups)
- Add comprehensive testing suite
- Create manual testing CLIs
2026-05-11 21:55:06 +02:00
francwa 62b5d0b998 Settings + fix startup 2026-04-30 12:41:42 +02:00
francwa 610dee365c mess: UV + settings KISS + fixes 2026-04-24 18:10:55 +02:00
francwa 58408d0dbe fix: fixed vectordb loneliness 2026-01-06 04:39:42 +01:00
francwa 2f1ac3c758 infra: simplified mongodb healthcheck 2026-01-06 04:36:52 +01:00
francwa d3b69f7459 feat: enabled logging for alfred(-core) 2026-01-06 04:33:59 +01:00
francwa 50c8204fa0 infra: added granular logging configuration for mongodb 2026-01-06 02:50:49 +01:00
francwa 507fe0f40e chore: updated dependencies 2026-01-06 02:41:18 +01:00
francwa b7b40eada1 fix: set proper database name for mongodb 2026-01-06 02:33:35 +01:00
francwa 9765386405 feat: named docker image to avoid docker picking the wrong one 2026-01-06 02:19:00 +01:00
francwa aa89a3fb00 doc: updated README.md
Renovate Bot / renovate (push) Successful in 20s
2026-01-04 13:30:54 +01:00
francwa 64aeb5fc80 chore: removed deprecated cli.py file 2026-01-04 13:29:38 +01:00
francwa 9540520dc4 feat: updated default start mode from core to full 2026-01-03 05:49:51 +01:00
francwa 300ed387f5 fix: fixed build vars generation not being called 2026-01-01 06:00:55 +01:00
francwa dea81de5b5 fix: updated CI workflow and added .env.make generation for CI 2026-01-01 05:33:39 +01:00
francwa 01a00a12af chore: bump version 0.1.6 → 0.1.7
CI/CD Awesome Pipeline / Test (push) Successful in 5m54s
CI/CD Awesome Pipeline / Build & Push to Registry (push) Failing after 1m9s
v0.1.7
2026-01-01 05:04:37 +01:00
francwa 504d0162bb infra: added proper settings handling & orchestration and app bootstrap (.env)
Reviewed-on: #18
2026-01-01 03:55:35 +00:00
francwa cda23d074f feat: added current alfred version from pyproject.py to healthcheck 2026-01-01 04:51:45 +01:00
francwa 0357108077 infra: added orchestration and app bootstrap (.env) 2026-01-01 04:48:32 +01:00
francwa ab1df3dd0f fix: forgot to lint/format 2026-01-01 04:48:32 +01:00
francwa c50091f6bf feat: added proper settings handling 2026-01-01 04:48:32 +01:00
francwa 8b406370f1 chore: updated some dependencies 2026-01-01 03:08:22 +01:00
francwa c56bf2b92c feat: added Claude.AI to available providers 2025-12-29 02:13:11 +01:00
francwa b1507db4d0 fix: fixed test image not being built before running tests
Renovate Bot / renovate (push) Successful in 1m26s
2025-12-28 13:24:03 +01:00
francwa 3074962314 infra: proper librechat integration & improved configuration handling
Reviewed-on: #11
2025-12-28 11:56:53 +00:00
francwa 84799879bb fix: added data dir to .dockerignore 2025-12-28 12:02:44 +01:00
francwa 1052c1b619 infra: added modules version to .env.example 2025-12-28 06:07:34 +01:00
francwa 9958b8e848 feat: created placeholders for various API models 2025-12-28 06:06:28 +01:00
francwa b15161dad7 fix: provided OpenAI API key and fixed docker-compose configuration to enable RAG service 2025-12-28 06:04:08 +01:00
francwa 52f025ae32 chore: ran linter & formatter again 2025-12-27 20:07:48 +01:00
francwa 2cfe7a035b infra: moved makefile logic to python and added .env setup 2025-12-27 19:51:40 +01:00
francwa 2441c2dc29 infra: rewrote docker-compose for proper integration of librechat 2025-12-27 19:49:43 +01:00
francwa 261a1f3918 fix: fixed real data directory being used in tests 2025-12-27 19:48:13 +01:00
francwa 253903a1e5 infra: made pyproject the SSOT 2025-12-27 19:46:27 +01:00
francwa 20a113e335 infra: updated .env.example 2025-12-27 19:43:31 +01:00
francwa fed83e7d79 chore: bumped fastapi version 2025-12-27 19:42:16 +01:00