abbu 0.1.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/docs/ABBU.md CHANGED
@@ -9,9 +9,34 @@
9
9
  They are **not** a single file format — they are a macOS "package" (a directory bundle that Finder
10
10
  presents as a single file). This means you can inspect the contents with `ls` or `open -a Finder`.
11
11
 
12
+ ## Evidence Standard
13
+
14
+ Apple does not publish a stable specification for every internal Contacts
15
+ archive schema represented by `.abbu` bundles. This document therefore
16
+ distinguishes observed repository-fixture behavior from documented Apple APIs
17
+ and from hypotheses that still require verification.
18
+
19
+ Never infer Apple Contacts storage semantics merely from Core Data table or
20
+ column names. Require observed fixture evidence, Apple documentation where
21
+ available, or reproducible verification, and record consequential discoveries
22
+ in this document.
23
+
24
+ For each new schema, relationship, source layout, image convention, or version
25
+ variation:
26
+
27
+ 1. record the macOS or Contacts version when known;
28
+ 2. identify the synthetic fixture, SQLite query, plist key path, file evidence,
29
+ or Apple documentation supporting the conclusion;
30
+ 3. add a deterministic regression fixture and spec; and
31
+ 4. label unresolved interpretations as hypotheses rather than format guarantees.
32
+
33
+ Real address-book exports contain sensitive personal data. Use them only for
34
+ local verification, sanitize the observed behavior into deterministic synthetic
35
+ fixtures, and never commit the original contacts, photos, or account identifiers.
36
+
12
37
  ## Structure
13
38
 
14
- Typical contents of a `.abbu` bundle:
39
+ The supported synthetic fixtures and observed exports use layouts such as:
15
40
 
16
41
  ```text
17
42
  Contacts.abbu/
@@ -36,12 +61,16 @@ Contacts.abbu/
36
61
 
37
62
  ### 1. SQLite (modern macOS)
38
63
 
39
- Newer macOS versions store the address book in a single SQLite database:
64
+ Supported modern fixtures contain one or more SQLite databases named:
40
65
 
41
66
  ```
42
67
  AddressBook-v22.abcddb
