|
| 1 | +# Data Source Discovery |
| 2 | + |
| 3 | +Annotask scans a project's data-fetching libraries and local code to build a catalog of data sources used by: |
| 4 | + |
| 5 | +- Audit > Data in the shell |
| 6 | +- `GET /__annotask/api/data-sources` |
| 7 | +- `GET /__annotask/api/data-source-examples/:name` |
| 8 | +- `GET /__annotask/api/data-source-details/:name` |
| 9 | +- `GET /__annotask/api/data-source-bindings/:name` |
| 10 | +- `annotask_get_data_sources`, `annotask_get_data_source_examples`, `annotask_get_data_source_details` |
| 11 | +- the `annotask data-sources`, `data-source-examples`, `data-source-details` CLI commands |
| 12 | +- per-task `data_context` enrichment (the primary source a task is bound to) |
| 13 | + |
| 14 | +The scanner is workspace-aware, so in monorepos it aggregates data libraries and definitions across sibling packages and MFEs rather than only the current package. |
| 15 | + |
| 16 | +## Two Layers |
| 17 | + |
| 18 | +The catalog distinguishes **libraries** (data-fetching packages found in `package.json`) from **project entries** (concrete hooks, stores, fetch wrappers, GraphQL operations, and tRPC routers defined in `src/`). |
| 19 | + |
| 20 | +A library entry only appears if it is both installed *and* at least one of its recognized identifiers is actually used somewhere in `src/`. This avoids listing packages that are declared but never imported. |
| 21 | + |
| 22 | +## What Gets Extracted |
| 23 | + |
| 24 | +### Library entries |
| 25 | + |
| 26 | +```ts |
| 27 | +interface DataSourceLibrary { |
| 28 | + name: string |
| 29 | + version?: string |
| 30 | + detected_patterns: string[] // identifiers this library exports that we recognize |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +### Project entries |
| 35 | + |
| 36 | +```ts |
| 37 | +interface ProjectDataEntry { |
| 38 | + kind: 'composable' | 'signal' | 'store' | 'fetch' | 'graphql' | 'loader' | 'rpc' |
| 39 | + name: string |
| 40 | + display_name?: string // e.g. "localhost:4320 /api/health" for inline fetches |
| 41 | + file: string // workspace-relative |
| 42 | + line?: number // 1-based definition line |
| 43 | + endpoint?: string // literal endpoint or query key when extractable |
| 44 | + resolved_endpoint?: string // rewritten through the nearest vite.config proxy |
| 45 | + used_count: number // ranking signal (non-definition references across src/) |
| 46 | + hint_symbols?: string[] // local vars holding the fetch result (for the binding analyzer) |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +### Catalog shape |
| 51 | + |
| 52 | +```ts |
| 53 | +interface DataSourceCatalog { |
| 54 | + libraries: DataSourceLibrary[] |
| 55 | + project_entries: ProjectDataEntry[] // sorted by used_count desc |
| 56 | + scannedAt: number |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +### Runtime reference (on tasks) |
| 61 | + |
| 62 | +The per-task `data_context.sources[]` uses a narrower `DataSource` shape — identifier, kind, module, endpoint, method, line, dynamic-endpoint flag, and (when the endpoint matches an API schema) a `response_schema_ref` + `schema_in_repo` pair. See `src/schema.ts` for the canonical definitions. |
| 63 | + |
| 64 | +## Current Shell UX |
| 65 | + |
| 66 | +The Audit > Data page lives in `src/shell/components/DataSourcesPage.vue` and is driven by `src/shell/composables/useDataSources.ts`. |
| 67 | + |
| 68 | +Three sub-tabs: |
| 69 | + |
| 70 | +- `hooks` — project data sources (composables, stores, signals, fetches, GraphQL, tRPC) |
| 71 | +- `libraries` — detected data-fetching libraries |
| 72 | +- `apis` — discovered API schemas (OpenAPI, GraphQL, tRPC) — served by a separate scanner (see `api-schema-scanner.ts`), but shown alongside for context |
| 73 | + |
| 74 | +Filters: |
| 75 | + |
| 76 | +- free-text search across name / file / endpoint |
| 77 | +- `All` vs `On page` (driven by live highlight rects from the iframe) |
| 78 | +- MFE filter when workspace MFEs are present |
| 79 | + |
| 80 | +Detail pane shows signature, return type, body excerpt, leading imports, co-located siblings, and the binding graph (rendering sites where this source is consumed). Highlights in the app iframe come from the binding-graph `sites`, not from grep. |
| 81 | + |
| 82 | +## Workspace Behavior |
| 83 | + |
| 84 | +`scanDataSources()` uses `resolveWorkspace()` to read dependencies and walk `src/` across every workspace package. File paths are relativized against the workspace root so cross-MFE references stay unambiguous, and `useWorkspace()` in the shell maps those paths back to MFE ids for filtering. |
| 85 | + |
| 86 | +Path-only endpoints like `/api/health` are resolved through the nearest `vite.config`'s `server.proxy` — so a Vue MFE proxying `/api` to a FastAPI service at :4320 doesn't get its highlights attributed to a Go service at :4330 that happens to expose the same path. |
| 87 | + |
| 88 | +## Scanner Strategies |
| 89 | + |
| 90 | +The scanner runs in order (see `scanDataSourcesUncached` in `src/server/data-source-scanner.ts`): |
| 91 | + |
| 92 | +### 1. Library detection |
| 93 | + |
| 94 | +Reads `dependencies` + `devDependencies` from every workspace `package.json` and cross-checks against `DATA_LIB_PATTERNS` — the hand-curated map of package name → identifiers we know how to recognize. A library only survives if at least one of its identifiers actually appears in source. |
| 95 | + |
| 96 | +### 2. Project entry detection |
| 97 | + |
| 98 | +Walks each package's `src/` (capped at 5000 files) and matches `ENTRY_PATTERNS` — specificity-ordered regexes for: |
| 99 | + |
| 100 | +- named composables / hooks (`export function useX(...)`, `export const useX = (...)`) |
| 101 | +- Pinia stores (`defineStore`) |
| 102 | +- Zustand stores (`create(...)`) |
| 103 | +- Jotai atoms |
| 104 | +- Svelte stores (`writable`, `readable`, `derived`) |
| 105 | +- Solid primitives (`createSignal`, `createResource`, `createStore`) |
| 106 | +- GraphQL operations (tagged `` gql`...` ``) |
| 107 | +- tRPC routers (`createTRPCRouter`) |
| 108 | +- Fetch wrappers in API-ish directories |
| 109 | +- Inline fetches in component files (`fetch()`, `axios.*()`, `ofetch()`, `$fetch()`, `new URL()`, htmx `hx-*` attributes) |
| 110 | + |
| 111 | +First match wins, so more specific patterns take precedence over generic ones. |
| 112 | + |
| 113 | +### 3. Endpoint resolution |
| 114 | + |
| 115 | +Literal endpoints extracted from the definition body are run through `parseViteProxy()` against the nearest `vite.config` so `/api/health` becomes `http://localhost:4320/api/health`. |
| 116 | + |
| 117 | +### 4. Usage counting |
| 118 | + |
| 119 | +One combined alternation regex per file counts non-definition references to each entry's name. Definition lines are excluded by line number. `used_count` drives the sort order and the `used_only` filter. |
| 120 | + |
| 121 | +### 5. Hint symbols for inline fetches |
| 122 | + |
| 123 | +Inline fetches like `const health = await fetch('/api/health').then(r => r.json())` get an endpoint-derived name (`apiHealth`) that never appears verbatim in source, which would leave the binding analyzer with nothing to match. `collectHintSymbols()` captures the local variables that hold the fetch result (`health`) so the analyzer can still trace them into templates / JSX. |
| 124 | + |
| 125 | +## Recognized Libraries |
| 126 | + |
| 127 | +| Library | Kind | |
| 128 | +|---------|------| |
| 129 | +| `@tanstack/{react,vue,solid,svelte}-query`, `swr` | `composable` | |
| 130 | +| `@apollo/client`, `urql`, `@urql/{vue,svelte}`, `graphql-request` | `composable` / `graphql` | |
| 131 | +| `axios`, `ofetch`, `htmx.org` | `fetch` | |
| 132 | +| `pinia`, `vuex`, `zustand`, `@reduxjs/toolkit`, `react-redux`, `jotai`, `valtio`, `mobx`, `svelte`, `svelte/store`, `$app/stores` | `store` | |
| 133 | +| `solid-js` (`createSignal`, `createMemo`, `createEffect`) | `signal` | |
| 134 | +| `solid-js` (`createResource`) | `composable` | |
| 135 | +| `solid-js` (`createStore`) | `store` | |
| 136 | +| `vue-router`, `react-router{,-dom}`, `@remix-run/react`, `next`, `@solidjs/router` | `loader` | |
| 137 | +| `@trpc/client`, `@trpc/react-query`, `@trpc/next` | `rpc` | |
| 138 | + |
| 139 | +Full map in `DATA_LIB_PATTERNS` (`src/server/data-source-scanner.ts`). |
| 140 | + |
| 141 | +## Binding Graph |
| 142 | + |
| 143 | +The second layer — `resolveBindingGraph()` in `src/server/binding-analysis/` — traces where a data source is rendered. Two-pass: |
| 144 | + |
| 145 | +1. **Seed pass** — walk every supported file and run the framework analyzer (Vue, Svelte, JSX/TSX, plus a regex fallback) with `sourceName` as the taint seed. Record render sites and component prop edges. |
| 146 | +2. **Prop propagation** — for each prop edge, re-analyze the child file with the prop name seeded as tainted. This picks up patterns like `<PlanetCard :planet="planet" />` → `{{ planet.moons }}` in the child. |
| 147 | + |
| 148 | +Returns a `SourceBindingGraph` with `sites` (file + line + tainted symbols), `prop_edges`, a `partial` flag when any file fell back to file-level heuristics, and per-file diagnostics. Cached 60s, keyed by `projectRoot::sourceName::hintSymbols::scopeFile`. |
| 149 | + |
| 150 | +The shell uses `sites` as DOM highlight targets via the `data-annotask-file` / `data-annotask-line` attributes the transform injects on every rendered element. |
| 151 | + |
| 152 | +## Data-Context vs Data-Sources |
| 153 | + |
| 154 | +The catalog is project-wide. The per-task `data_context` is a narrower slice — "which sources power *this* element?" — resolved at task-create time by `src/server/data-context.ts`: |
| 155 | + |
| 156 | +| | Data-Source Catalog | Data Context | |
| 157 | +|---|---|---| |
| 158 | +| Scope | project-wide | per-file / per-task | |
| 159 | +| Driver | filesystem scan + pattern matching | task file + line + binding graph | |
| 160 | +| Returns | `DataSourceCatalog` | `DataContext` (sources + rendered_identifiers + route_bindings) | |
| 161 | +| Caching | 60s TTL, coalesced | probe cache keyed by realpath+mtime, FIFO evicted at 500 | |
| 162 | +| Used by | Data tab, agent exploration | task enrichment, agent anchor | |
| 163 | + |
| 164 | +`resolveDataContext()` also cross-references `scanApiSchemas()` to populate `response_schema_ref` on sources whose endpoints match a known operation. `resolveElementDataContext()` uses the binding graph with a ±3-line tolerance for element-level precision. |
| 165 | + |
| 166 | +`sources[0]` on a task is the one an agent should anchor on — nearest to `task.line`, with ties broken in the order `composable > signal > store > fetch > graphql > loader > rpc`. |
| 167 | + |
| 168 | +## Caching |
| 169 | + |
| 170 | +Catalog scans are cached in memory with a 60-second TTL. Concurrent scans are coalesced behind a single in-flight promise. `clearDataSourceCache()` also clears the vite-proxy lookup cache used by endpoint resolution. |
| 171 | + |
| 172 | +The binding graph has its own 60s TTL keyed per source + hint + scope. |
| 173 | + |
| 174 | +The data-context probe cache is keyed by realpath + mtime and evicts FIFO at 500 entries — it backs the fast UX path that needs a boolean + primary signal per file without a full resolve. |
| 175 | + |
| 176 | +## HTTP, MCP, And CLI Access |
| 177 | + |
| 178 | +### HTTP |
| 179 | + |
| 180 | +```bash |
| 181 | +curl http://localhost:5173/__annotask/api/data-sources |
| 182 | +curl http://localhost:5173/__annotask/api/data-sources?kind=composable&used_only=1 |
| 183 | +curl http://localhost:5173/__annotask/api/data-source-examples/useUserQuery?limit=5 |
| 184 | +curl http://localhost:5173/__annotask/api/data-source-details/useUserQuery |
| 185 | +curl http://localhost:5173/__annotask/api/data-source-bindings/useUserQuery |
| 186 | +``` |
| 187 | + |
| 188 | +### MCP |
| 189 | + |
| 190 | +- `annotask_get_data_sources` — supports `kind`, `library`, `search`, `used_only` |
| 191 | +- `annotask_get_data_source_examples` — supports `name`, `kind`, `limit` |
| 192 | +- `annotask_get_data_source_details` — supports `name`, `kind`, `file`, `context_lines` |
| 193 | + |
| 194 | +When multiple definitions share a name, `annotask_get_data_source_details` returns `{ error: 'ambiguous', candidates: [...] }` — re-call with `file` and/or `kind` to disambiguate. |
| 195 | + |
| 196 | +### CLI |
| 197 | + |
| 198 | +```bash |
| 199 | +annotask data-sources |
| 200 | +annotask data-sources --kind=composable --used-only --mcp |
| 201 | +annotask data-source-examples useUserQuery --limit=5 --mcp |
| 202 | +annotask data-source-details useUserQuery --file=src/composables/useUserQuery.ts --mcp |
| 203 | +``` |
| 204 | + |
| 205 | +## When Agents Should Use It |
| 206 | + |
| 207 | +Reach for the data-source catalog when a task asks to: |
| 208 | + |
| 209 | +- modify a fetch contract, query, mutation, or store — this is the `api_update` task type's primary surface |
| 210 | +- add a new UI that needs to bind to existing data — start from `used_only=true` to see what's already fetched on this page |
| 211 | +- rewire a component to a different hook or store that matches project conventions |
| 212 | +- understand what data an element on the current page depends on — use the per-task `data_context`, then call `annotask_get_data_source_details` on the primary source for its shape |
| 213 | +- trace a source end-to-end through prop chains — the bindings endpoint returns the full render-site graph |
| 214 | + |
| 215 | +For API-contract work, pair data sources with `annotask_get_api_operation` / `annotask_resolve_endpoint` — the `response_schema_ref` on a source points directly at the schema the fetch returns. |
0 commit comments