yadflow 2.16.1 → 2.18.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.
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # reconcile-debt gate (Phase 6 — hotfix debt). A hotfix may ship code BEFORE its front gates approve
3
+ # (ship-first), but it opens a reconcile-debt.json entry: the front artifacts do not yet describe what
4
+ # is in production. That debt must be PAID (artifacts updated + a regression test added) before the NEXT
5
+ # normal change on the same feature thread can ship. This gate FAILs a non-maintenance commit whose
6
+ # owning epic is on a thread carrying an OPEN debt that this epic does not itself own.
7
+ #
8
+ # Thread-scoped (only the affected thread is frozen, never the whole repo). Reads the PRODUCT repo via
9
+ # specs/<story>/link.md `product-repo`; degrades to PASS-with-note when it is not reachable. Per commit;
10
+ # ci/chore/build/test exempt. Fails CLOSED on an unresolvable base.
11
+ set -euo pipefail
12
+
13
+ BASE="${1:-${SDLC_BASE:-origin/main}}"
14
+
15
+ if ! git rev-parse --verify --quiet "${BASE}^{commit}" >/dev/null; then
16
+ echo "FAIL [reconcile-debt]: base ref '${BASE}' not found — fetch full history / check the base branch."
17
+ exit 1
18
+ fi
19
+ RANGE="${BASE}..HEAD"
20
+ EXEMPT='ci|chore|build|test'
21
+
22
+ fm_val() { awk -v k="$1" 'NR==1 && /^---$/ {f=1; next} f && /^---$/ {exit} f && index($0, k":")==1 {sub("^" k ":[ \t]*", ""); print; exit}' "$2" 2>/dev/null | tr -d '\r'; }
23
+
24
+ # Thread ROOT of an epic: walk `parent:` to the genesis (no parent). COMPUTED — never trusts the
25
+ # denormalized `thread:` cache, so a missing/wrong cache cannot bypass the freeze. Cycle-safe.
26
+ thread_root() {
27
+ prod="$1"; cur="$2"; seen=" "
28
+ while : ; do
29
+ case "$seen" in *" $cur "*) break ;; esac # cycle guard -> stop at the last seen id
30
+ seen="$seen$cur "
31
+ em="${prod}/epics/${cur}/epic.md"
32
+ [ -f "$em" ] || break
33
+ p="$(fm_val parent "$em")"
34
+ [ -z "$p" ] && { printf '%s' "$cur"; return; } # genesis reached
35
+ cur="$p"
36
+ done
37
+ printf '%s' "$cur"
38
+ }
39
+
40
+ # Print "epicId" for any epic that (a) belongs to the target thread — by COMPUTED membership, not the
41
+ # debt's `thread` field — (b) is not the current epic, and (c) has an OPEN entry in its reconcile-debt
42
+ # ledger. Because each ledger lives under exactly one epic (one thread), "this epic is on the thread AND
43
+ # its ledger has an open status" is the correct, per-object-safe test.
44
+ open_debt_on_thread() {
45
+ prod="$1"; thread="$2"; self="$3"
46
+ for dj in "${prod}"/epics/*/.sdlc/reconcile-debt.json; do
47
+ [ -e "$dj" ] || continue
48
+ owner_epic="$(basename "$(dirname "$(dirname "$dj")")")" # the epic that OWNS this ledger
49
+ [ "$owner_epic" = "$self" ] && continue
50
+ [ "$(thread_root "$prod" "$owner_epic")" = "$thread" ] || continue # computed thread membership
51
+ grep -q "\"status\"[[:space:]]*:[[:space:]]*\"open\"" "$dj" 2>/dev/null || continue
52
+ printf '%s\n' "$owner_epic"
53
+ done
54
+ }
55
+
56
+ commits="$(git rev-list --no-merges "$RANGE")"
57
+ if [ -z "$commits" ]; then
58
+ echo "PASS [reconcile-debt]: no non-merge commits in ${RANGE}"
59
+ exit 0
60
+ fi
61
+
62
+ rc=0
63
+ seen_keys=""
64
+ while IFS= read -r sha; do
65
+ [ -z "$sha" ] && continue
66
+ short="$(git log -1 --format=%h "$sha")"
67
+ subject="$(git log -1 --format=%s "$sha")"
68
+ if printf '%s' "$subject" | grep -qE "^(${EXEMPT})(\([a-z0-9._-]+\))?!?: "; then
69
+ continue
70
+ fi
71
+ task="$(git log -1 --format='%(trailers:key=Task,valueonly)' "$sha" | sed '/^$/d' | head -1)"
72
+ printf '%s' "$task" | grep -qE '.+-T[0-9]+$' || continue
73
+ story="$(printf '%s' "$task" | sed -E 's/-T[0-9]+$//')"
74
+ link="specs/${story}/link.md"
75
+ [ -f "$link" ] || continue
76
+ product_rel="$(fm_val product-repo "$link")"
77
+ epic="$(fm_val epic "$link")"
78
+ # product-repo is relative to the link.md's directory (specs/<story>/), so join it there.
79
+ prod="specs/${story}/${product_rel}"
80
+ ep_dir="${prod}/epics/${epic}"
81
+ if [ -z "$product_rel" ] || [ ! -d "$ep_dir" ]; then
82
+ echo "PASS [reconcile-debt]: ${short} ${task} -> ${epic} (product repo not reachable — debt check deferred)."
83
+ continue
84
+ fi
85
+ thread="$(thread_root "$prod" "$epic")"
86
+ # De-dup by (thread, epic) — NOT thread alone. open_debt_on_thread excludes the CURRENT epic, so the
87
+ # blocker set depends on both; caching by thread would make later commits on a different epic in the
88
+ # same thread inherit the first epic's result (order-dependent, can miss a real block).
89
+ key="${thread}:${epic}"
90
+ case " $seen_keys " in *" $key "*) continue ;; esac
91
+ seen_keys="$seen_keys $key"
92
+ blockers="$(open_debt_on_thread "$prod" "$thread" "$epic")"
93
+ if [ -n "$blockers" ]; then
94
+ echo "FAIL [reconcile-debt]: thread ${thread} carries OPEN hotfix debt:"
95
+ printf '%s\n' "$blockers" | sed 's/^/ /'
96
+ echo " -> Pay it first: update the front artifacts + add the regression test, then mark the"
97
+ echo " reconcile-debt.json entry status: paid. The thread is frozen for new changes until then."
98
+ rc=1
99
+ continue
100
+ fi
101
+ echo "PASS [reconcile-debt]: thread ${thread} has no open hotfix debt."
102
+ done <<EOF
103
+ $commits
104
+ EOF
105
+ exit "$rc"
@@ -34,6 +34,31 @@ jobs:
34
34
  with: { node-version: "20" }
35
35
  - run: bash checks/build-test-lint.sh
36
36
 
37
+ # Phase 6 — feature-thread gates. lineage-check: the change links a real threaded epic. epic-open:
38
+ # a sealed epic (all stories shipped) refuses new behaviour, forcing a change-epic. reconcile-debt:
39
+ # a thread with open hotfix debt is frozen for new changes until paid. All build on spec-link's
40
+ # story->epic resolution and degrade to a note when the product repo is not reachable from CI.
41
+ lineage-check:
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ with: { fetch-depth: 0 }
46
+ - run: bash checks/lineage-check.sh "origin/${{ github.base_ref }}"
47
+
48
+ epic-open:
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ with: { fetch-depth: 0 }
53
+ - run: bash checks/epic-open.sh "origin/${{ github.base_ref }}"
54
+
55
+ reconcile-debt:
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ with: { fetch-depth: 0 }
60
+ - run: bash checks/reconcile-debt-check.sh "origin/${{ github.base_ref }}"
61
+
37
62
  # Pattern gates: commit subject + PR title + PR body all follow the convention (profile: code).
38
63
  commit-message:
39
64
  runs-on: ubuntu-latest
@@ -53,6 +53,26 @@ yad-build-test-lint:
53
53
  - npm ci
54
54
  - bash checks/build-test-lint.sh
55
55
 
56
+ # Phase 6 — feature-thread gates (lineage / seal / hotfix-debt). Build on spec-link's story->epic
57
+ # resolution; degrade to a note when the product repo is not reachable from CI.
58
+ yad-lineage-check:
59
+ extends: .sdlc_mr_only
60
+ needs: []
61
+ script:
62
+ - bash checks/lineage-check.sh "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
63
+
64
+ yad-epic-open:
65
+ extends: .sdlc_mr_only
66
+ needs: []
67
+ script:
68
+ - bash checks/epic-open.sh "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
69
+
70
+ yad-reconcile-debt:
71
+ extends: .sdlc_mr_only
72
+ needs: []
73
+ script:
74
+ - bash checks/reconcile-debt-check.sh "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
75
+
56
76
  # Pattern gates: commit subject + MR title + MR body all follow the convention (profile: code).
57
77
  yad-commit-message:
58
78
  extends: .sdlc_mr_only
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: yad-defects
3
+ description: 'Phase 6 output enrichment (never a gate) — the quality-gap report. Generates a per-epic AND per-thread defect/bug report (the vendored React/Vite/Tailwind shell HTML + a DEFECTS.md) that aggregates every kind:defect change-epic + each change.json defect block + shipped regressions in build-log.json BY escape_stage (the SDLC gate that should have caught the defect) and root_cause, and visualizes WHERE quality gaps systematically come from — e.g. "% of this feature''s defects that escaped at the test-cases gate" — so the team hardens the originating stage instead of just fixing symptoms. Degrades to markdown-only when no docs target is connected. Use when the user says "show the defect report", "where are our quality gaps", "generate the bug report for this epic", or "which gate is leaking defects".'
4
+ ---
5
+
6
+ # SDLC — Quality-Gap Report (Phase 6, output enrichment)
7
+
8
+ **Goal:** Turn the thread's defects into a **systemic quality signal**. Because every defect is a
9
+ first-class `kind: defect` change-epic carrying an `escape_stage` (the gate that *should* have caught it)
10
+ and a `root_cause`, this report can show not just *what* broke but *where the SDLC let it through* — so
11
+ the team fixes the originating stage (weak test design, an under-specified story, a missed architecture
12
+ risk), not just the symptom. It is an **output enrichment**, exactly like `yad-docs` — **never a gate**.
13
+
14
+ ## Conventions
15
+
16
+ - `{project-root}` resolves from the product hub.
17
+ - Reuses the **`yad-docs` shell** verbatim (`../yad-docs/templates/app/`) — generated `src/data/*.ts`,
18
+ themed, deployed via `yad docs deploy`; build-only / markdown-only when no docs target.
19
+ - Per **epic** (one epic's defects) and per **thread** (the whole feature; the thread report lives under
20
+ the genesis epic, since `thread == genesis id`). The thread is derived from `parent:` frontmatter.
21
+ - Deterministic generation, like `yad-docs` (stable sort, fixed key order, no timestamps in data).
22
+
23
+ ## Inputs
24
+
25
+ - `epic` — scope to one epic's defects, OR `thread` — `EP-<genesis>` for the whole feature. Ask if
26
+ neither is given; default to the thread.
27
+ - `action` — `generate` (default) | `deploy`.
28
+
29
+ ## On Activation
30
+
31
+ ### Step 1 — Collect the defects
32
+ Resolve the scope (`yad thread <id> --json` for a thread). Collect, across the scoped epic(s):
33
+ - every `kind: defect` (and `kind: hotfix`) change-epic + its `.sdlc/change.json` `defect` block
34
+ (`origin`, `severity`, `escape_stage`, `root_cause`);
35
+ - the shipped regression fixes from each `.sdlc/build-log.json` (the fix that closed the defect, linking
36
+ the change-epic → its regression story/test);
37
+ - open reconcile debt (a hotfix whose front truth is not yet restored).
38
+
39
+ ### Step 2 — Attribute each defect to the gate that should have caught it
40
+ A defect is attributed to its **earliest** responsible SDLC stage. Use `change.json.escape_stage`
41
+ (human-set at intake), cross-checked against the fix's shape: a missing negative test → `test-cases`; a
42
+ wrong/absent contract field → `architecture`; an unstated acceptance criterion → `stories`; a missed
43
+ market/requirement → `discovery`/`epic`. Record the attribution per defect.
44
+
45
+ ### Step 3 — Aggregate the quality signal
46
+ Compute, for the scope:
47
+ - **defects per escape-stage** (count + **% of scope defects** — the headline "X% escaped at the
48
+ `<stage>` gate");
49
+ - **defects per root_cause**;
50
+ - **severity mix** and **genesis→defect age** (how long after ship the defect surfaced);
51
+ - an **escape rate** per gate = defects attributed to a stage ÷ artifacts that passed that stage (a
52
+ normalized "how leaky is this gate").
53
+
54
+ ### Step 4 — Render the report (yad-docs shell)
55
+ Generate the site into `epics/<scope>/defects-site/` with sections:
56
+ 1. **Summary** — total defects, severity mix, escape rate.
57
+ 2. **Escape-stage breakdown** — the bar/heat view of where defects leak (the actionable headline).
58
+ 3. **Root-cause breakdown.**
59
+ 4. **Per-defect detail** — each defect linked to its change-epic and its regression test.
60
+ 5. **Severity & age.**
61
+ 6. **Recommendations** — which originating stage to harden, derived from the top escape-stages.
62
+
63
+ Also write a plain `epics/<scope>/DEFECTS.md` mirror. On `action: deploy`, `yad docs deploy` the site
64
+ (build-only when no target).
65
+
66
+ ## Hard rules
67
+
68
+ - **Never a gate.** No writes to `state.json`, `approvals.json`, or any `contract-lock.json`. It reads
69
+ defect data and renders it.
70
+ - **Attribute honestly.** Use the recorded `escape_stage`; when it is absent or contradicted by the fix,
71
+ cross-check and say so — do not invent an attribution.
72
+ - **Derived + deterministic.** Re-running on unchanged data yields byte-identical output.
73
+ - **Degrade gracefully.** No docs target → `DEFECTS.md` only; never fail because a tool is absent.
74
+
75
+ ## Reference
76
+ - The defect data: `.sdlc/change.json` `defect` blocks + `build-log.json` (`../yad-epic/references/state-schema.md`, Phase 6).
77
+ - The shell + deterministic generation it reuses: `../yad-docs/SKILL.md`, `../yad-docs/references/data-mapping.md`.
78
+ - The companion evolution view: `../yad-timeline/SKILL.md`.
79
+ - The intake that records `escape_stage` + `root_cause`: `../yad-change/SKILL.md`.
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: yad-discovery
3
+ description: 'Optional front-zero of the gated SDLC — the once-per-project discovery phase. With the field-expert lenses (analyst + pm), run market research, a competitor study, a feasibility study, and (brownfield) a current-state study, then distil a functional + non-functional requirements list and a phased roadmap (MVP and beyond) into the reserved EP-discovery. Greenfield AND brownfield. Its roadmap.md becomes the menu of features each yad-epic reads. Seeds the EP-discovery state and hands off to the team review gate; never auto-advances. Use when the user says "start the project", "do discovery", "market research / feasibility / roadmap", or "what should we build first".'
4
+ ---
5
+
6
+ # SDLC — Project Discovery (optional front-zero, "epic zero")
7
+
8
+ **Goal:** Produce a human-authored, AI-assisted **project-level discovery set** — the field expert's
9
+ requirement-gathering for the whole product — under the reserved `EP-discovery` ("epic zero"), then
10
+ hand off to `yad-review-gate`. The output `roadmap.md` is the menu of features; each feature is later
11
+ taken into the normal `yad-epic` flow, which reads the roadmap for project context.
12
+
13
+ This is a **front state**: human-authored with AI assist and **never auto-advances**. It runs **once
14
+ per project** and is **optional** — a team that already knows what to build can skip it and start at
15
+ `yad-epic`. It supports **both greenfield and brownfield**, and produces a **competitor study in both**.
16
+
17
+ This skill enforces the build plan's core rules: all state lives in files; IDs are engine-assigned
18
+ (the reserved `EP-discovery`, never a typed feature slug); front steps are locked to `human_approve`.
19
+
20
+ ## Conventions
21
+
22
+ - `{project-root}` resolves from the project working directory.
23
+ - Discovery artifacts live under `{project-root}/epics/EP-discovery/` (the reserved "epic zero").
24
+ - Speak in the configured `communication_language`; write documents in `document_output_language`.
25
+
26
+ ## On Activation
27
+
28
+ ### Step 1 — Entry guard (runs once per project)
29
+ The id is the reserved `EP-discovery` — never a feature slug. Discovery seeds its state exactly once:
30
+ if `{project-root}/epics/EP-discovery/.sdlc/state.json` already exists, **STOP** and point the user at
31
+ `yad next EP-discovery` (the phase is in review or done; edit the artifacts in place, don't re-seed).
32
+ When no `state.json` exists yet, proceed and seed state in Step 5.
33
+
34
+ Detect the project mode from `{project-root}/.sdlc/hub.json` `profile.codebase`
35
+ (`greenfield` | `brownfield`, set by `yad setup`). If absent, ask the user; default `greenfield`.
36
+
37
+ ### Step 2 — Shape with the field-expert lenses (assist: analyst + pm)
38
+ Adopt the **analyst** lens (`bmad-agent-analyst`, Mary) and the **pm** lens (`bmad-agent-pm`) to gather
39
+ requirements as a domain expert would. Drive the existing BMAD research skills as the assist — they
40
+ already exist in this project:
41
+ - `bmad-market-research` — market size, segments, demand, trends, positioning.
42
+ - `bmad-domain-research` — the problem domain, regulations, and constraints of the field.
43
+ - `bmad-product-brief` — personas, value proposition, success metrics.
44
+
45
+ Pressure-test: who are the users, what problem, what is the market, **who are the competitors and how
46
+ do we differ** (required in BOTH modes), what is feasible, what is the smallest valuable slice (MVP),
47
+ and what sequences after it.
48
+
49
+ ### Step 2b — Brownfield current-state (make discovery code-aware)
50
+ Read the registry `{project-root}/.sdlc/repos.json` (`config.yaml` `code_context`). For **every
51
+ connected repo**, load the lightweight code-map `{project-root}/.sdlc/code-context/<repo>/code-map.md`
52
+ and base `current-state.md` on **what already exists** — modules, endpoints, data, gaps — so the
53
+ roadmap extends the real system rather than re-proposing it.
54
+
55
+ - **Greenfield-safe:** if `repos.json` is absent/empty (greenfield), `current-state.md` is a short
56
+ "clean slate / assumptions & non-goals" note, and you proceed.
57
+ - **Staleness:** if a repo's current HEAD (`git -C <path> rev-parse HEAD`) ≠ its registry `syncedHead`,
58
+ warn and suggest `yad repo refresh <repo>` (a human decision — flag, never auto-refresh).
59
+ - **Backfill pointer:** for an existing codebase, point the user at `yad-backfill` to capture specs for
60
+ already-built features; discovery frames the *forward* roadmap, backfill captures the *current* one.
61
+
62
+ ### Step 3 — Open the authoring branch
63
+ Open the discovery authoring branch `discovery/EP-discovery` per the shared procedure
64
+ (`../yad-epic/references/state-schema.md` → "Authoring branches"): git-safe (skip with a note if
65
+ `{project-root}` is not a git work tree), check out the branch if it exists, else create it from the
66
+ hub's default branch. Author and commit the discovery set on it. Distinct from the bridge's
67
+ `review/EP-discovery/discovery` branch.
68
+
69
+ ### Step 4 — Write the discovery set
70
+ Write these files under `{project-root}/epics/EP-discovery/`. Each is a normal Markdown artifact; the
71
+ gate binds to the **whole set** (editing any one revokes approvals). `roadmap.md` summarises and links
72
+ the others and is the spine of the review.
73
+
74
+ - `market-research.md` — market, segments, demand, trends (assist: `bmad-market-research`).
75
+ - `competitor-analysis.md` — competitors, capabilities, gaps, our differentiation (**both modes**).
76
+ - `current-state.md` — brownfield: what exists today (Step 2b); greenfield: clean-slate assumptions.
77
+ - `feasibility.md` — technical/operational/economic feasibility, risks, viability, go/no-go.
78
+ - `requirements.md` — the consolidated requirements list, **functional AND non-functional**, as a
79
+ table (see `references/discovery-schema.md`). Functional rows are candidate features (registration,
80
+ login, …); non-functional rows are cross-cutting (performance, security, accessibility, i18n …).
81
+ - `roadmap.md` — the phased plan with an explicit **MVP** phase, then later phases. Each feature row
82
+ carries a proposed `EP-<slug>` id, its target phase, and a `status:` of `planned`
83
+ (see `references/discovery-schema.md` for the exact templates).
84
+
85
+ Leave `owner` for the user to set in each frontmatter. Fill the bodies with the user.
86
+
87
+ ### Step 5 — Seed the state machine
88
+ Create `{project-root}/epics/EP-discovery/.sdlc/state.json` describing the **2-step** front-zero
89
+ sequence, both steps `automation: human_approve` and `locked`, with the `kind: "discovery"` marker the
90
+ engine keys off. Use this exact shape (see `references/discovery-schema.md`):
91
+
92
+ ```json
93
+ {
94
+ "epicId": "EP-discovery",
95
+ "kind": "discovery",
96
+ "createdAt": "<YYYY-MM-DD>",
97
+ "currentStep": "discovery-review",
98
+ "steps": [
99
+ { "id": "discovery", "type": "author", "artifact": "discovery/", "assistance": "review", "automation": "human_approve", "locked": true, "status": "done", "risk_tags": [] },
100
+ { "id": "discovery-review", "type": "review+approve", "artifact": "discovery/", "assistance": "review", "automation": "human_approve", "locked": true, "status": "in_review", "risk_tags": [] }
101
+ ]
102
+ }
103
+ ```
104
+
105
+ Notes:
106
+ - The review step's artifact is the virtual base `discovery/` — the gate fingerprints the whole
107
+ discovery file set (`market-research`, `competitor-analysis`, `current-state`, `feasibility`,
108
+ `requirements`, `roadmap`), so editing any of them revokes prior approvals (mirrors `stories/`).
109
+ **All six must exist to review:** if any is missing the set is incomplete and non-reviewable (the
110
+ hash is `null`) and `yad gate open`/`sync` warn — so write all six (Step 4) before the gate.
111
+ - `discovery-review` carries no `risk_tags` — it is the **base** rule (owner + 1 reviewer); discovery
112
+ never escalates to domain owners (no contract surface is touched yet).
113
+ - Also create an empty approvals ledger `.sdlc/approvals.json` and comments ledger
114
+ `.sdlc/comments.json`, each containing `[]`, and the `reviews/` directory.
115
+
116
+ ### Step 6 — Stop at the gate (do NOT advance)
117
+ Report: the path to the discovery set, and that the next action is **review** via `yad-review-gate`
118
+ (base rule: owner + 1 reviewer) on the virtual artifact `discovery/`. **Never mark discovery-review
119
+ approved here** — only real reviewers do that through the gate. When the discovery gate passes, the
120
+ state moves to the `discovery-done` sentinel (not `ready-for-build` — discovery has no build half); the
121
+ roadmap is now the input that each `yad-epic` reads (its "Step 2c — read the roadmap"). When the hub
122
+ has a platform, the gate opens a review PR on the hub (via `yad-hub-bridge`) and
123
+ `yad-review-gate action: sync` pulls platform approvals/comments into the ledger; otherwise the review
124
+ is recorded file-only.
125
+
126
+ ## Reference
127
+ - Discovery artifact templates + the 2-step state shape: `references/discovery-schema.md`.
128
+ - State schema, chain shapes, and the authoring-branch procedure:
129
+ `../yad-epic/references/state-schema.md`.
130
+ - The epic step that consumes `roadmap.md`: `../yad-epic/SKILL.md` (Step 2c).
131
+ - Capturing already-built features in a brownfield codebase: `../yad-backfill/SKILL.md`.
132
+ - Connecting code repos + the code-context the brain reads: `../yad-connect-repos/SKILL.md`.
@@ -0,0 +1,106 @@
1
+ # Discovery schema — artifacts + the front-zero state shape
2
+
3
+ The project discovery phase ("epic zero") lives under `{project-root}/epics/EP-discovery/`. It reuses
4
+ the per-epic ledger files (`.sdlc/state.json`, `approvals.json`, `comments.json`, `reviews/`,
5
+ `hub-prs.json`) unchanged — `EP-discovery` is a valid epic id, so the existing gate, PR/MR bridge, CI
6
+ sync, and `yad next` all operate on it. What is special is the `kind: "discovery"` marker on the state
7
+ object and the 2-step chain.
8
+
9
+ ## State (`.sdlc/state.json`)
10
+
11
+ ```json
12
+ {
13
+ "epicId": "EP-discovery",
14
+ "kind": "discovery",
15
+ "createdAt": "<YYYY-MM-DD>",
16
+ "currentStep": "discovery-review",
17
+ "steps": [
18
+ { "id": "discovery", "type": "author", "artifact": "discovery/", "assistance": "review", "automation": "human_approve", "locked": true, "status": "done", "risk_tags": [] },
19
+ { "id": "discovery-review", "type": "review+approve", "artifact": "discovery/", "assistance": "review", "automation": "human_approve", "locked": true, "status": "in_review", "risk_tags": [] }
20
+ ]
21
+ }
22
+ ```
23
+
24
+ - `artifact: "discovery/"` is a **virtual** base: `artifactHash` fingerprints the whole discovery file
25
+ set (`discoveryHash` in `cli/epic-state.mjs`), so an edit to any discovery file revokes prior
26
+ approvals — exactly like `stories/` fingerprints the stories directory.
27
+ - The **full set is required to review**: if any of the six files is missing, `discoveryHash` returns
28
+ `null` — the discovery is **incomplete and non-reviewable** (no hash to bind an approval to), and
29
+ `yad gate open` / `yad gate sync` warn with the missing filenames. Write all six (in greenfield,
30
+ `current-state.md` is a short clean-slate note) before handing off to the gate.
31
+ - On approval the gate sets `currentStep: "discovery-done"` (a terminal sentinel — discovery has **no**
32
+ build half, so it never becomes `ready-for-build`).
33
+ - The discovery files (relative to the epic dir) the gate commits on the review branch and re-hashes at
34
+ merge are: `market-research.md`, `competitor-analysis.md`, `current-state.md`, `feasibility.md`,
35
+ `requirements.md`, `roadmap.md` (the `DISCOVERY_FILES` list).
36
+
37
+ ## Artifact templates
38
+
39
+ ### `requirements.md`
40
+
41
+ ```markdown
42
+ ---
43
+ id: EP-discovery
44
+ artifact: requirements
45
+ status: draft
46
+ owner:
47
+ ---
48
+
49
+ ## Functional requirements
50
+ <!-- candidate features — each becomes (or seeds) a feature epic later -->
51
+
52
+ | Ref | Requirement | Description | Priority | MVP? |
53
+ |-----|-------------|-------------|----------|------|
54
+ | F-01 | Registration | A new user can create an account | must | yes |
55
+ | F-02 | Login | A returning user can authenticate | must | yes |
56
+
57
+ ## Non-functional requirements
58
+ <!-- cross-cutting qualities the whole product must hold -->
59
+
60
+ | Ref | Category | Requirement | Target / acceptance |
61
+ |-----|----------|-------------|---------------------|
62
+ | N-01 | Performance | p95 page load | < 2s on 4G |
63
+ | N-02 | Security | auth + data-at-rest | OWASP ASVS L1; encrypted at rest |
64
+ | N-03 | Accessibility | WCAG conformance | AA |
65
+ ```
66
+
67
+ ### `roadmap.md` (the spine of the review)
68
+
69
+ ```markdown
70
+ ---
71
+ id: EP-discovery
72
+ artifact: roadmap
73
+ status: draft
74
+ owner:
75
+ ---
76
+
77
+ ## Summary
78
+ <!-- the product thesis in 2–3 lines; links to market-research, competitor-analysis,
79
+ current-state, feasibility, requirements -->
80
+
81
+ ## Phase 1 — MVP
82
+ <!-- the smallest valuable slice; the features here are built first -->
83
+
84
+ | Feature | Proposed epic id | Requirements | Status |
85
+ |---------|------------------|--------------|--------|
86
+ | Registration | EP-registration | F-01 | planned |
87
+ | Login | EP-login | F-02 | planned |
88
+
89
+ ## Phase 2 — <name>
90
+ | Feature | Proposed epic id | Requirements | Status |
91
+ |---------|------------------|--------------|--------|
92
+ | … | EP-… | F-… | planned |
93
+
94
+ ## Later / parked
95
+ <!-- explicitly deferred, with why -->
96
+ ```
97
+
98
+ Per-feature `status:` lifecycle (set by hand): `planned` → `epic-started` (a feature epic has been
99
+ seeded with `yad-epic`) → `shipped`. The proposed `EP-<slug>` ids are suggestions for the eventual
100
+ `yad-epic` runs — `yad-epic` still assigns the id (and skips the reserved `EP-discovery`).
101
+
102
+ The other four artifacts (`market-research.md`, `competitor-analysis.md`, `current-state.md`,
103
+ `feasibility.md`) are free-form Markdown with a `--- id / artifact / status / owner ---` frontmatter
104
+ block matching the two above. `competitor-analysis.md` is required in BOTH greenfield and brownfield;
105
+ `current-state.md` is the substantive code-aware study in brownfield and a short clean-slate note in
106
+ greenfield.
@@ -32,6 +32,18 @@ and never gates.
32
32
  | `yad-connect-learning` | `learning.json` |
33
33
  | `yad-connect-docs` | `docs.json` |
34
34
 
35
+ ### Path: Front-zero (`phase: 0-front`)
36
+ The OPTIONAL once-per-project discovery phase, modelled as the reserved "epic zero" `EP-discovery`.
37
+ Greenfield AND brownfield; a 2-step author→review chain whose review binds to the whole artifact set
38
+ and terminates at `discovery-done` (no build half).
39
+
40
+ | Step (skill) | Gate | Outputs / sideEffects |
41
+ |--------------|------|------------------------|
42
+ | `yad-discovery` *(optional)* | → `discovery-review` (base rule) | `market-research.md`, `competitor-analysis.md`, `current-state.md`, `feasibility.md`, `requirements.md`, `roadmap.md`; seeds `EP-discovery/.sdlc/state.json` |
43
+
44
+ `roadmap.md` is the menu of features each `yad-epic` reads (Step 2c) — reference-only, never
45
+ auto-seeds epics.
46
+
35
47
  ### Path: Front half (`phase: 1-front`)
36
48
  The gated authoring chain + the reusable review gate (10 steps, or 12 with the optional analysis).
37
49
 
@@ -85,8 +97,8 @@ The eight yadflow lenses, each to its relevant phase sections + paths:
85
97
 
86
98
  | Lens | Relevant phases / sections |
87
99
  |------|----------------------------|
88
- | analyst | Setup intent, analysis step, front-half discovery |
89
- | pm | epic, stories, roadmap; the front gates |
100
+ | analyst | Setup intent, project discovery (front-zero), analysis step, front-half discovery |
101
+ | pm | project discovery (market/feasibility/roadmap), epic, stories; the front gates |
90
102
  | architect | architecture + the locked contract; escalation |
91
103
  | ux | UI design, design tool connection, the design system |
92
104
  | dev | build half: spec → implement, the per-repo loop |
@@ -68,10 +68,23 @@ and let it inform which `repos` the epic should touch.
68
68
  - For depth on a specific area not in the map, do a live on-demand read (see `yad-connect-repos`
69
69
  `references/code-context.md`) — do not block on it.
70
70
 
71
+ ### Step 2c — Read the project roadmap (project context, only once discovery is APPROVED)
72
+ Consume the project front-zero (`yad-discovery`) **only after its review gate has passed** — never a
73
+ draft or in-review roadmap (that would bypass `discovery-review`). Gate on the **state**, not file
74
+ existence: read `{project-root}/epics/EP-discovery/.sdlc/state.json` and proceed **only when
75
+ `currentStep == "discovery-done"`**. When it is, read its `roadmap.md` and sibling `requirements.md`
76
+ for the project framing: which phase (MVP / later) this feature belongs to, the functional/non-functional
77
+ requirements it carries, and how it fits the wider plan — so the epic's **Goal / Scope / Acceptance
78
+ signals** stay consistent with the approved roadmap. **Optional & non-blocking:** if there is no
79
+ discovery, or it has not yet reached `discovery-done` (absent / still draft / in-review), proceed
80
+ unchanged — do not consume an unapproved roadmap. After seeding the epic, the matching roadmap row's
81
+ `status:` can be bumped `planned → epic-started` by hand (a human edit; discovery never auto-seeds epics).
82
+
71
83
  ### Step 3 — Generate the Epic ID (engine-assigned, never by hand) — analysis-skipped only
72
84
  *(Skip when analysis ran — the ID was already assigned by `yad-analysis`.)*
73
85
  Derive `EP-<slug>` where `slug` is **2–4 lowercase words joined by hyphens**, drawn from the idea
74
- (e.g. `EP-istifta-inquiries`). Lowercase except the fixed `EP` prefix. **The ID is assigned once and
86
+ (e.g. `EP-istifta-inquiries`). Lowercase except the fixed `EP` prefix. `EP-discovery` is **reserved**
87
+ for the project front-zero — never use it for a feature. **The ID is assigned once and
75
88
  never renamed** — renaming breaks every downstream link (build plan §6b).
76
89
  Check `{project-root}/epics/` for collisions; if the slug exists, append a distinguishing word.
77
90
 
@@ -229,3 +229,109 @@ same three-way shape, anchored to each step's human gate, never self-graded):
229
229
  `machine_advance` only when its slice of `trust-log.json` (same `step`, this story's repo or the
230
230
  project) has `>= min_runs` entries AND the fraction with `verdict == "approved-unchanged"` is
231
231
  `>= min_approved_unchanged`. The dial-setter in `yad-run` enforces this; `yad-status` surfaces it.
232
+
233
+ ---
234
+
235
+ # Phase 6 — feature threads (post-lock change management)
236
+
237
+ After the contract locks and code ships, a change must not **mutate** a locked artifact (that destroys
238
+ the lock + the audit trail). Instead every change request becomes a **new epic, threaded to its parent**
239
+ (`config.yaml` `change:`). A feature is a **thread** of linked epics (genesis → change → defect → …); a
240
+ change-epic **inherits** unchanged front artifacts from its parent by reference and only **re-authors**
241
+ what it changes. So artifacts are never stale, only *superseded*; the feature's current truth is the
242
+ head of the thread, composed by the resolver (`yad-timeline`). `yad-change` seeds a change-epic;
243
+ `yad-defects` / `yad-timeline` render the thread; `yad-reconcile` flags drift; three CI gates enforce it.
244
+
245
+ ## Lineage frontmatter (added to `epic.md`)
246
+
247
+ An enrichment block on `epic.md` — like the `design:` / `testing:` blocks, it does **not** change the
248
+ locked `state.json` step shape. Genesis epics are backfilled once with `kind: feature`, `thread: <self>`.
249
+
250
+ | Field | Values | Meaning |
251
+ |-------|--------|---------|
252
+ | `kind` | `feature` \| `change` \| `defect` \| `hotfix` | Genesis is `feature` (default when absent). |
253
+ | `thread` | `EP-<genesis>` | Stable thread id = the genesis epic's id (never renamed → stablest anchor). A **derived cache** — the authoritative thread is `parent` walked to the root; a mismatch is detectable corruption (`yad doctor`). Genesis: `thread == id`. |
254
+ | `parent` | `EP-<slug>` | The immediate predecessor epic. **Absent ⇔ `kind: feature`.** |
255
+ | `inherits` | subset of `[epic, architecture, contract, ui-design, stories, test-cases]` | Artifact bases carried **by reference**, not re-authored. The rest are re-authored in this epic. |
256
+ | `supersedes` | `[EP-<slug>-S0N, …]` | Optional — specific parent story IDs this epic replaces in the head. |
257
+ | `origin` | `production` \| `staging` \| `qa` \| `review` | **defect/hotfix only.** Where the defect was found. |
258
+ | `severity` | `sev1` … `sev4` | **defect/hotfix only.** |
259
+ | `escape_stage` | an SDLC stage id (`stories`, `test-cases`, `architecture`, …) | **defect/hotfix only.** The gate that *should* have caught it — feeds the `yad-defects` quality report. |
260
+ | `root_cause` | short tag | **defect/hotfix only.** e.g. `missing-negative-test`. |
261
+
262
+ ## Inherited steps in `state.json`
263
+
264
+ A change-epic's `state.json` is structurally identical (so `advanceState` / `nextAction` / `gatePredicate`
265
+ / the bridge run unchanged), but **inherited** steps are pre-marked `done` with two extra fields, and
266
+ only re-authored steps run. The seeder sets `currentStep` to the first re-authored step.
267
+
268
+ ```json
269
+ { "id": "architecture", "type": "author", "artifact": "architecture.md",
270
+ "assistance": "review", "automation": "human_approve", "locked": true,
271
+ "status": "done", "inherited": true, "inheritedFrom": "EP-istifta-inquiries",
272
+ "boundHash": "sha256:…", "risk_tags": [] }
273
+ ```
274
+
275
+ - `inherited` — `true` when this step's artifact is taken by reference from the thread (not authored here).
276
+ - `inheritedFrom` — the epic in the thread that owns the referenced artifact.
277
+ - `boundHash` — the artifact's hash at inherit time (contract surface hash for `architecture`; the
278
+ `storiesHash`/file hash for others). The gate predicate short-circuits an `inherited` step as
279
+ **satisfied** iff `boundHash` still equals the thread's current hash for that artifact — always true,
280
+ since the artifact lives in the parent and can't be edited from the child, so inherited steps never
281
+ block and are never re-reviewed.
282
+
283
+ `approvals.json` gets a **provenance** record per inherited gate (not a forged approval):
284
+
285
+ ```json
286
+ { "artifact": "architecture.md", "step": "architecture-review", "status": "inherited",
287
+ "from": "EP-istifta-inquiries", "boundHash": "sha256:…", "date": "<YYYY-MM-DD>" }
288
+ ```
289
+
290
+ ## The pointer-lock — `contract-lock.json` in a change-epic
291
+
292
+ When `architecture` is inherited, the seeder writes a **derived** `contract-lock.json` carrying the
293
+ parent's hash **verbatim** so `contract-check.sh` (which reads only `hash`) passes unchanged. There is
294
+ no `contract.md` in the child to edit, so the surface physically cannot drift.
295
+
296
+ ```json
297
+ { "artifact": "contract.md", "hash": "sha256:<parent hash, verbatim>", "lockedAt": "<date>",
298
+ "inheritedFrom": "EP-istifta-inquiries", "ref": "../../EP-istifta-inquiries/.sdlc/contract-lock.json" }
299
+ ```
300
+
301
+ Omitting `architecture` from `inherits` (depth `contract-surface`) is what triggers a **real re-lock**:
302
+ `yad-architecture` re-authors `contract.md`, computes a **new** hash, and `architecture-review` carries
303
+ `risk_tags: ["contract"]` → the usual domain-owner escalation. This unifies "route back to the
304
+ architecture gate" with "open a contract-surface change-epic" — one mechanism, not two.
305
+
306
+ ## `change.json`
307
+ Intake + triage record, one per change/defect/hotfix epic (sibling of `approvals.json`).
308
+
309
+ ```json
310
+ { "epicId": "EP-istifta-queue-filter", "thread": "EP-istifta-inquiries", "parent": "EP-istifta-inquiries",
311
+ "kind": "defect", "depth": "defect-fix", "intakeBy": "alice", "intakeDate": "<YYYY-MM-DD>",
312
+ "title": "Pending queue returns answered inquiries", "description": "…",
313
+ "affectedArtifacts": ["stories", "test-cases"],
314
+ "reauthors": ["stories", "test-cases"], "inherits": ["epic", "architecture", "contract", "ui-design"],
315
+ "defect": { "origin": "production", "severity": "sev2", "escape_stage": "test-cases",
316
+ "root_cause": "missing-negative-test" },
317
+ "hotfix": null }
318
+ ```
319
+
320
+ `depth` ∈ `defect-fix | behavioral-no-surface | contract-surface | new-capability` (`config.yaml`
321
+ `change.depths`). `defect` is `null` for a plain `change`; `hotfix` is `{ "shipFirst": true }` only for
322
+ a `hotfix`. Thread-level rollups (`yad-timeline` / `yad-defects`) are **derived** — walk every epic
323
+ sharing `thread` and read each `change.json`; there is no duplicated thread registry.
324
+
325
+ ## `reconcile-debt.json`
326
+ Append-only ledger of hotfix ship-first debt (a hotfix shipped code before its front gates approved).
327
+
328
+ ```json
329
+ [ { "thread": "EP-istifta-inquiries", "epicId": "EP-istifta-hotfix-x", "openedDate": "<date>",
330
+ "reason": "prod outage", "requires": ["artifacts-updated", "regression-test"],
331
+ "status": "open", "paidDate": null, "paidBy": null,
332
+ "evidence": { "artifacts": [], "regressionTest": "" } } ]
333
+ ```
334
+
335
+ `status: "open"` blocks the **next** normal change on the thread (`reconcile-debt-check.sh`) until it is
336
+ `"paid"` (evidence: the front artifacts updated **and** a regression test added). The debt lets a hotfix
337
+ jump the queue once, but freezes new thread work until the SDLC again describes production.