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.
- package/CHANGELOG.md +3 -3
- package/README.md +62 -9
- package/bin/yad.mjs +24 -0
- package/cli/artifact-status.mjs +6 -1
- package/cli/doctor.mjs +31 -1
- package/cli/epic-state.mjs +218 -4
- package/cli/gate.mjs +19 -2
- package/cli/manifest.mjs +8 -1
- package/cli/next.mjs +26 -11
- package/cli/thread.mjs +174 -0
- package/package.json +5 -4
- package/skills/sdlc/config.yaml +64 -5
- package/skills/sdlc/install.sh +1 -1
- package/skills/sdlc/module-help.csv +5 -0
- package/skills/yad-analysis/SKILL.md +12 -1
- package/skills/yad-change/SKILL.md +174 -0
- package/skills/yad-change/references/triage.md +102 -0
- package/skills/yad-checks/SKILL.md +13 -1
- package/skills/yad-checks/references/check-gates.md +27 -0
- package/skills/yad-checks/templates/checks/epic-open.sh +98 -0
- package/skills/yad-checks/templates/checks/lineage-check.sh +97 -0
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +105 -0
- package/skills/yad-checks/templates/github/yad-checks.yml +25 -0
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +20 -0
- package/skills/yad-defects/SKILL.md +79 -0
- package/skills/yad-discovery/SKILL.md +132 -0
- package/skills/yad-discovery/references/discovery-schema.md +106 -0
- package/skills/yad-docs-overview/references/pipeline-model.md +14 -2
- package/skills/yad-epic/SKILL.md +14 -1
- package/skills/yad-epic/references/state-schema.md +106 -0
- package/skills/yad-reconcile/SKILL.md +75 -0
- package/skills/yad-timeline/SKILL.md +78 -0
|
@@ -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
|
|
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 |
|
package/skills/yad-epic/SKILL.md
CHANGED
|
@@ -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.
|
|
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.
|