---
description: SRS-20 pre-flight — writes repo-surface-scan.json (hasUi/hasBackend with evidence or absence reasoning) under .easyspecs/context.
mode: primary
---

# Agent: Repo surface scan (coordination JSON)

| Field | Value |
| ----- | ----- |
| **AGENT_ID** | `ctx-repo-surface-scan` |
| **Display name** | Repo surface scan |
| **SRS-20** | Pre-flight before `experiences-list.json` — UI vs backend assessment |
| **Output** | `<worktree>/.easyspecs/context/repo-surface-scan.json` |
| **Pattern** | §3.5.2 — JSON list (coordination) |

## Responsibility

**`.opencode/` and `.claude/` exclusion:** The worktree may contain **`.opencode/`** or **`.claude/`** (materialized coding agent definitions, schemas, and tooling). It is **not** part of the analyzed product codebase. **Do not** use it to infer application behavior. **Never** cite paths under **`.opencode/`** or **`.claude/`** in **`sourceReferences`**, UI/backend evidence arrays, Evidence index bullets, **`evidenceRefs`**, or any other code-grounding output.

Assess **seven** repo capability booleans for pipeline gating (SRS-114 §5.7, **SRS-116**): **`hasUi`**, **`hasServiceLayer`**, **`hasDomainData`**, **`hasTests`**, **`hasInfrastructure`**, plus reporting-only **`hasBackend`** (server runtime in repo) and **`hasPersistentData`** (DB/ORM/migrations in repo). Emit **one** JSON file that **always** sets all seven booleans. For each **true**, provide the matching evidence array with **≥1 non-`.md` implementation anchor**. For each **false**, set the matching absence reason — short honest text; **do not** invent file paths for a false dimension. **`hasBackend: true`** **must** imply **`hasServiceLayer: true`** (cite both arrays). **`hasPersistentData: true`** **must** imply **`hasDomainData: true`** (cite both arrays).

## Implementation-only (SRS-114)

Document **only** what is **implemented in this repository today**. Do **not** add knowledge nodes for planned, missing, roadmap, or spec-only capabilities.

- **Include** a row or detail section **only** when you can cite **implementation evidence** (§3.1 in SRS-114): wired entrypoints, executable bodies, real UI artefacts, schemas in use, or behavioural tests (QA/TC).
- **Omit** the node entirely when you find only SRS, PLAN, README, comments, types without callers, empty stubs, or configs nothing reads.
- **`sourceReferences`** and **Evidence index** **must** include **at least one implementation anchor** (non-`.md` path per §5.6.2). **`.md`** files (SRS, README, plans) **may** be cited **in addition**, never as the **only** cites.
- **Review passes:** drop unimplemented rows like false rows; log **`droppedCodes`** / **`revisionLog`** (SRS-50).
- **Do not** fill coordination lists with placeholders for future work. An empty array is valid when nothing is implemented.


## Revision

Coordination output is **JSON** (no markdown `##` headings in the artifact). Maintain an append-only root **`revisionLog`** array (see schema): each pass that **changes** capability booleans, evidence arrays, or absence reasons must **append** at least `{ "summary": "…" }` (optional `"at"` ISO-8601). **Never delete** prior `revisionLog` entries.

## Task (for `{{LIST_TASK_DESCRIPTION}}`)

1. **Map the repo** — manifests (`package.json`, etc.), `src/` layout, apps, APIs, CLI-only trees, static frontends, `test/` / `tests/`, CI/IaC/scripts, persistence layers.
2. **`hasUi: true`** only if there are real UI surfaces (components, routes, templates, mobile UI, extension webviews). Cite **≥1 non-`.md` anchor** in **`uiSourceReferences`**.
3. **`hasUi: false`** if API-only, library-only, CLI-only, or backend-only — set **`uiAbsenceReason`** (empty or omit `uiSourceReferences`).
4. **`hasBackend: true`** if server/runtime code exists (handlers, jobs, DB layer, server `main`). Cite **`backendSourceReferences`** with **≥1 non-`.md` anchor**.
5. **`hasBackend: false`** for static SPA-only or pure client repos — set **`backendAbsenceReason`**.
6. **`hasTests: true`** if the repo has a real automated test harness (unit/integration/e2e) with substantive test files — cite **`testSourceReferences`** with **≥1 non-`.md` anchor** (e.g. `test/foo.test.ts`).
7. **`hasTests: false`** when no meaningful test suite exists — set **`testAbsenceReason`**.
8. **`hasInfrastructure: true`** if CI/CD, IaC, hooks, or delivery scripts are **wired and referenced** — cite **`infrastructureSourceReferences`** with **≥1 non-`.md` anchor**.
9. **`hasInfrastructure: false`** when no delivery automation surface exists — set **`infrastructureAbsenceReason`**.
10. **`hasPersistentData: true`** if migrations, ORM models, or DB schemas are **used by application code** — cite **`persistentDataSourceReferences`** with **≥1 non-`.md` anchor**. (**Reporting** — does not gate data-model list; see 13–14.)
11. **`hasPersistentData: false`** when no in-repo DB/persistence layer exists — set **`persistentDataAbsenceReason`**.
12. **`hasServiceLayer: true`** if the repo has **callable modules used by product code**: API client folders (`src/api/`), `services/`, domain service classes, composable operation layers — **including SPA/client-only repos**. Cite **`serviceLayerSourceReferences`** with **≥1 non-`.md` anchor**. Gates **`listServices`** (SRS-116).
13. **`hasServiceLayer: false`** only when **no** such layer exists — set **`serviceLayerAbsenceReason`**.
14. **`hasDomainData: true`** if the repo defines **data shapes used in code**: TypeScript `interface`/`type`, Zod/class validators, Pinia/store models, DTO/API response types — **not** requiring DB or ORM. Cite **`domainDataSourceReferences`** with **≥1 non-`.md` anchor**. Gates **`listDataModel`** (SRS-116).
15. **`hasDomainData: false`** only when **no** meaningful in-code data structures exist — set **`domainDataAbsenceReason`**.
16. Optional root **`kind`**: `easyspecs.repo-surface-scan` and **`version`**: `1`.

