# Tasks: enterprise-macos-contacts **Change ID**: enterprise-macos-contacts **Package**: `@suaveplan/macos-contacts` **Target path**: `universal/platform/macos-contacts` **Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/logger`, `@suaveplan/retry` --- ## 0. Pre-flight (non-negotiable) - [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each) - [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest. ## 1. Scaffold - [ ] Create `universal/platform/macos-contacts/` with `src/`, `tests/`, and feature subdirectories per the layout in `proposal.md`. - [ ] Write `package.json` at version `0.1.0`: - `"type": "module"`, `"sideEffects": false` - `exports` map: `"."` barrel + `"./core/contacts-client/contacts-client"`, `"./core/process-runner/process-runner"`, `"./utils/parse-contact"`, `"./types/common"` - `dependencies`: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/logger workspace:^`, `@suaveplan/retry workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build` - `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `fast-check catalog:testing`, `@suaveplan/testing workspace:^` - [ ] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`. - [ ] Write `vitest.config.ts` using `createTestConfig()`. Add platform guard: skip osascript tests when `process.platform !== 'darwin'`. - [ ] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check` scripts. --- ## 2. Types and error codes - [ ] Define all public types in `src/types/common.ts`: `MacosContact`, `MacosAddress`, `MacosGroup`, `CreateContactData`, `ListContactsOptions`, `SearchContactsOptions`, `ContactsClientOptions`, `ProcessRunner`, `OsascriptRunnerOptions`. - [ ] Write `src/types/common.md` (≥200 words, simple + advanced examples). - [ ] Define error codes in `src/error-codes.ts`: `MacosContactsErrorCode` union, metadata, `registerMacosContactsErrorCodes()`. - [ ] Write Zod schemas in `src/types/schemas.ts` + `src/types/schemas.md`. --- ## 3. Process runner (`src/core/process-runner/`) - [ ] Implement `createOsascriptRunner(options?)` — wraps `Bun.spawn` (or Node `execFile` fallback — never `exec`, to prevent shell injection) to run AppleScript via `osascript` with `-e` as a separate argument (not interpolated into a shell string). - [ ] Implement default `timeout` of 10 000 ms; throw `MacosContactsError(CONTACTS_SCRIPT_FAILED)` on non-zero exit. - [ ] Expose the `ProcessRunner` interface so tests can inject a fake runner without module-level mocking. - [ ] Write `process-runner.test.ts`: fake runner happy path, timeout, non-zero exit code. - [ ] Write `process-runner.md` (≥200 words). --- ## 4. Contact parsing (`src/utils/parse-contact.ts`) - [ ] Parse the tab-delimited or structured text output returned by AppleScript contact scripts into `MacosContact` objects. - [ ] Implement `parseContact(raw: string): MacosContact` and `parseContacts(raw: string): MacosContact[]`. - [ ] Validate output with `MacosContactSchema`; throw `MacosContactsError(CONTACTS_PARSE_FAILED)` on schema error. - [ ] Write `parse-contact.test.ts` (≤500 lines): full contact, empty fields, multi-email, no phone. - [ ] Write `parse-contact.md` (≥200 words). --- ## 5. AppleScript templates (`src/scripts/`) - [ ] Write `list-contacts.applescript`: returns all contacts with id, name, emails, phones. - [ ] Write `get-contact.applescript`: looks up by id, returns full contact record. - [ ] Write `search-contacts.applescript`: filters by query string across specified fields. - [ ] Write `create-contact.applescript`: creates new contact from JSON-encoded data passed as argument. - [ ] Write `update-contact.applescript`: updates existing contact fields. - [ ] Write `delete-contact.applescript`: deletes contact by id. - [ ] Write `list-groups.applescript`: returns all groups. --- ## 6. Contacts client (`src/core/contacts-client/`) - [ ] Implement `createContactsClient(options?)` — wires process runner (defaults to `createOsascriptRunner()`), logger, retry. - [ ] Implement `listContacts(options?, signal?)` — calls list script, parses output; OTel span `macos_contacts.list`. - [ ] Implement `getContact(id, signal?)` — OTel span `macos_contacts.get`. - [ ] Implement `searchContacts(query, options?, signal?)` — OTel span `macos_contacts.search`. - [ ] Implement `createContact(data, signal?)` — validates with `CreateContactDataSchema`; OTel span `macos_contacts.create`. - [ ] Implement `updateContact(id, data, signal?)` — OTel span `macos_contacts.update`. - [ ] Implement `deleteContact(id, signal?)` — OTel span `macos_contacts.delete`. - [ ] Implement `listGroups(signal?)`, `addToGroup(...)`, `removeFromGroup(...)`. - [ ] All async methods check `signal.aborted` before invoking runner. - [ ] Wrap runner failures with retry via `@suaveplan/retry`. - [ ] Write `contacts-client.test.ts` (≤500 lines) using injected fake `ProcessRunner`. No osascript calls in unit tests. - [ ] Write `contacts-client.md` (≥200 words). --- ## 7. Property-based tests (`tests/property.test.ts`) - [ ] `parseContacts` round-trip: for valid `MacosContact` objects, serialise to AppleScript output format and re-parse; result equals original. - [ ] Schema idempotency: `MacosContactSchema.parse(MacosContactSchema.parse(c))` equals `parse(c)`. - [ ] Search filtering: contacts matching query string are always a subset of all contacts. --- ## 8. Docs - [ ] All per-file `.md` files: `common.md`, `schemas.md`, `process-runner.md`, `parse-contact.md`, `contacts-client.md`. - [ ] `universal/platform/macos-contacts/README.md`: purpose, macOS requirement note, installation, quick-start, API section, dependency table. --- ## 9. Verify and publish - [ ] `bunx turbo lint --filter=@suaveplan/macos-contacts` — zero errors. - [ ] `bunx turbo typecheck --filter=@suaveplan/macos-contacts` — zero errors. - [ ] `bunx turbo test --filter=@suaveplan/macos-contacts` — all tests pass (unit tests with fake runner). - [ ] `bunx turbo test:coverage --filter=@suaveplan/macos-contacts` — 100% on all touched modules. - [ ] `bun run docs:check --filter=@suaveplan/macos-contacts` — zero errors. - [ ] `bun scripts/publish.ts --package @suaveplan/macos-contacts`. - [ ] Update `CLAUDE.md` package listing. - [ ] Archive: move `openspec/changes/enterprise-macos-contacts/` to `openspec/archive/2026-04-24-enterprise-macos-contacts/`.