yadflow 2.17.0 → 2.18.1

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`.
@@ -56,7 +56,7 @@ git work tree). Generate and commit on it.
56
56
  Map the pipeline onto the same data structures `yad-docs` uses (concrete mapping in
57
57
  `references/pipeline-model.md`):
58
58
 
59
- - **Flow paths** = the **phases** — `Setup`, `Front half`, `Build half`, `Automation`.
59
+ - **Flow paths** = the **phases** — `Setup`, `Front-zero` (discovery), `Front half`, `Build half`, `Automation`, `Change management` (feature threads).
60
60
  - **Flow steps** = the **skills/gates in order** (from `module-help.csv` `preceded-by`/`followed-by`),
61
61
  each step's `messages` = the skill's `outputs`, and `sideEffects` = the `.sdlc/` files it writes.
62
62
  - **System components** = the **durable state objects** — the product hub, each `.sdlc/*.json`
@@ -10,7 +10,7 @@ ordering source of truth is `skills/sdlc/module-help.csv` (`phase`, `preceded-by
10
10
 
11
11
  | Shell primitive | Overview meaning |
12
12
  |-----------------|------------------|
13
- | `FlowPath` (`paths.ts`) | a **phase**: Setup, Front half, Build half, Automation. |
13
+ | `FlowPath` (`paths.ts`) | a **phase**: Setup, Front-zero (discovery), Front half, Build half, Automation, Change management (feature threads). |
14
14
  | `FlowStep` (within a path) | a **skill or gate** in order; `messages` = its `outputs`; `sideEffects` = the `.sdlc/` files it writes; `status`/`bookingStatus` annotate gated vs. enrichment vs. earned. |
15
15
  | `SystemComponent` (`components.ts`) | a **durable state object** (the hub, each `.sdlc/*.json`, code repos, the design/testing/learning tools, the platform). |
16
16
  | `RoleConfig` (`roles.ts`) | a **lens** → its relevant sections + paths. |
@@ -66,7 +66,7 @@ Per-story, per-repo: `spec → tasks → implement → checks → engineer-revie
66
66
  |--------------|------------------------|
67
67
  | `yad-spec` | `specs/<story-id>/` (Spec Kit layout), `link.md` |
68
68
  | `yad-implement` | a branch + commit per atomic task |
69
- | `yad-checks` | `checks/*.sh`, CI workflows |
69
+ | `yad-checks` | `checks/*.sh`, CI workflows — the gate set: `spec-link · contract-check · build-test-lint · verified-commits · commit-message · pr-title · pr-template · lineage-check · epic-open · reconcile-debt` |
70
70
  | `yad-pr-template` | PR/MR template + routing helpers |
71
71
  | `yad-commit` / `yad-open-pr` / `yad-ship` | one commit / one PR/MR |
72
72
  | `yad-engineer-review` | engineer review + ship recorded in `build-log.json` |
@@ -83,11 +83,30 @@ node classes from the diagram.
83
83
  | `yad-status` | read-only view (no writes) |
84
84
  | `yad-docs` / `yad-docs-overview` / `yad-docs-sync` | the docs sites + their `docs-build.json` manifests |
85
85
 
86
+ ### Path: Change management — feature threads (`phase: 6-change`)
87
+ The post-lock evolution layer. Once an epic is **sealed** (all stories shipped), behaviour can no longer
88
+ be mutated in place — a change request becomes a **new epic threaded to its parent** (genesis → change →
89
+ defect), inheriting unchanged front artifacts **by reference** and re-authoring only what changes, so
90
+ locked artifacts are never mutated and never go stale, only superseded. Three CI gates enforce the thread
91
+ (`lineage-check`, `epic-open`, `reconcile-debt`). This path never gates the front chain; `yad-change` is
92
+ the intake, then it hands off to the normal authoring skills + the review gate.
93
+
94
+ | Step (skill) | Outputs / sideEffects |
95
+ |--------------|------------------------|
96
+ | `yad-change` | intake + triage depth (defect-fix / behavioral-no-surface / contract-surface / new-capability); seeds a threaded change-epic — lineage frontmatter, inherited-step `state.json`, pointer-lock `contract-lock.json`, `change.json`, and `reconcile-debt.json` for hotfixes |
97
+ | `yad-timeline` | `TIMELINE.md` (the thread evolution view) + `thread-resolved.md` (the resolved current truth) |
98
+ | `yad-defects` | `DEFECTS.md` (quality-gap report by `escape_stage` + root cause) |
99
+ | `yad-reconcile` | advisory drift / orphan / debt sweep (read-only; mirrors `yad-docs-sync`) |
100
+
101
+ CLI: `yad thread [<epic>]` prints a thread + its resolved current truth + open debt; `yad reconcile`
102
+ runs the sweep. The three thread gates ride in the build-half `yad-checks` set above.
103
+
86
104
  ## System components = the durable state objects
87
105
 
88
106
  `components.ts` renders these on the canvas (deterministic positions): the **product hub**; each
89
107
  `.sdlc/*.json` (`state.json`, `approvals.json`, `comments.json`, `hub.json`, `repos.json`, `design.json`,
90
- `testing.json`, `learning.json`, `docs.json`, `contract-lock.json`, `build-state/*`, `trust-log.json`);
108
+ `testing.json`, `learning.json`, `docs.json`, `contract-lock.json`, `build-state/*`, `trust-log.json`,
109
+ and the change-thread ledgers `change.json`, `reconcile-debt.json`, `build-log.json`);
91
110
  the **connected code repos**; the **design / testing / learning tools**; and the **platform**
92
111
  (GitHub/GitLab + Pages). A skill's `sideEffects` link its step to the component it writes.
93
112
 
@@ -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.
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: yad-reconcile
3
+ description: 'Phase 6 maintenance/CI — the change reconciler (mirrors yad-docs-sync; NEVER a gate). Detects post-lock DRIFT and ORPHANS across feature threads: shipped code or a repo HEAD advance (the repos.json syncedHead-vs-current-HEAD rule) with NO owning change-epic in any thread, a broken lineage (cycle, missing parent, or a thread cache that disagrees with the computed root), and open hotfix reconcile debt — reporting which thread drifted and WHY. `check` (default, read-only) reports; `refresh` points the human at opening a reconcile change-epic with yad-change (never silent); `wire` commits an advisory CI job ([skip ci] + concurrency, like yad-docs-sync) that runs the check on push. The hard merge BLOCK is the lineage-check / epic-open / reconcile-debt CI gates — this only DISCOVERS. Use when the user says "reconcile the changes", "check for thread drift", "is anything shipped without an epic", or "find open hotfix debt".'
4
+ ---
5
+
6
+ # SDLC — Change Reconciler (Phase 6, the drift/orphan sweep)
7
+
8
+ **Goal:** Keep the feature threads honest the way `yad-docs-sync` keeps the doc sites honest — by
9
+ **detecting drift, not authoring**. It answers: is any shipped code missing an owning change-epic in a
10
+ thread? Is any lineage broken? Is any hotfix debt still open (freezing the next change)? It **reports**;
11
+ the human decides. It is **never a gate** — it never touches `state.json`, approvals, or the contract
12
+ lock. The hard enforcement is the CI gates (`lineage-check`, `epic-open`, `reconcile-debt`); this skill
13
+ is the read-only counterpart that surfaces problems *before* a PR hits those gates.
14
+
15
+ ## Conventions
16
+
17
+ - `{project-root}` resolves from the product hub.
18
+ - It drives the **`yad reconcile` CLI** (`cli/thread.mjs`): `yad reconcile [check|refresh|wire]
19
+ [--thread EP-<genesis>]`. The CLI is the engine; this skill orchestrates + explains.
20
+ - The thread is **derived** from `parent:` frontmatter (no registry). Repo drift uses the exact
21
+ `repos.json` `syncedHead`-vs-current-HEAD rule (`config.yaml` `code_context.staleness: head-sha`),
22
+ reused from `yad-docs-sync` / `yad repo`.
23
+ - Refresh is **never silent** — it points at `yad-change`, the same discipline as `yad repo refresh`.
24
+
25
+ ## Inputs
26
+
27
+ - `action` — `check` (default, read-only) | `refresh` | `wire`.
28
+ - `thread` — optional `EP-<genesis>` to scope to one feature thread; default sweeps every thread.
29
+
30
+ ## On Activation
31
+
32
+ ### Step 1 — `check` (default, read-only) — detect drift + orphans + debt
33
+ Run `yad reconcile check` (optionally `--thread EP-<genesis>`). For each thread it reports, in
34
+ `yad check` drift style, any of:
35
+ - **Broken lineage** — a change-epic whose `parent` is missing, a cycle, or a `thread` cache that
36
+ disagrees with the computed root (the same signal `yad doctor` reports).
37
+ - **Orphan / drift** — code shipped (`build-log.json`) or a touched repo's HEAD advanced past its
38
+ `repos.json` `syncedHead` with **no owning change-epic** in any thread — i.e. behaviour reached
39
+ production that no epic in the thread describes. Name the repo (`<repo>: <old>→<new>`).
40
+ - **Open hotfix debt** — a `reconcile-debt.json` entry still `open`; the next normal change on that
41
+ thread is blocked until it is paid.
42
+
43
+ Writes nothing. This is the read-only sweep a human (or CI) runs to see the picture.
44
+
45
+ ### Step 2 — `refresh` (advisory, never silent)
46
+ For each flagged thread, **point the human at the fix** — open a reconcile change-epic with `yad-change`
47
+ (`kind: change`, threaded to the affected feature) to bring the front artifacts back in step with what
48
+ shipped, and pay any open debt (update the artifacts + add a regression test, then set the
49
+ `reconcile-debt.json` entry `status: paid`). It never seeds the epic itself — opening a change-epic is a
50
+ human, triaged act (`yad-change` Step 2).
51
+
52
+ ### Step 3 — `wire` (advisory CI, no block)
53
+ Commit an advisory CI job that runs `yad reconcile --check` on push, carrying `[skip ci]` on any commit
54
+ it makes and a concurrency group — the same loop-prevention `yad-docs-sync` uses. The job **reports**;
55
+ it never blocks. The blocking enforcement is the `yad-checks` gates, not this.
56
+
57
+ ### Step 4 — Report
58
+ Summarise per thread: clean, or the concrete drift/debt found and what to do. Never advance any epic;
59
+ the reconciler is advisory.
60
+
61
+ ## Hard rules
62
+
63
+ - **Never a gate.** It never touches `state.json`, `approvals.json`, or any `contract-lock.json`. Drift
64
+ blocks nothing here — it only flags. The CI gates do the blocking.
65
+ - **Reconcile, don't author.** It detects; opening a change-epic is delegated to `yad-change` (a human,
66
+ triaged act). `refresh` is never silent.
67
+ - **HEAD-sha drift, reused.** Repo drift uses the exact `repos.json` `syncedHead` rule, like
68
+ `yad-docs-sync` and `yad repo`.
69
+ - **Loop-prevention in CI.** The wired job carries `[skip ci]` + a concurrency group.
70
+
71
+ ## Reference
72
+ - The drift/refresh/wire discipline this mirrors: `../yad-docs-sync/SKILL.md`.
73
+ - The thread model + ledgers: `../yad-epic/references/state-schema.md` (Phase 6).
74
+ - The change-epic this hands off to: `../yad-change/SKILL.md`.
75
+ - The gates that block at merge: `../yad-checks/` (lineage-check, epic-open, reconcile-debt).
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: yad-timeline
3
+ description: 'Phase 6 output enrichment (never a gate) — render a feature THREAD as an evolution view AND resolve its current truth. Walks the thread (genesis -> changes -> defects, linked by parent: frontmatter), renders the evolution as the vendored React/Vite/Tailwind shell HTML + a TIMELINE.md summary (each node: kind, what it re-authored vs inherited, ship events, contract re-lock history, open debt), and emits thread-resolved.md — the composed CURRENT-TRUTH map (the latest epic that owns each artifact + the resolved contract-lock hash). That resolved map is the source-of-truth AI/humans read to plan the next change, instead of a stale genesis epic. Degrades to markdown-only when no docs target is connected. Use when the user says "show the feature timeline", "how did this feature evolve", "resolve the current truth", or "render the thread".'
4
+ ---
5
+
6
+ # SDLC — Feature Timeline + Current-Truth Resolver (Phase 6, output enrichment)
7
+
8
+ **Goal:** Make a feature's *evolution* legible and its *current truth* explicit. A feature is a thread of
9
+ linked epics; this skill renders that thread (genesis → changes → defects) as an interactive evolution
10
+ view and resolves the inheritance chain into the authoritative **current artifact set** — so an AI or a
11
+ human planning the next change reads *what the feature actually is now*, not a superseded genesis epic.
12
+ It is an **output enrichment**, exactly like `yad-docs` — **never a gate**: it never touches
13
+ `state.json`, approvals, or the contract lock.
14
+
15
+ ## Conventions
16
+
17
+ - `{project-root}` resolves from the product hub.
18
+ - Reuses the **`yad-docs` shell** verbatim (`../yad-docs/templates/app/`) — generated `src/data/*.ts`,
19
+ themed, deployed via `yad docs deploy`; build-only / markdown-only when no docs target
20
+ (`.sdlc/docs.json`). The resolver part drives the **`yad thread` CLI** (`cli/thread.mjs`).
21
+ - The thread is **derived** from `parent:` frontmatter (no registry). The thread report lives under the
22
+ **genesis** epic (`thread == genesis id`).
23
+ - Deterministic generation (stable-id sort, fixed key order, no timestamps in data files), like
24
+ `yad-docs`, so an unchanged thread re-renders byte-identically.
25
+
26
+ ## Inputs
27
+
28
+ - `thread` — `EP-<genesis>` (or any epic in the thread; it resolves to the root). Ask if not given.
29
+ - `action` — `generate` (default) | `deploy`.
30
+
31
+ ## On Activation
32
+
33
+ ### Step 1 — Resolve the thread
34
+ Run `yad thread <thread> --json` (`resolveThread` + `threadEpics` + `resolveCurrentArtifacts`). This
35
+ gives the genesis-first chain, each epic's lineage (`kind`, `parent`, `inherits`), the resolved
36
+ current-truth map, and any open reconcile debt. **STOP** and report if the lineage is broken (point at
37
+ `yad doctor` / `yad-reconcile`).
38
+
39
+ ### Step 2 — Read each node's evolution facts
40
+ For each epic in the chain read `epic.md` (lineage + the change brief), `.sdlc/change.json` (depth,
41
+ defect block), `.sdlc/build-log.json` (ship events), and the contract-lock (a real lock = a re-lock
42
+ event; a pointer-lock = inherited). Greenfield-safe: an absent input degrades its part of the view.
43
+
44
+ ### Step 3 — Render the evolution view (yad-docs shell)
45
+ Generate the site into `epics/<thread>/timeline-site/` (copy the shell verbatim; generate `src/data/*.ts`
46
+ deterministically; theme from the design system). The thread maps onto the shell primitives:
47
+ - **Flow path** = the thread; **flow steps** = the epics in order (genesis → tip), each step's messages
48
+ = what it re-authored, its side-effects = the ships it produced + any contract re-lock.
49
+ - **System components** = the artifacts (epic/architecture/contract/ui/stories/test-cases), each labelled
50
+ with the epic that currently **owns** it (from the resolved map).
51
+ - Colour nodes by `kind` (feature/change/defect/hotfix); mark sealed epics and open debt.
52
+
53
+ ### Step 4 — Emit `thread-resolved.md` (the current-truth map — derived, non-authoritative)
54
+ Write `epics/<thread>/thread-resolved.md`: for each artifact base, the **owning epic** (the latest in the
55
+ chain that re-authored it) and, for the contract, the **resolved lock hash**. Mark it clearly as a
56
+ *derived, regenerable* file (it is composed from immutable artifacts; it is not itself an artifact). This
57
+ is the file the next `yad-change` / `yad-epic` reads as "the feature's current truth".
58
+
59
+ ### Step 5 — Emit `TIMELINE.md` + (optional) deploy
60
+ Write a short `epics/<thread>/TIMELINE.md` (the chain, what each node changed, ships, open debt) for a
61
+ plain-text read. On `action: deploy`, `yad docs deploy` the site (build-only when no target).
62
+
63
+ ## Hard rules
64
+
65
+ - **Never a gate.** No writes to `state.json`, `approvals.json`, or any `contract-lock.json`. It reads
66
+ the thread and renders it.
67
+ - **Derived, regenerable.** `thread-resolved.md` and the site are composed from immutable artifacts;
68
+ re-running yields byte-identical output. They are enrichment, never the source of approval.
69
+ - **Degrade gracefully.** No docs target → markdown-only (`TIMELINE.md` + `thread-resolved.md`); never
70
+ fail because a tool is absent.
71
+
72
+ ## Reference
73
+ - The resolver + thread engine: `cli/thread.mjs` (`yad thread`), `cli/epic-state.mjs`
74
+ (`resolveThread` / `resolveCurrentArtifacts`).
75
+ - The shell + deterministic generation it reuses: `../yad-docs/SKILL.md`,
76
+ `../yad-docs/references/data-mapping.md`.
77
+ - The thread model + ledgers: `../yad-epic/references/state-schema.md` (Phase 6).
78
+ - The companion report: `../yad-defects/SKILL.md`.