docs(agents): add .agents/docs context-engineering home + Ink-parity loop (#28)

Establishes the committed .agents/docs/ convention (distinct from the
uncommitted docs/ working-notes folder) and seeds the Ink-parity
verification loop:

- ink-parity-loop.md: design spec + reusable /loop prompt (audit →
  test-first fix → codex review → PR → CI → auto-merge, hard codex gate).
- ink-parity.md: pinned Ink reference (v7.0.4, commit 40b3a75) + the
  intentional-divergence allowlist the audit skips.
- parity-ledger.md: working ledger of audit sweeps and confirmed gaps.
- AGENTS.md: "Context Engineering" section documenting the convention.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Yunfei He
2026-05-29 22:35:14 +08:00
committed by GitHub
parent 6d856f8913
commit 7af3ff3e00
4 changed files with 274 additions and 0 deletions
+180
View File
@@ -0,0 +1,180 @@
# Ink-Parity Verification Loop
> Design spec + the reusable `/loop` prompt for continuously verifying that vue-tui
> stays aligned with Ink: audit → confirm gaps → test-first fix → codex review → PR →
> CI → auto-merge, repeating until a clean sweep finds nothing.
>
> Related: [[ink-parity]] (allowlist + pinned Ink version), [[parity-ledger]] (working gap ledger).
## Goal
Catch places where vue-tui has drifted from Ink's behavior — missing features, wrong
output, mishandled input/escape sequences, divergent lifecycle — without "fixing"
the differences that are **deliberate** design choices. Ship each confirmed gap as its
own reviewable PR and merge it autonomously once it is provably correct.
## Non-goals
- Chasing API-shape / naming / export-surface differences. Those are catalogued as
intentional in `ink-parity.md` and skipped. (See the allowlist.)
- React-concurrent-mode features (Suspense/useTransition) — no Vue equivalent, N/A.
- Editing the `ink-parity.md` allowlist. The loop's **only** permitted write to that file
is appending an item under "Candidate intentional divergences (needs human review)". It
never edits existing allowlist entries and never promotes a candidate into the allowlist
— a human does that.
## Context-engineering convention
This loop relies on two committed docs under `.agents/docs/` (the project's
context-engineering home, distinct from the uncommitted `docs/` working-notes folder).
Philosophy borrowed from rolldown: one concept per file, files cross-link, you read the
relevant doc before working in an area, and **you update the doc in the same change**
that affects it.
A short "Context Engineering" section in `AGENTS.md` documents this convention so the
rule is discoverable from the source of truth.
## Artifacts
### `.agents/docs/ink-parity.md` (committed) — pinned reference + allowlist
- **Target Ink**: version + git tag + pinned commit SHA the audit diffs against.
- **Intentional divergences** (the allowlist): each entry is `area — what Ink does —
what vue-tui does instead — why it's deliberate`. The audit drops any candidate gap
that matches an entry here. Curated by a human; the loop only appends a
"Candidate intentional divergences (needs human review)" section, never promotes
entries itself.
Seed entries (from prior parity work, Ink v7.0.4):
`render() → createApp()` rename · exports `measureText`/`measureTextNatural` (Ink does
not) · `useExit()` instead of `useApp()` full AppContext · `AriaRole`/`AriaState` props ·
named type re-exports (BoxProps/TextProps/…) intentionally absent.
### `.agents/docs/parity-ledger.md` (committed) — the working ledger
Records audit sweeps and confirmed gaps so the loop survives restarts and never
re-does merged work.
```
## Sweep history
| sweep | Ink SHA | candidates → confirmed | status |
|-------|---------|------------------------|--------|
## Confirmed gaps
| id | area | summary | priority | status | branch | PR |
|----|------|---------|----------|--------|--------|----|
```
`status` ∈ `todo · in-progress · pr-open · merged · blocked`.
**Recording `merged` (a merged PR can't update itself).** A fix PR commits its own row as
`pr-open`. The flip to `merged` happens at the **next wake's reconciliation step**: any
`pr-open` row whose PR is now merged is set to `merged`, and that edit rides along in the
next fix PR (bundled at its top). The loop therefore trails reality by one PR; the
stop-condition's final reconciliation lands the last `merged` flip as its own tiny
`chore(parity): reconcile ledger` PR. This keeps every ledger write inside a reviewed PR.
## Phase A — Audit sweep
Runs only when there is no work in flight — i.e. the ledger has no `todo`,
`in-progress`, or `pr-open` rows (or on first run). A `blocked` row is **not** a reason to
audit: it pauses for the user (see stop condition).
1. **Pin & clone** Ink at the tag recorded in `ink-parity.md` → `/tmp/ink-<sha>` (a local
throwaway clone, re-pulled each sweep — agents and codex read it from disk so no network
is needed mid-review). Record the SHA in a new ledger sweep row.
2. **Fan-out diff (Workflow)** — parallel agents, one per Ink area, comparing real Ink
source to vue-tui source: components (Box/Text/Static/Transform/Newline), hooks
(input/focus/app/stdout/stderr), render lifecycle, `io/` escape sequences + writes,
text wrapping, exit semantics. Each returns candidate differences with file refs.
3. **Allowlist filter** — drop every candidate matching `ink-parity.md`.
4. **Adversarial verify** — a second, independent agent tries to _refute_ each survivor:
is it actually intentional? already covered by an existing test? not really present
in Ink at this SHA? Default to "not a gap" when uncertain. Only survivors are
confirmed.
5. **Rank & record** — write confirmed gaps to the ledger, priority-ordered
(correctness/behavior bugs first, omissions second).
## Phase B — Fix loop (one cohesive gap per PR, priority order)
First, **reconcile**: for every `pr-open` row whose PR has merged, set its row to
`merged`; carry those edits into this iteration's PR (bundled at the top). Then take the
highest-priority `todo` gap:
1. Branch from `main` → `fix/parity-<slug>`; mark ledger row `in-progress` (working state).
2. **Test-first** — write a failing test reproducing the gap. Honor the project's test
discipline: `FORCE_COLOR` for ANSI, `CI: "false"` injected, process-global tests in
`*.sequential.test.*`. **Confirm the test fails for the right reason.**
3. **Fix** the code; confirm the test passes.
4. `vp run ready` (fmt → build → lint → type → test) must be fully green.
5. **Codex review** — run a codex review of the diff; address every finding.
6. Push; open PR (squash; co-author line included — this is the user's own repo). The PR's
commits set this gap's ledger row to `pr-open` with branch + PR link (its flip to
`merged` comes from the next wake's reconciliation above).
7. **Wait for CI** (dynamic wake on completion).
8. **Merge gate (hard)** — merge only when **both**:
- CI is fully green, **and**
- a codex pass reports **zero unaddressed findings**.
Green CI alone is _not_ sufficient. If codex still has findings, address and re-push;
do not merge.
9. On gate pass → **auto-merge** (squash). The row stays `pr-open` until reconciled next
wake.
10. On CI red → systematic-debugging, push fix, re-wait. After **3** failed attempts on
the same gap → mark `blocked` and **pause + ping the user**.
## Stop condition
Only consider stopping once **no `todo`, `in-progress`, or `pr-open` rows remain** (all
known work merged and reconciled). If any `blocked` row exists, do **not** declare
victory — pause and ping the user to resolve it. Otherwise land a final
`chore(parity): reconcile ledger` PR to flip the last `pr-open`→`merged`, then run **one
fresh audit sweep**. If that sweep yields zero new confirmed gaps, report
`parity verified against Ink <sha>, no actionable gaps` and end the loop. Otherwise
continue with the new gaps.
## Pacing & rails
- **Dynamic `/loop`** (self-paced): wake when CI / background work completes; set a long
fallback wake (~1200s) so a hung CI never freezes the loop.
- Never touch allowlisted divergences. The loop's only permitted write to `ink-parity.md`
is appending under "Candidate intentional divergences (needs human review)" — never edit
or promote allowlist entries. Never commit anything under `docs/` (the `.agents/docs/`
artifacts here are the committed exception).
- One gap per PR keeps every merge reviewable.
- Codex findings are a hard merge block (above).
## The reusable `/loop` prompt
Paste this whole block after `/loop` (no interval → self-paced):
```
Verify vue-tui's alignment with Ink and close real gaps, one PR at a time, until a clean sweep finds nothing.
State lives in two committed docs — read both before doing anything:
- .agents/docs/ink-parity.md → target Ink version+SHA and the intentional-divergence ALLOWLIST. Your ONLY allowed write here is appending under "Candidate intentional divergences (needs human review)"; never edit or promote existing allowlist entries.
- .agents/docs/parity-ledger.md → sweep history + confirmed-gap table (id/area/summary/priority/status/branch/PR). status ∈ todo·in-progress·pr-open·merged·blocked.
Every wake, FIRST reconcile: for each `pr-open` row whose PR has merged, set it to `merged` (carry these edits into this iteration's PR, bundled at the top). Then do the next actionable thing:
A) If, after reconciliation, the ledger has no `todo`/`in-progress`/`pr-open` rows → run an AUDIT SWEEP (a `blocked` row is NOT a reason to audit — see STOP):
1. Clone Ink at the pinned SHA from ink-parity.md into /tmp/ink-<sha> (local throwaway clone; agents read it from disk, no network needed mid-review). Add a sweep row to the ledger.
2. Run a Workflow that fans out parallel agents to diff Ink source vs vue-tui source, one agent per area (components, hooks, render lifecycle, io/ escape sequences, text wrapping, exit semantics). Collect candidate differences with file refs.
3. Drop every candidate that matches the ink-parity.md allowlist.
4. Adversarially verify each survivor with a second agent that tries to REFUTE it (intentional? already tested? not actually in Ink at this SHA?). Default to "not a gap" when unsure.
5. Write confirmed gaps to the ledger, priority-ordered (correctness/behavior first, omissions next).
STOP only when no todo/in-progress/pr-open rows remain. If any `blocked` row exists, pause and ping me instead of declaring victory. Otherwise land a final `chore(parity): reconcile ledger` PR, then run one fresh sweep; if it finds zero new confirmed gaps → report "parity verified against Ink <sha>, no actionable gaps" and STOP the loop.
B) Otherwise take the highest-priority `todo` gap and ship it:
1. Branch from main → fix/parity-<slug>; set ledger row in-progress (working state).
2. TEST-FIRST: write a failing test reproducing the gap (FORCE_COLOR for ANSI, inject CI:"false", process-global tests in *.sequential.test.*). Confirm it fails for the right reason.
3. Fix the code; confirm the test passes.
4. Run `vp run ready` — fmt/build/lint/type/test must all be green.
5. Run a codex review of the diff; address every finding.
6. Push; open a squash PR (co-author line included). The PR's commits set this gap's row to pr-open with branch+PR link (its flip to `merged` comes from next wake's reconciliation).
7. Wait for CI (schedule a dynamic wake on completion; long fallback ~1200s).
8. HARD MERGE GATE — merge ONLY when CI is fully green AND a codex pass reports zero unaddressed findings. Green CI alone is not enough; if codex still has findings, fix, re-push, re-wait.
9. On gate pass → squash-merge. Row stays pr-open until reconciled next wake.
10. On CI red → debug systematically, push a fix, re-wait. After 3 failed attempts on the same gap, mark it `blocked` and STOP to ping me.
Rails: never touch allowlisted divergences; the only write to ink-parity.md is appending the candidate section; never commit under docs/; one gap per PR; update the ledger in the same PR as the change.
```
+54
View File
@@ -0,0 +1,54 @@
# Ink Parity — Reference Pin & Intentional-Divergence Allowlist
> The audit in [[ink-parity-loop]] diffs vue-tui against the Ink source pinned below.
> Anything listed under "Intentional divergences" is **deliberate** and is NOT a gap —
> the audit skips it. Confirmed gaps and sweep history live in [[parity-ledger]].
## Target Ink reference
| field | value |
| ----------------- | ---------------------------------------------------------------------------------------------- |
| package | `ink` (vadimdemedes/ink) |
| version | v7.0.4 |
| tag | `v7.0.4` |
| pinned commit SHA | `40b3a7578811fd616341ca4e31cc7748aeeff12f` |
| clone | `git clone --depth 1 --branch v7.0.4 https://github.com/vadimdemedes/ink.git /tmp/ink-40b3a75` |
| verify | `git -C /tmp/ink-40b3a75 rev-parse HEAD` must equal the pinned commit SHA above |
> Note: `git ls-remote …/ink refs/tags/v7.0.4` returns `c4f638cf…`, the **annotated tag
> object** SHA — _not_ the commit. The commit the tag points to is `40b3a75…` (what we pin
> and what `rev-parse HEAD` resolves to after checkout). Pin the commit, not the tag object.
Re-pin deliberately when bumping the target Ink version: update the row above, note the
bump in the ledger sweep history, and re-run a full audit against the new SHA.
## Intentional divergences (allowlist — skip these)
These are differences the audit must treat as **by design**. Format:
`area — what Ink does — what vue-tui does — why`.
- **Entry API** — Ink exposes `render()`; vue-tui exposes `createApp()`. Renamed to match
Vue's `createApp` mental model. Deliberate.
- **Text measurement exports** — Ink does not export its `measure-text` module; vue-tui
exports `measureText` / `measureTextNatural` from the public index. Deliberate public
surface.
- **App composable** — Ink's `useApp()` returns the full AppContext (exit +
`waitUntilRenderFlush` + stdout/stdin/…); vue-tui exposes `useExit()` returning only the
exit fn. Intentional minimal surface. (`waitUntilRenderFlush` is not exposed as a
composable.)
- **Accessibility props** — vue-tui adds `AriaRole` / `AriaState` props with no Ink
equivalent. Additive, deliberate.
- **Named type/prop re-exports** — Ink re-exports BoxProps, TextProps, StaticProps,
TransformProps, NewlineProps, WindowSize, CursorPosition, DOMElement, RenderOptions,
Instance, App/Stdin/Stdout/StderrProps. vue-tui intentionally does not re-export these
names (uses TuiApp / MountOptions and its own type surface instead).
- **React concurrent mode** — Suspense / useTransition have no Vue equivalent. N/A, not a
gap.
## Candidate intentional divergences (needs human review)
_(The loop appends here when it finds a difference it suspects is intentional but that is
not yet on the allowlist. A human promotes entries up into the allowlist — the loop never
does.)_
- _(none yet)_
+20
View File
@@ -0,0 +1,20 @@
# Ink Parity — Working Ledger
> Audit sweeps and confirmed gaps for the loop in [[ink-parity-loop]]. Reference pin and
> the intentional-divergence allowlist live in [[ink-parity]]. Update a gap's row in the
> **same** PR that fixes it.
## Sweep history
| sweep | Ink SHA | candidates → confirmed | status |
| ---------------------------------------- | ------- | ---------------------- | ------ |
| _(none yet — first run will record one)_ | | | |
## Confirmed gaps
`status``todo · in-progress · pr-open · merged · blocked`. Priority: correctness /
behavior bugs first, omissions next.
| id | area | summary | priority | status | branch | PR |
| ------------ | ---- | ------- | -------- | ------ | ------ | --- |
| _(none yet)_ | | | | | | |
+20
View File
@@ -14,6 +14,26 @@
- After completing any task, run `vp run ready` (or `vpr ready`) to verify: lint, type-check, test all packages, and build.
- Never commit anything under `docs/`. That directory is for local working notes and specs — it must stay out of git.
# Context Engineering
Long-lived design context lives in `.agents/docs/` (committed — distinct from the
uncommitted `docs/` working-notes folder). Convention, borrowed from rolldown:
- One concept per file; files cross-link with `[[other-doc]]`.
- If a design doc covers the area you're about to work in, **read it first**.
- If your change affects a design doc, **update it in the same change**. Docs that drift
from reality are worse than no docs.
- Capture the _why_ — trade-offs considered, alternatives rejected, known pitfalls — not
just what the code does.
Current docs:
- `.agents/docs/ink-parity-loop.md` — the Ink-parity verification loop (audit → fix → PR
→ merge) and its reusable `/loop` prompt.
- `.agents/docs/ink-parity.md` — pinned Ink reference version/SHA + the
intentional-divergence allowlist (differences that are by design, not gaps).
- `.agents/docs/parity-ledger.md` — working ledger of audit sweeps and confirmed gaps.
<!--VITE PLUS START-->
# Using Vite+, the Unified Toolchain for the Web