43
68
  ```
44
69
 
70
+ The following tables and mappings are exercised by the repository's generated
71
+ SQLite fixture and parser specs. Their names alone are not evidence that the
72
+ same semantics apply to every macOS version.
73
+
45
74
  Key tables:
46
75
 
47
76
  | Table | Purpose |
@@ -77,12 +106,194 @@ Notable columns in `ZABCDRECORD`:
77
106
  | `ZPRONOUNS` | Pronouns |
78
107
  | `ZRINGTONE` | Ringtone |
79
108
  | `ZTEXTTONE` | Text tone |
109
+ | `ZCREATIONDATE` | Optional record creation timestamp |
110
+ | `ZMODIFICATIONDATE` | Optional record modification timestamp |
111
+
112
+ ### Timestamp and source provenance
113
+
114
+ Observed Contacts databases may include `ZCREATIONDATE` and `ZMODIFICATIONDATE` on
115
+ `ZABCDRECORD`. ABBU interprets numeric values in those columns as Apple absolute time:
116
+ seconds since 2001-01-01 00:00:00 UTC. The columns are optional because exported schemas
117
+ vary across macOS releases and account providers; when either column is absent or invalid,
118
+ the corresponding `Contact` value is `nil`.
119
+
120
+ These values describe timestamps stored on the record. They must not be interpreted as
121
+ proof of a user-initiated creation or edit, because syncing and migration can also affect
122
+ them.
123
+
124
+ Every parsed contact includes source provenance with the absolute source path, its path
125
+ relative to the `.abbu` root, and whether it came from the root bundle or a database under
126
+ `Sources/<identifier>/`. Legacy plist contacts receive the same file-level provenance.
127
+
128
+ ### Opt-in live Contacts stores
129
+
130
+ The CLI uses `--live` only for auto-discovery and `--live-path PATH` for an explicit
131
+ store. These forms are mutually exclusive and accept no positional archive/path arguments.
132
+ Option ordering does not change input selection. Synthetic WAL-mode and concurrent-writer
133
+ validation remains a follow-up; read-only handles do not establish snapshot consistency
134
+ across an actively changing Contacts store.
135
+
136
+ `Abbu.open_live` and the CLI's live modes can read an AddressBook directory
137
+ without first exporting an `.abbu` archive. This mode is deliberately separate from
138
+ `Abbu.open`: archive validation and plist fallback do not apply to a live store.
139
+
140
+ When no path is supplied on macOS, ABBU checks the observed per-user location at
141
+ `~/Library/Application Support/AddressBook`. Callers may instead provide a directory,
142
+ which keeps automation and tests independent of the host platform and user account.
143
+ ABBU discovers databases directly under that directory and one level below
144
+ `Sources/<identifier>/`, preserving the same root/source provenance used for archives.
145
+ Only files matching the observed `AddressBook-v*.abcddb` shape are candidates; this
146
+ filename pattern is discovery evidence, not a guarantee of stable Apple semantics.
147
+
148
+ Every live-store database is opened using SQLite's read-only mode. ABBU has no live-store
149
+ write API and never creates, updates, or deletes Contacts data. macOS privacy controls may
150
+ deny access even when the path exists. In that case ABBU raises
151
+ `Abbu::LiveStore::PermissionError` with instructions to grant the calling terminal or
152
+ application Full Disk Access in **System Settings → Privacy & Security → Full Disk
153
+ Access**. Missing directories and databases raise `Abbu::LiveStore::NotFoundError`, and
154
+ automatic discovery without a caller-supplied path raises
155
+ `Abbu::LiveStore::UnsupportedPlatformError` outside macOS.
156
+
157
+ The repository verifies live-store behavior only with deterministic synthetic SQLite
158
+ fixtures. It does not inspect or commit a developer's real Contacts store.
159
+ ### Provenance-aware identity evidence
160
+
161
+ ABBU treats deduplication as a suggestion boundary rather than proof that two records are
162
+ the same person. `Utils::Deduplicator#matches` compares normalized email, phone, name, and
163
+ organization signals while returning both original contacts, both source records, raw
164
+ evidence, normalized comparison values, confidence, and ambiguity status.
165
+
166
+ Email comparison trims surrounding whitespace and applies Unicode-aware case folding.
167
+ Names and organizations use Unicode NFKC normalization, case folding, and whitespace or
168
+ punctuation normalization without transliterating distinct characters. Explicit `+` and
169
+ `00` phone forms are compared as international numbers. The trimmed raw value must start
170
+ with a literal ASCII `+` or contiguous `00`; punctuation removal never establishes an
171
+ international prefix. For example, `(001) 512-555-0100` remains national-format evidence.
172
+ National-format numbers remain
173
+ source-local evidence because ABBU has no country or numbering-plan evidence with which
174
+ to infer a global identity.
175
+
176
+ SQLite primary keys, source identifiers, private Apple link identifiers, and image stems
177
+ are not treated as global contact identifiers. The repository fixtures do not establish
178
+ such semantics. Competing candidates and weak name/organization or source-local phone
179
+ matches remain ambiguous, and no contact is merged unless the caller supplies an explicit
180
+ merge policy.
181
+
182
+ ### Image resolution and extraction
183
+
184
+ The synthetic SQLite fixture demonstrates a `ZIMAGEURI` value whose stem matches a file
185
+ under an `Images/` directory. Resolution covers the root bundle and nested
186
+ `Sources/<identifier>/Images/` directories. When duplicate stems exist, ABBU uses the
187
+ contact's database provenance to select only an image beside that database; it does not
188
+ guess when the available evidence remains ambiguous.
189
+
190
+ `Archive#extract_images(output_dir)` and the CLI `--extract-images DIR` copy resolved
191
+ images without changing `Contact#image_uri`, `Contact#image_path`, or `Contact#source`.
192
+ Exported filenames combine a sanitized contact name, the original image identifier, and
193
+ a stable provenance digest. Path separators, control characters, and reserved filename
194
+ characters cannot create subdirectories or traverse outside the selected output directory.
195
+
196
+ The selected output directory (including symlinked parents) is resolved to its canonical
197
+ directory before copying; callers must control that directory and prevent concurrent
198
+ directory replacement. Each image is created exclusively with owner-only permissions.
199
+ Existing destinations are never overwritten, including regular files, hard links, and
200
+ symlinks (even dangling ones). These collisions produce a `destination_exists` extraction
201
+ diagnostic and no successful file record; other images continue. Repeating extraction
202
+ into the same directory therefore reports collisions instead of replacing earlier output.
203
+ Directory creation/resolution failures raise filesystem errors before extraction begins.
204
+
205
+ Extraction diagnostics are a separate API from `archive.diagnostics`: they may contain
206
+ contact names, raw image identifiers, source paths, and filesystem error details. Treat
207
+ them and the CLI's image warnings as sensitive contact data, not safe-to-publish logs.
208
+
209
+ ABBU recognizes JPEG, PNG, GIF, and common HEIF/HEIC-compatible brands from file
210
+ signatures and chooses the exported extension from those bytes rather than the source
211
+ extension. Unknown content is reported as a diagnostic instead of being relabeled. HEIC
212
+ data is copied unchanged; ABBU does not transcode it.
213
+
214
+ No repository fixture currently demonstrates a reliable Apple thumbnail-versus-full-size
215
+ naming or selection rule. ABBU therefore exports the image resolved by the observed
216
+ `ZIMAGEURI` relationship and does not infer size semantics from filenames or directories.
217
+
218
+ ### Recovery and diagnostics
219
+
220
+ By default, ABBU recovers from malformed individual plist records, missing
221
+ optional SQLite relationship tables, and unresolved image references. Each
222
+ recovery appends an `Abbu::Diagnostic` to `archive.diagnostics` with a category,
223
+ parser, source path, non-PII context, and a stable message. Required contact
224
+ schema failures still raise because no evidence-backed contact record can be
225
+ recovered safely.
226
+
227
+ An absent optional SQLite table produces one diagnostic per database and table,
228
+ regardless of contact count. ABBU does not place record identifiers or raw image
229
+ references in these schema- and image-level diagnostic contexts.
230
+
231
+ Pass `strict: true` to `Abbu.open` or `--strict` to the CLI to raise
232
+ `Abbu::ParseError` on the first recoverable condition. The CLI prints a
233
+ diagnostic summary to standard error so exported data on standard output remains
234
+ pipeable.
235
+
236
+ ### Schema diagnostics
237
+
238
+ `Archive#schema_report` and `abbu Contacts.abbu --schema` inspect every discovered
239
+ SQLite database and return deterministic schema metadata. Reports identify recognized
240
+ and unrecognized tables and columns, recognized items that are absent, declared SQLite
241
+ types, primary-key and nullability metadata, source provenance, and exact owner/contact-
242
+ style column names that may represent contact links.
243
+
244
+ These reports are research evidence, not parser mappings. In particular, a
245
+ `contact_link_candidate` flag records only an exact column-name shape such as `ZOWNER`,
246
+ `ZCONTACT`, or `Z_CONTACT`; it does not claim a foreign-key target or assign Apple
247
+ Contacts semantics. Unknown tables and columns must be reproduced in a sanitized fixture
248
+ or supported by documentation before ABBU uses them to populate contacts.
249
+
250
+ Missing recognized tables and columns remain visible as diagnostic observations. The
251
+ parser tolerates absent established email, phone, and postal-address tables by returning
252
+ empty collections, while the schema report preserves the absence for compatibility
253
+ research. If one of those tables exists but lacks an expected column, parsing raises the
254
+ SQLite schema error instead of silently treating the contact as having no corresponding
255
+ data. The core `ZABCDRECORD` table remains required for contact parsing.
256
+
257
+ ### Labeled values
258
+
259
+ The synthetic SQLite and plist fixtures include both custom labels and Apple's
260
+ observed standard-label wrapper, such as `_$!<Work>!$_`. ABBU exposes the
261
+ human-facing value as `label` (`Work`) and preserves the exact stored value as
262
+ `raw_label`. Custom, blank, malformed, Unicode, and already-normalized labels
263
+ are not otherwise rewritten. Direct plist keys such as `Birthday` have no
264
+ stored label, so their normalized label is derived from the key and
265
+ `raw_label` is `nil`.
266
+
267
+ Normalization applies to email addresses, phone numbers, postal addresses,
268
+ URLs, related names, date components, and instant-message handles. JSON keeps
269
+ both values. Human-facing CSV uses normalized labels, while vCard anniversary
270
+ labels prefer `raw_label` so Apple label wrappers and custom source values
271
+ survive parse → model → interchange export.
80
272
 
