Commit Graph

9 Commits

Author SHA1 Message Date
df0af4a5f8 Rewrite filter.Apply as single-pass with early-exit and pre-allocation
Replace the multi-pass where() chain in Apply() with a single loop that
evaluates all filter predicates per item and skips immediately on first
mismatch. This eliminates N intermediate slice allocations (one per
active filter) and avoids re-scanning the full dataset for each filter
dimension.

Key changes in filter.go:
- Single loop with continue-on-mismatch for BOGO, category, department,
  and query filters — combined categories check scans item.Categories
  once for both BOGO and category instead of twice
- Pre-allocate result slice capped at min(len(items), opts.Limit) to
  avoid grow-and-copy churn
- Fast-path bypass when no filters are active (just apply limit)
- Break early once limit is reached instead of filtering everything
  and truncating after
- Remove the now-unused where() helper function
- Add early-return fast paths to CleanText() for the common case where
  input contains no HTML entities or newlines, avoiding unnecessary
  html.UnescapeString and ReplaceAll calls

Test coverage:
- filter_equivalence_test.go (new): Reference implementation of the
  original multi-pass algorithm with 500 randomized test cases verifying
  behavioral equivalence. Includes allocation budget guardrail (<=80
  allocs/op for 1k items) to catch accidental regression to multi-pass.
  Benchmarks for new vs legacy reference on identical workload.
- filter_test.go: Benchmark comparisons for CleanText on plain text
  (fast path) vs escaped HTML (full path), new vs legacy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:11:38 -05:00
4310375dc9 Replace io.ReadAll + json.Unmarshal with streaming JSON decoder
Refactor the internal HTTP helper from get() returning raw bytes to
getAndDecode() that streams directly into the target struct via
json.NewDecoder. This eliminates the intermediate []byte allocation
from io.ReadAll on every API response.

The new decoder also validates that responses contain exactly one JSON
value by attempting a second Decode after the primary one — any content
beyond the first value (e.g., concatenated objects from a misbehaving
proxy) returns an error instead of silently discarding it.

Changes:
- api/client.go: Replace get() with getAndDecode(), update FetchStores
  and FetchSavings callers to use the new signature
- api/client_test.go: Add TestFetchSavings_TrailingJSONIsRejected and
  TestFetchStores_MalformedJSONReturnsDecodeError covering the new
  decoder error paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:11:24 -05:00
4f483c82e5 Add README and agent documentation
README covers installation, quick start, all commands and flags,
filtering behavior, JSON output schemas, structured error format,
exit codes, CLI input tolerance examples, and shell completion.

AGENTS.md provides a concise reference for AI agents interacting
with pubcli: accepted flexible input forms, canonical syntax
examples, and error behavior notes.
2026-02-22 21:42:15 -05:00
e299e16844 Add CLI input tolerance with fuzzy flag/command matching
Agent-friendly argument normalization that auto-corrects common
CLI syntax mistakes before cobra parses them:
- Single-dash long flags: -zip -> --zip
- Bare key=value: zip=33101 -> --zip=33101
- Typos via Levenshtein distance (max 2): --ziip -> --zip
- Command typos: categoriess -> categories
- Flag aliases: --zipcode, --dept, --search -> canonical names

Corrections emit a "note:" line to stderr showing what was rewritten.
Positional arguments for completion/help subcommands are preserved
(e.g., "completion zsh" is not rewritten). Integration tests verify
end-to-end behavior including tolerance notes, double-dash boundaries,
and help output for rewritten args.
2026-02-22 21:42:09 -05:00
cca04bc11c Add CLI commands with structured errors and robot-mode behavior
Three cobra commands forming the CLI surface:
- root: fetch and filter weekly deals (--store/--zip with BOGO,
  category, department, query, and limit filters)
- stores: list nearby Publix locations by ZIP code
- categories: show available deal categories with counts

Structured error system with typed error codes (INVALID_ARGS,
NOT_FOUND, UPSTREAM_ERROR, INTERNAL_ERROR) and semantic exit codes
(0-4). Errors render as human-readable text or JSON depending on
output mode. Robot-mode features: auto-JSON when stdout is not a TTY,
compact quick-start help when invoked with no args, and JSON error
payloads for programmatic consumers.
2026-02-22 21:42:01 -05:00
73d55bc30e Add terminal display layer with lipgloss styling and JSON output
Rendering layer for deals, stores, and categories with two output
modes: styled terminal text using lipgloss (color-coded BOGO tags,
price highlights, dim metadata, word-wrapped descriptions) and
compact JSON for programmatic consumption.

JSON output types (DealJSON, StoreJSON) normalize raw API fields —
cleaning HTML entities, dereferencing nullable pointers, and
computing derived fields like isBogo. Terminal output includes
contextual headers with item counts and date ranges. Tests verify
both rendering modes including HTML entity handling and nil safety.
2026-02-22 21:41:53 -05:00
12eb55f4b8 Add deal filtering engine with BOGO, category, department, and keyword support
Composable filter pipeline that processes SavingItem slices through
chained predicates: BOGO detection (category match), exact category
match, substring department match, and keyword search across title
and description fields. All text matching is case-insensitive.

Includes utility functions for HTML entity unescaping (CleanText),
nil-safe string pointer dereferencing (Deref), and case-insensitive
slice membership (ContainsIgnoreCase). An optional limit truncates
results after all filters are applied. Tests cover each filter in
isolation, combined filters, nil field safety, and the Categories
aggregation helper.
2026-02-22 21:41:46 -05:00
5efe7581ed Add Publix API client for savings and store location endpoints
HTTP client that wraps the Publix services API with two endpoints:
- /api/v4/savings — fetches weekly ad deals for a given store number
- /api/v1/storelocation — finds nearby stores by ZIP code

Includes request types (SavingsResponse, SavingItem, StoreResponse,
Store) mapping directly to the Publix JSON schema. The client sends
a PublixStore header for store-scoped requests and uses a 15-second
timeout. Tests use httptest servers to verify header propagation,
JSON decoding, and error handling for non-200 responses.
2026-02-22 21:41:39 -05:00
c4a8ddab4a Add Go module scaffold with dependencies and entrypoint
Initialize the publix-deals Go module (go 1.24.4) with core
dependencies: cobra for CLI structure, lipgloss for styled terminal
output, testify for assertions, and x/term for TTY detection.

The main entrypoint at cmd/pubcli/main.go delegates to cmd.Execute().
The .gitignore covers Go build artifacts, editor files, coverage
output, and jj VCS state.
2026-02-22 21:41:31 -05:00