@nurix/etna 0.19.87 → 0.19.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/docs/structure.md +11 -0
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/skills/documentation/SKILL.md +1 -0
- package/data/harnesses/code/skills/documentation/templates/Readme.md +1 -0
- package/data/harnesses/code/skills/documentation/templates/ux-behavior.md +66 -0
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/skills/documentation/SKILL.md +1 -0
- package/data/harnesses/delivery/skills/documentation/templates/Readme.md +1 -0
- package/data/harnesses/delivery/skills/documentation/templates/ux-behavior.md +66 -0
- package/data/harnesses/discover/CLAUDE.md +1 -1
- package/data/harnesses/discover/harness.json +2 -2
- package/data/harnesses/discover/rules/cartography-coverage.md +4 -1
- package/data/harnesses/discover/rules/cartography-flows.md +9 -0
- package/data/harnesses/discover/rules/ingestion.md +41 -5
- package/data/harnesses/discover/rules/tradecraft.md +34 -0
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/skills/documentation/SKILL.md +1 -0
- package/data/harnesses/etna/skills/documentation/templates/Readme.md +1 -0
- package/data/harnesses/etna/skills/documentation/templates/ux-behavior.md +66 -0
- package/package.json +2 -2
package/data/harness-index.json
CHANGED
|
@@ -16,6 +16,8 @@ Each topic has one canonical location. Read from here, not from plans or changel
|
|
|
16
16
|
| API surface | `docs/architecture/` | api.md |
|
|
17
17
|
| Security | `docs/architecture/` | security.md |
|
|
18
18
|
| Feature behavior | `docs/features/` | one `kebab-case.md` doc per feature |
|
|
19
|
+
| UX behavior — one surface | `docs/features/` | `{surface}-ux.md`, `type: ux` |
|
|
20
|
+
| UX behavior — cross-cutting (routing, information architecture, stream presentation) | `docs/architecture/` | `type: ux`, owns no code |
|
|
19
21
|
| Product specification (PRD, personas, journeys, information architecture) | `docs/architecture/` | prd.md, personas.md, journeys.md, information-architecture.md |
|
|
20
22
|
| Acceptance criteria | `docs/acceptance/` | one `kebab-case.md` doc per capability, `AC-n` keyed assert lines |
|
|
21
23
|
| Setup & development | `docs/operations/` | setup.md |
|
|
@@ -48,6 +50,15 @@ Every doc is a **truth document** (states current absolutes, topic-named, update
|
|
|
48
50
|
never by renumbering. Distinct from `docs/tests/`: acceptance criteria state what the product
|
|
49
51
|
promises up front; the checklists in `docs/tests/` are the verification gate's own driving
|
|
50
52
|
scripts, authored and maintained at the gate.
|
|
53
|
+
- **UX behavior** (`type: ux`) is placed by **scope, never by medium** — there is no `docs/ui/`.
|
|
54
|
+
One surface's observable behavior is a feature concern and lives beside its LLD as
|
|
55
|
+
`docs/features/{surface}-ux.md`; routing, information architecture, and the presentation
|
|
56
|
+
contracts every surface obeys are whole-product concerns the interface merely renders, so they
|
|
57
|
+
live in `docs/architecture/` and own no code. The genre exists because an LLD states what a
|
|
58
|
+
feature *is* — which stays plausibly true through a rewrite of the surface — while nothing else
|
|
59
|
+
states what a person *observes*, so a wire change lands, the interface absorbs it, and the
|
|
60
|
+
absorption reads as maintenance. This document is what such a change contradicts. It **decides**
|
|
61
|
+
behavior and is written before the code; the `docs/tests/` checklist only verifies it afterwards.
|
|
51
62
|
- `docs/tests/`: Testing strategy and test docs. Present when the repo has a test posture worth documenting.
|
|
52
63
|
- `docs/history/plans/`: Records. Must have `Status:` field. Never modify after completion.
|
|
53
64
|
- `docs/history/changelog/`: Records. One entry per unit of work (`YYYY-MM-DD-{three-words}.md`), written by whoever does the work and frozen inside the commit it describes. Entries carry the refinement context a diff cannot show — the ask in the user's words, iterations and rejections, skill survived/overridden verdicts, discoveries, pins. Format is the `documentation` skill's `templates/changelog-entry.md`; trigger and lifecycle are the `changelog` rule; distillation is the `retro` agent. Immutability begins at commit — a draft in the working tree is mutable until then, never after. Every entry appends a row to `docs/history/CHANGELOG.md`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "code",
|
|
3
3
|
"name": "code",
|
|
4
|
-
"version": "3.10.
|
|
4
|
+
"version": "3.10.12",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
"docs/memory/main.md",
|
|
16
16
|
".mcp.json"
|
|
17
17
|
],
|
|
18
|
-
"digest": "
|
|
18
|
+
"digest": "8b4f074b1311"
|
|
19
19
|
}
|
|
@@ -71,6 +71,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
71
71
|
| Document env vars / config | [`templates/environment-registry.md`](templates/environment-registry.md) |
|
|
72
72
|
| Security / threat model | [`templates/security-overview.md`](templates/security-overview.md) |
|
|
73
73
|
| Testing strategy | [`templates/testing-strategy.md`](templates/testing-strategy.md) |
|
|
74
|
+
| What a person sees and can do on a surface (UX behavior) | [`templates/ux-behavior.md`](templates/ux-behavior.md) — decides observable behavior BEFORE the code; per-surface in `docs/features/{surface}-ux.md` owning the component globs, cross-cutting in `docs/architecture/`. Not the checklist, which only verifies it |
|
|
74
75
|
| Behavioral checklist for one feature (`docs/tests/{feature}.md`) | [`templates/behavioral-checklist.md`](templates/behavioral-checklist.md) — the verification gate's driving script, and the source its suite tests are drawn from |
|
|
75
76
|
| Acceptance criteria for one capability (`docs/acceptance/{capability}.md`) | [`templates/acceptance-criteria.md`](templates/acceptance-criteria.md) — the promises the built product must keep, as stable `AC-n` keyed assert lines verification surfaces parse as claims |
|
|
76
77
|
| Recurring procedure/setup | [`templates/operations-procedure.md`](templates/operations-procedure.md) |
|
|
@@ -15,6 +15,7 @@ One file per template. Every template is either a **truth document** (states cur
|
|
|
15
15
|
| Security overview | [security-overview.md](security-overview.md) | Truth |
|
|
16
16
|
| Testing strategy | [testing-strategy.md](testing-strategy.md) | Truth |
|
|
17
17
|
| Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | Truth |
|
|
18
|
+
| UX behavior | [ux-behavior.md](ux-behavior.md) | Truth |
|
|
18
19
|
| Operations procedure | [operations-procedure.md](operations-procedure.md) | Truth |
|
|
19
20
|
| Implementation map | [implementation-map.md](implementation-map.md) | Truth |
|
|
20
21
|
| Bookkeeping ledger | [bookkeeping.md](bookkeeping.md) | Truth (work queue) |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# UX Behavior — template
|
|
2
|
+
|
|
3
|
+
**Truth document.** States what a person sees and can do on a surface, and what the surface promises never to do. Present tense, like every truth document.
|
|
4
|
+
|
|
5
|
+
**`type: ux`.**
|
|
6
|
+
|
|
7
|
+
**Why this genre exists.** A feature doc (LLD) states what a feature is, how its data flows, and which contracts it honours — all of which stay plausibly true through a rewrite of the surface. Nothing else in the repo states what a person *observes*. So a change to the wire lands, the interface absorbs it, and the absorption reads as maintenance: no test fails, no lint fires, no document is contradicted. This document is the thing a silent change contradicts.
|
|
8
|
+
|
|
9
|
+
**Not a test plan.** `docs/tests/{feature}.md` records that behavior was driven and holds `Pinned:` traces; this document *decides* the behavior it later verifies. Written after the code, it is a transcript. Write it first.
|
|
10
|
+
|
|
11
|
+
**Scope decides placement, not medium.**
|
|
12
|
+
|
|
13
|
+
| The behavior is… | Where it lives |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| One surface's — a panel, an editor, a dialog | `docs/features/{surface}-ux.md`, with `owns:` over that surface's component paths |
|
|
16
|
+
| Cross-cutting — routing, information architecture, the state vocabulary every surface obeys, presentation of streams | `docs/architecture/` — owns no code, like every architecture doc |
|
|
17
|
+
|
|
18
|
+
**Ownership: take the component globs only where they are unclaimed.** A surface's components are frequently already owned — by the page's LLD, by the feature behind one panel — and claiming them again is an ownership conflict, not protection. So: unclaimed globs go in `owns:`; otherwise the doc declares `owns: []` and is named in each owning LLD's **coordinated edits** row, phrased as *different axes, not a conflict* — the LLD owns the code, this document owns what a person observes.
|
|
19
|
+
|
|
20
|
+
That matters because a doc nothing implicates is a doc that rots, and a surface rewritten under an unimplicated document is the failure this genre exists to prevent. The coordinated-edit row is the weaker of the two bindings; prefer real ownership wherever the globs are free.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Required sections
|
|
25
|
+
|
|
26
|
+
### Purpose
|
|
27
|
+
|
|
28
|
+
One sentence: what a person comes to this surface to do. Then the sharper half — **what this surface is not for**, naming the neighbouring surface that is. A purpose that admits everything cannot refuse anything, and a surface that cannot refuse accretes.
|
|
29
|
+
|
|
30
|
+
### Anatomy
|
|
31
|
+
|
|
32
|
+
The regions, and what each is for. An ASCII sketch beats a paragraph. Name each region the way the interface names it, not the way the component tree does — a reader holds the screen, not the file list.
|
|
33
|
+
|
|
34
|
+
### States
|
|
35
|
+
|
|
36
|
+
**The complete set, exhaustively.** Every surface has more states than its happy path, and an unenumerated state is one the interface improvised — which is exactly how a surface starts lying. One row per state: what causes it, what a person sees, and what they can do from it.
|
|
37
|
+
|
|
38
|
+
| State | Entered when | The person sees | Can do |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
|
|
41
|
+
Never omit a state because it is rare or ugly. The failed, empty, and stale states are the ones a person meets on their worst day, and they are the ones no one designs.
|
|
42
|
+
|
|
43
|
+
### Affordances
|
|
44
|
+
|
|
45
|
+
What a person can do, and what each action does. A disabled control states why it is disabled — a control that is simply inert teaches nothing and gets clicked again.
|
|
46
|
+
|
|
47
|
+
### Promises
|
|
48
|
+
|
|
49
|
+
**What this surface never does**, stated as absolutes. This is the section the rest of the system is measured against: a backend change that would break one of these is a breaking change, whoever ships it. Write the ones that are easy to violate accidentally — never silently discard typed input, never show a completed state over incomplete work, never move a control under a cursor mid-interaction.
|
|
50
|
+
|
|
51
|
+
### Transitions
|
|
52
|
+
|
|
53
|
+
What moves the surface between states, and who causes each move — the person, the server, or elapsed time. Server-caused transitions are the ones worth naming precisely, because they are the ones a person did not ask for.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Optional sections
|
|
58
|
+
|
|
59
|
+
**Copy** — the exact words, where the wording is load-bearing (a refusal, an irreversible confirmation). **Density and rhythm** — where a surface deliberately differs from the design system's default. **Open questions** — decisions deliberately not yet made, so an implementer knows what to ask rather than guessing.
|
|
60
|
+
|
|
61
|
+
## Rules
|
|
62
|
+
|
|
63
|
+
- **Present tense, no history.** The journey lives in `docs/history/`; this document simply becomes correct.
|
|
64
|
+
- **Describe the observable, not the implementation.** "The rail shows every phase, the current one marked" — never "`railFromView` maps `view.phases`". Naming a component in a UX document is a boundary leak, and the name goes stale before the behavior does.
|
|
65
|
+
- **Every state gets a person-facing sentence.** If you cannot say what a person sees, the state is not designed yet — write it as an open question rather than eliding it.
|
|
66
|
+
- **No screenshots.** They rot invisibly and cannot be diffed.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "delivery",
|
|
3
3
|
"name": "delivery",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.18",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
8
8
|
".vscode/settings.json"
|
|
9
9
|
],
|
|
10
10
|
"seeds": [],
|
|
11
|
-
"digest": "
|
|
11
|
+
"digest": "a11ca1e20bd4"
|
|
12
12
|
}
|
|
@@ -71,6 +71,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
71
71
|
| Document env vars / config | [`templates/environment-registry.md`](templates/environment-registry.md) |
|
|
72
72
|
| Security / threat model | [`templates/security-overview.md`](templates/security-overview.md) |
|
|
73
73
|
| Testing strategy | [`templates/testing-strategy.md`](templates/testing-strategy.md) |
|
|
74
|
+
| What a person sees and can do on a surface (UX behavior) | [`templates/ux-behavior.md`](templates/ux-behavior.md) — decides observable behavior BEFORE the code; per-surface in `docs/features/{surface}-ux.md` owning the component globs, cross-cutting in `docs/architecture/`. Not the checklist, which only verifies it |
|
|
74
75
|
| Behavioral checklist for one feature (`docs/tests/{feature}.md`) | [`templates/behavioral-checklist.md`](templates/behavioral-checklist.md) — the verification gate's driving script, and the source its suite tests are drawn from |
|
|
75
76
|
| Acceptance criteria for one capability (`docs/acceptance/{capability}.md`) | [`templates/acceptance-criteria.md`](templates/acceptance-criteria.md) — the promises the built product must keep, as stable `AC-n` keyed assert lines verification surfaces parse as claims |
|
|
76
77
|
| Recurring procedure/setup | [`templates/operations-procedure.md`](templates/operations-procedure.md) |
|
|
@@ -15,6 +15,7 @@ One file per template. Every template is either a **truth document** (states cur
|
|
|
15
15
|
| Security overview | [security-overview.md](security-overview.md) | Truth |
|
|
16
16
|
| Testing strategy | [testing-strategy.md](testing-strategy.md) | Truth |
|
|
17
17
|
| Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | Truth |
|
|
18
|
+
| UX behavior | [ux-behavior.md](ux-behavior.md) | Truth |
|
|
18
19
|
| Operations procedure | [operations-procedure.md](operations-procedure.md) | Truth |
|
|
19
20
|
| Implementation map | [implementation-map.md](implementation-map.md) | Truth |
|
|
20
21
|
| Bookkeeping ledger | [bookkeeping.md](bookkeeping.md) | Truth (work queue) |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# UX Behavior — template
|
|
2
|
+
|
|
3
|
+
**Truth document.** States what a person sees and can do on a surface, and what the surface promises never to do. Present tense, like every truth document.
|
|
4
|
+
|
|
5
|
+
**`type: ux`.**
|
|
6
|
+
|
|
7
|
+
**Why this genre exists.** A feature doc (LLD) states what a feature is, how its data flows, and which contracts it honours — all of which stay plausibly true through a rewrite of the surface. Nothing else in the repo states what a person *observes*. So a change to the wire lands, the interface absorbs it, and the absorption reads as maintenance: no test fails, no lint fires, no document is contradicted. This document is the thing a silent change contradicts.
|
|
8
|
+
|
|
9
|
+
**Not a test plan.** `docs/tests/{feature}.md` records that behavior was driven and holds `Pinned:` traces; this document *decides* the behavior it later verifies. Written after the code, it is a transcript. Write it first.
|
|
10
|
+
|
|
11
|
+
**Scope decides placement, not medium.**
|
|
12
|
+
|
|
13
|
+
| The behavior is… | Where it lives |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| One surface's — a panel, an editor, a dialog | `docs/features/{surface}-ux.md`, with `owns:` over that surface's component paths |
|
|
16
|
+
| Cross-cutting — routing, information architecture, the state vocabulary every surface obeys, presentation of streams | `docs/architecture/` — owns no code, like every architecture doc |
|
|
17
|
+
|
|
18
|
+
**Ownership: take the component globs only where they are unclaimed.** A surface's components are frequently already owned — by the page's LLD, by the feature behind one panel — and claiming them again is an ownership conflict, not protection. So: unclaimed globs go in `owns:`; otherwise the doc declares `owns: []` and is named in each owning LLD's **coordinated edits** row, phrased as *different axes, not a conflict* — the LLD owns the code, this document owns what a person observes.
|
|
19
|
+
|
|
20
|
+
That matters because a doc nothing implicates is a doc that rots, and a surface rewritten under an unimplicated document is the failure this genre exists to prevent. The coordinated-edit row is the weaker of the two bindings; prefer real ownership wherever the globs are free.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Required sections
|
|
25
|
+
|
|
26
|
+
### Purpose
|
|
27
|
+
|
|
28
|
+
One sentence: what a person comes to this surface to do. Then the sharper half — **what this surface is not for**, naming the neighbouring surface that is. A purpose that admits everything cannot refuse anything, and a surface that cannot refuse accretes.
|
|
29
|
+
|
|
30
|
+
### Anatomy
|
|
31
|
+
|
|
32
|
+
The regions, and what each is for. An ASCII sketch beats a paragraph. Name each region the way the interface names it, not the way the component tree does — a reader holds the screen, not the file list.
|
|
33
|
+
|
|
34
|
+
### States
|
|
35
|
+
|
|
36
|
+
**The complete set, exhaustively.** Every surface has more states than its happy path, and an unenumerated state is one the interface improvised — which is exactly how a surface starts lying. One row per state: what causes it, what a person sees, and what they can do from it.
|
|
37
|
+
|
|
38
|
+
| State | Entered when | The person sees | Can do |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
|
|
41
|
+
Never omit a state because it is rare or ugly. The failed, empty, and stale states are the ones a person meets on their worst day, and they are the ones no one designs.
|
|
42
|
+
|
|
43
|
+
### Affordances
|
|
44
|
+
|
|
45
|
+
What a person can do, and what each action does. A disabled control states why it is disabled — a control that is simply inert teaches nothing and gets clicked again.
|
|
46
|
+
|
|
47
|
+
### Promises
|
|
48
|
+
|
|
49
|
+
**What this surface never does**, stated as absolutes. This is the section the rest of the system is measured against: a backend change that would break one of these is a breaking change, whoever ships it. Write the ones that are easy to violate accidentally — never silently discard typed input, never show a completed state over incomplete work, never move a control under a cursor mid-interaction.
|
|
50
|
+
|
|
51
|
+
### Transitions
|
|
52
|
+
|
|
53
|
+
What moves the surface between states, and who causes each move — the person, the server, or elapsed time. Server-caused transitions are the ones worth naming precisely, because they are the ones a person did not ask for.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Optional sections
|
|
58
|
+
|
|
59
|
+
**Copy** — the exact words, where the wording is load-bearing (a refusal, an irreversible confirmation). **Density and rhythm** — where a surface deliberately differs from the design system's default. **Open questions** — decisions deliberately not yet made, so an implementer knows what to ask rather than guessing.
|
|
60
|
+
|
|
61
|
+
## Rules
|
|
62
|
+
|
|
63
|
+
- **Present tense, no history.** The journey lives in `docs/history/`; this document simply becomes correct.
|
|
64
|
+
- **Describe the observable, not the implementation.** "The rail shows every phase, the current one marked" — never "`railFromView` maps `view.phases`". Naming a component in a UX document is a boundary leak, and the name goes stale before the behavior does.
|
|
65
|
+
- **Every state gets a person-facing sentence.** If you cannot say what a person sees, the state is not designed yet — write it as an open question rather than eliding it.
|
|
66
|
+
- **No screenshots.** They rot invisibly and cannot be diffed.
|
|
@@ -86,7 +86,7 @@ Cheap, read-only. Scope the target, grade its access, detect which dimensions it
|
|
|
86
86
|
|
|
87
87
|
Dispatch one collector per available dimension; collect exhaustively before context is lost. Every collector writes `research/<target>/dimensions/<dim>/_summary.md` (with the **provenance frontmatter** — method · confidence · completeness · gaps) + `raw/`, obeying `.claude/rules/ingestion.md`.
|
|
88
88
|
|
|
89
|
-
- **Run independents in parallel** — dispatch `docs`, `packages`, `api`, `website`, `community`, `deployed-client-bundle`, `infra-backend-fingerprint`, `distribution-artifacts` together (`subagent_type='<dimension>'`, one message, multiple Agent calls). Seed each collector with **warm entry points (URLs / repos / hosts), not claimed facts** — any fact **or inference** carried from Discovery (a price, a tier name, a version — **or a derived hypothesis like "localStorage-key X ⇒ backend is Y"**) is tagged `verify:may-be-stale` so the collector **re-derives it instead of anchoring** on a possibly-stale (or possibly-wrong) value. A Discovery _inference_ is never seeded as a plan fact: label it a hypothesis in `00-recon-plan.md` and tag it `verify:` in the seed, so a collector that inherits it reports it hedged rather than echoing it as observed (a real run seeded an `apolloAnonId ⇒ GraphQL backend` guess that two captures then repeated before the session refuted it). **A sibling collector's _summary_ is a claim, not a source** — when seeding one collector with something another collector reported, tag it `verify:` too, unless the main session has actually read the underlying `raw/` artifact. A returned summary is a compressed, unverified assertion; relaying it into a fresh brief launders it into apparent provenance, and the receiving sub-agent (which cannot see the conversation) has no way to tell a seeded claim from an established fact. (Origin: Emergent — the orchestrator relayed a "documented limitation: the connector cannot deploy" line from one collector's summary into another's brief; it was **not in the corpus**, and only the receiving agent's own verification caught it.) Only one dimension may drive the browser at a time (see `ingestion` §7 rule 9): the parallel collectors use WebFetch/curl, leaving Chrome to `session`.
|
|
89
|
+
- **Run independents in parallel** — dispatch `docs`, `packages`, `api`, `website`, `community`, `deployed-client-bundle`, `infra-backend-fingerprint`, `distribution-artifacts` together (`subagent_type='<dimension>'`, one message, multiple Agent calls). Seed each collector with **warm entry points (URLs / repos / hosts), not claimed facts** — any fact **or inference** carried from Discovery (a price, a tier name, a version — **or a derived hypothesis like "localStorage-key X ⇒ backend is Y"**) is tagged `verify:may-be-stale` so the collector **re-derives it instead of anchoring** on a possibly-stale (or possibly-wrong) value. A Discovery _inference_ is never seeded as a plan fact: label it a hypothesis in `00-recon-plan.md` and tag it `verify:` in the seed, so a collector that inherits it reports it hedged rather than echoing it as observed (a real run seeded an `apolloAnonId ⇒ GraphQL backend` guess that two captures then repeated before the session refuted it). **A sibling collector's _summary_ is a claim, not a source** — when seeding one collector with something another collector reported, tag it `verify:` too, unless the main session has actually read the underlying `raw/` artifact. A returned summary is a compressed, unverified assertion; relaying it into a fresh brief launders it into apparent provenance, and the receiving sub-agent (which cannot see the conversation) has no way to tell a seeded claim from an established fact. (Origin: Emergent — the orchestrator relayed a "documented limitation: the connector cannot deploy" line from one collector's summary into another's brief; it was **not in the corpus**, and only the receiving agent's own verification caught it.) **Every QUANTITATIVE claim in a brief carries its lane.** The rule above covers a *sibling's summary*; it does not cover a number the **orchestrator itself** picked up in passing — from a search result, a page it skimmed, or its own recollection — and then re-emitted as framing. A figure the main session cannot name a `raw/` anchor for is either seeded **`verify:`** or **omitted**; it is never stated flat. A number in a brief reads as established even when nothing established it, and the receiving agent cannot tell the difference. (Origin: indus-sarvam — the orchestrator seeded a "₹98.68 crore IndiaAI funding" figure into the positioning brief as context; the receiving agent traced it to a **third-party user's GitHub issue body**, not to any first-party source, and had to de-launder it. The vendor states only "compute provided under the IndiaAI mission", with no amount.) Only one dimension may drive the browser at a time (see `ingestion` §7 rule 9): the parallel collectors use WebFetch/curl, leaving Chrome to `session`.
|
|
90
90
|
- **Codebase fan-out** — dispatch `codebase`; it clones + maps + returns a per-package work-list; then the main session fans out one reader per substantial package.
|
|
91
91
|
- **Gated dimensions** — `session` and `wire-capture` (rung ≥2) need explicit user authorization first (see `ingestion` §7). `session` follows `.claude/rules/ingestion.md` §9. When `session` is authorized, **dispatch the non-browser collectors in the background (`run_in_background`) and drive the gated browser session in the main session concurrently** — they don't contend (Chrome belongs to the main session; the collectors use WebFetch/curl), so the whole of Ingestion runs in parallel.
|
|
92
92
|
- **Shared seam** — **seed the two shared files `dimensions/_shared/{api-path-catalog.md, feature-flags.md}` (empty, with a header) once, before the collector fan-out**; bundle/session/wire/ distribution then each _append_ a `source:`-tagged section to the same physical file (never their own copy). One owner dimension per static asset; a second mine of the same asset is one source, not corroboration (`ingestion` §6).
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "discover",
|
|
3
3
|
"name": "discover",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.16",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
8
8
|
".vscode/settings.json"
|
|
9
9
|
],
|
|
10
10
|
"seeds": [],
|
|
11
|
-
"digest": "
|
|
11
|
+
"digest": "0bf7a6a6f479"
|
|
12
12
|
}
|
|
@@ -111,7 +111,10 @@ experiential / IA coverage axis (`self-correction.md` rubric, axis 2, max 20) is
|
|
|
111
111
|
unlocated capabilities and hiding them would inflate the rate. But they are **dispositioned**, so they
|
|
112
112
|
never draw the −2 un-dispositioned penalty, and the scorecard carries `features_not_yet_reachable` so a
|
|
113
113
|
reader can tell a *policy/authorization* boundary from a *thoroughness* failure.
|
|
114
|
-
- `flow_coverage` = `100 × (primary journeys diagrammed in ux-flows.md ÷ journeys identified)
|
|
114
|
+
- `flow_coverage` = `100 × (primary journeys diagrammed in ux-flows.md ÷ journeys identified)` — **read both
|
|
115
|
+
numbers from `ux-flows.md`'s `journeys_diagrammed` / `journeys_identified` frontmatter**
|
|
116
|
+
(`cartography-flows.md`), never re-derive the denominator here. A reconstructed denominator lets the
|
|
117
|
+
scorer pick the number that flatters the score, and makes the metric non-reproducible across runs.
|
|
115
118
|
- `screenshot_coverage` = `100 × (primary surfaces with a screenshot ÷ all primary surfaces)`.
|
|
116
119
|
- `ia_nav_complete` = every D0 nav destination has a surface card.
|
|
117
120
|
|
|
@@ -60,6 +60,15 @@ job id, a streamed result). Stop at the terminal write or external response.
|
|
|
60
60
|
|
|
61
61
|
````markdown
|
|
62
62
|
# <Target> — UX Flows
|
|
63
|
+
---
|
|
64
|
+
journeys_identified: <N> # REQUIRED — every primary journey the corpus identifies, whether or not it
|
|
65
|
+
# was diagrammed. This is the DENOMINATOR of feature-coverage's
|
|
66
|
+
# `flow_coverage`; without it the metric is reconstructed by whoever scores
|
|
67
|
+
# it, and a run that identifies few journeys scores the same as one that
|
|
68
|
+
# identifies many and diagrams them all.
|
|
69
|
+
journeys_diagrammed: <N> # the count of sequence diagrams below
|
|
70
|
+
undiagrammed: [<name>, …] # the named difference — never an empty list with N_identified > N_diagrammed
|
|
71
|
+
---
|
|
63
72
|
## TL;DR (how the product gets work done: sync-REST vs async-job, where realtime lives,
|
|
64
73
|
the approval/gate pattern, what's brokered server-side)
|
|
65
74
|
## Flow index (table: flow · category · trigger · participants · complexity)
|
|
@@ -78,11 +78,11 @@ Keep `status` and `confidence` honest — they drive the entire evaluation weigh
|
|
|
78
78
|
|
|
79
79
|
## 3. Method & confidence vocabulary (drives evaluation weighting)
|
|
80
80
|
|
|
81
|
-
`method` is one of: `clone-and-map`, `source-map-reassembly`, `registry-metadata`, `ats-api`, `openapi-verbatim`, `graphql-introspection`, `crawl-clip`, `docs-reconstructed`, `bundle-string-mine`, `dns-ct-fingerprint`, `wire-tap-browser`, `wire-har`, `wire-mitmproxy`, `binary-extract` (asar/apk/crx), `session-playbook`, `sdk-harness`, `inferred`.
|
|
81
|
+
`method` is one of: `clone-and-map`, `source-map-reassembly`, `registry-metadata`, `ats-api`, `first-party-api-json`, `openapi-verbatim`, `graphql-introspection`, `crawl-clip`, `docs-reconstructed`, `bundle-string-mine`, `dns-ct-fingerprint`, `wire-tap-browser`, `wire-har`, `wire-mitmproxy`, `binary-extract` (asar/apk/crx), `session-playbook`, `sdk-harness`, `inferred`.
|
|
82
82
|
|
|
83
83
|
**Confidence is a function of method** (evaluation relies on this mapping):
|
|
84
84
|
|
|
85
|
-
- **high** — verbatim machine artifacts: `openapi-verbatim`, `graphql-introspection`, `registry-metadata`, `ats-api`, `clone-and-map`, `binary-extract`.
|
|
85
|
+
- **high** — verbatim machine artifacts: `openapi-verbatim`, `graphql-introspection`, `registry-metadata`, `ats-api`, `first-party-api-json`, `clone-and-map`, `binary-extract`.
|
|
86
86
|
|
|
87
87
|
> **`ats-api` — a first-party job-board / ATS JSON API is NOT a crawl.** A response from an official
|
|
88
88
|
> applicant-tracking endpoint (`boards-api.greenhouse.io/v1/boards/<slug>/jobs?content=true`,
|
|
@@ -94,6 +94,20 @@ Keep `status` and `confidence` honest — they drive the entire evaluation weigh
|
|
|
94
94
|
> (Origin: Emergent — a 433 KB verbatim Greenhouse JSON body carrying a byte-identical declared tech stack
|
|
95
95
|
> across two postings was labelled `crawl-clip` with `confidence: high`, a mismatch against this very
|
|
96
96
|
> table. The confidence was right and the vocabulary had no word for the artifact.)
|
|
97
|
+
|
|
98
|
+
> **`first-party-api-json` — the general case of the rule above.** `ats-api` fixed the *hiring* instance
|
|
99
|
+
> of a wider gap: **any verbatim JSON response from a first-party or official platform API is a machine
|
|
100
|
+
> artifact, not a crawl**, and maps to **high**. Use it for a GitHub org/issues/releases API response
|
|
101
|
+
> (`api.github.com/orgs/<org>/repos`, `/search/issues?q=org:<org>`), a status-page API
|
|
102
|
+
> (`status.<vendor>.com/api/v2/summary.json`), a changelog/release JSON feed, a public app-store listing
|
|
103
|
+
> API, or any comparable official endpoint. **The distinction is the artifact, not the topic:** the
|
|
104
|
+
> *rendered page* of the same information is `crawl-clip` (medium); the *API body* is
|
|
105
|
+
> `first-party-api-json` (high). Prefer the more specific term where one exists — `registry-metadata` for
|
|
106
|
+
> npm/PyPI/crates, `ats-api` for job boards, `openapi-verbatim` for a served spec — and reach for this one
|
|
107
|
+
> only when none fits. (Origin: indus-sarvam — the `community` lane mined the GitHub API and, finding no
|
|
108
|
+
> correct term, graded itself `registry-metadata`. The evidence class and the `high` confidence were both
|
|
109
|
+
> right; the label named the wrong registry. A vocabulary with no word for an artifact does not stop a
|
|
110
|
+
> collector using it — it just makes the provenance record lie.)
|
|
97
111
|
- **medium** — `crawl-clip`, `docs-reconstructed`, `bundle-string-mine`, `dns-ct-fingerprint`, `wire-har`.
|
|
98
112
|
- **low** — `inferred`, `session-playbook` Pass-1-only, `wire-tap-browser` shapes-only, any wire-drift observation without a spec.
|
|
99
113
|
|
|
@@ -284,7 +298,7 @@ the elements needing redaction change as you navigate.
|
|
|
284
298
|
|
|
285
299
|
`deployed-client-bundle` (static unauth SPA), `session` (authenticated browser runtime), and `wire-capture` (non-browser client) can all surface the same **API path catalog** and **feature flags**. To keep them consumable instead of triplicated **N times** (a real run forked `api-path-catalog.md` up to 7×, and "two copies agree" then mis-reads as corroboration):
|
|
286
300
|
|
|
287
|
-
- **One physical file, one canonical location, append-only.** The seam files live at `dimensions/_shared/api-path-catalog.md` and `dimensions/_shared/feature-flags.md` — **Ingestion seeds the two empty files (with a header) once, before the collector fan-out.** Every contributing dimension **appends a `source:`-tagged section** (`source: bundle | session | wire | distribution`) and **never creates its own copy**; each collector's `_summary.md` Artifacts list **links** the shared file rather than duplicating it. **Shared-seam allowlist:** these two filenames are the _only_ shared/single-physical artifacts — every other `raw/*` is dimension-private.
|
|
301
|
+
- **One physical file, one canonical location, append-only.** The seam files live at `dimensions/_shared/api-path-catalog.md` and `dimensions/_shared/feature-flags.md` — **Ingestion seeds the two empty files (with a header) once, before the collector fan-out.** Every contributing dimension **appends a `source:`-tagged section** (`source: bundle | session | wire | distribution | api`) and **never creates its own copy**; each collector's `_summary.md` Artifacts list **links** the shared file rather than duplicating it. **Shared-seam allowlist:** these two filenames are the _only_ shared/single-physical artifacts — every other `raw/*` is dimension-private.
|
|
288
302
|
- Evaluation owns the **API-path diff** (an _adaptive N-way_ diff over whichever runtime sources were actually run — see evaluation template 3) in `data-model-api-surface.md`. A path that appears in `bundle` but never in `session`/`wire` is a dormant/unshipped route — a finding, not noise.
|
|
289
303
|
- **The cloned source tree is a shared working copy too (a sequencing race, not just the seam files).** On a `source:full` target, `codebase` clones into `source/<target>/`, and other dimensions (`docs`/`packages`) may read in-repo files from it. A clone-dependent collector dispatched **concurrently** with the clone can read a **mid-checkout tree** (a `.git`-only state) and silently fall back to a weaker source. So either (a) treat the `codebase` clone as a **sequencing barrier** — let it finish before dispatching any collector that reads `source/<target>/` — or (b) require each clone-dependent collector to **fetch its own copy** (GitHub API / its own shallow clone) and never assume the shared clone is ready. (Origin: Twenty — `docs` raced the `codebase` clone, saw a `.git`-only tree, and fell back to the live docs site; `packages` correctly fetched its own manifests and was unaffected.)
|
|
290
304
|
|
|
@@ -294,7 +308,9 @@ the elements needing redaction change as you navigate.
|
|
|
294
308
|
>
|
|
295
309
|
> **Paired inputs make the diff feedable (capture cue).** The split is only emittable if BOTH lanes were captured: when a target has **a published API AND an authed session**, the **pre-nav in-page fetch/XHR tap** (`session` — the app-own paths it actually calls) and the **unauth bundle/route-manifest** (`deployed-client-bundle` — the published paths) are **paired required inputs**. Install the tap _before_ navigation (else on-load app calls are missed) and capture the manifest unauth; a session that only screenshots, or a bundle mine with no session, leaves the diff un-feedable. (Origin: Akeneo — 54 session-observed app-own paths vs a 124-path unauth `/api/rest/v1` manifest, near-disjoint.)
|
|
296
310
|
>
|
|
297
|
-
> **Host-level dimensions cite, they don't append (seam allowlist).** The shared `api-path-catalog.md` `## source:` allowlist is **bundle / session / wire / distribution**
|
|
311
|
+
> **Host-level dimensions cite, they don't append (seam allowlist).** The shared `api-path-catalog.md` `## source:` allowlist is **bundle / session / wire / distribution** (the path-contributing *runtime* lanes) **plus `api`** (the *published* lane — see below). A host-level dimension (`infra-backend-fingerprint`) that wants to cross-reference the catalog does so **from its own `raw/`** (cite hosts/paths by reference), and does **not** append its own `## source:` section to the shared file. (Origin: Akeneo — infra appended a host-level note to the path catalog; benign but blurs the allowlist.)
|
|
312
|
+
>
|
|
313
|
+
> **`api` IS on the allowlist — but it is the PUBLISHED lane, and it never corroborates a runtime path.** The published developer/partner spec belongs in the same physical file as the runtime lanes because the **published-vs-app-own diff** (evaluation template 3) is the whole point of the seam, and it is far easier to compute when both sides sit side by side under one header. Two hard constraints ride with the inclusion: (a) an `## source: api` section carries the **spec-declared** surface only — never a path the collector merely inferred; and (b) **`api` is excluded from the promotion rule for app-own paths** — a path appearing in `## source: api` and in `## source: session` is *the published surface and the runtime surface agreeing*, which is a **finding about drift (or its absence)**, not two independent dimensions corroborating one claim. (Origin: indus-sarvam — the `api` lane appended a 70-operation published catalog in violation of the then-allowlist; deleting it would have made the corpus strictly worse, because that section is exactly what made the published-vs-app-own diff feedable. The rule was wrong, not the collector.)
|
|
298
314
|
|
|
299
315
|
---
|
|
300
316
|
|
|
@@ -559,7 +575,27 @@ A single linear sweep wastes context on things you can read for free and wastes
|
|
|
559
575
|
|
|
560
576
|
**Pass 2 — fresh-run validation (with explicit cost/state-change confirmation).** Only after Pass 1, and only with explicit user OK stating the exact action + cheapest mode + estimated cost. Catches what Pass 1 can't: the submit/write request body, live state-machine transitions, the completion event, server-side resolution of shorthand, ID/slug-generation, the real iteration endpoint, and the full realtime event catalog. **Legitimate to skip** if the user opts out — leave write-side shapes as open questions; never fabricate them.
|
|
561
577
|
|
|
562
|
-
> **Record the write-side decision as a machine flag, not re-derived prose.** A Pass-1-only run leaves the _entire_ write/generate/launch surface unobserved — feature _existence/gating_ is solid, but feature _execution behavior_ rests on marketing/docs and must never be promoted to fact (this caveat otherwise gets re-hedged across 3–4 rollups every cost-gated run). So the session `_summary.md` frontmatter carries `write_side_observed: <true|false>` and `pass2: <not-applicable | offered-pending | declined-by-user | deferred | done>` (this flag is the load-bearing part — it lets Evaluation render the write-side gap _once_).
|
|
578
|
+
> **Record the write-side decision as a machine flag, not re-derived prose.** A Pass-1-only run leaves the _entire_ write/generate/launch surface unobserved — feature _existence/gating_ is solid, but feature _execution behavior_ rests on marketing/docs and must never be promoted to fact (this caveat otherwise gets re-hedged across 3–4 rollups every cost-gated run). So the session `_summary.md` frontmatter carries `write_side_observed: <true|false>` and `pass2: <not-applicable | offered-pending | declined-by-user | deferred | authorized-declined | done>` (this flag is the load-bearing part — it lets Evaluation render the write-side gap _once_).
|
|
579
|
+
|
|
580
|
+
> **An AUTHORIZED Pass-2 that is never exercised is a DEFECT, not a default.** When the user has granted
|
|
581
|
+
> Pass-2 (explicitly, or by saying the run may spend credits / test it), the session lane must do one of
|
|
582
|
+
> exactly two things before Evaluation:
|
|
583
|
+
>
|
|
584
|
+
> 1. **Exercise the cheapest write action** that closes the gap — and there is almost always a cheap one:
|
|
585
|
+
> a simulated/dry run, a draft save, a version bump, a single smallest-unit generation. Price it, name
|
|
586
|
+
> it, run it, capture the request+response envelope.
|
|
587
|
+
> 2. **Record `pass2: authorized-declined` with the reason** (the user wrapped the run early; the cheapest
|
|
588
|
+
> action still mutated shared state; no non-destructive write exists), plus the exact actions and costs
|
|
589
|
+
> that would close it.
|
|
590
|
+
>
|
|
591
|
+
> `pass2: deferred` is **not** a valid terminal state for an authorized run — it is the state that lets an
|
|
592
|
+
> authorization quietly expire. Mode 5 treats an authorized-but-unexercised Pass-2 as a **medium Ingestion
|
|
593
|
+
> defect** and charges it, because a run that holds permission to observe the write side and ships without
|
|
594
|
+
> it has left its single largest blind spot open by inattention rather than by policy. (Origin:
|
|
595
|
+
> indus-sarvam — the user granted credit spend mid-run; the orchestrator acknowledged it, planned around
|
|
596
|
+
> it, asked which action to take, and shipped `write_side_observed: false` when no reply arrived. The
|
|
597
|
+
> whole write surface, the deploy transition and the live-call realtime plane went unobserved with 100
|
|
598
|
+
> credits unspent.) When it would help a later run resume, optionally add `captures/pass2-followup.md` listing the **exact cheapest-mode write actions + est. cost** that would close the gap. Discovery seeds the `pass2:` decision in `00-recon-plan.md` for any `auth:have-login` target.
|
|
563
599
|
|
|
564
600
|
**Pass 3 — synthesis.** Walk the captures, pull each finding into the dimension's `_summary.md`, note the cleverest design choices, defaults imposed, performance characteristics, and open questions.
|
|
565
601
|
|
|
@@ -128,6 +128,40 @@ pbpaste > capture.json # macOS
|
|
|
128
128
|
xclip -o > capture.json # Linux
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
> **⚠ VERIFY THE PASTE — `copied=true` is NOT proof the OS clipboard changed.** `execCommand('copy')`
|
|
132
|
+
> reports success from *inside the page* and can still leave the system clipboard holding a **previously
|
|
133
|
+
> copied payload** — most reliably when Chrome is not the OS-focused window (the agent's terminal is).
|
|
134
|
+
> A naive `pbpaste > raw/x.json` then writes **the wrong body** into `raw/` under a confident `_meta`,
|
|
135
|
+
> which is the worst artifact class there is: a stale file that scores silently.
|
|
136
|
+
>
|
|
137
|
+
> **Always assert the length matches what the tap reported**, before trusting the file:
|
|
138
|
+
>
|
|
139
|
+
> ```bash
|
|
140
|
+
> EXPECT=14866 # the len= the __CP call returned
|
|
141
|
+
> GOT=$(pbpaste | wc -m | tr -d ' ') # -m = CHARACTERS, never -c (bytes). See below.
|
|
142
|
+
> [ "$GOT" -ge $((EXPECT-100)) ] && [ "$GOT" -le $((EXPECT+100)) ] \
|
|
143
|
+
> && pbpaste > raw/capture.json && echo "ok len=$GOT" \
|
|
144
|
+
> || echo "STALE CLIPBOARD: expected ~$EXPECT, got $GOT — do NOT write"
|
|
145
|
+
> ```
|
|
146
|
+
>
|
|
147
|
+
> **Compare CHARACTERS (`wc -m`), not BYTES (`wc -c`).** JavaScript's `String.length` counts UTF-16 code
|
|
148
|
+
> units; `wc -c` counts UTF-8 bytes. Any non-ASCII payload makes the two diverge badly — Devanagari,
|
|
149
|
+
> Bengali, Tamil, CJK and emoji are 3–4 bytes per character, so a perfectly fresh 16,378-character capture
|
|
150
|
+
> measures 19,740 bytes and a byte-based guard **rejects a good artifact**. Allow ±100 for the trailing
|
|
151
|
+
> newline and minor encoding slack. (Origin: indus-sarvam — this guard's own first use produced a false
|
|
152
|
+
> positive on a Hindi-language agent transcript; the clipboard was fine and the check was wrong. On a
|
|
153
|
+
> multilingual target the byte form fails *more often than it succeeds*.)
|
|
154
|
+
>
|
|
155
|
+
> Cheap corroboration: also compare a head token (`pbpaste | head -c 40`) against the head the tap
|
|
156
|
+
> returned. **Escalation ladder when it is stale:** (1) click inside the page and retry — it is flaky, not
|
|
157
|
+
> permanently broken; (2) retry once more with the textarea left mounted a beat longer; (3) fall back to
|
|
158
|
+
> paging via `javascript_tool` slices (budget ~1 KB of *usable* output per slice, not the 3 KB the slice
|
|
159
|
+
> size suggests); (4) if paging is uneconomic, write a **decoded** artifact with `verbatim: false`,
|
|
160
|
+
> `_meta.why_not_verbatim` naming the failure, and `_meta.refetch` carrying the exact command — an honest
|
|
161
|
+
> decoded artifact beats a confident wrong one. (Origin: indus-sarvam — a 14,866-byte flag payload
|
|
162
|
+
> reported `copied=true` three times while `pbpaste` kept returning a 195,216-byte body copied minutes
|
|
163
|
+
> earlier. Caught only because the length was checked.)
|
|
164
|
+
|
|
131
165
|
**Scrub before copying**, not after: build the object with `__SCRUB` first, then `window.__CP(pack)`. The
|
|
132
166
|
clipboard is local, so this stays inside the no-exfiltration rule (ingestion §7 rule 7).
|
|
133
167
|
|
|
@@ -71,6 +71,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
71
71
|
| Document env vars / config | [`templates/environment-registry.md`](templates/environment-registry.md) |
|
|
72
72
|
| Security / threat model | [`templates/security-overview.md`](templates/security-overview.md) |
|
|
73
73
|
| Testing strategy | [`templates/testing-strategy.md`](templates/testing-strategy.md) |
|
|
74
|
+
| What a person sees and can do on a surface (UX behavior) | [`templates/ux-behavior.md`](templates/ux-behavior.md) — decides observable behavior BEFORE the code; per-surface in `docs/features/{surface}-ux.md` owning the component globs, cross-cutting in `docs/architecture/`. Not the checklist, which only verifies it |
|
|
74
75
|
| Behavioral checklist for one feature (`docs/tests/{feature}.md`) | [`templates/behavioral-checklist.md`](templates/behavioral-checklist.md) — the verification gate's driving script, and the source its suite tests are drawn from |
|
|
75
76
|
| Acceptance criteria for one capability (`docs/acceptance/{capability}.md`) | [`templates/acceptance-criteria.md`](templates/acceptance-criteria.md) — the promises the built product must keep, as stable `AC-n` keyed assert lines verification surfaces parse as claims |
|
|
76
77
|
| Recurring procedure/setup | [`templates/operations-procedure.md`](templates/operations-procedure.md) |
|
|
@@ -15,6 +15,7 @@ One file per template. Every template is either a **truth document** (states cur
|
|
|
15
15
|
| Security overview | [security-overview.md](security-overview.md) | Truth |
|
|
16
16
|
| Testing strategy | [testing-strategy.md](testing-strategy.md) | Truth |
|
|
17
17
|
| Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | Truth |
|
|
18
|
+
| UX behavior | [ux-behavior.md](ux-behavior.md) | Truth |
|
|
18
19
|
| Operations procedure | [operations-procedure.md](operations-procedure.md) | Truth |
|
|
19
20
|
| Implementation map | [implementation-map.md](implementation-map.md) | Truth |
|
|
20
21
|
| Bookkeeping ledger | [bookkeeping.md](bookkeeping.md) | Truth (work queue) |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# UX Behavior — template
|
|
2
|
+
|
|
3
|
+
**Truth document.** States what a person sees and can do on a surface, and what the surface promises never to do. Present tense, like every truth document.
|
|
4
|
+
|
|
5
|
+
**`type: ux`.**
|
|
6
|
+
|
|
7
|
+
**Why this genre exists.** A feature doc (LLD) states what a feature is, how its data flows, and which contracts it honours — all of which stay plausibly true through a rewrite of the surface. Nothing else in the repo states what a person *observes*. So a change to the wire lands, the interface absorbs it, and the absorption reads as maintenance: no test fails, no lint fires, no document is contradicted. This document is the thing a silent change contradicts.
|
|
8
|
+
|
|
9
|
+
**Not a test plan.** `docs/tests/{feature}.md` records that behavior was driven and holds `Pinned:` traces; this document *decides* the behavior it later verifies. Written after the code, it is a transcript. Write it first.
|
|
10
|
+
|
|
11
|
+
**Scope decides placement, not medium.**
|
|
12
|
+
|
|
13
|
+
| The behavior is… | Where it lives |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| One surface's — a panel, an editor, a dialog | `docs/features/{surface}-ux.md`, with `owns:` over that surface's component paths |
|
|
16
|
+
| Cross-cutting — routing, information architecture, the state vocabulary every surface obeys, presentation of streams | `docs/architecture/` — owns no code, like every architecture doc |
|
|
17
|
+
|
|
18
|
+
**Ownership: take the component globs only where they are unclaimed.** A surface's components are frequently already owned — by the page's LLD, by the feature behind one panel — and claiming them again is an ownership conflict, not protection. So: unclaimed globs go in `owns:`; otherwise the doc declares `owns: []` and is named in each owning LLD's **coordinated edits** row, phrased as *different axes, not a conflict* — the LLD owns the code, this document owns what a person observes.
|
|
19
|
+
|
|
20
|
+
That matters because a doc nothing implicates is a doc that rots, and a surface rewritten under an unimplicated document is the failure this genre exists to prevent. The coordinated-edit row is the weaker of the two bindings; prefer real ownership wherever the globs are free.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Required sections
|
|
25
|
+
|
|
26
|
+
### Purpose
|
|
27
|
+
|
|
28
|
+
One sentence: what a person comes to this surface to do. Then the sharper half — **what this surface is not for**, naming the neighbouring surface that is. A purpose that admits everything cannot refuse anything, and a surface that cannot refuse accretes.
|
|
29
|
+
|
|
30
|
+
### Anatomy
|
|
31
|
+
|
|
32
|
+
The regions, and what each is for. An ASCII sketch beats a paragraph. Name each region the way the interface names it, not the way the component tree does — a reader holds the screen, not the file list.
|
|
33
|
+
|
|
34
|
+
### States
|
|
35
|
+
|
|
36
|
+
**The complete set, exhaustively.** Every surface has more states than its happy path, and an unenumerated state is one the interface improvised — which is exactly how a surface starts lying. One row per state: what causes it, what a person sees, and what they can do from it.
|
|
37
|
+
|
|
38
|
+
| State | Entered when | The person sees | Can do |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
|
|
41
|
+
Never omit a state because it is rare or ugly. The failed, empty, and stale states are the ones a person meets on their worst day, and they are the ones no one designs.
|
|
42
|
+
|
|
43
|
+
### Affordances
|
|
44
|
+
|
|
45
|
+
What a person can do, and what each action does. A disabled control states why it is disabled — a control that is simply inert teaches nothing and gets clicked again.
|
|
46
|
+
|
|
47
|
+
### Promises
|
|
48
|
+
|
|
49
|
+
**What this surface never does**, stated as absolutes. This is the section the rest of the system is measured against: a backend change that would break one of these is a breaking change, whoever ships it. Write the ones that are easy to violate accidentally — never silently discard typed input, never show a completed state over incomplete work, never move a control under a cursor mid-interaction.
|
|
50
|
+
|
|
51
|
+
### Transitions
|
|
52
|
+
|
|
53
|
+
What moves the surface between states, and who causes each move — the person, the server, or elapsed time. Server-caused transitions are the ones worth naming precisely, because they are the ones a person did not ask for.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Optional sections
|
|
58
|
+
|
|
59
|
+
**Copy** — the exact words, where the wording is load-bearing (a refusal, an irreversible confirmation). **Density and rhythm** — where a surface deliberately differs from the design system's default. **Open questions** — decisions deliberately not yet made, so an implementer knows what to ask rather than guessing.
|
|
60
|
+
|
|
61
|
+
## Rules
|
|
62
|
+
|
|
63
|
+
- **Present tense, no history.** The journey lives in `docs/history/`; this document simply becomes correct.
|
|
64
|
+
- **Describe the observable, not the implementation.** "The rail shows every phase, the current one marked" — never "`railFromView` maps `view.phases`". Naming a component in a UX document is a boundary leak, and the name goes stale before the behavior does.
|
|
65
|
+
- **Every state gets a person-facing sentence.** If you cannot say what a person sees, the state is not designed yet — write it as an open question rather than eliding it.
|
|
66
|
+
- **No screenshots.** They rot invisibly and cannot be diffed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.89",
|
|
4
4
|
"description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"agent",
|
|
43
43
|
"plugin"
|
|
44
44
|
],
|
|
45
|
-
"etnaFingerprint": "
|
|
45
|
+
"etnaFingerprint": "f3094aa08430"
|
|
46
46
|
}
|