81
273
  ### 2. Plist / `.abcdp` (legacy macOS)
82
274
 
83
- Older macOS versions stored each contact as a separate binary plist file under `Records/`.
84
- Each file is a serialised `ABPerson` dictionary. The `abbu` gem currently stubs this parser
85
- and returns an empty array with a warning.
275
+ Older macOS versions stored contacts as separate plist files under `Records/`.
276
+ The repository fixtures demonstrate dictionaries that the plist parser
277
+ normalizes into the same contact model used by the SQLite parser. Additional
278
+ plist keys or layouts require fixture evidence before they are treated as
279
+ supported semantics.
280
+
281
+ ## Repository Evidence
282
+
283
+ - `spec/fixtures/TestContacts.abbu/` exercises the supported synthetic SQLite,
284
+ nested source, and image-resolution behavior.
285
+ - `spec/fixtures/PlistContacts.abbu/` exercises the supported synthetic legacy
286
+ plist behavior.
287
+ - `spec/fixtures/identity_cases.yml` contains deterministic synthetic cross-source and
288
+ Unicode near-collision identity evidence.
289
+ - `spec/support/fixture_generator.rb` is the reproducible source for generated
290
+ SQLite fixture structure and data.
291
+ - `spec/abbu/schema_inspector_spec.rb` builds deterministic temporary SQLite
292
+ schemas for missing tables, unknown contact-link candidates, and column drift.
293
+
294
+ These fixtures prove only the variations they contain. Table names, column
295
+ names, entity numbers, UUIDs, and directory names alone are not sufficient
296
+ evidence for new behavior.
86
297
 
