An ATProto professional-networking prototype. Reuses Sifa and XPTracker lexicons rather than minting its own.
linkedat AGENTS.md
6.4 kB · 87 lines
Markdown
at main

LinkedAt Agent Guide #

Project Snapshot #

LinkedAt is a pnpm/Turbo TypeScript workspace for an ATProto professional-networking MVP. The product intentionally reuses existing public records wherever possible:

  • Sifa id.sifa.profile.* records for profiles, positions, education, and skills.
  • XPTracker app.xptracker.job.listing records for MVP job listings.
  • LinkedAt-owned app.linkedat.profile.resume only as a resume metadata sidecar.

ATProto repository records and blobs are public. UI copy and implementation decisions must not imply that uploaded resumes, imported JSON Resume data, or profile/job records are private.

Workspace Layout #

  • apps/web: React Router v7 Framework Mode app, React UI, route loaders/actions, public pages, the in-memory index and its stand-ins under app/data/, Node test-runner tests discovered recursively through scripts/run-tests.mjs, and Playwright e2e tests.
  • apps/indexer: Jetstream/backfill worker and record normalization handlers.
  • packages/atproto: OAuth, identity resolution, record read/write adapters, and schema.org projection.
  • packages/db: Postgres client, schema, repositories, and migration script.
  • packages/lexicons: pinned external lexicons, LinkedAt sidecar lexicons, validation scripts, and lexicon tests.
  • docs: protocol research, namespace policy, and schema mapping decisions.

Commands #

Use pnpm, not npm or yarn. This repo expects Node >=24 and pnpm >=11.

  • Install dependencies: pnpm install
  • Start the web app: pnpm dev
  • Start the indexer: pnpm indexer:dev
  • Build all workspaces: pnpm build
  • Type check: pnpm typecheck
  • Lint: pnpm lint (ESLint, type-aware)
  • Format: pnpm format / check with pnpm format:check (Prettier)
  • Run all unit tests: pnpm test
  • Run web e2e tests: pnpm --filter @linkedat/web test:e2e
  • Run database migrations: pnpm db:migrate
  • Validate lexicons: pnpm --filter @linkedat/lexicons validate
  • Refresh pinned external lexicons when they are missing or intentionally stale: pnpm --filter @linkedat/lexicons fetch:external

For narrow changes, prefer the relevant filtered package command first, then run the broader command when the change crosses package boundaries.

What Is Real #

Read the Status table in README.md before assuming a feature works. Most of this app is not wired to anything yet, and the parts that are faked are confined to three files:

  • apps/web/app/data/repositories.server.ts stands in for the index.
  • apps/web/app/data/record-writer.server.ts stands in for a PDS.
  • apps/web/app/data/dev-actor.server.ts stands in for the signed-in user.

Two rules follow from that:

  • Do not build a feature that only appears to work. If a form cannot publish, disable it and say why in the UI, the way the profile, experience, and resume forms do. An action that validates input and discards the result is worse than an obvious gap, because it invites the next person to build on nothing.
  • Do not widen the fakes. New functionality either runs the real code path against a stand-in, the way job posting does, or it is explicitly not implemented. Adding a fourth stand-in needs a reason.

Testing Rules #

  • A test must be able to fail for a reason a user would care about. Do not assert that a source file contains a string, that a stylesheet contains a hex value, or that an import exists. Those tests only fail when someone edits the file they read. This suite had 26 of them; they were deleted, not replaced.
  • Prove new tests can fail. Break the thing on purpose, watch the test go red, then put it back. This is cheap and it is the only evidence that a green suite means anything.
  • Visual and layout behaviour belongs in Playwright, against a real browser.
  • Tests that share the app's in-memory index (job-publish-flow.test.ts) must use distinctive data and must never assert on totals.

Development Conventions #

  • TypeScript is strict, with noUncheckedIndexedAccess and exactOptionalPropertyTypes enabled.
  • Shared package imports should use workspace package names such as @linkedat/atproto, @linkedat/db, and @linkedat/lexicons.
  • Keep package responsibilities separated: web UI and server actions in apps/web, protocol adapters in packages/atproto, persistence in packages/db, and lexicon validation/data in packages/lexicons.
  • Tests use Node's built-in test runner for package/unit tests. The web app runs tests through apps/web/scripts/run-tests.mjs, which invokes node --import tsx --test with explicit .test.ts and .test.tsx files discovered under apps/web/tests. Web e2e tests use Playwright.
  • Do not add secrets to the repo. Copy .env.example to .env locally for real OAuth, database, or indexer runs.

Protocol Rules #

  • Do not create a primary app.linkedat.job.* lexicon for the MVP.
  • Do not create LinkedAt-owned replacements for Sifa profile, position, education, or skill records.
  • LinkedAt job creation must satisfy XPTracker's required fields: title, company, sourceUri, and createdAt.
  • If richer job semantics are needed later, first pursue compatible XPTracker extensions. A LinkedAt-owned job namespace is a future decision point only.
  • Keep external lexicons pinned under packages/lexicons/external. Refresh them intentionally with pnpm --filter @linkedat/lexicons fetch:external rather than ad hoc edits, especially before validation when pinned files are missing or stale.
  • Schema.org JSON-LD should only project truthful data present in indexed Sifa or XPTracker records. Omit unavailable fields instead of inventing values.

Verification Expectations #

Before handing off code changes, run the smallest meaningful verification command and report exactly what ran. Typical defaults:

  • Shared/package logic: pnpm typecheck, pnpm lint, and pnpm test
  • Web rendering or server-action changes: pnpm --filter @linkedat/web test plus pnpm typecheck
  • Browser workflow changes: pnpm --filter @linkedat/web test:e2e
  • Lexicon changes: pnpm --filter @linkedat/lexicons validate and pnpm --filter @linkedat/lexicons test

If a command cannot run because local services or environment variables are missing, state that explicitly and include the next best verification performed. Never report work as done without running something; "typecheck passes" is not evidence that a feature works.