@naswerks/engineering-loop 0.1.0
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/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +8 -0
- package/CHANGELOG.md +32 -0
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/bin/verify-staged.mjs +247 -0
- package/package.json +35 -0
- package/references/docs-workflow.md +417 -0
- package/references/engineering-loop.md +298 -0
- package/skills/docs-audit-feature/SKILL.md +88 -0
- package/skills/docs-audit-full/SKILL.md +54 -0
- package/skills/docs-backlog/SKILL.md +41 -0
- package/skills/docs-process/SKILL.md +146 -0
- package/skills/docs-status/SKILL.md +47 -0
- package/skills/docs-write/SKILL.md +43 -0
- package/skills/init/SKILL.md +163 -0
- package/skills/init/scripts/doctor.mjs +176 -0
- package/skills/spec-child/SKILL.md +149 -0
- package/skills/spec-ignite/SKILL.md +277 -0
- package/skills/spec-parent/SKILL.md +634 -0
- package/skills/spec-pipeline/SKILL.md +453 -0
- package/skills/spec-retro/SKILL.md +398 -0
- package/skills/spec-review/SKILL.md +266 -0
- package/skills/spec-seat/SKILL.md +512 -0
- package/skills/spec-witness/SKILL.md +222 -0
- package/templates/claude-md-stanza.md +13 -0
- package/templates/docs/_meta/doc-index.md +90 -0
- package/templates/docs/infrastructure/background-work.md +53 -0
- package/templates/docs/infrastructure/realtime-events.md +54 -0
- package/templates/docs/patterns/backend-patterns.md +96 -0
- package/templates/docs/patterns/codegen.md +69 -0
- package/templates/docs/patterns/frontend-patterns.md +92 -0
- package/templates/docs/patterns/state-management.md +73 -0
- package/templates/docs/patterns/testing.md +103 -0
- package/templates/docs/patterns/ui-style-guide.md +76 -0
- package/templates/docs/patterns/vertical-slice-anatomy.md +61 -0
- package/templates/docs/readme-archive.md +9 -0
- package/templates/docs/readme-features.md +7 -0
- package/templates/docs/readme-guides.md +7 -0
- package/templates/docs/readme-infrastructure.md +7 -0
- package/templates/docs/readme-patterns.md +9 -0
- package/templates/docs/readme-research.md +9 -0
- package/templates/docs/readme-working.md +9 -0
- package/templates/settings-pin.json +10 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-witness
|
|
3
|
+
description: Become the WITNESS seat for a running or finished spec pipeline — a read-only verifier with substrate access (the control plane's read lanes, transcripts, endpoints, source) and no in-band blindness. Use when the human is nervous about where a pipeline is, when actors' claims conflict, to verify a specific claim about a run, or run spec-witness {pipeline id or topic}. The seats act, the human judges, the witness testifies. ZERO writes, ZERO terminal input, ZERO rulings.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# spec-witness
|
|
7
|
+
|
|
8
|
+
Turn this session into the **witness seat** for a spec-pipeline effort: the read-only fourth seat —
|
|
9
|
+
every dispute in a live run ("the push never parked", a backstop scare, a mode-bug confusion,
|
|
10
|
+
a wrong-chip flip) is resolved by substrate consultation, never by testimony.
|
|
11
|
+
|
|
12
|
+
## The contract (hard fences)
|
|
13
|
+
|
|
14
|
+
- **READ-ONLY, absolutely:** no file writes outside your own scratchpad, no terminal input to
|
|
15
|
+
any session, no gate approvals, no rulings, no endpoint calls that mutate (reads only). You TESTIFY;
|
|
16
|
+
the coordinator rules; the human judges.
|
|
17
|
+
**AND THIS FENCE IS NOT ONLY PROSE.** A witness ticket structurally cannot carry the write scope: the
|
|
18
|
+
control plane mints the witness kind the four read scopes and **never `acts:write`**. The charter
|
|
19
|
+
survives a seat that decides to be helpful — but it is still yours to keep, because the mint bounds the
|
|
20
|
+
SEAT SURFACE and not your shell.
|
|
21
|
+
- **THE PRIME DIRECTIVE: in-band testimony is a hypothesis; the substrate is the witness.**
|
|
22
|
+
A seat says pushed-clean: check the park rows. A coordinator says channel-broken: check the gate code
|
|
23
|
+
and the trace lane. The UI says a mode: check the store (a second truth). Every actor gets fact-checked
|
|
24
|
+
against rows — INCLUDING YOU: state your own claims with their receipts or mark them hypotheses.
|
|
25
|
+
**And your OWN inferences get the same treatment as anyone's testimony — especially when your
|
|
26
|
+
feed is thin.** A hypothesis built on data you did not receive is not more reliable for having
|
|
27
|
+
been reasoned rather than reported; it is less, because you cannot see the shape of what is
|
|
28
|
+
missing. Ask what would have to have arrived for this conclusion to be safe, and whether it did.
|
|
29
|
+
The seat that fact-checks everyone is the one nobody is fact-checking.
|
|
30
|
+
- Report shape, always: **STATE** (per seat: its state, its gate posture, its last act, its phase),
|
|
31
|
+
**ANOMALIES** (each with its receipt), **WHOSE TURN IS IT** (who the run is waiting on: a coordinator
|
|
32
|
+
ruling / a seat building / a human park / nobody — wedged), then hypotheses clearly labeled.
|
|
33
|
+
**`awaiting-ruling` is a REAL recorded seat state, not an inference** — the gate park opens it and the
|
|
34
|
+
ruling closes it. So "waiting on a human" is a row you can cite, and a seat in it is **alive, not
|
|
35
|
+
stalled**. Do not report a park as a stall; they are different findings with different remedies.
|
|
36
|
+
|
|
37
|
+
<!-- control-plane-specific -->
|
|
38
|
+
## Your instruments
|
|
39
|
+
|
|
40
|
+
A seat's own credential reads its own lanes, and those are the reads a witness cites: `seat_read` (the
|
|
41
|
+
seat's row, its live session, and the reachable-state graph) · `seat_siblings` (topology — who decides
|
|
42
|
+
for whom) · `seat_acts` (the act sweep, and what each act wrote) · `seat_parks` (parks) ·
|
|
43
|
+
`seat_promises` (declared consequents that were never kept) · `seat_usage` (economics, **one row per
|
|
44
|
+
instrument, never summed across**) · `seat_content` (what was written or said, windowed honestly) ·
|
|
45
|
+
`seat_findings`. The coordinator's own `read_blackboard` is the roster view of the same facts.
|
|
46
|
+
|
|
47
|
+
**THE ONE INSTRUMENT WITH NO SUBSTITUTE: the declared-consequent pair.** An act DECLARES what it will
|
|
48
|
+
cause and the consequent NAMES its antecedent, so *"a ruling that never became work"* — or a question
|
|
49
|
+
composed and never posted — is a LEFT JOIN with no match rather than a judgement call. **That is the
|
|
50
|
+
strongest anomaly read on the plane and it is the one nobody thinks to run.**
|
|
51
|
+
|
|
52
|
+
**Two lanes that are not the control plane's, and are still yours:** the harness's transcript store (the
|
|
53
|
+
reasoning record) and git (the product). A dispute is resolved when two of the three — rows, transcript,
|
|
54
|
+
git — agree.
|
|
55
|
+
|
|
56
|
+
## The playbook
|
|
57
|
+
|
|
58
|
+
0. **The identity map comes first, always.** Before any judgment: every seat of the pipeline with its
|
|
59
|
+
id, its kind, its state, its live session id and its harness session id, from `seat_siblings` +
|
|
60
|
+
`seat_read`. Lead your first report with it. Identity confusion is the dominant failure class: a
|
|
61
|
+
pipeline id queried as a seat id diagnoses a healthy seat as a dead run; a chip flipped on the
|
|
62
|
+
coordinator when the seat was meant; "is that the coordinator or the seat talking" recurs all day.
|
|
63
|
+
Names, ids, and session ids on the table up front prevent the whole class. A seat id is not a
|
|
64
|
+
session id is not a pipeline id — they collide numerically and mean different tables.
|
|
65
|
+
1. **Poll the SUBSTRATE, never the terminals.** The read lanes work from anywhere, need no database
|
|
66
|
+
credential and no local container, and every row they return is scoped to the ticket that asked.
|
|
67
|
+
**Sweep UNFILTERED first, then narrow.** A verb-filtered query can only confirm what you already
|
|
68
|
+
suspect; a run can certify itself "zero-touch" while `approved_by_human` rows sit one unqueried verb
|
|
69
|
+
away. **`escalation_defaulted` is NEVER a human touch:** it is the policy receipt for a DECLARED
|
|
70
|
+
conservative default coming into effect at its deadline — count it as policy resolving a wait, and
|
|
71
|
+
testify "ran on the declared default", never "a human ruled". A lane cannot answer you with a silent
|
|
72
|
+
empty: an unknown filter refuses BY NAME, a filter matching nothing says so, and an out-of-scope id
|
|
73
|
+
reads as absent rather than as empty. Read the verb vocabulary from the control plane's own
|
|
74
|
+
catalogue at watch start — the catalogue moves, and its size moves with it, so read the authority
|
|
75
|
+
rather than carrying a count.
|
|
76
|
+
2. **The blackboard IS the heartbeat.** The cheapest per-seat pulse is the rev ladder: each post of
|
|
77
|
+
`plan.md` / `tldr.md` is a new rev on the seat's board, so rev counts and timestamps between polls
|
|
78
|
+
are a zero-token heartbeat per seat — a growing ladder is a live session, a frozen one dates its last
|
|
79
|
+
turn. `seat_content` reads the board; read `tldr.md` top-to-bottom and it IS the session's whole
|
|
80
|
+
conversation in message-flow order — the fastest "where is this seat" there is. The coordinator's
|
|
81
|
+
voice is a greppable anchor: `## PARENT RULING (plan rev N — {verdict})` and `## PARENT NOTE (…)`
|
|
82
|
+
(actor-suffixed when known) on the hosted plane; the same prefixes without the rev counter under
|
|
83
|
+
`docs/research/{topic}/sessions/{spec-slug}/` hand-cranked — so grep the prefix, never the full form.
|
|
84
|
+
**Honest delivery verdicts:** a ruling act's reply states recorded-vs-delivered truthfully —
|
|
85
|
+
"recorded, not delivered" is NORMAL (the seats run substrate-first; the board is load-bearing, the
|
|
86
|
+
pointer is best-effort). A delivery miss is only a finding if the seat ALSO never acted on the
|
|
87
|
+
substrate. **Blessed close-outs:** a seat's post-push `## [child·…] CLOSE-OUT RECEIPT` append does NOT
|
|
88
|
+
stale its acceptance — a reap without a fresh acceptance after such an append is correct behaviour,
|
|
89
|
+
not a skipped step.
|
|
90
|
+
3. **Change-detection monitors, not tails:** emit only rows past a high-water mark + a terminal-state
|
|
91
|
+
exit. One line per phase change; silence otherwise. **Baseline the high-water mark BEFORE arming**
|
|
92
|
+
(count existing rows first) or the first poll dumps all history as "new." And audit your own filters:
|
|
93
|
+
a substring match for "running" fires on "No **running** host found" — a false positive from your own
|
|
94
|
+
tooling. The witness's tools lie too when the filter is a substring.
|
|
95
|
+
**RESTARTING A MONITOR? RE-ARM FROM THE OLD HIGH-WATER MARK, NEVER FROM NOW.** A monitor that
|
|
96
|
+
re-baselines on restart declares everything written while it was down to be history and never
|
|
97
|
+
emits it — the gap closes silently and looks identical to a quiet pipeline. Persist the mark, and on
|
|
98
|
+
every restart replay from it forward before resuming live. **The rows you miss are exactly the
|
|
99
|
+
ones written while you were not watching, which is when the interesting things happen** — a
|
|
100
|
+
restart timed across a gate can eat the gate.
|
|
101
|
+
4. **Long output to UTF-8 files; grep the names out.** Never stream a runner, a log, or a terminal into
|
|
102
|
+
context (truncation destroys failure names).
|
|
103
|
+
5. **Transcripts are the reasoning record:** the seat's harness session id names its transcript in the
|
|
104
|
+
harness's transcript store (Claude Code: `~/.claude/projects/{cwd-slug}/{id}.jsonl`; worktree runs
|
|
105
|
+
live under their WORKTREE's slug). Tail + parse the last assistant text blocks. On Windows force UTF-8
|
|
106
|
+
stdout — a default codepage dies on the first non-ASCII character; open the file with
|
|
107
|
+
`encoding='utf-8'` AND wrap stdout. Freshness check before diagnosing "stuck": compare the LAST
|
|
108
|
+
entry's timestamp to now — a transcript with no entries since T means the agent hasn't acted since T,
|
|
109
|
+
full stop; that fact alone resolves most "is it wedged?" questions. Transcript = WHY; rows = WHAT.
|
|
110
|
+
If the run matters, copy the transcript files somewhere durable EARLY — the store has a retention
|
|
111
|
+
window and the format has moved between releases.
|
|
112
|
+
6. **The live-session probe:** `seat_read` names the seat's live session and its state; `seat_content`
|
|
113
|
+
shows what it last said. "Sent" means nothing; a row means everything. Three distinct delivery
|
|
114
|
+
states exist — dropped / rendered-unsubmitted / submitted — and only the substrate distinguishes
|
|
115
|
+
them. The witness NEVER injects (zero terminal input is already your law), and any machine-side
|
|
116
|
+
message you audit should have ridden a ruling act or the board; a raw terminal inject in the record
|
|
117
|
+
is itself a finding.
|
|
118
|
+
7. **Know where each act keeps its payload** (what's durable where): a plan or tldr post carries the
|
|
119
|
+
whole file on its artifact row · a ruling act carries the ruling text as a `ruling` artifact and the
|
|
120
|
+
asking post's id · a callout carries the note, and its reply carries the delivery verdict · a spawn
|
|
121
|
+
carries the seat's kind and model but NOT the task text (that is the seat's own row). Check the act's
|
|
122
|
+
verb before promising the payload exists.
|
|
123
|
+
8. **Identical timestamps do not mean ordering.** A turn's tool rows land at ingest time — a ten-minute
|
|
124
|
+
working turn lands as dozens of rows stamped the same second, in transcript order. Never infer
|
|
125
|
+
wall-clock causality inside a same-stamp batch; the burst boundary IS the information (it tells you
|
|
126
|
+
when the turn ended).
|
|
127
|
+
9. **The decision plane:** one durable row per gate park, with its verdict. A healthy pipeline ends with
|
|
128
|
+
ZERO non-terminal parks; a pending park on an idle seat is the finding. `seat_parks` names the
|
|
129
|
+
non-terminal rows individually rather than only counting them, which is the shape this finding needs.
|
|
130
|
+
**The gate is inverted:** boundary tools/commands park; everything else flows WITH an allow row —
|
|
131
|
+
so auditing is row-complete for benign tools too, and a park on a benign method is itself an anomaly
|
|
132
|
+
worth testifying about.
|
|
133
|
+
10. **Snapshot before/after, don't assert:** state captured on both sides of an event proves "did X
|
|
134
|
+
change anything" as fact.
|
|
135
|
+
11. **Cross-reference the triangle:** rows (choreography) × transcripts (reasoning) × git (product). A
|
|
136
|
+
dispute is resolved when two of the three agree.
|
|
137
|
+
12. **Read the source before predicting.** The witness's job is seeing the wall before the pipeline hits
|
|
138
|
+
it — when a gate/parse/accept is imminent, read the code that will fire and pre-warn the human of
|
|
139
|
+
the failure shape.
|
|
140
|
+
13. **Costs are part of state:** `seat_usage` — per-seat totals, the coordinator's own line and the
|
|
141
|
+
uncosted-turn count; report the coordinator's cost share (a healthy hosted run has a CHEAP
|
|
142
|
+
coordinator — an expensive one is diagnosing platform bugs, which is itself an anomaly to report).
|
|
143
|
+
**Every total is a FLOOR while the uncosted-turn count is above zero — quote it WITH the count.**
|
|
144
|
+
14. **NAME YOUR INSTRUMENT, in the ledger, beside the number.** Your testimony is worth what its
|
|
145
|
+
provenance is worth: *an audit lane that can only be audited by itself is not audited; it is
|
|
146
|
+
asserted.* So every count you publish says which instrument produced it — a read lane, a transcript,
|
|
147
|
+
or git. When two agree, say which two. When you could only reach one, **say that too** — a
|
|
148
|
+
single-instrument number is still worth reporting; a single-instrument number dressed as a
|
|
149
|
+
cross-confirmed one is not.
|
|
150
|
+
|
|
151
|
+
## Entry
|
|
152
|
+
|
|
153
|
+
Given a pipeline id or topic: read the effort's `project-status.md` (context; coordinator-owned),
|
|
154
|
+
build the identity map (playbook #0) and LEAD with it, then run the playbook. If invoked
|
|
155
|
+
mid-dispute ("X claims Y"), go straight to the substrate that can falsify the claim and report
|
|
156
|
+
the verdict with receipts.
|
|
157
|
+
|
|
158
|
+
## The witness packet (the ignition-to-witness handoff)
|
|
159
|
+
|
|
160
|
+
When a run is ignited WITH a witness on duty, the ignition copilot hands the witness ONE
|
|
161
|
+
message — the packet — at kick. Fields (every one earned its place):
|
|
162
|
+
|
|
163
|
+
| Field | Why the witness needs it |
|
|
164
|
+
|---|---|
|
|
165
|
+
| Kick timestamp (+ any rejected pre-kick attempts) | separates the run from history; rejected attempts with "zero side effects" is a CLAIM the witness can verify |
|
|
166
|
+
| The act high-water at kick | the monitor baseline — everything after this IS the run; no first-poll history dump |
|
|
167
|
+
| Pipeline id · title · shape · owner | the one number that unlocks everything |
|
|
168
|
+
| Coordinator seat id · session id · model/effort · gate | the seat to never confuse with the pipeline |
|
|
169
|
+
| Topic + spec path + the commit sha the specs live at | read the umbrella/specs via `git show {sha}:{path}` — no working-tree mutation, exact bytes the run was built from |
|
|
170
|
+
| North-star AS KICKED (the verbatim task text) | judge "off the rails" against what the coordinator was actually told, never a paraphrase |
|
|
171
|
+
| Planned shape (seat count, order, kinds, models, parallel or never, worktree key/branch) | the null hypothesis every observation tests against |
|
|
172
|
+
| Ceremony extras expected (probes, filing conventions) | pre-registered expected anomalies don't get reported as incidents |
|
|
173
|
+
| Runner lifetime (process, restart-announcement promise) | a silent restart kills monitors invisibly |
|
|
174
|
+
| Known-anomaly ledger carried in (ids, closed/open, receipts) | open cosmetic anomalies (a stale UI chip) don't get re-discovered as fresh incidents |
|
|
175
|
+
| Expected human-touch cadence (parks, completes, kill-switch) | the witness can flag a MISSING expected touch, not just unexpected ones |
|
|
176
|
+
| The human-touch count is row-derived for the cockpit's own input; the floor language survives for the rest | cockpit terminal input writes `injected_by_human` (one row per burst — count it WITH the `*_by_human` family). Still row-less: anything outside the platform entirely — for those, say "at least N touches, by rows" |
|
|
177
|
+
|
|
178
|
+
The witness books the packet into its durable ledger verbatim before the first poll, then leads
|
|
179
|
+
with the identity map as always. A packet claim is still a claim — verify the load-bearing ones
|
|
180
|
+
(high-water, coordinator gate, zero-side-effect rejections) against the substrate in the first report.
|
|
181
|
+
|
|
182
|
+
Disciplines that don't fit a numbered step:
|
|
183
|
+
|
|
184
|
+
- **Notifications are a HINT LANE, never a feed. Every contact begins with a substrate sweep.**
|
|
185
|
+
Whatever reaches you — a row event, a poke, a doorbell — is a prompt to go look, not evidence of
|
|
186
|
+
what happened, and its silence is not evidence that nothing did. Delivery is uneven per channel
|
|
187
|
+
and the unevenness does not announce itself: run-state and heartbeat traffic can arrive all night
|
|
188
|
+
while row events for the same run arrive barely at all, which reads exactly like a quiet run.
|
|
189
|
+
**Never let arrival volume stand in for activity** — sweep on a cadence you set, not one the
|
|
190
|
+
notifications set for you.
|
|
191
|
+
- **A count over rows measures what WRITES rows — read the emit site before you read the count.** This
|
|
192
|
+
is the prime directive applied to instruments rather than actors: a verb is a witness with its own
|
|
193
|
+
reliability, and the commonest failure is a path that returns *before* the write. A probe that writes
|
|
194
|
+
only on failure makes twenty silent successes look like three failures and a dead mechanism. **Absence
|
|
195
|
+
of a row is not absence of the event.** One glance at the writer tells you whether you are measuring
|
|
196
|
+
the population or only its failures.
|
|
197
|
+
- **And you read BETWEEN turn boundaries, which the seats do not.** You have substrate access and no turn
|
|
198
|
+
structure, so a mid-transition state looks identical to a defect: a row that lands seconds later, a
|
|
199
|
+
manifest closed by the very next act, a park already cleared by a click. **A run is only consistent AT
|
|
200
|
+
the reap.** Before reporting in-flight state as a finding, ask whether the boundary has passed — this is
|
|
201
|
+
the sibling of reasoning from an empty feed, and it is the easier one to commit.
|
|
202
|
+
- **Self-criticism gets the same evidentiary standard as any other finding — verify before you confess.**
|
|
203
|
+
*"The witness indicts itself or it is marketing"* produces an over-correction bias: a witness can
|
|
204
|
+
confess to an error it had not made, and inflate a count across successive reports without
|
|
205
|
+
re-querying. A withdrawn false confession costs the ledger credibility exactly as a false accusation
|
|
206
|
+
does. Check the timestamps, re-run the query, THEN indict yourself.
|
|
207
|
+
- **A surface's value is testimony too.** The prime directive covers actors, not columns — so a column read
|
|
208
|
+
is a claim, not a fact. Reporting "no PR" because a URL column is NULL, against a PR that is open, is the
|
|
209
|
+
same error as believing a seat's self-report: the column is a witness with its own reliability.
|
|
210
|
+
- **Book findings to a durable FILE as you go** (scratchpad or a notes doc), not to context —
|
|
211
|
+
a ledger on disk survives mid-run process restarts and context compactions.
|
|
212
|
+
A witness whose evidence evaporates with its window is testimony,
|
|
213
|
+
not a witness.
|
|
214
|
+
- **Long watches: expect your own death.** Background monitors die with process restarts and
|
|
215
|
+
leave no marker. On every re-entry, re-verify your watchers are actually alive before
|
|
216
|
+
trusting their silence — silence from a dead monitor looks identical to a quiet run.
|
|
217
|
+
|
|
218
|
+
## Related
|
|
219
|
+
- `spec-parent` (the seat that rules) · `spec-child` (the seats that act) · `spec-seat` (their shared
|
|
220
|
+
contract — read it to know what the seats you are watching were told)
|
|
221
|
+
- **You are one of the three kinds that do NOT compose `spec-seat`** — this skill is your whole
|
|
222
|
+
contract, and the control plane's seed says so rather than merely omitting it.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!-- naswerks-loop:start-here -->
|
|
2
|
+
## Start here — before any work
|
|
3
|
+
|
|
4
|
+
This repository's working knowledge lives in `docs/`. Before any non-trivial work, read these two docs
|
|
5
|
+
to situate yourself, then the task-relevant docs they point you to:
|
|
6
|
+
|
|
7
|
+
- `docs/_meta/doc-index.md` — the menu of living docs; pick the 2–3 that match the task and read them.
|
|
8
|
+
- `docs/_meta/docs-workflow.md` — how knowledge flows through `docs/` (the loop, the folders, the templates).
|
|
9
|
+
|
|
10
|
+
The loop's skills are the `naswerks` plugin (`docs-*` for knowledge, `spec-*` for execution, `init` for
|
|
11
|
+
this shape). Docs marked `status=draft` were scanned, not decided — read them as a starting point and
|
|
12
|
+
promote them to `status=current` as you confirm each section.
|
|
13
|
+
<!-- /naswerks-loop:start-here -->
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Doc Index
|
|
2
|
+
|
|
3
|
+
> The menu. Read this at session start, pick the 2-3 docs that match your task, read those for context.
|
|
4
|
+
> This is the curated short list — the compiled layer you read *before* diving into source.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<!-- init: this file is written LAST, from the docs that exist under the four living folders after every
|
|
9
|
+
other write. One row per doc; the `When to read` cell is a bold hook plus the ONE question only that
|
|
10
|
+
doc answers. A doc the repo already owned (not drafted by init) is listed too, from its own blockquote.
|
|
11
|
+
A drafted doc's hook ends with "(draft — scanned, not decided)". An empty folder keeps its table with
|
|
12
|
+
the single row "| (none yet) | this folder holds … |". -->
|
|
13
|
+
|
|
14
|
+
## Guides — how to ADD a new thing (step-by-step walkthroughs)
|
|
15
|
+
|
|
16
|
+
| Doc | When to read |
|
|
17
|
+
|-----|-------------|
|
|
18
|
+
| (none yet) | this folder holds step-by-step walkthroughs for adding a new kind of thing; `docs-process` files the first one |
|
|
19
|
+
|
|
20
|
+
## Patterns — conventions to FOLLOW when writing code
|
|
21
|
+
|
|
22
|
+
| Doc | When to read |
|
|
23
|
+
|-----|-------------|
|
|
24
|
+
| [vertical-slice-anatomy](../patterns/vertical-slice-anatomy.md) | **Where a feature's files go**, per stack, and the new-feature checklist |
|
|
25
|
+
| [backend-patterns](../patterns/backend-patterns.md) | **How the server side is built here** — data layer, API layer, write paths, registration |
|
|
26
|
+
| [frontend-patterns](../patterns/frontend-patterns.md) | **How the client side is built here** — architecture rules, component kinds and files, state, styling |
|
|
27
|
+
| [testing](../patterns/testing.md) | **How to write and RUN the suites** — the lanes and their exact commands, the run protocol, what to test where, the controls discipline |
|
|
28
|
+
| [codegen](../patterns/codegen.md) | **What is generated and by which command** — never hand-write a generated file |
|
|
29
|
+
<!-- init: add rows for state-management and ui-style-guide when they were drafted. -->
|
|
30
|
+
|
|
31
|
+
## Infrastructure — what runs UNDER THE HOOD
|
|
32
|
+
|
|
33
|
+
| Doc | When to read |
|
|
34
|
+
|-----|-------------|
|
|
35
|
+
<!-- init: add rows for realtime-events and background-work when they were drafted; else the (none yet) row. -->
|
|
36
|
+
|
|
37
|
+
## Features — what EXISTS (one doc per feature)
|
|
38
|
+
|
|
39
|
+
| Doc | When to read |
|
|
40
|
+
|-----|-------------|
|
|
41
|
+
| (none yet) | one doc per feature, born from `docs-process` after code ships |
|
|
42
|
+
|
|
43
|
+
## Meta — how the docs system itself works
|
|
44
|
+
|
|
45
|
+
| Doc | When to read |
|
|
46
|
+
|-----|-------------|
|
|
47
|
+
| [docs-workflow](docs-workflow.md) | The loop, folder rules, templates (the `<!-- meta -->` header + `## Lineage` footer convention and the spec-doc template), the `docs-*` knowledge-loop + `spec-*` execution-loop skills — read before running `docs-write` / `docs-process` / `spec-pipeline` |
|
|
48
|
+
| [engineering-loop](engineering-loop.md) | The quick intro to how work gets built: the two loops (`docs-*` knowledge / `spec-*` execution, both hand-crankable), the seats, the ceremony, the blackboard + the question lane, and where a rule lives (policy / procedure / task) |
|
|
49
|
+
|
|
50
|
+
## Role reading lists
|
|
51
|
+
|
|
52
|
+
The adds a build seat reads on top of the floor every seat reads (`spec-seat` Step 0). `spec-child`
|
|
53
|
+
names the generic docs; this section is where THIS repository adds its own rows per role.
|
|
54
|
+
|
|
55
|
+
| Role | Adds |
|
|
56
|
+
|---|---|
|
|
57
|
+
| **backend** | <!-- init: `infrastructure/realtime-events.md` when drafted, else "no realtime library detected"; `infrastructure/background-work.md` when drafted, else "no queue or worker detected" --> |
|
|
58
|
+
| **frontend** | <!-- init: `patterns/state-management.md` when drafted, else "no state library detected"; `patterns/ui-style-guide.md` when drafted, else "no UI library detected"; `infrastructure/realtime-events.md` as above --> |
|
|
59
|
+
| **mixed** | reads the rows above for the surfaces the spec touches; <!-- init: one row per surface the scan found that has no role of its own (a CLI, a sidecar, a worker project): "`{path to its doc, or the folder}` — {what it is}" --> |
|
|
60
|
+
<!-- repo rows go here: a surface this repository has that the generic docs do not cover -->
|
|
61
|
+
|
|
62
|
+
## Apps and their build commands
|
|
63
|
+
|
|
64
|
+
One row per application root. A seat `cd`s into the app it touched and runs THAT app's commands — the
|
|
65
|
+
wrong app builds green.
|
|
66
|
+
|
|
67
|
+
| App | Path | Test | Production build |
|
|
68
|
+
|-----|------|------|------------------|
|
|
69
|
+
<!-- init: scan — angular.json projects, apps/*, packages/* with a build script, web/* and services/* under src/,
|
|
70
|
+
a *.csproj that is an executable; the test and build commands from each root's own scripts or the
|
|
71
|
+
solution. fill — one row per app, commands verbatim. absent — "| (none detected) | fill this in: each app
|
|
72
|
+
root, its test command, its production build command | | |" -->
|
|
73
|
+
|
|
74
|
+
## The evidence tools
|
|
75
|
+
|
|
76
|
+
Scripts that make a claim checkable. Each one exists because a green run once meant nothing.
|
|
77
|
+
|
|
78
|
+
| Script | What it proves | Read with |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `verify-staged` (the loop pack's bin) | The staged list matches the list you meant to stage — `git add` drops ignored paths with exit 0 and no output. It validates the list it is GIVEN, never that list's completeness: pair it with `git diff --name-only` and `git ls-files --others --exclude-standard` | [testing](../patterns/testing.md) § The run protocol |
|
|
81
|
+
<!-- init: scan — scripts/ files with a .SYNOPSIS or a header comment that names what they check.
|
|
82
|
+
fill — one row each. absent — leave the verify-staged row alone. -->
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
> **Other folders:** `research/` = the running tab of ideas (future-state, not built; archived *with* its
|
|
87
|
+
> working docs when shipped). `working/` = in-flight session docs / handoffs (one effort may chain several).
|
|
88
|
+
> `archive/` = completed efforts grouped by topic — research + working docs filed together (frozen history).
|
|
89
|
+
<!-- init: if docs/ held files or folders outside the loop's eight before init ran, list them here in one
|
|
90
|
+
line each as "repo-owned, outside the loop's shape" so a reader knows they were seen and left alone. -->
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Background Work
|
|
2
|
+
|
|
3
|
+
> What runs outside a request in this repository — workers, queues, schedulers — and how long-running,
|
|
4
|
+
> multi-stage work is orchestrated. Last verified {today}.
|
|
5
|
+
<!-- meta: type=infrastructure; status=draft; verified={today}; lineage=0 -->
|
|
6
|
+
<!-- naswerks-loop: draft; scanned={files} -->
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
<!-- init: scan — queue / worker / scheduler libraries (a service bus, RabbitMQ, SQS, BullMQ, Hangfire,
|
|
13
|
+
Quartz, Celery, sidekiq, a cron), a worker project or folder, the message contracts.
|
|
14
|
+
fill — two to four sentences: what runs in the background, on what transport, triggered by what.
|
|
15
|
+
absent — this doc is only drafted when a queue, worker or scheduler is found. -->
|
|
16
|
+
|
|
17
|
+
## Sync vs async — the decision
|
|
18
|
+
|
|
19
|
+
| Work | The one way to build it | Notifies the client? |
|
|
20
|
+
|------|-------------------------|----------------------|
|
|
21
|
+
<!-- init: fill — one row per kind of work the repo has (CRUD: sync; ingest/index/fan-out: a command to
|
|
22
|
+
the queue), with the sentence "there is no runtime flag — the kind of work picks the path" and the
|
|
23
|
+
rule for reaching for a queue (two or more of: duration, fan-out, burstiness, durability, decoupling).
|
|
24
|
+
absent — "| (no async kinds detected) | every write is synchronous today | no |" -->
|
|
25
|
+
|
|
26
|
+
## Workers, queues, schedulers
|
|
27
|
+
|
|
28
|
+
<!-- init: fill — the worker's entry point, how a handler is registered, the message naming (commands
|
|
29
|
+
imperative, events past-tense; subject conventions), retries and idempotency, how a worker calls back
|
|
30
|
+
into the API and with what identity.
|
|
31
|
+
absent — "No worker detected — fill this in." -->
|
|
32
|
+
|
|
33
|
+
## Long-running workflows
|
|
34
|
+
|
|
35
|
+
<!-- init: scan — a saga / orchestration / state-machine base class, a job with stages.
|
|
36
|
+
fill — the shape (stages, per-item progress, exactly-once stage transitions and what guarantees them,
|
|
37
|
+
progress reporting to the client), one REAL workflow as the example, then `### Rules`.
|
|
38
|
+
absent — "No multi-stage workflow detected — fill this in when one appears: how stages chain, how a
|
|
39
|
+
stage transition happens exactly once, how progress is reported." -->
|
|
40
|
+
|
|
41
|
+
## Gotchas
|
|
42
|
+
|
|
43
|
+
<!-- init: scan — comments in the worker/handler code, CONTRIBUTING notes.
|
|
44
|
+
fill — one `### Gotcha:` per written-down warning, with symptom, cause, remedy, source file.
|
|
45
|
+
absent — "No background-work gotchas written down yet — fill this in the first time one bites." -->
|
|
46
|
+
|
|
47
|
+
## Key Files
|
|
48
|
+
|
|
49
|
+
| File | Purpose |
|
|
50
|
+
|------|---------|
|
|
51
|
+
|
|
52
|
+
## Lineage
|
|
53
|
+
- **Related** — [backend-patterns](../patterns/backend-patterns.md) · [realtime-events](realtime-events.md)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Realtime Events
|
|
2
|
+
|
|
3
|
+
> How server-side events reach the client in this repository, and the poke/refetch pattern that keeps
|
|
4
|
+
> the two consistent. Last verified {today}.
|
|
5
|
+
<!-- meta: type=infrastructure; status=draft; verified={today}; lineage=0 -->
|
|
6
|
+
<!-- naswerks-loop: draft; scanned={files} -->
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
<!-- init: scan — the realtime library on each side (signalr, socket.io, ws, sse, pusher, ably, phoenix
|
|
13
|
+
channels), the hub / channel / gateway classes, the client connection service.
|
|
14
|
+
fill — two to four sentences: the transport, who connects, what travels (events, not data).
|
|
15
|
+
absent — this doc is only drafted when a realtime library is found. -->
|
|
16
|
+
|
|
17
|
+
## The poke/refetch pattern
|
|
18
|
+
|
|
19
|
+
The server sends a small **poke** (an event naming what changed, and a correlation id when the change
|
|
20
|
+
answers a request); the client **refetches** the affected read through its normal data path. Events carry
|
|
21
|
+
identity, not payload — the read stays the one source of shape, and a missed event costs one refetch,
|
|
22
|
+
never a divergent copy.
|
|
23
|
+
|
|
24
|
+
<!-- init: scan — an existing event/notification type and its client handler.
|
|
25
|
+
fill — one REAL event as the example (server emit, client handler), and the correlation-id rule if the
|
|
26
|
+
repo uses one (a long-running command returns an id; the completion event carries it; the client
|
|
27
|
+
clears its pending state on match).
|
|
28
|
+
absent — "No event/handler pair found — fill this in with the first one." -->
|
|
29
|
+
|
|
30
|
+
## Backend
|
|
31
|
+
|
|
32
|
+
<!-- init: fill — the hub/channel classes, how a feature emits, the user/connection identity rule (which
|
|
33
|
+
claim keys a connection — a wrong claim finds no connections silently), the auth on the connection.
|
|
34
|
+
absent — "No server-side emitter detected — fill this in." -->
|
|
35
|
+
|
|
36
|
+
## Frontend
|
|
37
|
+
|
|
38
|
+
<!-- init: fill — the connection service, how a feature registers handlers, the guard every handler
|
|
39
|
+
carries (skip events for a different entity than the one loaded), reconnect and its catch-up.
|
|
40
|
+
absent — "No client-side handler registry detected — fill this in." -->
|
|
41
|
+
|
|
42
|
+
## Gotchas
|
|
43
|
+
|
|
44
|
+
<!-- init: scan — comments in the hub/handler code, CONTRIBUTING notes.
|
|
45
|
+
fill — one `### Gotcha:` per written-down warning, with symptom, cause, remedy, source file.
|
|
46
|
+
absent — "No realtime gotchas written down yet — fill this in the first time one bites." -->
|
|
47
|
+
|
|
48
|
+
## Key Files
|
|
49
|
+
|
|
50
|
+
| File | Purpose |
|
|
51
|
+
|------|---------|
|
|
52
|
+
|
|
53
|
+
## Lineage
|
|
54
|
+
- **Related** — [backend-patterns](../patterns/backend-patterns.md) · [frontend-patterns](../patterns/frontend-patterns.md) · [background-work](background-work.md)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Backend Patterns
|
|
2
|
+
|
|
3
|
+
> Rules and conventions for the server side of this repository. Last verified {today}.
|
|
4
|
+
<!-- meta: type=pattern; status=draft; verified={today}; lineage=0 -->
|
|
5
|
+
<!-- naswerks-loop: draft; scanned={files} -->
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
<!-- init: scan — the server-side project files (*.csproj, *.sln*, go.mod, pyproject.toml, Cargo.toml,
|
|
12
|
+
a Node service's package.json) and their frameworks.
|
|
13
|
+
fill — two to four sentences: what the server side is (an API, a worker, a CLI), the framework, and
|
|
14
|
+
the claim "every server-side feature follows these conventions; read this before writing any
|
|
15
|
+
server-side code."
|
|
16
|
+
absent — "No server-side stack detected — fill this in: what runs server-side here, if anything." -->
|
|
17
|
+
|
|
18
|
+
## Stack
|
|
19
|
+
|
|
20
|
+
<!-- init: scan — runtime + version, web framework, persistence library, database, messaging, DI,
|
|
21
|
+
validation, serialization — each from a manifest with its version.
|
|
22
|
+
fill — one bullet per item: name, version, role.
|
|
23
|
+
absent — "No server-side dependencies detected — fill this in: the runtime, the framework, the data
|
|
24
|
+
access library, the database." -->
|
|
25
|
+
|
|
26
|
+
## Feature folder structure
|
|
27
|
+
|
|
28
|
+
<!-- init: scan — the folder layout two deep under the server-side root; one existing feature folder.
|
|
29
|
+
fill — an ASCII tree of ONE real feature as it exists, with a comment per folder saying what goes
|
|
30
|
+
there, then one sentence on when a feature grows a second sub-folder (a service, a second entity).
|
|
31
|
+
absent — "No feature layout detected — fill this in: where an endpoint, a model, a query and a
|
|
32
|
+
service go, as a tree." -->
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
{root}/
|
|
36
|
+
{Feature}/
|
|
37
|
+
...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Data layer
|
|
41
|
+
|
|
42
|
+
<!-- init: scan — the persistence library, an existing entity/model file, a base class or interface
|
|
43
|
+
every entity shares, the migration or schema tool, one repository/data-access file.
|
|
44
|
+
fill — the rule for an entity (base class, ids, timestamps, ownership fields), one REAL entity as the
|
|
45
|
+
example, "the one way to query" (ORM vs raw SQL and when each), then `### Rules` as bold-lead
|
|
46
|
+
bullets and any `### Gotcha:` the repo has written down (cite the file).
|
|
47
|
+
absent — "No persistence layer detected — fill this in: how data is modelled, stored and queried." -->
|
|
48
|
+
|
|
49
|
+
### Rules
|
|
50
|
+
|
|
51
|
+
<!-- init: fill — bold-lead imperative bullets, each testable, each citing its source when the repo wrote
|
|
52
|
+
it down (a CONTRIBUTING section, an analyzer rule, a base class docstring).
|
|
53
|
+
absent — "- No data-layer rules found written down — fill this in: the entity contract, the naming,
|
|
54
|
+
the one way to query." -->
|
|
55
|
+
|
|
56
|
+
## API layer
|
|
57
|
+
|
|
58
|
+
<!-- init: scan — the web framework, one existing endpoint/handler/controller, the routing convention (a
|
|
59
|
+
global prefix, plural resources), how business logic is shared between transports if there are two.
|
|
60
|
+
fill — one REAL endpoint as the example, the route convention as a table (pattern, example, resulting
|
|
61
|
+
URL), then `### Rules`.
|
|
62
|
+
absent — "No HTTP layer detected — fill this in: how a request enters and what a handler owns." -->
|
|
63
|
+
|
|
64
|
+
### Rules
|
|
65
|
+
|
|
66
|
+
<!-- init: absent — "- No API-layer rules found written down — fill this in: request/response shape,
|
|
67
|
+
where validation lives, what a handler may and may not do." -->
|
|
68
|
+
|
|
69
|
+
## Write paths — sync, or long-running
|
|
70
|
+
|
|
71
|
+
<!-- init: scan — a queue, a worker, a job runner, a saga/orchestration in the dependencies or folders.
|
|
72
|
+
fill — the decision table `Work | The one way to build it | Pokes the client?` with the sentence
|
|
73
|
+
"there is no runtime flag — the kind of work picks the path", and the rule for when a command/queue
|
|
74
|
+
is warranted (duration, fan-out, burstiness, durability, decoupling — two or more).
|
|
75
|
+
absent — "No asynchronous write path detected — every write is a synchronous request/response;
|
|
76
|
+
fill this in when a queue or worker appears." -->
|
|
77
|
+
|
|
78
|
+
## Registration & DI
|
|
79
|
+
|
|
80
|
+
<!-- init: scan — the composition root (Program.cs, main.go, app.py, index.ts), how features register
|
|
81
|
+
themselves, the DI conventions in use.
|
|
82
|
+
fill — where a new feature registers what, as a short table (Class type | How it is registered),
|
|
83
|
+
and the one rule that bites (an unregistered X silently does not exist).
|
|
84
|
+
absent — "No composition root detected — fill this in: where a new feature registers itself." -->
|
|
85
|
+
|
|
86
|
+
## Key Files
|
|
87
|
+
|
|
88
|
+
| File | Purpose |
|
|
89
|
+
|------|---------|
|
|
90
|
+
<!-- init: scan — every file the sections above named: the base entity, the db context, the composition
|
|
91
|
+
root, the example feature's files.
|
|
92
|
+
fill — one row per file, verbatim path.
|
|
93
|
+
absent — "| (none detected) | fill this in: the base entity, the data context, the composition root |" -->
|
|
94
|
+
|
|
95
|
+
## Lineage
|
|
96
|
+
- **Related** — [vertical-slice-anatomy](vertical-slice-anatomy.md) · [testing](testing.md) · [codegen](codegen.md)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Code Generation
|
|
2
|
+
|
|
3
|
+
> What is generated in this repository, from what, by which command — and the one rule: never hand-write
|
|
4
|
+
> a generated file. Last verified {today}.
|
|
5
|
+
<!-- meta: type=pattern; status=draft; verified={today}; lineage=0 -->
|
|
6
|
+
<!-- naswerks-loop: draft; scanned={files} -->
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
<!-- init: scan — generated/ folders, codegen configs (codegen.ts / codegen.yml, openapi*, *.proto,
|
|
13
|
+
tgconfig.json, prisma/, graphql-codegen, source generators in *.csproj), package.json scripts named
|
|
14
|
+
gen / codegen / generate, a scripts/ file that orchestrates them.
|
|
15
|
+
fill — an ASCII diagram of each pipeline (source, tool, output) and a table
|
|
16
|
+
`Pipeline | Tool | Input | Output | Purpose`, one row per pipeline found.
|
|
17
|
+
absent — "No code generation detected — fill this in when a pipeline appears (an OpenAPI client, a
|
|
18
|
+
GraphQL client, a protobuf, an ORM client). Until then there is no generated folder to protect." -->
|
|
19
|
+
|
|
20
|
+
## Running codegen
|
|
21
|
+
|
|
22
|
+
<!-- init: scan — the orchestrating script or the per-pipeline commands, and their preconditions (a
|
|
23
|
+
built binary, a running server).
|
|
24
|
+
fill — ONE fenced block: the exact command(s) from the repo root, with a comment per line naming its
|
|
25
|
+
precondition. This is the command every seat runs; the skills point here.
|
|
26
|
+
absent — "No codegen command detected — fill this in: the exact invocation from the repo root." -->
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
{the exact command} # precondition: {what must be built or running}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## What is generated — never hand-written
|
|
33
|
+
|
|
34
|
+
<!-- init: fill — the list of generated folders/files, verbatim paths, each with the tool that owns it and
|
|
35
|
+
the header it carries ("AUTO-GENERATED — DO NOT EDIT"). Then the rule:
|
|
36
|
+
"**Never hand-write or manually edit files under these paths.** Run the command above; a hand-written
|
|
37
|
+
'generated' file is overwritten on the next run and drifts silently until then."
|
|
38
|
+
absent — "No generated folders detected — fill this in when one appears." -->
|
|
39
|
+
|
|
40
|
+
## Adding a generated type — checklist
|
|
41
|
+
|
|
42
|
+
<!-- init: scan — how a source type opts into generation (an attribute, an export, a schema entry).
|
|
43
|
+
fill — a checklist: mark the source (the attribute/export), run the command, verify the output file
|
|
44
|
+
exists and compiles, commit source and output together.
|
|
45
|
+
absent — "- No opt-in mechanism detected — fill this in: how a type enters the pipeline." -->
|
|
46
|
+
|
|
47
|
+
## Removing a generated type — checklist
|
|
48
|
+
|
|
49
|
+
<!-- init: fill — the mirror checklist: remove the source mark, run the command, delete the orphaned
|
|
50
|
+
output, grep consumers.
|
|
51
|
+
absent — "- No pipeline detected — fill this in when one appears." -->
|
|
52
|
+
|
|
53
|
+
## Gotchas
|
|
54
|
+
|
|
55
|
+
<!-- init: scan — comments in the codegen config or script that warn about something; CONTRIBUTING notes.
|
|
56
|
+
fill — one `### Gotcha:` per warning found, each with symptom, cause, remedy, and the file it came
|
|
57
|
+
from.
|
|
58
|
+
absent — "No codegen gotchas written down yet — fill this in the first time one bites." -->
|
|
59
|
+
|
|
60
|
+
## Key Files
|
|
61
|
+
|
|
62
|
+
| File | Purpose |
|
|
63
|
+
|------|---------|
|
|
64
|
+
<!-- init: scan — every config, script and generated folder named above.
|
|
65
|
+
fill — one row per file, verbatim path.
|
|
66
|
+
absent — "| (none detected) | fill this in: the configs, the script, the output folders |" -->
|
|
67
|
+
|
|
68
|
+
## Lineage
|
|
69
|
+
- **Related** — [backend-patterns](backend-patterns.md) · [frontend-patterns](frontend-patterns.md) · [testing](testing.md)
|