Files
redmine/docs/2026-07-14_localsearch_before_sanity.md
T
Jason Thistlethwaite 9ee9eb3ec6 Organized
2026-10-17 20:50:35 -04:00

7.3 KiB
Raw Blame History

Local search in redmine_tickets.py (pre-sanity snapshot)

Date: 2026-07-14
Status: documentation of current behavior before any search sanity/cleanup pass
Script: /home/jason/projects/redmine/redmine_tickets.py
Typical cache: /home/jason/projects/redmine/cache/redmine_tickets

This note captures what local search does and does not do as of 2026-07-14, after remote sync filters and run-log work landed. It is intentionally frank: docs had been thin; this is the baseline.


Mental model

sync   = talk to Redmine → update local JSON cache
search = filter/list what is already cached

search is a cache browser / structured filterer, not:

  • a live Redmine issue query tool
  • full-text corpus search
  • the semantic Redmine search path (that lives elsewhere)

If a ticket is not already under the cache directory for the selected project, search cannot see it.


Where data comes from

Cached issues live as one JSON file per ticket:

<cache-dir>/<project>/<issue-id>.json

Example for the durable customer-service cache:

/home/jason/projects/redmine/cache/redmine_tickets/customer-service/*.json
/home/jason/projects/redmine/cache/redmine_tickets/manifest.json

search loads via TicketCache.iter_cached_issues() (reads every *.json in the project subdir). It does not call Redmine and does not require REDMINE_API_KEY.


CLI surface (current)

Parent options still apply:

  • --base-url (unused by search, but accepted on the parent parser)
  • --project
  • --cache-dir

Subcommand:

./redmine_tickets.py \
  --project customer-service \
  --cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
  search [filters...] [output...]

Filters (all local, AND’d)

Flag Matches on Notes
--updated-since issue updated_on ≥ value via parse_dt (absolute date/datetime)
--updated-before issue updated_on ≤ value same
--status status name case-insensitive exact match (Closed, New, …) — not status_id
--category category name case-insensitive exact match

Implementation: filter_issues() in redmine_tickets.py.

Order + limit

Flag Behavior
--order newest-updated-first default
--order oldest-updated-first
--order id-asc / id-desc
--order as-is filesystem glob order from iter_cached_issues
--max-tickets N after filter + sort, keep first N

Implementation: select_issues().

Output

Mode What you get
default table #id, updated_on, status name, category name, subject (via print_issue_rows)
--json full cached issue payloads as one JSON array
--jsonl one full issue JSON object per line (good for piping)

Human table is summary-only. Full subject/body/journals/helpdesk are only in --json / --jsonl.


What it allows (summary)

  1. Scope one project + one cache root.
  2. Filter cached issues by updated_on window, status name, category name.
  3. Sort and cap results.
  4. Emit table, JSON, or JSONL.
  5. Run offline against cache (no Redmine, no API key).

What it does not allow (current gaps)

This is the important half of the snapshot:

  • No free-text search (subject / description / journals / from-address)
  • No sender / email / contact filters
  • No tracker, assignee, priority, tags
  • No status_id / open / closed shorthand on search (only status name)
  • No remote/API filters on search
    (--remote-updated-on, --remote-updated-since, --remote-status-id are sync-only)
  • No relative time helpers like 30d / 2 days ago on local search flags
    (those helpers exist for remote sync convenience flags; local --updated-since wants a real parseable date/datetime)
  • No boolean logic (AND/OR/NOT nesting)
  • No query language comparable to Redmine UI filters or rg
  • No ranking / relevance
  • No “search then sync missing ones”
  • No pagination UI beyond --max-tickets
  • No writes (search never mutates cache or Redmine)

Practical content search today

Pipe full payloads and use external tools:

./redmine_tickets.py \
  --project customer-service \
  --cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
  search --jsonl | rg -i 'gaylord|rate sheet'

Or inspect per-ticket files under the project cache dir directly.


Examples that work

cd /home/jason/projects/redmine

# Everything currently cached for customer-service (table)
./redmine_tickets.py \
  --project customer-service \
  --cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
  search

# Cached tickets updated since a date, newest first, cap 50
./redmine_tickets.py \
  --project customer-service \
  --cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
  search \
  --updated-since 2026-06-14 \
  --max-tickets 50

# Only closed, as full JSONL for downstream tools
./redmine_tickets.py \
  --project customer-service \
  --cache-dir /home/jason/projects/redmine/cache/redmine_tickets \
  search \
  --status Closed \
  --jsonl

Command Relation to local search
sync fills the cache that search reads; supports server-side status_id + updated_on
spam-check walks cache, scores spam; not general search
known-spam-scan profile match over cache
decompose one-ticket analysis, cache-first
semantic index / external search separate stack; not this subcommand

Remote vs local filter naming (context for sanity later)

As of the 2026-07-14 sync work:

Side Flags (examples) Used by
Remote (API) --status-id / --remote-status-id, --remote-updated-on, --remote-updated-since, --remote-updated-before sync list call only
Local (post) --updated-since, --updated-before, --status, --category sync post-filter and search

Help text for the shared local flags still says things like “applied after API list,” which is accurate for sync but slightly misleading when those same flags appear under search (where there is no API list). A future sanity pass may want search-specific wording.


Implementation pointers

Piece Location
Filter logic filter_issues()
Sort / max select_issues()
Cache read TicketCache.iter_cached_issues()
Table output print_issue_rows()
CLI wiring build_parser() → search subparser; main() branch args.command == "search"
Shared filter args add_common_filters()

Bottom line (baseline for sanity)

Does: local structured filter/list over already-synced ticket JSON — date window, status name, category name, sort, limit, table/json/jsonl.

Doesn’t: live Redmine query, free-text, contact/sender, remote filters, relative-date helpers on local flags, or advanced query syntax.

Any “make search sane” work after this file should treat the above as the pre-change contract and call out intentional expansions (e.g. --from, --subject-contains, --text, relative dates on local filters) separately.