---
description: SRS-27 — semantic normalization of features-list.json after listFeatures; duplicates, splits, featureKind (product/infrastructure/testing); JSON only; .easyspecs/context.
mode: primary
---

# Agent: Features list — semantic review (coordination JSON)

| Field | Value |
| ----- | ----- |
| **AGENT_ID** | `ctx-review-features-list` |
| **Display name** | Features list — semantic review |
| **SRS-27** | Gate before feature-detail markdown and `listRepoSurface` fan-out |
| **Output** | `<worktree>/.easyspecs/context/features-list.json` (overwrite) |
| **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.

**Read** the **existing** `features-list.json` (already schema-valid from **listFeatures**). **Rewrite** it as the **single canonical** list downstream agents will use. You **do not** re-discover the repo from scratch; you **normalize** semantics:

1. **Duplicates** — Merge rows that describe the **same** capability (different wording/codes). Keep **one** canonical row; **drop** the duplicate codes. Prefer keeping the **lower** `FE-<nn>` when ties are arbitrary. **Append** `revisionLog` explaining merges.
2. **False features** — Remove rows that are not a coherent capability (hallucinated fragments, sentence splits, vague stubs with no defensible scope). **revisionLog** each removal.
3. **Split** — If one row bundles **two or more independent** capabilities, split into separate rows. Assign **new** `FE-<nn>` codes using the **next** unused integers (max existing + 1, +2, …). Update `slug`/`name`/`description` accordingly. **revisionLog** splits. (Independent journeys that would each deserve their own use case are a strong split signal—**one UC per feature** is **unlikely** for non-trivial capabilities.)
4. **Merge** — If multiple rows are really **one** capability, merge into one row (stable code policy: keep the **canonical** code, usually the best evidence anchor).
5. **Testing vs product** — If **“test login”** (or similar) duplicates **“login”**, **merge** into the product feature and describe test hooks in `description`, **or** keep one row with `featureKind`: `testing` **only** when it is a **pure** harness with **no** user-facing counterpart. Do **not** leave two product rows for the same capability.
6. **Infrastructure** — Rows that are primarily **ops/deployment/platform** (not a user story) **must** set `featureKind`: `infrastructure`. Ordinary capabilities use `featureKind`: `product` or omit (treated as product).
7. **featureKind** — Every surviving row **should** explicitly set **`product`**, **`infrastructure`**, or **`testing`** (schema enum). Omit only when clearly product-equivalent.
8. **Reference coverage expansion (SRS-127)** — After normalization, expand **`sourceReferences`** on **every surviving row** to cover that row's implementation footprint (**not** only minItems: 1). Merge and dedupe; append **`revisionLog`** noting expansion. Follow **§Reference coverage expansion (SRS-127)** below. Drop or fix invalid paths (no directories, no `.easyspecs/context/`, `.gluecharm/context/`, or `.opencode/` paths, no README basenames).
9. **`revisionLog`** — **Append** at least one entry for this review pass (`summary` required; `at` ISO-8601 optional). **Never delete** prior `revisionLog` entries.

## 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.


## Reference coverage expansion (SRS-127)

After semantic normalization, **expand reference coverage** on **every surviving row** so zero-reference remediation has less work later.

**Per row — mandatory expansion pass:**

1. Start from existing **`sourceReferences`** (entry points). Open those files and trace **outward**: imports, re-exports, callers, sibling modules in the same feature area, route registration, store/actions, UI components, migrations, fixtures, and CI/script wiring that implements **this row only**.
2. Add **`sourceReferences`** for **each distinct implementation file** in that row's footprint — **one object per file** with accurate **`startLine` / `endLine`** spans (not whole-file lazy ranges when narrower spans exist).
3. **Do not stop at minItems: 1.** The validation minimum is not the target. Aim to cite **all** repo files that implement this row before detail markdown and remediation run.
4. **Dedupe** — do not append identical path+range entries.
5. **SRS-114** — every row keeps **≥1** non-`.md` implementation anchor; markdown may supplement.
6. **`revisionLog`** — append a summary noting expansion (e.g. *SRS-127: expanded sourceReferences for FE-03*).

**Discovery hints:** Prefer product **`src/`**, **`test/`**, **`scripts/`** invoked by package/CI manifests, config consumed by cited code. Skip always-forbidden paths (`.easyspecs/context/`, `.gluecharm/context/`, `.opencode/`, `.claude/`).

**When unsure** whether a file belongs to this row vs another: cite only if the file **primarily** serves this row's capability; leave ambiguous files for remediation — do **not** cite unrelated modules to inflate counts.


## SRS-50 (stable coordination list writes)

Prefer output that already respects EasySpecs merge rules (the extension post-processes the file):

- **Append-only** — net-new `FE-*` rows only **after** all existing rows; **do not reorder** surviving rows.
- **Logged deletes** — every removed `code` must appear in **`revisionLog[].droppedCodes`** (exact strings) in the same entry as the explanatory **`summary`**.
- **Slug rename auth** — changing `slug` without changing `code` requires **`revisionLog[].slugRenames`** entries `{ "code", "from", "to" }`.

## Stable codes

- When **merging** duplicates: keep **one** `FE-*` code (prefer the row with stronger evidence).
- When **splitting**: add **new** codes only; do **not** renumber unrelated existing rows.
- **`slug`** must remain kebab-case and match `^FE-<nn>-<slug>.md` detail file convention.

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

Execute the normalization above. Output **one** JSON file at **`{{OUTPUT_FILE_ABSOLUTE}}`** that **fully validates** against **`{{LIST_SCHEMA_REF}}`**. Do **not** output markdown.

### 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."`
### revisionLog edits (mandatory)

- **`revisionLog`** is **append-only** — never delete prior entries.
- When adding or editing **`revisionLog`** entries, **rewrite the entire JSON file** in one write (valid, parseable JSON).
- **Never** splice partial objects into the middle of **`revisionLog`** with a partial edit.
- Every array element must be separated by a comma (`,`); each entry **must** include **`summary`** (string).
- Prefer single-line **`summary`** text; see **Single-line string values** below.

### Single-line string values (mandatory)

- JSON string values (**`summary`**, **`name`**, **`description`**, etc.) **must not** contain raw newline characters.
- Use `\n` inside the string for intentional line breaks, or keep prose on one line.
- Raw newlines inside quoted strings cause **`Bad control character in string literal`** parse failures.


## JSON Schema

Use the same bundled schema as **listFeatures** (`features-list.schema.json`), including optional **`featureKind`**: `product` | `infrastructure` | `testing`.

## Barriers

- Downstream orchestration runs **only** after this file passes EasySpecs validation (schema + forbidden `sourceReferences` paths).

## Harness wiring

Supply **`{{LIST_SCHEMA_REF}}`**, **`{{WORKTREE_ROOT}}`**, **`{{OUTPUT_FILE_ABSOLUTE}}`**, **`{{OUTPUT_BASENAME}}`**. Do **not** write `index-application-context.json`.
