abbu 0.8.1 → 0.18.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.
- checksums.yaml +4 -4
- data/README.md +129 -2
- data/bin/abbu +158 -6
- data/bin/abbu-mcp +36 -0
- data/docs/ABBU.md +28 -0
- data/docs/ALTERNATE_CALENDAR_EVIDENCE.md +82 -0
- data/docs/CHANGELOG.md +98 -0
- data/docs/CONTRIBUTING.md +3 -0
- data/docs/FUZZY_MATCHING.md +72 -0
- data/docs/HISTORY_EVIDENCE.md +98 -0
- data/docs/ICALENDAR.md +101 -0
- data/docs/MACHINE_JSON.md +128 -0
- data/docs/MCP.md +148 -0
- data/docs/MERGE_PLANS.md +78 -0
- data/docs/MY_CARD_EVIDENCE.md +92 -0
- data/docs/PORTABLE_SQLITE.md +142 -0
- data/docs/RELEASING.md +76 -68
- data/docs/SNAPSHOT_DIFF.md +74 -0
- data/docs/SNAPSHOT_HISTORY.md +93 -0
- data/docs/STREAMING.md +73 -0
- data/docs/TODO.md +4 -4
- data/docs/WRITER_DECISION.md +90 -0
- data/lib/abbu/archive.rb +14 -0
- data/lib/abbu/exporters/csv_exporter.rb +7 -0
- data/lib/abbu/exporters/icalendar_encoding.rb +35 -0
- data/lib/abbu/exporters/icalendar_event.rb +61 -0
- data/lib/abbu/exporters/icalendar_exporter.rb +86 -0
- data/lib/abbu/exporters/json_exporter.rb +2 -2
- data/lib/abbu/exporters/jsonl_exporter.rb +23 -0
- data/lib/abbu/exporters/sqlite_exporter.rb +85 -0
- data/lib/abbu/exporters/sqlite_rows.rb +113 -0
- data/lib/abbu/exporters/sqlite_schema.rb +76 -0
- data/lib/abbu/exporters/vcard_exporter.rb +10 -11
- data/lib/abbu/exporters/vcard_photo.rb +50 -0
- data/lib/abbu/history_tracker.rb +96 -0
- data/lib/abbu/live_store.rb +11 -0
- data/lib/abbu/machine_command.rb +159 -0
- data/lib/abbu/machine_output.rb +52 -0
- data/lib/abbu/mcp/read_session.rb +57 -0
- data/lib/abbu/mcp/server.rb +100 -0
- data/lib/abbu/mcp.rb +11 -0
- data/lib/abbu/merge_plan.rb +132 -0
- data/lib/abbu/merge_preview.rb +35 -0
- data/lib/abbu/merge_values.rb +26 -0
- data/lib/abbu/parsers/plist_parser.rb +9 -0
- data/lib/abbu/parsers/sqlite_parser.rb +25 -0
- data/lib/abbu/snapshot_diff.rb +125 -0
- data/lib/abbu/snapshot_history.rb +66 -0
- data/lib/abbu/utils/fuzzy_matcher.rb +116 -0
- data/lib/abbu/version.rb +1 -1
- data/lib/abbu.rb +9 -0
- data/sig/fuzzy_matcher.rbs +22 -0
- data/sig/icalendar_exporter.rbs +13 -0
- data/sig/machine_output.rbs +23 -0
- data/sig/mcp.rbs +19 -0
- data/sig/merge_plan.rbs +16 -0
- data/sig/snapshot_diff.rbs +8 -0
- data/sig/snapshot_history.rbs +10 -0
- data/sig/sqlite_exporter.rbs +10 -0
- data/sig/streaming.rbs +27 -0
- data/sig/vcard_exporter.rbs +1 -1
- metadata +46 -2
data/docs/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,104 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
### Release Tooling
|
|
15
|
+
|
|
16
|
+
- Browser-initiated GitHub releases accept an exact approved version and commit,
|
|
17
|
+
verify the supported Ruby matrix, and create immutable tags with the built-in
|
|
18
|
+
GitHub token before OIDC publication. Protected Sheriff approvals remain required.
|
|
19
|
+
|
|
20
|
+
## [0.18.0] - 2026-10-03
|
|
21
|
+
|
|
22
|
+
This release includes the previously unreleased 0.12.0–0.17.0 development milestones.
|
|
23
|
+
|
|
24
|
+
### Added — optional MCP adapter
|
|
25
|
+
|
|
26
|
+
- Optional `abbu-mcp` stdio executable and `require 'abbu/mcp'` adapter, using
|
|
27
|
+
the separately installed official MCP Ruby SDK. Core `abbu` has no MCP runtime
|
|
28
|
+
dependency and never loads the SDK automatically.
|
|
29
|
+
- Five read-only tools for bounded contact queries, statistics, sources/groups
|
|
30
|
+
and explicit diagnostics, bound to one operator-selected archive or live path.
|
|
31
|
+
No arbitrary path tool argument, file export, Contacts mutation, default
|
|
32
|
+
contact logging, or unevidenced My Card detection.
|
|
33
|
+
|
|
34
|
+
### Added — snapshot history
|
|
35
|
+
|
|
36
|
+
- Streaming snapshot-history transitions with bounded absent-contact retention,
|
|
37
|
+
per-analysis timelines, explicit ambiguity breaks and observed reappearances.
|
|
38
|
+
Ruby ordered-list API and directory JSON Lines CLI do not infer edit events.
|
|
39
|
+
|
|
40
|
+
### Added — streaming
|
|
41
|
+
|
|
42
|
+
- `Archive#each_contact` and `LiveStore#each_contact` enumerate contacts without
|
|
43
|
+
populating the cached array; SQLite readers close on block termination/errors.
|
|
44
|
+
- Incremental CSV/vCard `write_to(io)`, JSONL export, and opt-in CLI `--stream`.
|
|
45
|
+
Late failures can leave partial output. Existing buffered exports are unchanged.
|
|
46
|
+
- Reproducible synthetic 10,000-contact benchmark and documented memory bounds.
|
|
47
|
+
|
|
48
|
+
### Added — portable SQLite
|
|
49
|
+
|
|
50
|
+
- Portable normalized SQLite exporter and `--format sqlite --output FILE`, with
|
|
51
|
+
explicit schema version, relational contact/source/group/multivalue/date data,
|
|
52
|
+
raw-label and primitive-value evidence, transactional staging, and atomic no-overwrite publication.
|
|
53
|
+
Existing exports and source stores remain unchanged; this is not an Apple writer.
|
|
54
|
+
|
|
55
|
+
### Added — merge planning
|
|
56
|
+
|
|
57
|
+
- Immutable merge-plan previews with explicit union, timestamp, source and
|
|
58
|
+
completeness policies. Conflicts and original provenance remain reviewable;
|
|
59
|
+
materialization requires an explicit policy and never writes source stores.
|
|
60
|
+
`--merge-preview` provides a JSON-only preview, never an apply operation.
|
|
61
|
+
- Reject merge options in machine JSON, snapshot-diff, and calendar combinations
|
|
62
|
+
before input reads; machine-mode conflicts retain structured JSON errors.
|
|
63
|
+
|
|
64
|
+
### Added — snapshot comparison
|
|
65
|
+
|
|
66
|
+
- Identity-evidence snapshot comparison through `SnapshotDiff` and `--diff`,
|
|
67
|
+
with raw field changes, provenance, explicit ambiguity and stable JSON output.
|
|
68
|
+
- Snapshot JSON uses the machine dispatcher and structured error contract;
|
|
69
|
+
conflicting machine operations and calendar metadata fail before input reads.
|
|
70
|
+
|
|
71
|
+
### Added — iCalendar
|
|
72
|
+
|
|
73
|
+
- Deterministic iCalendar birthday/anniversary reminders through a Ruby exporter
|
|
74
|
+
and `--format icalendar`. Explicit recurrence year, revision time, and calendar
|
|
75
|
+
namespace avoid fabricated metadata. Unknown years remain unknown; invalid and
|
|
76
|
+
alternate-calendar dates are diagnosed and omitted. Existing exports are unchanged.
|
|
77
|
+
- Machine JSON rejects calendar metadata before opening inputs, rather than
|
|
78
|
+
silently ignoring export-only options.
|
|
79
|
+
|
|
80
|
+
## [0.11.0] - 2026-10-02
|
|
81
|
+
|
|
82
|
+
### Added — machine JSON
|
|
83
|
+
|
|
84
|
+
- First-class `--json` for contacts, statistics, schema, sources/groups, duplicate
|
|
85
|
+
groups, identity matches, and image extraction. New `--diagnostics` and
|
|
86
|
+
`--matches` are JSON-only operations. Public `JsonExporter#payload` and
|
|
87
|
+
machine serializers reuse the unchanged contact JSON representation.
|
|
88
|
+
- Documented machine stdout, stderr, exit codes, privacy, and SemVer contract.
|
|
89
|
+
|
|
90
|
+
### Changed — machine JSON
|
|
91
|
+
|
|
92
|
+
- JSON-mode errors now emit a single `{ "error": { "code", "message" } }`
|
|
93
|
+
document instead of empty stdout or a Ruby exception trace. Invalid inputs
|
|
94
|
+
and conflicting options exit 2; unavailable live inputs and filesystem errors
|
|
95
|
+
exit 1. JSON callers must check exit status before treating stdout as data.
|
|
96
|
+
- Bare `--json` lists contacts. JSON combinations are validated before reading
|
|
97
|
+
or writing, rather than silently prioritizing one operation. Human modes and
|
|
98
|
+
successful existing contact/source/group/schema JSON shapes are unchanged.
|
|
99
|
+
|
|
100
|
+
### Added — fuzzy matching
|
|
101
|
+
|
|
102
|
+
- Opt-in bounded, explainable fuzzy name suggestions using Unicode-preserving
|
|
103
|
+
Levenshtein distance. Exact identity evidence retains precedence; fuzzy-only
|
|
104
|
+
suggestions remain ambiguous and never merge contacts automatically.
|
|
105
|
+
|
|
106
|
+
### Added — embedded photos
|
|
107
|
+
|
|
108
|
+
- Opt-in embedded JPEG/PNG/GIF vCard photos via `photo_mode: :embedded` and
|
|
109
|
+
`--photo-mode embedded`. URI mode remains the default. Missing, unreadable,
|
|
110
|
+
or unsupported image evidence fails before output; HEIC embedding is deferred.
|
|
111
|
+
|
|
14
112
|
## [0.8.1] - 2026-10-01
|
|
15
113
|
|
|
16
114
|
### Fixed
|
data/docs/CONTRIBUTING.md
CHANGED
|
@@ -57,6 +57,9 @@ matrix runs this same command with its Bundler-managed installation paths.
|
|
|
57
57
|
|
|
58
58
|
## Pull Request Guidelines
|
|
59
59
|
|
|
60
|
+
For authorized publication, use the [GitHub-hosted release runbook](RELEASING.md).
|
|
61
|
+
It documents exact-commit dispatch, environment approvals, and safe recovery.
|
|
62
|
+
|
|
60
63
|
- Base branch: `main`
|
|
61
64
|
- Commit style: [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `chore:`)
|
|
62
65
|
- All specs must pass and coverage must remain at 100%
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!-- docs/FUZZY_MATCHING.md -->
|
|
2
|
+
|
|
3
|
+
# Explainable Fuzzy Name Matching
|
|
4
|
+
|
|
5
|
+
`Abbu::Utils::FuzzyMatcher.new(contact, candidates, threshold: 0.85).matches`
|
|
6
|
+
returns existing `Deduplicator::Match` objects in candidate order. The original
|
|
7
|
+
anchor object is skipped. Existing `Deduplicator#matches` remains unchanged.
|
|
8
|
+
Callers should supply distinct candidate objects. This API suggests comparisons;
|
|
9
|
+
it never selects a winner, collapses records or modifies source evidence.
|
|
10
|
+
|
|
11
|
+
## Algorithm And Evidence Boundary
|
|
12
|
+
|
|
13
|
+
ABBU uses normalized Levenshtein edit distance over Unicode codepoints:
|
|
14
|
+
`similarity = 1 - distance / maximum_name_length`. This local, dependency-free
|
|
15
|
+
algorithm exposes an integer edit count. Unlike prefix-boosted metrics, it adds
|
|
16
|
+
no assumption that a matching prefix is more identifying. It is an ABBU
|
|
17
|
+
convention, not an Apple Contacts storage or identity rule.
|
|
18
|
+
|
|
19
|
+
Names use NFC, downcasing, trimming and whitespace collapse only. Accents,
|
|
20
|
+
punctuation, scripts and compatibility characters remain distinct; no ASCII
|
|
21
|
+
transliteration, nickname expansion, initial expansion or token reordering is
|
|
22
|
+
performed. Canonically equivalent Unicode spellings compare equally. Original
|
|
23
|
+
names remain in evidence and in the contacts. Empty names supply no fuzzy signal.
|
|
24
|
+
|
|
25
|
+
Thresholds are inclusive, finite numeric values in `[0, 1]`. Comparison uses the
|
|
26
|
+
unrounded similarity. Each qualifying fuzzy signal exposes `algorithm`,
|
|
27
|
+
`distance`, `similarity`, both normalized names, both raw names and `contribution`.
|
|
28
|
+
Threshold 1 requires equality after the documented normalization, not raw equality.
|
|
29
|
+
Even exact normalized names alone remain ambiguous.
|
|
30
|
+
|
|
31
|
+
## Exact Evidence And Scores
|
|
32
|
+
|
|
33
|
+
Each pair is also checked by the existing provenance-aware exact matcher. Its
|
|
34
|
+
qualifying score and status take precedence; fuzzy evidence then contributes
|
|
35
|
+
zero, so similarity cannot inflate exact confidence. Exact signal contributions
|
|
36
|
+
follow existing weights, counted once per type and capped in evidence order at
|
|
37
|
+
the total score. Otherwise the fuzzy-only score is `0.15 * similarity`, rounded
|
|
38
|
+
to four decimals, with low confidence and ambiguous status. Scores are ranking
|
|
39
|
+
heuristics, never probabilities. Names are not global identifiers.
|
|
40
|
+
|
|
41
|
+
Two or more qualifying exact candidates for the anchor make every returned
|
|
42
|
+
suggestion ambiguous. Fuzzy suggestions alone do not demote a single exact
|
|
43
|
+
candidate. Candidate-to-candidate relationships are not evaluated; scope and
|
|
44
|
+
candidate selection remain the caller's responsibility. `Match#merge` still
|
|
45
|
+
requires an explicit caller-provided policy; review remains necessary.
|
|
46
|
+
|
|
47
|
+
## Resource And Privacy Boundaries
|
|
48
|
+
|
|
49
|
+
Defaults are 10,000 candidates and 128 normalized codepoints per name. Both
|
|
50
|
+
`max_candidates:` and `max_name_length:` require positive integers. Exceeding a
|
|
51
|
+
bound raises `ArgumentError`, never silent truncation. Candidate enumeration
|
|
52
|
+
reads at most limit+1 elements; names are validated during `matches`. Results
|
|
53
|
+
are buffered. Callers raising limits accept the additional cost.
|
|
54
|
+
|
|
55
|
+
For N candidates and bounded name length L, edit-distance work is O(N × L²)
|
|
56
|
+
with O(L) distance-row memory. Exact comparison also traverses contact identifier
|
|
57
|
+
collections, so these bounds do not bound arbitrary email/phone arrays. The
|
|
58
|
+
deterministic 1,000-candidate synthetic spec exercises this baseline without a
|
|
59
|
+
flaky wall-clock assertion or personal Contacts data. This is not a benchmark
|
|
60
|
+
claim for arbitrary archive sizes or real-world identity accuracy.
|
|
61
|
+
|
|
62
|
+
Returned contacts, raw names, exact evidence and source paths are sensitive;
|
|
63
|
+
do not publish match results as sanitized diagnostics. Parsing, exporters and
|
|
64
|
+
existing JSON contracts are unchanged. No private Apple schema assumptions or
|
|
65
|
+
new runtime dependencies are introduced.
|
|
66
|
+
|
|
67
|
+
[README](../README.md) · [Contributing](CONTRIBUTING.md)
|
|
68
|
+
|
|
69
|
+
—
|
|
70
|
+
Stan Carver II
|
|
71
|
+
Made in Texas 🤠
|
|
72
|
+
https://stancarver.com
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!-- docs/HISTORY_EVIDENCE.md -->
|
|
2
|
+
|
|
3
|
+
# Contacts History: Evidence Before Events
|
|
4
|
+
|
|
5
|
+
Research date: 2026-10-01. Issue [#35](https://github.com/scarver2/abbu/issues/35).
|
|
6
|
+
Decision proposed for review: do not expose private save/edit-history fields
|
|
7
|
+
without reproducible storage evidence. This is a research boundary, not a
|
|
8
|
+
completed history parser or a claim that such records cannot exist.
|
|
9
|
+
|
|
10
|
+
## Three Different Questions
|
|
11
|
+
|
|
12
|
+
1. What timestamp does a record expose? ABBU already reads observed optional
|
|
13
|
+
creation/modification fields; these are not a sequence of edits.
|
|
14
|
+
2. What changed between two observed snapshots? Future snapshot comparison can
|
|
15
|
+
describe differences, not infer actions or exact times between observations.
|
|
16
|
+
3. What events does a supported history provider report? This requires its own
|
|
17
|
+
evidence and semantics; it must not replace the original record timestamps.
|
|
18
|
+
|
|
19
|
+
No timestamp alone proves a human edit, an author, or an interaction with the
|
|
20
|
+
Contacts UI. Sync, migration and normalization are alternative hypotheses to
|
|
21
|
+
test, not asserted explanations for a given record.
|
|
22
|
+
|
|
23
|
+
## Primary Evidence
|
|
24
|
+
|
|
25
|
+
Apple documents [CNChangeHistoryFetchRequest](https://developer.apple.com/documentation/contacts/cnchangehistoryfetchrequest)
|
|
26
|
+
as a framework query for change history. Its overview says changes are coalesced
|
|
27
|
+
to eliminate redundant adds, updates and deletes. Therefore even this documented
|
|
28
|
+
surface must not be described as a complete keystroke or immutable audit log.
|
|
29
|
+
|
|
30
|
+
The documented [starting token](https://developer.apple.com/documentation/contacts/cnchangehistoryfetchrequest/startingtoken)
|
|
31
|
+
is opaque. A nil starting token requests a reset event and then add events for
|
|
32
|
+
current contacts/groups; those add events are not proof of original creation
|
|
33
|
+
times. Tokens are not dates, monotonically meaningful integers, or archive keys.
|
|
34
|
+
|
|
35
|
+
Apple's [unification option](https://developer.apple.com/documentation/contacts/cnchangehistoryfetchrequest/shouldunifyresults)
|
|
36
|
+
distinguishes unified from individual history. A future adapter must preserve
|
|
37
|
+
which mode it requested rather than silently treating the outputs identically.
|
|
38
|
+
|
|
39
|
+
Sources inspected on the research date. These documents do not specify private
|
|
40
|
+
SQLite history table layouts or mappings into an exported ABBU. No native API
|
|
41
|
+
calls, private live-store reads or personal database experiments were performed.
|
|
42
|
+
|
|
43
|
+
## Repository Evidence Audit
|
|
44
|
+
|
|
45
|
+
At accepted main `77eaeda`, the SQLite parser reads `ZCREATIONDATE` and
|
|
46
|
+
`ZMODIFICATIONDATE` as optional record timestamps. Synthetic fixture generation
|
|
47
|
+
and timestamp regressions exercise those fields. The schema inspector reports
|
|
48
|
+
unknown structure without interpreting it. The repository has no verified
|
|
49
|
+
save-history mapping with attributed macOS/Contacts build and controlled event
|
|
50
|
+
sequence. See [format evidence](ABBU.md) and the [compatibility matrix](FORMAT_COMPATIBILITY.md).
|
|
51
|
+
|
|
52
|
+
A table named like history, transaction, save or change does not establish
|
|
53
|
+
event semantics. Neither file modification times nor WAL page changes identify
|
|
54
|
+
the person who edited a contact. Third-party reverse engineering may supply
|
|
55
|
+
candidate observations, but is not an accepted mapping without reproduction.
|
|
56
|
+
|
|
57
|
+
## Required Experiment Before Implementation
|
|
58
|
+
|
|
59
|
+
- Use disposable, unsynced accounts and invented contacts, with explicit
|
|
60
|
+
experiment approval. Record exact OS/Contacts builds and capture method.
|
|
61
|
+
- Establish a known initial state, then perform one controlled operation per
|
|
62
|
+
observation: add, scalar edit, labeled-value edit, group membership change,
|
|
63
|
+
delete, no-op save, and reopen. Separately test sync/migration if in scope.
|
|
64
|
+
- Capture original bytes and compare candidate records without modifying the
|
|
65
|
+
source store. Record ordering, clock/time-zone assumptions, transaction
|
|
66
|
+
grouping, retention/reset behavior and missing events. Do not guess epochs.
|
|
67
|
+
- Confirm relationships under duplicate names, source-local ID collisions,
|
|
68
|
+
linked/unified contacts, and deletion of the referenced record. Keep old
|
|
69
|
+
schema cases. Unknown layouts must remain unsupported, not empty history.
|
|
70
|
+
- Independently reproduce any external observation. Preserve raw values beside
|
|
71
|
+
proposed interpretation and document counterexamples and alternative causes.
|
|
72
|
+
|
|
73
|
+
## Future Public Contract
|
|
74
|
+
|
|
75
|
+
History would be optional provenance evidence, never replacement `created_at`
|
|
76
|
+
or `modified_at`. Require explicit source/snapshot attribution, raw event data,
|
|
77
|
+
evidenced timestamp interpretation, ordering limits and unknown fields.
|
|
78
|
+
Distinguish unsupported backend, unavailable/permission failure, reset/truncated
|
|
79
|
+
history, and genuinely empty results from a capable provider. Never imply a
|
|
80
|
+
human actor without independent evidence. Define stable JSON and RBS only after
|
|
81
|
+
the source model is justified; no speculative API is shipped here.
|
|
82
|
+
|
|
83
|
+
Do not log contact values, identifiers, tokens or filesystem paths by default.
|
|
84
|
+
Archived evidence and tokens remain sensitive. A framework adapter would be a
|
|
85
|
+
separately reviewed, opt-in macOS boundary, not a mandatory portable-gem
|
|
86
|
+
dependency or a query against the current user's store when opening an archive.
|
|
87
|
+
|
|
88
|
+
The research outcome is insufficient evidence for a private parser. #35 can
|
|
89
|
+
close on this scoped boundary under the development-train directive; reopen
|
|
90
|
+
with a reproducible fixture/event sequence. #43/#44 snapshot work remains
|
|
91
|
+
independently useful but must not advertise unseen historical events.
|
|
92
|
+
|
|
93
|
+
Return to [README](../README.md), [roadmap](TODO.md), or [format evidence](ABBU.md).
|
|
94
|
+
|
|
95
|
+
—
|
|
96
|
+
Stan Carver II
|
|
97
|
+
Made in Texas 🤠
|
|
98
|
+
https://stancarver.com
|
data/docs/ICALENDAR.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<!-- docs/ICALENDAR.md -->
|
|
2
|
+
|
|
3
|
+
# iCalendar Reminders
|
|
4
|
+
|
|
5
|
+
`Abbu::Exporters::IcalendarExporter` consumes the public `Contact#birthday` and
|
|
6
|
+
`Contact#anniversary` fields, not private Apple tables. It emits one all-day,
|
|
7
|
+
transparent yearly reminder per supported field. Arbitrary `Contact#dates`
|
|
8
|
+
entries are not classified as birthdays or anniversaries by this exporter.
|
|
9
|
+
It does not create alarms, calculate ages, change contacts, or write source stores.
|
|
10
|
+
|
|
11
|
+
## Explicit Metadata
|
|
12
|
+
|
|
13
|
+
The constructor requires `year:` (Integer 1–9996), `generated_at:` (Time whose UTC
|
|
14
|
+
year is 1–9999), and a nonempty `calendar_id:` String. The latter is a caller-owned
|
|
15
|
+
unique namespace: choose a unique UUID or identifier under a domain you control,
|
|
16
|
+
and reuse it only for the same logical ordered input. The timestamp is the real
|
|
17
|
+
export revision time, converted to UTC seconds; subsecond precision is omitted.
|
|
18
|
+
No wall clock, random identifier, source timestamp, or fabricated epoch is used.
|
|
19
|
+
|
|
20
|
+
`to_ical` returns UTF-8 text; `to_file(path)` writes identical bytes;
|
|
21
|
+
`to_stdout` prints those bytes. Same ordered contacts and metadata produce the
|
|
22
|
+
same output. Every call rebuilds `diagnostics`, a frozen array of frozen hashes
|
|
23
|
+
with `category`, zero-based `contact_index`, and `field`. Categories are
|
|
24
|
+
`invalid_date` and `unsupported_calendar`. These omit names, paths and date values.
|
|
25
|
+
|
|
26
|
+
## Date Policy
|
|
27
|
+
|
|
28
|
+
An absent year (`nil`) or integer zero is unknown. Other year/month/day values
|
|
29
|
+
must be integers and form a valid proleptic Gregorian date, with known years in
|
|
30
|
+
1–9999. Unknown years use a leap-year validation reference internally; that
|
|
31
|
+
reference is never exported as a birth year. Invalid values are diagnosed and
|
|
32
|
+
omitted, not repaired. No private Apple sentinel year is guessed.
|
|
33
|
+
|
|
34
|
+
`DTSTART` is the first valid occurrence at or after the caller's year and, when
|
|
35
|
+
known, the source year. February 29 advances to the next leap year if needed;
|
|
36
|
+
yearly recurrence skips non-leap years, never silently shifts to February 28 or
|
|
37
|
+
March 1. `DURATION:P1D` avoids cross-year end-date overflow. All events contain
|
|
38
|
+
an explanatory DESCRIPTION and `X-ABBU-YEAR-UNKNOWN:TRUE` or `FALSE`. Known source
|
|
39
|
+
years appear in DESCRIPTION; unknown source years never gain an asserted age or
|
|
40
|
+
original year. Calendar software may display recurrence starts as dates; these
|
|
41
|
+
are reminders, not birthday-field synchronization or Apple round-trip backups.
|
|
42
|
+
|
|
43
|
+
`lunar_birthday` is always diagnosed and omitted. A supplied `calendar` key on a
|
|
44
|
+
date permits only absent/nil, `'gregorian'`, or `:gregorian`; anything else is
|
|
45
|
+
diagnosed and omitted. Existing parsers do not expose every private calendar
|
|
46
|
+
attribute, so absence of an attribute is not proof of original Apple calendar
|
|
47
|
+
semantics. This is an explicit Gregorian convention over the current public
|
|
48
|
+
normalized fields, not alternate-calendar conversion or expanded parse support.
|
|
49
|
+
|
|
50
|
+
SUMMARY preserves `raw_label` when present (including blank), otherwise `label`,
|
|
51
|
+
otherwise Birthday/Anniversary. Custom and wrapped labels remain exact after
|
|
52
|
+
TEXT decoding; the Contact and its evidence are never rewritten.
|
|
53
|
+
|
|
54
|
+
## Wire And Identity Contract
|
|
55
|
+
|
|
56
|
+
The standards basis is [RFC 5545](https://www.rfc-editor.org/rfc/rfc5545):
|
|
57
|
+
content lines and UTF-8 in §3.1, TEXT escaping in §3.3.11, DATE in §3.3.4,
|
|
58
|
+
yearly recurrence in §3.3.10, all-day VEVENT in §3.6.1, UID in §3.8.4.7, and
|
|
59
|
+
DTSTAMP in §3.8.7.2. Output uses CRLF, escaped backslashes/commas/semicolons and
|
|
60
|
+
newlines, and UTF-8-safe folding at 75 octets including continuation whitespace.
|
|
61
|
+
Invalid text/control characters raise before opening an output file.
|
|
62
|
+
|
|
63
|
+
UIDs hash the namespace, zero-based input position, field kind, source-relative
|
|
64
|
+
path, and full name. Duplicate contacts remain separate. These are generated
|
|
65
|
+
reminder identifiers, not Apple IDs or proof of contact identity. Reordering,
|
|
66
|
+
inserting contacts, changing names/provenance, or changing namespace changes
|
|
67
|
+
UIDs; this is not a persistent synchronization feed. Hashes are not anonymization.
|
|
68
|
+
No source paths or contact identifiers are exported in clear text, but names,
|
|
69
|
+
labels, source years, dates and correlatable identifiers are sensitive.
|
|
70
|
+
|
|
71
|
+
With no supported events, output is the empty string, not an invalid componentless
|
|
72
|
+
VCALENDAR. Tolerant omissions can therefore produce no bytes. Inspect diagnostics.
|
|
73
|
+
`strict:`/`--strict` belongs to parsing only; it does not alter exporter omissions.
|
|
74
|
+
Existing output files are overwritten on successful export, even empty output;
|
|
75
|
+
file writing is buffered but not atomic/no-clobber. Use a separate destination
|
|
76
|
+
outside the source bundle. Filesystem failures can leave partial output.
|
|
77
|
+
|
|
78
|
+
## CLI
|
|
79
|
+
|
|
80
|
+
Use `--format icalendar`, `--calendar-year`, `--calendar-stamp` (full ISO 8601
|
|
81
|
+
timestamp with timezone), and `--calendar-id`. Archive and explicit live-store
|
|
82
|
+
input are supported. Only output selection and parser strictness accompany
|
|
83
|
+
calendar export; conflicting operations are rejected. Stdout contains only
|
|
84
|
+
calendar bytes (or none); omission diagnostics go to stderr. Success including
|
|
85
|
+
omissions exits 0, invalid metadata/conflicts exit 2. Existing input/access
|
|
86
|
+
failure behavior is unchanged. The exporter adds no runtime dependency.
|
|
87
|
+
|
|
88
|
+
## Evidence And Limits
|
|
89
|
+
|
|
90
|
+
`spec/support/calendar_fixtures.rb` reproduces previously supported plist keys
|
|
91
|
+
and SQLite date columns using synthetic data, not a particular macOS build.
|
|
92
|
+
API and CLI specs exercise byte preservation, unknown-year/leap policies,
|
|
93
|
+
Unicode folding, raw labels, malformed input, and deterministic output. No live
|
|
94
|
+
personal store is inspected. No calendar-client import certification is claimed.
|
|
95
|
+
|
|
96
|
+
[README](../README.md) · [ABBU evidence](ABBU.md)
|
|
97
|
+
|
|
98
|
+
—
|
|
99
|
+
Stan Carver II
|
|
100
|
+
Made in Texas 🤠
|
|
101
|
+
https://stancarver.com
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<!-- docs/MACHINE_JSON.md -->
|
|
2
|
+
|
|
3
|
+
# Machine JSON Contract
|
|
4
|
+
|
|
5
|
+
`--json` selects one structured operation with no human chatter on stdout.
|
|
6
|
+
Every handled success, no-match, validation, or input/output failure emits one
|
|
7
|
+
UTF-8 JSON document followed by a newline. This is buffered JSON, not streaming
|
|
8
|
+
JSON Lines. Consumers must inspect exit status before interpreting the document.
|
|
9
|
+
Broken output pipes and unexpected programmer errors are not guaranteed a JSON
|
|
10
|
+
response. Human CLI modes and `--format json` without `--json` remain unchanged.
|
|
11
|
+
|
|
12
|
+
## Operations
|
|
13
|
+
|
|
14
|
+
| Options | Success document | Empty result |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| `--json` (optionally `--format json`) | Existing contact JSON array | `[]`, status 0 |
|
|
17
|
+
| `--search TERM --json`, `--email ADDRESS --json`, `--phone NUMBER --json` | Existing contact JSON array | `[]`, status 1 |
|
|
18
|
+
| Timestamp bounds plus `--json` | Existing contact JSON array | `[]`, status 1 |
|
|
19
|
+
| `--stats --json` | Integer `total_contacts`, `with_email`, `with_phone` | All zero, status 0 |
|
|
20
|
+
| `--schema --json` | Existing `schema_report` object | Empty `databases`, status 0 |
|
|
21
|
+
| `--sources --json` | Existing source metadata array | `[]`, status 0 |
|
|
22
|
+
| `--groups --json` | Existing group metadata array | `[]`, status 0 |
|
|
23
|
+
| `--dedupe --json` | Array of `{email, contacts}` | `[]`, status 0 |
|
|
24
|
+
| `--matches` (optional `--json`) | Identity suggestion array | `[]`, status 0 |
|
|
25
|
+
| `--diagnostics` (optional `--json`) | Parser diagnostic array after parsing | `[]`, status 0 |
|
|
26
|
+
| `--extract-images DIR --json` | `{files, diagnostics}` | Two empty arrays, status 0 |
|
|
27
|
+
| `--diff PATH --json` | [Snapshot comparison](SNAPSHOT_DIFF.md), schema version 1 | Empty category arrays, status 0 |
|
|
28
|
+
| `--version --json`, `--help --json` | `{version}` or `{help}` strings | Not applicable |
|
|
29
|
+
|
|
30
|
+
Only one operation is allowed, except timestamp bounds can accompany one search.
|
|
31
|
+
`--output`, photo mode, or a non-JSON export format conflict with machine mode.
|
|
32
|
+
`--format json` may accompany bare `--json`, but not another operation. Validation
|
|
33
|
+
precedes input reads and image copying. Help/version terminate immediately as
|
|
34
|
+
before. Supply exactly one archive path, or `--live`/`--live-path PATH` with no
|
|
35
|
+
positional arguments. Live schema/search/extraction remain unsupported; live
|
|
36
|
+
stats, contacts, sources/groups, diagnostics, duplicates and matches are supported.
|
|
37
|
+
No live-store snapshot guarantee is added. `--strict` retains fail-fast parsing.
|
|
38
|
+
|
|
39
|
+
## Schemas And Meaning
|
|
40
|
+
|
|
41
|
+
Contact objects are exactly `JsonExporter#payload`: absent optional scalar
|
|
42
|
+
fields are omitted, existing empty collections remain arrays, provenance and
|
|
43
|
+
raw labels survive, and timestamps use the existing ISO 8601 representation.
|
|
44
|
+
This method is now public for callers needing Ruby hashes without serializing
|
|
45
|
+
and reparsing JSON. Source/group metadata and schema reports retain their
|
|
46
|
+
previous shapes. Do not depend on whitespace or JSON object key ordering.
|
|
47
|
+
|
|
48
|
+
Duplicate `email` is the existing first-email hash used by `Deduplicator#duplicates`,
|
|
49
|
+
not a newly normalized string. `contacts` contains the full contact objects.
|
|
50
|
+
`--matches` instead uses `Deduplicator#matches`: each object has full `left` and
|
|
51
|
+
`right` contacts, two `sources` (possibly null), `evidence` containing `type`,
|
|
52
|
+
`normalized`, `left_raw`, `right_raw`, numeric `score`, string `confidence`, and
|
|
53
|
+
string `status`. These are suggestions, not identity truth; no merge is performed.
|
|
54
|
+
Weak or competing candidates retain the existing ambiguity status.
|
|
55
|
+
|
|
56
|
+
Parser diagnostics contain `category`, `message`, `parser`, `source`, `context`.
|
|
57
|
+
With `--diagnostics` they appear on stdout only. For other machine operations,
|
|
58
|
+
nonfatal parser diagnostics stay on stderr as text and never corrupt stdout.
|
|
59
|
+
Schema inspection alone does not parse contacts or force parser diagnostics.
|
|
60
|
+
|
|
61
|
+
Image results contain `files` and extraction `diagnostics`. Each file includes
|
|
62
|
+
the full `contact`, raw `image_uri`, absolute string `source_path` and output
|
|
63
|
+
`path`, and `media_type`. Extraction diagnostics use the existing `code`,
|
|
64
|
+
`contact_name`, `image_uri`, and optional `source_path`/`detail`. Partial copies
|
|
65
|
+
and collisions are reported in that array with status 0, as in existing
|
|
66
|
+
extraction; callers must inspect diagnostics to require complete extraction.
|
|
67
|
+
Directory creation failures instead produce an error and status 1. Source
|
|
68
|
+
archives remain read-only; extraction creates separate files without overwriting.
|
|
69
|
+
|
|
70
|
+
## Errors And Exit Codes
|
|
71
|
+
|
|
72
|
+
| Status | Meaning in machine mode |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| 0 | Operation completed (possibly with nonfatal diagnostics) |
|
|
75
|
+
| 1 | Search had no matches (`[]`), or live input/filesystem failure (`error` object) |
|
|
76
|
+
| 2 | Invalid options/input, conflicting operations, invalid timestamp, or parse failure |
|
|
77
|
+
|
|
78
|
+
Errors have exactly the shape `{"error":{"code":"invalid_input","message":"..."}}`.
|
|
79
|
+
Codes are `invalid_option` for option parsing, `invalid_input` for validation or
|
|
80
|
+
parse failures, and `input_output_error` for live access/filesystem errors.
|
|
81
|
+
Messages aid humans; branch on the code and exit status, not message wording.
|
|
82
|
+
Parser diagnostics can still appear on stderr after a strict failure.
|
|
83
|
+
|
|
84
|
+
Migration from 0.9: JSON failures now produce this document rather than empty
|
|
85
|
+
stdout; previously ignored operation combinations are rejected. Bare `--json`
|
|
86
|
+
now lists contacts. Search no-match status and successful legacy JSON shapes are
|
|
87
|
+
unchanged. Human CLI error behavior is not normalized by this change.
|
|
88
|
+
|
|
89
|
+
## Automation And Privacy
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
abbu Contacts.abbu --stats --json >stats.json 2>diagnostics.log
|
|
93
|
+
abbu Contacts.abbu --diagnostics >parser-diagnostics.json
|
|
94
|
+
abbu Contacts.abbu --matches >identity-suggestions.json 2>diagnostics.log
|
|
95
|
+
abbu Contacts.abbu --sources --json | jq '.[].contact_count'
|
|
96
|
+
|
|
97
|
+
# Do not let jq's status hide ABBU's failure; inspect the producer first.
|
|
98
|
+
if abbu Contacts.abbu --email person@example.test --json >result.json 2>diagnostics.log; then
|
|
99
|
+
jq '.[].name' result.json
|
|
100
|
+
else
|
|
101
|
+
status=$?
|
|
102
|
+
printf 'ABBU status: %s\n' "$status" >&2
|
|
103
|
+
jq . result.json
|
|
104
|
+
fi
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
All outputs are sensitive. Full contact/identity/image payloads expose contact
|
|
108
|
+
values, raw labels, identifiers, photo paths and provenance; image extraction
|
|
109
|
+
also writes photos. Parser diagnostics deliberately retain the existing limited
|
|
110
|
+
contexts, but paths and messages can identify local accounts. Schema/source/group
|
|
111
|
+
reports are not sanitized logs. Error messages may contain caller paths or input.
|
|
112
|
+
JSON does not grant permission to send any of this to a remote agent, store it in
|
|
113
|
+
public CI artifacts, or publish logs. Use controlled destinations and permissions.
|
|
114
|
+
No Apple-private semantics are inferred by these serializers.
|
|
115
|
+
|
|
116
|
+
JSON schemas are public API under the repository's pre-1.0 SemVer policy.
|
|
117
|
+
Breaking shape/type/meaning changes require explicit migration notes and a minor
|
|
118
|
+
version; compatible bug fixes use a patch. Future diff/history or optional MCP
|
|
119
|
+
adapters should consume these public representations without changing established
|
|
120
|
+
operations or imposing an MCP runtime dependency on ABBU core.
|
|
121
|
+
|
|
122
|
+
See [README](../README.md), [API stability](API_STABILITY.md), and
|
|
123
|
+
[format evidence](ABBU.md).
|
|
124
|
+
|
|
125
|
+
—
|
|
126
|
+
Stan Carver II
|
|
127
|
+
Made in Texas 🤠
|
|
128
|
+
https://stancarver.com
|
data/docs/MCP.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<!-- docs/MCP.md -->
|
|
2
|
+
|
|
3
|
+
# Optional Read-Only MCP Adapter
|
|
4
|
+
|
|
5
|
+
The adapter connects a trusted local MCP host to one explicitly selected ABBU
|
|
6
|
+
archive or live-store directory. It consumes public ABBU APIs and the
|
|
7
|
+
[machine JSON contract](MACHINE_JSON.md); it never reads parser internals or
|
|
8
|
+
defines new Apple storage semantics.
|
|
9
|
+
|
|
10
|
+
## Dependency And Execution Boundary
|
|
11
|
+
|
|
12
|
+
The adapter ships as an opt-in executable/require path inside ABBU rather than a
|
|
13
|
+
second companion gem. This keeps its version, public payloads and tests together
|
|
14
|
+
without a second release pipeline. The official `mcp` gem is **not** a runtime
|
|
15
|
+
dependency of `abbu`, and `require 'abbu'` does not load it. Only adapter users
|
|
16
|
+
install/declare it. Repository development/test uses the SDK for real protocol
|
|
17
|
+
integration tests, not a mock protocol implementation.
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# Application Gemfile (once this ABBU development version is released)
|
|
21
|
+
gem 'abbu', '~> 0.18.0'
|
|
22
|
+
gem 'mcp', '~> 1.6.1'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
abbu-mcp --help
|
|
27
|
+
abbu-mcp /absolute/path/Contacts.abbu
|
|
28
|
+
abbu-mcp --live-path /absolute/path/AddressBook --strict
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use the same Ruby/Bundler environment for both gems. From this repository:
|
|
32
|
+
`mise exec -- bundle exec ruby bin/abbu-mcp /absolute/path/Contacts.abbu`.
|
|
33
|
+
The executable uses Ruby directly, as does packaged `abbu`; it does not source
|
|
34
|
+
development-only shell helpers that are absent from the installed gem.
|
|
35
|
+
Configure an MCP host's stdio subprocess with that command/argument array.
|
|
36
|
+
Do not put shell expansion or credentials in arguments. There is no HTTP server,
|
|
37
|
+
network listener, remote transport or automatically discovered live store.
|
|
38
|
+
|
|
39
|
+
Missing SDK, invalid startup configuration or unavailable input exits 2 with a
|
|
40
|
+
generic stderr message, no path/exception dump. `--help` prints usage and exits
|
|
41
|
+
without opening input. Once serving, stdout is exclusively SDK JSON-RPC frames.
|
|
42
|
+
|
|
43
|
+
## Tools
|
|
44
|
+
|
|
45
|
+
| Tool | Arguments | `structuredContent.data` |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `abbu_query` | `mode`, `value`, optional `offset`, `limit` | `{contacts, offset, limit, has_more}` |
|
|
48
|
+
| `abbu_stats` | None | `{total_contacts, with_email, with_phone}` |
|
|
49
|
+
| `abbu_sources` | None | Existing source metadata array |
|
|
50
|
+
| `abbu_groups` | None | Existing group metadata array |
|
|
51
|
+
| `abbu_diagnostics` | None | Existing parser diagnostic array |
|
|
52
|
+
|
|
53
|
+
Query modes are `search` (partial name/email), `email` and `phone` (existing exact
|
|
54
|
+
normalized lookup), and `modified_since` (timezone-explicit ISO 8601 lower bound).
|
|
55
|
+
Every query requires a string `value`. Empty search retains public Query semantics:
|
|
56
|
+
no matches. Results keep parser ordering and are never automatically deduplicated.
|
|
57
|
+
Offset defaults to 0, limit to 100; maximum limit is 500. `has_more` explicitly
|
|
58
|
+
indicates remaining matches. These are payload bounds, not streaming parsing or
|
|
59
|
+
an archive size limit: the input and query still eagerly materialize contacts.
|
|
60
|
+
|
|
61
|
+
Each successful tool result includes the same JSON envelope in text content and
|
|
62
|
+
structured content. Output schemas specify the envelope, array/object types,
|
|
63
|
+
query pagination fields and integer statistics; nested contact/source/group
|
|
64
|
+
objects retain their existing ABBU contracts. Consumers must accept optional
|
|
65
|
+
contact keys. No-match query results are successful with `contacts: []`.
|
|
66
|
+
|
|
67
|
+
Tool schemas reject undeclared arguments, so a caller cannot substitute another
|
|
68
|
+
path. The operator chooses the input before server startup. My Card is omitted
|
|
69
|
+
until evidence establishes it. Image extraction, export, merging, writing,
|
|
70
|
+
tagging, releases, shell execution and arbitrary filesystem access have no tool.
|
|
71
|
+
|
|
72
|
+
Output validation remains enabled. In the pinned SDK 1.6.1,
|
|
73
|
+
[`validate_tool_call_result!`](https://github.com/modelcontextprotocol/ruby-sdk/blob/v1.6.1/lib/mcp/server.rb#L1539-L1545)
|
|
74
|
+
validates successful results against the declared `{data: ...}` schema and
|
|
75
|
+
deliberately skips that success schema for `isError: true`. Adapter failures use
|
|
76
|
+
the separately documented `{error: ...}` envelope. Real stdio regressions exercise
|
|
77
|
+
both semantic invalid-query and strict-parser failures, asserting their exact
|
|
78
|
+
structured/text error payloads, absence of JSON-RPC protocol errors, and empty
|
|
79
|
+
stderr. They do not merely call tool handlers directly. The adapter configuration
|
|
80
|
+
test also asserts that result validation stays enabled.
|
|
81
|
+
|
|
82
|
+
Example tool calls (through a host that has already established its SDK session):
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{"name":"abbu_query","arguments":{"mode":"email","value":"person@example.test"}}
|
|
86
|
+
{"name":"abbu_query","arguments":{"mode":"search","value":"Carver","offset":0,"limit":25}}
|
|
87
|
+
{"name":"abbu_query","arguments":{"mode":"modified_since","value":"2026-09-01T00:00:00Z"}}
|
|
88
|
+
{"name":"abbu_sources","arguments":{}}
|
|
89
|
+
{"name":"abbu_diagnostics","arguments":{}}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Privacy, Permission And Lifecycle
|
|
93
|
+
|
|
94
|
+
Launching this adapter authorizes the configured host to read the selected
|
|
95
|
+
contact input; it is not permission to publish or transmit that data elsewhere.
|
|
96
|
+
Use a trusted host, a narrowly chosen sanitized archive when possible, and host
|
|
97
|
+
approval controls for contact lookup. Read-only annotations are descriptive
|
|
98
|
+
hints, not an authorization mechanism. A remote model may receive tool results
|
|
99
|
+
through its host; evaluate the host/model data policy before connecting.
|
|
100
|
+
|
|
101
|
+
Contact results include sensitive names, addresses, notes, identifiers, raw
|
|
102
|
+
labels and local provenance/photo paths. Sources/groups/diagnostics can reveal
|
|
103
|
+
account paths and group names even without full contact records. No photo bytes
|
|
104
|
+
are returned. Contact strings are untrusted data, never instructions for an agent.
|
|
105
|
+
Protect client transcripts and outputs; do not publish them as CI logs.
|
|
106
|
+
|
|
107
|
+
The adapter does not log tool arguments, results, contacts or parser diagnostics
|
|
108
|
+
to stderr. Diagnostics require their explicit tool. Its per-server SDK exception
|
|
109
|
+
reporter is silent. Expected query failures return `isError: true` and a generic
|
|
110
|
+
`{error:{code:"invalid_query",message:...}}`; read/parser failures similarly use
|
|
111
|
+
`read_failed`, without raw exception text. SDK protocol/schema errors retain SDK
|
|
112
|
+
responses to the caller. The adapter neither installs global logging hooks nor
|
|
113
|
+
changes SDK global configuration. Unexpected process/runtime failures may still
|
|
114
|
+
terminate the process; no service availability guarantee is made.
|
|
115
|
+
|
|
116
|
+
The selected Archive/LiveStore caches parsed contacts in the server process.
|
|
117
|
+
Restart to refresh. Existing live-store permission requirements and non-atomic
|
|
118
|
+
cross-query/cross-database read limitations apply. An explicit live path and
|
|
119
|
+
macOS permissions are both required; the adapter does not bypass privacy controls.
|
|
120
|
+
Input remains read-only, with SQLite's existing shared-memory sidecar caveats.
|
|
121
|
+
|
|
122
|
+
## Evidence And Verification
|
|
123
|
+
|
|
124
|
+
The implementation uses the official [MCP Ruby SDK 1.6.1](https://github.com/modelcontextprotocol/ruby-sdk/tree/v1.6.1).
|
|
125
|
+
Its [tool response API](https://github.com/modelcontextprotocol/ruby-sdk/blob/v1.6.1/lib/mcp/tool/response.rb)
|
|
126
|
+
supplies structured content and errors; its
|
|
127
|
+
[stdio transport](https://github.com/modelcontextprotocol/ruby-sdk/blob/v1.6.1/lib/mcp/server/transports/stdio_transport.rb)
|
|
128
|
+
owns framing and protocol lifecycle. ABBU does not implement JSON-RPC itself.
|
|
129
|
+
The [MCP tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)
|
|
130
|
+
informs the explicit input/output schemas and duplicate text/structured content.
|
|
131
|
+
|
|
132
|
+
Synthetic tests cover direct public APIs and a real SDK stdio subprocess:
|
|
133
|
+
2025-11-25 initialization, tools listing, structured stats, and rejection of
|
|
134
|
+
undeclared arguments. SDK-supported newer/older protocol versions remain SDK
|
|
135
|
+
capabilities, not independent ABBU conformance certification. No real address
|
|
136
|
+
books or live user account are read by these tests. Run canonical `bin/spec`,
|
|
137
|
+
`bin/lint`, `bin/package`, and `mise exec -- rbs -I sig validate`.
|
|
138
|
+
|
|
139
|
+
The adapter is pre-1.0 public API and follows ABBU SemVer. Publication remains
|
|
140
|
+
Sheriff-gated; installing a development branch is not a published release.
|
|
141
|
+
|
|
142
|
+
See [README](../README.md), [format evidence](ABBU.md), and
|
|
143
|
+
[API stability](API_STABILITY.md).
|
|
144
|
+
|
|
145
|
+
—
|
|
146
|
+
Stan Carver II
|
|
147
|
+
Made in Texas 🤠
|
|
148
|
+
https://stancarver.com
|