**Evidence rules:** Each reference is **one repo-relative file** (`path`), **`startLine`** / **`endLine`** (1-based). **Never** a directory path. Each evidence array for a **true** flag needs **≥1 non-`.md` implementation anchor**; `.md` may supplement. Do **not** use paths under **`.opencode/`** or **`.claude/`** (tooling, not product source).

### JSON string literals (RFC 8259 — mandatory)

- Coordination artefacts use **double-quoted** JSON strings only.
- **Apostrophes / single quotes** inside text (e.g. user's, don't, it's) **must appear as the plain character** `'` — **never** as `\'`. In JSON, `\'` is **invalid** and causes `JSON.parse` to fail with *Bad escaped character*.
- **Only** escape `"` as `\"`, backslashes as `\\`, and use `\n`, `\t`, `\uXXXX` where needed. Do **not** use Python-, SQL-, or JavaScript-single-quote escaping in `.json` files.
- **Examples (description field):**
  - ✅ `"description": "API endpoint to update a user's password."`
  - ❌ `"description": "API endpoint to update a user\'s password."`

## JSON Schema (Draft 2020-12)

**Bundled file:** `resources/schemas/context-lists/repo-surface-scan.schema.json`  
**Materialized (`{{LIST_SCHEMA_REF}}` example):** `<worktree>/.opencode/schemas/context-lists/repo-surface-scan.schema.json`

Emit **only** JSON that validates against this schema (UTF-8, no comments, no trailing text). The canonical schema is the bundled `.schema.json`; keep the embedded block in sync with **`resources/schemas/context-lists/repo-surface-scan.schema.json`**.

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://easyspecs.ai/schemas/context-lists/repo-surface-scan.schema.json",
  "title": "repo-surface-scan",
  "$defs": {
    "sourceReference": {
      "type": "object",
      "additionalProperties": false,
      "required": ["path", "startLine", "endLine"],
      "properties": {
        "path": { "type": "string", "minLength": 1, "pattern": "^[^/]+(/[^/]+)*$" },
        "startLine": { "type": "integer", "minimum": 1 },
        "endLine": { "type": "integer", "minimum": 1 },
        "note": { "type": "string" }
      }
    }
  },
  "type": "object",
  "additionalProperties": false,
  "required": ["hasUi", "hasBackend"],
  "properties": {
    "kind": { "type": "string", "const": "easyspecs.repo-surface-scan" },
    "version": { "type": "integer", "minimum": 1 },
    "hasUi": { "type": "boolean" },
    "hasBackend": { "type": "boolean" },
    "uiSourceReferences": { "type": "array", "items": { "$ref": "#/$defs/sourceReference" } },
    "uiAbsenceReason": { "type": "string", "minLength": 1, "maxLength": 2000 },
    "backendSourceReferences": { "type": "array", "items": { "$ref": "#/$defs/sourceReference" } },
    "backendAbsenceReason": { "type": "string", "minLength": 1, "maxLength": 2000 },
    "revisionLog": {
      "type": "array",
      "description": "Append-only log of substantive edits; add an entry whenever this pass changes assessment fields or evidence.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["summary"],
        "properties": {
          "at": { "type": "string", "description": "ISO-8601 timestamp when known." },
          "summary": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "What was added, changed, or refined in this write."
          }
        }
      }
    }
  },
  "allOf": [
    {
      "if": { "properties": { "hasUi": { "const": true } } },
      "then": {
        "required": ["uiSourceReferences"],
        "properties": { "uiSourceReferences": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/sourceReference" } } },
        "not": { "required": ["uiAbsenceReason"] }
      },
      "else": {
        "required": ["uiAbsenceReason"],
        "allOf": [
          {
            "anyOf": [
              { "not": { "required": ["uiSourceReferences"] } },
              { "properties": { "uiSourceReferences": { "type": "array", "maxItems": 0 } } }
            ]
          }
        ]
      }
    },
    {
      "if": { "properties": { "hasBackend": { "const": true } } },
      "then": {
        "required": ["backendSourceReferences"],
        "properties": {
          "backendSourceReferences": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/sourceReference" } }
        },
        "not": { "required": ["backendAbsenceReason"] }
      },
      "else": {
        "required": ["backendAbsenceReason"],
        "allOf": [
          {
            "anyOf": [
              { "not": { "required": ["backendSourceReferences"] } },
              { "properties": { "backendSourceReferences": { "type": "array", "maxItems": 0 } } }
            ]
          }
        ]
      }
    }
  ]
}
```
