Merge pull request 'ADR 0080: the development cycle is checked, not trusted — plus review fixes' (#48) from process/the-development-cycle into main
This commit was merged in pull request #48.
This commit is contained in:
@@ -48,3 +48,10 @@ what to do.
|
|||||||
|
|
||||||
State what incident it would have caught, and make it fail before you make it pass. A check
|
State what incident it would have caught, and make it fail before you make it pass. A check
|
||||||
whose failure has never been observed is a guess about its own correctness.
|
whose failure has never been observed is a guess about its own correctness.
|
||||||
|
|
||||||
|
## cycle.py
|
||||||
|
|
||||||
|
The development cycle, checked ([ADR 0080](../../02-DECISIONS/0080-the-development-cycle-is-checked.md)):
|
||||||
|
a to-be design names a decision, an in-progress/implemented design names its owning code, a
|
||||||
|
located/fixed issue names its owner, a fixed/resolved issue says what fixed it, a graduated
|
||||||
|
research overview says what it became. `python3 00-META/checks/cycle.py`
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""The development cycle, checked.
|
||||||
|
|
||||||
|
The knowledge flow (00-META/process/00-overview.md) says work moves idea -> research ->
|
||||||
|
decision -> to-be design -> code, and symptom -> issue -> diagnosis -> fix. Those are rules,
|
||||||
|
and a rule states how it is checked (AGENTS.md) -- this is how. Everything here reads only
|
||||||
|
frontmatter, because status lives in frontmatter and nowhere else.
|
||||||
|
|
||||||
|
What is enforced:
|
||||||
|
|
||||||
|
design every 03-DESIGN doc parses, carries `layer:` matching its directory, and a
|
||||||
|
known `status:`. A TO-BE doc names at least one decision (`decisions:`) -- no
|
||||||
|
design without a decision -- and once `in-progress` or `implemented` it names
|
||||||
|
its owning code (`code:`) -- no development without a design that says where.
|
||||||
|
issues a known `status:`; once `located` or `fixed`, `located-in:` names the owner;
|
||||||
|
once `fixed` or `resolved`, `fixed-by:` says what fixed it (prose counts --
|
||||||
|
"nothing, the capability existed" is an answer).
|
||||||
|
research a known `status:`; a `graduated` overview says what it `became:`, and every
|
||||||
|
target it names exists.
|
||||||
|
|
||||||
|
Deliberately NOT enforced: `resolved` issues may leave `located-in` empty (a symptom that
|
||||||
|
turned out not to be a defect has no owner), and as-is docs need no decisions (they
|
||||||
|
describe what exists, not what was decided).
|
||||||
|
|
||||||
|
python3 00-META/checks/cycle.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
DESIGN_STATUSES = {"proposed", "designed", "in-progress", "implemented", "abandoned"}
|
||||||
|
ISSUE_STATUSES = {"open", "diagnosing", "located", "fixed", "resolved"}
|
||||||
|
RESEARCH_STATUSES = {"active", "graduated", "abandoned"}
|
||||||
|
|
||||||
|
|
||||||
|
def rel(path):
|
||||||
|
return os.path.relpath(path, ROOT)
|
||||||
|
|
||||||
|
|
||||||
|
def frontmatter(path):
|
||||||
|
"""The YAML block between the first two --- lines, as {key: raw-value-string}.
|
||||||
|
|
||||||
|
Minimal on purpose, like records.py: enough for the fields these checks read. A list
|
||||||
|
value (block or inline) is joined into its items; a scalar stays a string.
|
||||||
|
"""
|
||||||
|
text = open(path, encoding="utf-8").read()
|
||||||
|
m = re.match(r"^---\n(.*?)\n---", text, re.S)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
front, out, key = m.group(1), {}, None
|
||||||
|
for line in front.split("\n"):
|
||||||
|
item = re.match(r"^\s+-\s*(.+?)\s*$", line)
|
||||||
|
if item and key:
|
||||||
|
out[key].append(item.group(1))
|
||||||
|
continue
|
||||||
|
kv = re.match(r"^([A-Za-z-]+):\s*(.*)$", line)
|
||||||
|
if not kv:
|
||||||
|
continue
|
||||||
|
key, value = kv.group(1), kv.group(2).strip()
|
||||||
|
if value.startswith("[") and value.endswith("]"):
|
||||||
|
out[key] = [v.strip() for v in value[1:-1].split(",") if v.strip()]
|
||||||
|
elif value == "":
|
||||||
|
out[key] = [] # a block list may follow; stays [] if nothing does
|
||||||
|
else:
|
||||||
|
out[key] = value
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def listy(front, key):
|
||||||
|
v = front.get(key)
|
||||||
|
if v is None:
|
||||||
|
return []
|
||||||
|
return v if isinstance(v, list) else ([v] if str(v).strip() else [])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
failures = []
|
||||||
|
|
||||||
|
def bad(path, why):
|
||||||
|
failures.append(" %s: %s" % (rel(path), why))
|
||||||
|
|
||||||
|
# ---- design ------------------------------------------------------------------------
|
||||||
|
for layer, name in (("00-as-is", "as-is"), ("01-to-be", "to-be")):
|
||||||
|
for path in sorted(glob.glob(os.path.join(ROOT, "03-DESIGN", layer, "*.md"))):
|
||||||
|
if os.path.basename(path) == "README.md":
|
||||||
|
continue
|
||||||
|
front = frontmatter(path)
|
||||||
|
if front is None:
|
||||||
|
bad(path, "no frontmatter")
|
||||||
|
continue
|
||||||
|
if front.get("layer") != name:
|
||||||
|
bad(path, "layer is %r; this directory is %s" % (front.get("layer"), name))
|
||||||
|
status = front.get("status")
|
||||||
|
if status not in DESIGN_STATUSES:
|
||||||
|
bad(path, "status %r is not one of %s" % (status, sorted(DESIGN_STATUSES)))
|
||||||
|
if name == "to-be":
|
||||||
|
if not listy(front, "decisions"):
|
||||||
|
bad(path, "names no decisions -- no design without a decision")
|
||||||
|
if status in ("in-progress", "implemented") and not listy(front, "code"):
|
||||||
|
bad(path, "status %s but code: names no owner -- no development "
|
||||||
|
"without a design that says where" % status)
|
||||||
|
|
||||||
|
# ---- issues ------------------------------------------------------------------------
|
||||||
|
for path in sorted(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))):
|
||||||
|
front = frontmatter(path)
|
||||||
|
if front is None:
|
||||||
|
bad(path, "no frontmatter")
|
||||||
|
continue
|
||||||
|
status = front.get("status")
|
||||||
|
if status not in ISSUE_STATUSES:
|
||||||
|
bad(path, "status %r is not one of %s" % (status, sorted(ISSUE_STATUSES)))
|
||||||
|
if status in ("located", "fixed") and not listy(front, "located-in"):
|
||||||
|
bad(path, "status %s but located-in is empty" % status)
|
||||||
|
if status in ("fixed", "resolved") and not listy(front, "fixed-by"):
|
||||||
|
bad(path, "status %s but fixed-by says nothing" % status)
|
||||||
|
|
||||||
|
# ---- research ----------------------------------------------------------------------
|
||||||
|
for path in sorted(glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md"))):
|
||||||
|
front = frontmatter(path)
|
||||||
|
if front is None:
|
||||||
|
bad(path, "no frontmatter")
|
||||||
|
continue
|
||||||
|
status = front.get("status")
|
||||||
|
if status not in RESEARCH_STATUSES:
|
||||||
|
bad(path, "status %r is not one of %s" % (status, sorted(RESEARCH_STATUSES)))
|
||||||
|
if status == "graduated":
|
||||||
|
became = listy(front, "became")
|
||||||
|
if not became:
|
||||||
|
bad(path, "graduated but became: names nothing")
|
||||||
|
for target in became:
|
||||||
|
if not os.path.exists(os.path.join(ROOT, target)):
|
||||||
|
bad(path, "became names %s, which does not exist" % target)
|
||||||
|
|
||||||
|
checked = (
|
||||||
|
len(glob.glob(os.path.join(ROOT, "03-DESIGN", "0*", "*.md")))
|
||||||
|
+ len(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md")))
|
||||||
|
+ len(glob.glob(os.path.join(ROOT, "01-RESEARCH", "*", "00-overview.md")))
|
||||||
|
)
|
||||||
|
if failures:
|
||||||
|
print("cycle: %d document(s) break the development cycle:" % len(failures))
|
||||||
|
print("\n".join(failures))
|
||||||
|
return 1
|
||||||
|
print("cycle: %d documents checked, the chain holds" % checked)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
+2
-2
@@ -9,7 +9,7 @@ another — and a mesh you cannot name precisely is a mesh two people describe d
|
|||||||
|
|
||||||
- **node** — a machine in the mesh. There are 0..n of them, and each runs the host agent. A node is
|
- **node** — a machine in the mesh. There are 0..n of them, and each runs the host agent. A node is
|
||||||
just a machine that has joined; being one implies nothing about what it runs.
|
just a machine that has joined; being one implies nothing about what it runs.
|
||||||
- **control-node** — the one node that also holds the `the-controller` seat. There is exactly one
|
- **control-node** — the one node that also holds the `mesh-controller` seat. There is exactly one
|
||||||
per mesh. "control-node" is not a separate kind of machine — it is a node that additionally runs
|
per mesh. "control-node" is not a separate kind of machine — it is a node that additionally runs
|
||||||
the controller (and, today, the foundation). Lose it and the other nodes keep running what they
|
the controller (and, today, the foundation). Lose it and the other nodes keep running what they
|
||||||
were last told; they simply cannot be told anything new.
|
were last told; they simply cannot be told anything new.
|
||||||
@@ -21,7 +21,7 @@ another — and a mesh you cannot name precisely is a mesh two people describe d
|
|||||||
- **controller** — the component that decides what each node should be, holds the mesh's records,
|
- **controller** — the component that decides what each node should be, holds the mesh's records,
|
||||||
and tells nodes over the broker. Replaces **"control plane"** (borrowed from networking's
|
and tells nodes over the broker. Replaces **"control plane"** (borrowed from networking's
|
||||||
control-plane/data-plane, and opaque here).
|
control-plane/data-plane, and opaque here).
|
||||||
- **mesh-controller** — the module that runs the controller. It **claims** the `the-controller`
|
- **mesh-controller** — the module that runs the controller. It **claims** the `mesh-controller`
|
||||||
seat at mesh scope, which is what makes it singular. Replaces the module name **`mesh-control`**.
|
seat at mesh scope, which is what makes it singular. Replaces the module name **`mesh-control`**.
|
||||||
(The git repository has been renamed `mesh-control` -> `mesh-controller` on the forge; the module,
|
(The git repository has been renamed `mesh-control` -> `mesh-controller` on the forge; the module,
|
||||||
container and image it produces are `mesh-controller`.)
|
container and image it produces are `mesh-controller`.)
|
||||||
|
|||||||
@@ -52,6 +52,17 @@ the expensive half.
|
|||||||
| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh |
|
| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh |
|
||||||
| [07](07-feature-branches.md) | Feature branches across repos | Work that changes code, in one repo or several at once |
|
| [07](07-feature-branches.md) | Feature branches across repos | Work that changes code, in one repo or several at once |
|
||||||
|
|
||||||
|
## The cycle is checked
|
||||||
|
|
||||||
|
The flow above is a rule, and a rule states how it is checked:
|
||||||
|
[`00-META/checks/cycle.py`](../checks/cycle.py) refuses a to-be design that names no
|
||||||
|
decision, an `in-progress`/`implemented` design that names no owning code, an issue marked
|
||||||
|
`located`/`fixed` with no owner or `fixed`/`resolved` with no fix, and a `graduated`
|
||||||
|
research overview that does not say what it became. Run it with `records.py` and `index.py`
|
||||||
|
before any HQ merge. What the checks cannot see — that code work actually started from a
|
||||||
|
handoff — is held by playbooks [04](04-build-handoff.md) and [07](07-feature-branches.md):
|
||||||
|
a feature branch exists because a design or an issue sent it.
|
||||||
|
|
||||||
## Status lives in frontmatter
|
## Status lives in frontmatter
|
||||||
|
|
||||||
Research overviews, design docs, issue reports and decision records each carry their status as
|
Research overviews, design docs, issue reports and decision records each carry their status as
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ status: graduated
|
|||||||
initiated: 2026-08-22
|
initiated: 2026-08-22
|
||||||
touches: [03-DESIGN/00-as-is/05-runtime-and-installation.md]
|
touches: [03-DESIGN/00-as-is/05-runtime-and-installation.md]
|
||||||
became:
|
became:
|
||||||
- 02-DECISIONS/0005-the-node-host.md
|
|
||||||
- 02-DECISIONS/0005-the-node-host.md
|
- 02-DECISIONS/0005-the-node-host.md
|
||||||
- 03-DESIGN/01-to-be/05-the-node-host.md
|
- 03-DESIGN/01-to-be/05-the-node-host.md
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
topic: how we work
|
||||||
|
status: accepted
|
||||||
|
date: 2026-09-17
|
||||||
|
deciders: jochen
|
||||||
|
reconstructed: false
|
||||||
|
extends: 0019-how-this-repository-works.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# 80. The development cycle is checked, not trusted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
[ADR 0019](0019-how-this-repository-works.md) made this repository the source of truth, and the
|
||||||
|
process overview drew the flow work must follow: an idea or a symptom, a decision, a to-be design,
|
||||||
|
a build in a code repository, an as-is update on shipping. The playbooks describe every step, and
|
||||||
|
frontmatter carries every status.
|
||||||
|
|
||||||
|
But the flow itself was enforced by nothing. A design could appear citing no decision; a design
|
||||||
|
could sit `in-progress` naming no code; an issue could be `fixed` by nobody knows what. Each is
|
||||||
|
indistinguishable from correct work until somebody reads carefully — and the whole point of the
|
||||||
|
playbooks is that nobody should have to hold this repository in their head. A session that starts
|
||||||
|
cold (or an agent after a context clear) must be able to *find* the chain by following frontmatter
|
||||||
|
pointers, which only works if the pointers are reliably there.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
The development cycle is enforced mechanically, to the extent frontmatter can carry it:
|
||||||
|
|
||||||
|
- **No design without a decision** — every to-be design names at least one record in `decisions:`.
|
||||||
|
- **No development without a design that says where** — an `in-progress` or `implemented` design
|
||||||
|
names its owning code in `code:`.
|
||||||
|
- **No owner-less diagnosis, no fix-less fix** — an issue marked `located` or `fixed` names
|
||||||
|
`located-in:`; one marked `fixed` or `resolved` says `fixed-by:` (prose counts — "nothing, the
|
||||||
|
capability existed" is an answer).
|
||||||
|
- **No silent graduation** — a `graduated` research overview says what it `became:`, and the
|
||||||
|
targets exist.
|
||||||
|
|
||||||
|
[`00-META/checks/cycle.py`](../00-META/checks/cycle.py) refuses violations, beside `records.py`
|
||||||
|
and `index.py`; all three run before any merge here. What frontmatter cannot see — that code work
|
||||||
|
actually started from a handoff — remains held by playbooks 04 and 07: a feature branch exists
|
||||||
|
because a design or an issue sent it, and a merge is a human checkpoint.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
A `/clear` costs little: [`AGENTS.md`](../AGENTS.md) now carries the cycle and a where-to-look
|
||||||
|
table, and the chain a fresh session needs is guaranteed present in frontmatter rather than
|
||||||
|
reconstructed from memory. The checks are the floor, not the ceiling — they verify pointers exist,
|
||||||
|
not that their content is true; reading remains the job.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [`00-META/process/00-overview.md`](../00-META/process/00-overview.md) — the flow, and its new
|
||||||
|
"The cycle is checked" section.
|
||||||
|
- [ADR 0019](0019-how-this-repository-works.md) — the repository this disciplines.
|
||||||
@@ -165,5 +165,6 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **0025** — [The design record is read where it is written, never copied to be found](0025-the-design-record-is-read-not-copied.md)
|
- **0025** — [The design record is read where it is written, never copied to be found](0025-the-design-record-is-read-not-copied.md)
|
||||||
- **0032** — [The local account owns the mesh; a surface delegates to a module](0032-the-local-account-owns-the-mesh.md) *(superseded)*
|
- **0032** — [The local account owns the mesh; a surface delegates to a module](0032-the-local-account-owns-the-mesh.md) *(superseded)*
|
||||||
- **0034** — [The local account owns the mesh, and a web application's login is not that](0034-the-local-account-owns-the-mesh.md)
|
- **0034** — [The local account owns the mesh, and a web application's login is not that](0034-the-local-account-owns-the-mesh.md)
|
||||||
|
- **0080** — [The development cycle is checked, not trusted](0080-the-development-cycle-is-checked.md)
|
||||||
|
|
||||||
<!-- index:end -->
|
<!-- index:end -->
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ decisions:
|
|||||||
|
|
||||||
# The foundation
|
# The foundation
|
||||||
|
|
||||||
|
**One store, one broker — enforced, not conventional.** Each foundation module claims a
|
||||||
|
mesh-scoped seat named after its server (`mesh-store`, `mesh-broker`, `mesh-controller` —
|
||||||
|
[ADR 0079](../../02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md)), so
|
||||||
|
assigning a second holder anywhere in the mesh is refused at resolution rather than silently
|
||||||
|
raising a second server.
|
||||||
|
|
||||||
Tier 1. Defined the same way [the controller](06-the-controller.md) is, because the same
|
Tier 1. Defined the same way [the controller](06-the-controller.md) is, because the same
|
||||||
gap applied: the word was load-bearing and unpinned.
|
gap applied: the word was load-bearing and unpinned.
|
||||||
|
|
||||||
|
|||||||
@@ -129,8 +129,8 @@ two.
|
|||||||
|
|
||||||
| # | module | provides | note |
|
| # | module | provides | note |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1 | `postgres` | `postgres-database` | **the controller's own records and every module's.** One server, not two |
|
| 1 | `postgres` | `postgres-database` | **the controller's own records and every module's.** One server, not two — it *claims* the mesh-scoped `mesh-store` seat, so a second is refused |
|
||||||
| 2 | `lavinmq` | `amqp` | the broker every node dials, and what modules are granted vhosts on |
|
| 2 | `lavinmq` | `amqp` | the broker every node dials, and what modules are granted vhosts on — *claims* `mesh-broker`, one per mesh |
|
||||||
| 3 | `mesh-controller` | *claims* `mesh-controller` | decides what runs where |
|
| 3 | `mesh-controller` | *claims* `mesh-controller` | decides what runs where |
|
||||||
| 4 | `distribution` | `artifact-store` | what the mesh built, pinned by digest — the module is the software (Distribution), the provision is the job |
|
| 4 | `distribution` | `artifact-store` | what the mesh built, pinned by digest — the module is the software (Distribution), the provision is the job |
|
||||||
| 5 | `builder` | — | turns source into artifacts |
|
| 5 | `builder` | — | turns source into artifacts |
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
---
|
---
|
||||||
layer: to-be
|
layer: to-be
|
||||||
status: in-progress
|
status: in-progress
|
||||||
|
code:
|
||||||
|
- mesh-host
|
||||||
|
- mesh-controller
|
||||||
|
- mesh-catalog
|
||||||
|
- mesh-lab
|
||||||
updated: 2026-09-15
|
updated: 2026-09-15
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0067-genesis-is-a-pivot.md
|
- 02-DECISIONS/0067-genesis-is-a-pivot.md
|
||||||
|
|||||||
+3
-4
@@ -2,10 +2,9 @@
|
|||||||
status: resolved
|
status: resolved
|
||||||
opened: 2026-09-16
|
opened: 2026-09-16
|
||||||
located-in:
|
located-in:
|
||||||
- mesh-controller/cmd/mesh-controller/modules.go
|
- mesh-controller
|
||||||
- mesh-controller/cmd/mesh-controller/build.go
|
- mesh-catalog
|
||||||
- mesh-catalog/modules/lavinmq/module.json
|
fixed-by: mesh-controller PR 27 (5718add); mesh-catalog PR 24 (a24362b)
|
||||||
fixed-by: mesh-controller multi-node/broker-reaches-over-overlay; mesh-catalog fix/broker-declares-amqps-port
|
|
||||||
amended-design:
|
amended-design:
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
status: resolved
|
status: resolved
|
||||||
opened: 2026-09-17
|
opened: 2026-09-17
|
||||||
located-in: [mesh-controller, mesh-host]
|
located-in: [mesh-controller, mesh-catalog]
|
||||||
fixed-by: mesh-catalog + mesh-controller (the foundation modules claim mesh-scoped seats)
|
fixed-by: mesh-catalog + mesh-controller (the foundation modules claim mesh-scoped seats)
|
||||||
amended-design: 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md
|
amended-design: 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md
|
||||||
---
|
---
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ fixed-by:
|
|||||||
amended-design:
|
amended-design:
|
||||||
---
|
---
|
||||||
|
|
||||||
# 059 — A provisioner runtime crash-loops until the overlay is up
|
# 058 — A provisioner runtime crash-loops until the overlay is up
|
||||||
|
|
||||||
## Symptom
|
## Symptom
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,33 @@ through them. Thin skills in `.claude/skills/` wrap these playbooks for invocati
|
|||||||
`hq-handoff`, `hq-sync-constitution`, `hq-status`); each defers to its playbook as
|
`hq-handoff`, `hq-sync-constitution`, `hq-status`); each defers to its playbook as
|
||||||
authoritative and adds only the mechanical scaffolding.
|
authoritative and adds only the mechanical scaffolding.
|
||||||
|
|
||||||
|
## The development cycle
|
||||||
|
|
||||||
|
Work enters as an **idea** (playbook [01 — research](00-META/process/01-research.md)) or a
|
||||||
|
**symptom** (playbook [03 — issues](00-META/process/03-issues.md)), becomes a **decision**
|
||||||
|
([02-DECISIONS](02-DECISIONS/), via playbook [02](00-META/process/02-graduation.md)), lands in a
|
||||||
|
**to-be design** naming that decision, is handed to a code repository (playbook
|
||||||
|
[04](00-META/process/04-build-handoff.md), on a feature branch per playbook
|
||||||
|
[07](00-META/process/07-feature-branches.md)) — and on shipping the as-is is updated and the
|
||||||
|
design flips to `implemented`. **No design without a decision; no development without a design
|
||||||
|
that names its owner.** Enforced by [`00-META/checks/cycle.py`](00-META/checks/cycle.py)
|
||||||
|
alongside `records.py` and `index.py` — run all three before any merge here.
|
||||||
|
|
||||||
|
## Where to look (before assuming anything)
|
||||||
|
|
||||||
|
| Question | Read |
|
||||||
|
|---|---|
|
||||||
|
| What does this word mean? | [`00-META/glossary.md`](00-META/glossary.md) |
|
||||||
|
| How do I do X in this repo? | [`00-META/process/`](00-META/process/) — the playbook index is in `00-overview.md` |
|
||||||
|
| What was decided, and why? | [`02-DECISIONS/README.md`](02-DECISIONS/README.md) (reading order), then the record |
|
||||||
|
| What is being built / already runs? | [`03-DESIGN/01-to-be/`](03-DESIGN/01-to-be/) / [`03-DESIGN/00-as-is/`](03-DESIGN/00-as-is/) — each doc's frontmatter says its status, decisions and owning code |
|
||||||
|
| What is broken or was? | [`04-ISSUES/`](04-ISSUES/) — frontmatter carries status/owner/fix |
|
||||||
|
| Which repo owns what code? | [`00-META/repos.md`](00-META/repos.md) |
|
||||||
|
| Cross-cutting status view? | the `hq-status` skill (generated, never stored) |
|
||||||
|
|
||||||
|
Statuses live **only** in frontmatter; follow the pointers there (`decisions:`, `code:`,
|
||||||
|
`became:`, `fixed-by:`) instead of reconstructing history from memory.
|
||||||
|
|
||||||
## Words
|
## Words
|
||||||
|
|
||||||
One name per thing. [`00-META/glossary.md`](00-META/glossary.md) is the authority on
|
One name per thing. [`00-META/glossary.md`](00-META/glossary.md) is the authority on
|
||||||
|
|||||||
Reference in New Issue
Block a user