87
298
  ## Export Steps
88
299
 
@@ -96,9 +307,17 @@ The "Contacts Archive" option produces a `.abbu` bundle.
96
307
 
97
308
  ## References
98
309
 
99
- - [Apple Contacts Framework (private)](https://developer.apple.com/documentation/contacts)
310
+ - [Apple Contacts framework](https://developer.apple.com/documentation/contacts)
311
+ - [iQueryContacts forensic schema notes](https://github.com/MetadataForensics/iQueryContacts)
312
+ - [Observed Contacts timestamp epoch](https://apple.stackexchange.com/questions/115551/how-to-sort-contacts-by-creation-date-or-modification-date-in-ios-contacts-or-os/229313)
313
+ - [LifeOS Apple Contacts timestamp conversion](https://github.com/nbramia/LifeOS/blob/main/scripts/apple_data_export.py)
100
314
  - [SQLite3 gem](https://github.com/sparklemotion/sqlite3-ruby)
101
- - macOS `AddressBook.framework` private headers (reverse-engineered)
315
+ - [macos-ts live Contacts reader](https://github.com/evantahler/macos-ts)
316
+ - Repository fixtures and regression specs listed above
317
+
318
+ Apple's public Contacts framework documents application-facing concepts, not a
319
+ stable `.abbu` storage contract. Private framework names and Core Data names are
320
+ not normative references.
102
321
 
103
322
  ---
104
323
  Stan Carver II
data/docs/CHANGELOG.md ADDED
@@ -0,0 +1,193 @@
1
+ <!-- CHANGELOG.md -->
2
+
3
+ # Changelog
4
+
5
+ All notable changes to `abbu` are documented here.
6
+
7
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
8
+ Versioning follows [Semantic Versioning](https://semver.org/).
9
+
10
+ ---
11
+
12
+ ## [Unreleased]
13
+
14
+ ## [0.4.0] - 2026-09-30
15
+
16
+ ### Fixed
17
+
18
+ - Live CLI input uses distinct `--live` auto-discovery and `--live-path PATH` forms;
19
+ mixed modes and unexpected live positional arguments are rejected.
20
+
21
+ - Deduplication recognizes international phone prefixes from trimmed raw input, not
22
+ punctuation-stripped digits, keeping national-looking `(001)` values source-local.
23
+
24
+ - Image extraction exclusively creates destination files and reports `destination_exists`
25
+ rather than overwriting existing files, symlinks, or hard links. Output directory aliases
26
+ are resolved before extraction; extraction diagnostic privacy is documented.
27
+
28
+ ### Added
29
+
30
+ - Explicit, read-only live Contacts access through `Abbu.open_live` and
31
+ `abbu --live` / `abbu --live-path PATH`, with root and `Sources/*` database discovery
32
+ - Actionable live-store errors for missing databases, unsupported automatic
33
+ discovery, and macOS Full Disk Access restrictions
34
+ - Platform-independent synthetic coverage for live-store discovery, source
35
+ provenance, SQLite read-only enforcement, and CLI behavior
36
+ - Provenance-aware `Utils::Deduplicator#matches` suggestions with normalized email,
37
+ international/source-local phone, Unicode name, and organization evidence
38
+ - Match confidence, ambiguity status, original source records, and raw evidence, plus an
39
+ explicit callable merge-policy boundary that never silently collapses contacts
40
+
41
+ - `Archive#extract_images` and `Abbu::ImageExtractor` for copying contact photos to a
42
+ caller-selected directory with structured missing, unreadable, and unsupported-image
43
+ diagnostics
44
+ - `--extract-images DIR` CLI support with content-aware JPEG, PNG, GIF, and HEIC extension
45
+ selection and safe, Unicode-preserving, collision-resistant filenames
46
+ - Source-local resolution for duplicate image stems in nested account directories without
47
+ guessing when provenance cannot disambiguate candidates
48
+
49
+ - Structured tolerant-parsing diagnostics with strict API/CLI mode for corrupt
50
+ plist records, missing optional SQLite data, and unresolved image references
51
+ - Missing optional SQLite tables emit one non-PII diagnostic per database and
52
+ table instead of repeating schema-level warnings for every contact
53
+ - Evidence-safe SQLite schema diagnostics through `Archive#schema_report` and
54
+ `abbu <archive> --schema`, including unknown tables/columns, absent recognized
55
+ schema elements, and owner/contact-style relationship candidates
56
+ - Deterministic schema-variation coverage for missing optional tables, unknown
57
+ contact-linked tables, and column drift
58
+ - Normalized Apple standard labels with the original source value preserved as
59
+ `raw_label` on labeled contact values
60
+ - Chainable `Abbu::Query` and `Archive#where` APIs for contact filtering
61
+ - Exact normalized email and phone lookup that returns all matches across sources
62
+ - Case-insensitive partial name and email search through Ruby and tab-separated CLI output
63
+ - Stable `--json` search output using the regular contact JSON schema
64
+ - Contact creation and modification timestamps from optional SQLite `ZCREATIONDATE` and `ZMODIFICATIONDATE` columns
65
+ - Provenance metadata identifying each contact's source database or plist and its location within the ABBU bundle
66
+ - Creation, modification, and source metadata in JSON exports
67
+ - Repository constitution and focused local skills for ABBU format evidence,
68
+ Ruby gem development, testing, and Sheriff-gated releases
69
+ - Canonical `bin/spec` and `bin/package` workflows, with CI reusing project-local
70
+ `bin/spec` and `bin/lint` instead of duplicating their commands
71
+
72
+ ### Changed
73
+
74
+ - CI now runs the Ruby matrix once for pull requests and on pushes to canonical `main`, avoiding duplicate feature-branch push and pull-request runs
75
+ - SQLite parsing now tolerates absent established email, phone, and postal-address
76
+ tables and returns empty collections while retaining the variation in schema diagnostics;
77
+ unexpected column drift and other SQL errors on present tables continue to surface
78
+ - vCard anniversary export now prefers the original `raw_label` so Apple and
79
+ custom source representations survive parse-and-export round trips
80
+ - Minimum supported Ruby and RuboCop target are now 3.3; CI covers Ruby 3.3,
81
+ 3.4, and 4.0, with Ruby 3.3 as the designated lint/tooling job
82
+ - Agent guidance is consolidated in `AGENTS.md`; the redundant `CLAUDE.md` has
83
+ been removed
84
+ - Gem packaging now includes only the public `bin/abbu` executable instead of
85
+ repository-only developer commands
86
+
87
+ ## [0.3.0] - 2026-09-29
88
+
89
+ ### Added
90
+
91
+ - `Contact#image_uri` and `Contact#image_path` accessors
92
+ - `Parsers::SqliteParser` extracts `ZIMAGEURI` from `ZABCDRECORD`
93
+ - `Utils::ImageResolver` builds an index of every image file under the bundle's `**/Images/` directories (jpg, jpeg, png, heic — case-insensitive) and resolves a contact's `image_uri` to a `Pathname` within the bundle
94
+ - `Archive#contacts` automatically resolves `image_path` for any contact that has a non-nil `image_uri`
95
+ - CSV export: new `ImagePath` column (last column)
96
+ - JSON export: `image_uri` and `image_path` fields (omitted via `.compact` when blank)
97
+ - vCard export: `PHOTO;VALUE=URI:file://<absolute path>` line, emitted when `image_path` is present
98
+ - `spec/fixtures/TestContacts.abbu/Images/stan-photo.jpg` stub image, regenerated by `spec/support/fixture_generator.rb`
99
+
100
+ ### Notes
101
+
102
+ - PlistParser does not extract images — legacy `.abcdp` contacts typically embed image data inline, which is a separate extraction path
103
+ - vCard `PHOTO` references the absolute bundle path; base64 embedding and the `--extract-images` CLI flag are tracked for follow-up
104
+
105
+ ### Fixed
106
+
107
+ - vCard `PHOTO` file URIs now percent-encode spaces, reserved characters, and non-ASCII bytes in image paths
108
+ - `abbu:export` Rake task wrote hash literals (e.g. `{:address=>"…", :label=>"…"}`) into the `Email` and `Phone` CSV columns. Now correctly extracts the first address and number from each contact's multi-value field.
109
+
110
+ ## [0.2.0] - 2026-05-03
111
+
112
+ ### Added
113
+
114
+ - `Parsers::PlistParser` — full implementation of legacy `.abcdp` plist contact parsing, replacing the previous stub
115
+ - `PlistParser::FIELD_MAP` constant for flat-field mapping
116
+ - Multi-value field extraction for emails, phones, addresses, URLs, notes, related names, and social profiles
117
+ - `Archive` recursively scans `**/*.abcdp` across the entire bundle tree
118
+ - `plist` gem (~> 3.7) runtime dependency
119
+ - Plist fixture files (`spec/fixtures/PlistContacts.abbu/`) for integration testing
120
+ - `middle_name` attribute on `Contact`; `ZMIDDLENAME` / `Middle` plist key
121
+ - `dates` attribute on `Contact` (array of `{ year:, month:, day:, label: }` hashes)
122
+ - `birthday`, `anniversary`, and `lunar_birthday` accessor methods derived from the dates list
123
+ - `instant_messages` attribute (AIM, Jabber, Skype, etc.); `ZABCDMESSAGINGADDRESS` / `InstantMessage` key
124
+ - `verification_code` attribute on `Contact`; `ZVERIFICATIONCODE` column / `VerificationCode` plist key
125
+ - `phonetic_middle_name` attribute; `ZPHONETICMIDDLENAME` column / `PhoneticMiddle` plist key
126
+ - `ZABCDDATECOMPONENTS` parsing (year/month/day split columns)
127
+ - `BDAY`, `X-LUNAR-BDAY`, `X-ABDATE`, `X-ABLABEL` vCard fields
128
+ - `IMPP` vCard field for instant messaging
129
+ - Birthday, anniversary, lunar birthday, instant messages, and verification code in CSV and JSON exports
130
+ - Plist fixture for testing all the above (`spec/fixtures/PlistContacts.abbu/`)
131
+
132
+ ### Changed
133
+
134
+ - `Contact#full_name` now includes middle name (`Honorable Stan The Man "Stretch" Carver II`)
135
+ - vCard `N` field includes middle name component
136
+ - `SqliteParser::RECORD_FIELD_MAP` extended with the new column → attr mappings
137
+ - `JsonExporter#contact_hash` includes all new fields; blank fields are dropped via `.compact`
138
+ - `CsvExporter` extended-field section now exports lunar birthday and verification code
139
+
140
+ ## [0.1.2] - 2026-04-26
141
+
142
+ ### Added
143
+
144
+ - Full Apple Contacts schema support: job title, department, maiden name, phonetic names, pronouns, ringtone, texttone
145
+ - Relational table parsing: URLs, notes, related names (family/business), social profiles (Twitter, etc.)
146
+ - Nickname, prefix, and suffix fields with smart `full_name` formatting
147
+ - Hash-based email/phone data preserving custom labels (e.g. "Direct Line", "Work")
148
+ - Address, group, URL, notes, related names, and social profiles in CSV export
149
+ - Comprehensive JSON export with all contact fields
150
+ - vCard 3.0 export with ADR, URL, NICKNAME, TITLE, NOTE, X-SOCIALPROFILE
151
+ - `rubocop-rspec` plugin integration
152
+ - 100% line coverage across all 44 specs
153
+
154
+ ### Changed
155
+
156
+ - Refactored `SqliteParser` to use `RECORD_FIELD_MAP` constant for maintainability
157
+ - Refactored `CsvExporter` into `core_fields`/`extended_fields` for cleaner ABC metrics
158
+ - All specs comply with rubocop-rspec conventions
159
+
160
+ ## [0.1.1] - 2026-04-23
161
+
162
+ ### Fixed
163
+
164
+ - `require 'pathname'` missing in `archive.rb` causing `NameError` in isolation
165
+ - Added regression guard spec for file require isolation
166
+
167
+
168
+ ## [0.1.0] - 2026-04-12
169
+
170
+ ### Added
171
+
172
+ - `Abbu.open(path)` entry point returning an `Archive`
173
+ - `Archive#contacts` — reads contacts from SQLite or falls back to plist stub
174
+ - `Archive#sqlite?` — detects modern `.abcddb` bundles
175
+ - `Contact` object with `first_name`, `last_name`, `emails`, `phones`, `company`, `full_name`
176
+ - `Parsers::SqliteParser` — queries `ZABCDRECORD`, `ZABCDEMAILADDRESS`, `ZABCDPHONENUMBER`
177
+ - `Parsers::PlistParser` — stub with warning (legacy `.abcdp` support in v0.2)
178
+ - `Exporters::CsvExporter` — `to_file` and `to_stdout`
179
+ - `Exporters::JsonExporter` — `to_file` and `to_stdout`
180
+ - `Exporters::VcardExporter` — `to_file` and `to_stdout` (vCard 3.0)
181
+ - `Utils::Deduplicator` — groups contacts by first email, returns duplicates hash
182
+ - `bin/abbu` CLI with `--format`, `--output`, `--stats`, `--dedupe`, `--version`
183
+ - Rake tasks: `abbu:export`, `abbu:dedupe`, `abbu:stats`
184
+ - Example scripts: CSV, JSON, vCard, API, CRM sync, stats, dedupe
185
+ - `docs/ABBU.md` — file format reference
186
+ - RSpec test suite with 100% coverage target
187
+ - Guard + RuboCop DX loop
188
+ - GitHub Actions CI (Ruby 3.2 + 3.3)
189
+
190
+ ---
191
+ Stan Carver II
192
+ Made in Texas 🤠
193
+ https://stancarver.com
@@ -0,0 +1,61 @@
1
+ <!-- CONTRIBUTING.md -->
2
+
3
+ # Contributing to abbu
4
+
5
+ Thank you for your interest in contributing!
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ git clone https://github.com/scarver2/abbu
11
+ cd abbu
12
+ mise exec -- bundle install
13
+ ```
14
+
15
+ ## DX Loop
16
+
17
+ ```bash
18
+ mise exec -- bundle exec guard
19
+ ```
20
+
21
+ This runs RSpec and RuboCop automatically on file changes.
22
+
23
+ ## Running Tests
24
+
25
+ ```bash
26
+ bin/spec
27
+ ```
28
+
29
+ ## Linting
30
+
31
+ ```bash
32
+ bin/lint
33
+ mise exec -- bundle exec rubocop -a # autocorrect
34
+ ```
35
+
36
+ ## Packaging
37
+
38
+ ```bash
39
+ bin/package
40
+ ```
41
+
42
+ This builds the current gem, verifies its metadata, installs it into an isolated
43
+ gem home, and loads that installed copy. A successful package check is evidence
44
+ only; it does not authorize a release tag or RubyGems publication.
45
+
46
+ ## Pull Request Guidelines
47
+
48
+ - Base branch: `main`
49
+ - Commit style: [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `chore:`)
50
+ - All specs must pass and coverage must remain at 100%
51
+ - RuboCop must pass with no offenses
52
+ - Add an entry to `docs/CHANGELOG.md` under `[Unreleased]`
53
+
54
+ ## Reporting Issues
55
+
56
+ Open an issue on GitHub with a minimal reproduction case.
57
+
58
+ ---
59
+ Stan Carver II
60
+ Made in Texas 🤠
61
+ https://stancarver.com