113 lines
6.9 KiB
Markdown
113 lines
6.9 KiB
Markdown
# 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/`.
|