Organized

This commit is contained in:
Jason Thistlethwaite
2026-10-17 20:50:35 -04:00
parent bd26c8894f
commit 9ee9eb3ec6
130 changed files with 24172 additions and 140 deletions
@@ -0,0 +1,218 @@
# 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
```text
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:
```text
<cache-dir>/<project>/<issue-id>.json
```
Example for the durable customer-service cache:
```text
/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:
```bash
./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:
```bash
./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
```bash
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
```
---
## Related commands people confuse with “search”
| 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.