yadflow 3.5.3 → 3.6.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.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,9 @@
1
- ## [3.5.3](https://github.com/abdelrahmannasr/yadflow/compare/v3.5.2...v3.5.3) (2026-07-03)
1
+ ## [3.6.1](https://github.com/abdelrahmannasr/yadflow/compare/v3.6.0...v3.6.1) (2026-07-04)
2
2
 
3
3
 
4
4
  ### Bug Fixes
5
5
 
6
- * fill the PR spec dir and summary from the task and commit ([a402a54](https://github.com/abdelrahmannasr/yadflow/commit/a402a545715eeea8aef2dde5426eb7f2538687a0))
6
+ * **gate:** include the Checklist section in the hub review-PR body ([3134f89](https://github.com/abdelrahmannasr/yadflow/commit/3134f89658a172a3a346cf0945f62e6fa63bac74)), closes [#103](https://github.com/abdelrahmannasr/yadflow/issues/103)
7
7
 
8
8
  # [2.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.1.0...v2.2.0) (2026-06-14)
9
9
 
package/README.md CHANGED
@@ -72,7 +72,7 @@ In one pass it produces:
72
72
 
73
73
  - **The `yad` CLI** — zero-dependency Node (`setup`, `gate`, `commit`, `open-pr`, `ship`, `repo`,
74
74
  `thread`, `reconcile`, `usage`, `doctor`), run via `npx` or a global install.
75
- - **37 workflow skills** installed into your AI assistant — **Claude Code** (`.claude/`) first-class,
75
+ - **38 workflow skills** installed into your AI assistant — **Claude Code** (`.claude/`) first-class,
76
76
  plus `.agents`, Zencoder, and OpenCode.
77
77
  - **`.sdlc/` config** — the product hub, connected repos, reviewer roster, and tool connections
78
78
  (design, testing, learning), all as plain JSON you can read and diff.
@@ -157,7 +157,7 @@ workflow-hygiene flags — derived read-only, so an EM can see how the team actu
157
157
  - **[Terminology & workflow report](https://abdelrahmannasr.github.io/yadflow/)** — every term, artifact, gate, and skill on one illustrated page.
158
158
  - **[TEAM-GUIDE.md](TEAM-GUIDE.md)** — the short, plain-language version for a developer team.
159
159
  - **[docs/CLI.md](docs/CLI.md)** — the full `yad` command reference, the PR-driven gate, and `yad doctor` codes.
160
- - **[docs/SKILLS.md](docs/SKILLS.md)** — the catalogue of all 37 agent skills.
160
+ - **[docs/SKILLS.md](docs/SKILLS.md)** — the catalogue of all 38 agent skills.
161
161
  - **[docs/WALKTHROUGH.md](docs/WALKTHROUGH.md)** — the by-hand, end-to-end path through every phase.
162
162
  - **[CONTRIBUTING.md](CONTRIBUTING.md)** · **[RESEARCH-NOTES.md](RESEARCH-NOTES.md)** · **[RELEASING.md](RELEASING.md)**
163
163
 
@@ -396,6 +396,18 @@ export function buildNextActions(buildStates = []) {
396
396
  }));
397
397
  }
398
398
 
399
+ // Classify a stub / backfill anchor from its ledger state — the SINGLE source of truth so `nextAction`
400
+ // and `preconditionsMet` can never disagree, even on a partially-applied `promote`. The `stub` check
401
+ // takes precedence over `backfill-done`, so a half-cleared promote (`kind:stub` still set while
402
+ // `currentStep` already moved) reads as still-a-stub — the conservative side (needs promoting). Returns
403
+ // `'stub'` (un-promoted anchor), `'documented'` (light-promoted anchor), or `null` (a normal epic).
404
+ export function backfillAnchorKind(state) {
405
+ if (!state) return null;
406
+ if (state.kind === 'stub' || state.currentStep === 'backfill-pending') return 'stub';
407
+ if (state.currentStep === 'backfill-done') return 'documented';
408
+ return null;
409
+ }
410
+
399
411
  // PURE precondition guard. Is `stepId` runnable right now? A step is runnable iff every step BEFORE it
400
412
  // in the chain is `done` and the step itself is not already `done`. With no state yet (greenfield), the
401
413
  // only runnable steps are the entry authoring steps (analysis | epic). Used by `yad next --check`
@@ -405,6 +417,18 @@ export function preconditionsMet(state, stepId) {
405
417
  const ok = stepId === 'epic' || stepId === 'analysis' || stepId === 'discovery';
406
418
  return { ok, blockedBy: null, reason: ok ? 'entry step (no state seeded yet)' : `start with yad-epic — no epic state for '${stepId}'` };
407
419
  }
420
+ // A stub anchor (backfill-pending) or a light-promoted anchor (backfill-done) has NO runnable front
421
+ // step: its front chain is intentionally left `blocked`. It evolves via `yad-backfill promote` / a
422
+ // threaded `yad-change`, never by authoring `epic` against the anchor itself — so the precondition
423
+ // guard must not green-light one (its blocked steps would otherwise read as "entry step ready").
424
+ const anchorKind = backfillAnchorKind(state);
425
+ if (anchorKind) {
426
+ const anchor = anchorKind === 'documented';
427
+ return { ok: false, blockedBy: null,
428
+ reason: anchor
429
+ ? `${stepId} is not runnable — this is a documented backfill anchor; evolve it with yad-change`
430
+ : `${stepId} is not runnable — this is a stub (backfill pending); run yad-backfill then promote, or thread a change with yad-change` };
431
+ }
408
432
  const i = state.steps.findIndex((s) => s.id === stepId);
409
433
  if (i === -1) return { ok: false, blockedBy: null, reason: `unknown step '${stepId}'` };
410
434
  if (state.steps[i].status === 'done') return { ok: false, blockedBy: null, reason: `${stepId} is already done` };
@@ -449,6 +473,26 @@ export function nextAction(ledger, { epic } = {}) {
449
473
  why: dpr ? `review PR #${dpr.number} is open — sync its state to advance` : `${dstep.id} is open — create the review PR/MR` };
450
474
  }
451
475
 
476
+ // A STUB genesis epic (yad-stub) or a light-promoted anchor: classified by the SHARED
477
+ // `backfillAnchorKind` helper (the same one `preconditionsMet` uses), so the two readers can never
478
+ // disagree — even on a partially-applied `promote`. A stub is (epic.md `stub:backfill-pending`) ⟺
479
+ // (state.kind:stub + currentStep:backfill-pending); `yad-backfill promote` clears ALL of these
480
+ // atomically (see state-schema.md), keeping this sentinel in step with `isStubEpic` (frontmatter).
481
+ const anchorKind = backfillAnchorKind(state);
482
+ if (anchorKind === 'stub') {
483
+ // No build half until backfilled + promoted — route to yad-backfill (not to authoring the epic),
484
+ // and remind that bugs can thread off it now with yad-change.
485
+ return { epicId, kind: 'backfill-pending', step: 'backfill-pending', status: 'stub',
486
+ why: 'stub epic (backfill pending) — document the code with yad-backfill then `yad-backfill promote` to make it real; thread bugs now with yad-change' };
487
+ }
488
+ if (anchorKind === 'documented') {
489
+ // `yad-backfill promote` documented the feature (verified) but did NOT wake the front chain (its docs
490
+ // live in the backfill spec). Terminal like `discovery-done` — no build half runs directly; the
491
+ // feature evolves by threading a change/defect off it.
492
+ return { epicId, kind: 'backfill-done', step: 'backfill-done', status: 'documented',
493
+ why: 'backfilled anchor (documented) — no build half runs directly; evolve it by threading a change/defect with yad-change' };
494
+ }
495
+
452
496
  // The parallel test-cases track stays workable even once the epic is ready-for-build.
453
497
  const tc = state.steps.find((s) => s.id === 'test-cases');
454
498
  const tcOpen = !!tc && tc.status !== 'done' && tc.status !== 'blocked';
@@ -527,6 +571,14 @@ export function epicLineage(root, epic) {
527
571
  };
528
572
  }
529
573
 
574
+ // Is this a STUB genesis epic (minted by yad-stub as a brownfield thread anchor)? A stub is kind:feature
575
+ // but carries `stub: backfill-pending` in epic.md frontmatter until `yad-backfill promote` flips it to a
576
+ // real, verified epic (which clears the marker). Missing/greenfield-safe. Read by yad thread / yad-status
577
+ // / the reconciler to render "stub (backfill pending)" and never treat it as a fully-specced feature.
578
+ export function isStubEpic(root, epic) {
579
+ return readFrontmatter(path.join(epicRoot(root, epic), 'epic.md')).stub === 'backfill-pending';
580
+ }
581
+
530
582
  // Walk `parent` to the thread root. Cycle- and missing-safe. Returns the genesis-first `chain`, the
531
583
  // computed `rootId`, and a `broken` reason (missing parent dir, a cycle, or a denormalized `thread`
532
584
  // cache that disagrees with the computed root) — the signal yad doctor / yad next --check report.
package/cli/gate.mjs CHANGED
@@ -645,7 +645,7 @@ export async function gateTrailer(root, { epic, artifact, body, number, getBody
645
645
  // ---- helpers ------------------------------------------------------------------------------------
646
646
  const base = (artifact) => artifactBase(artifact);
647
647
 
648
- function fillHubTemplate({ epic, artifact, step, owner, domains }) {
648
+ export function fillHubTemplate({ epic, artifact, step, owner, domains }) {
649
649
  return [
650
650
  '## Artifact under review',
651
651
  `- Epic: \`${epic}\``,
@@ -660,6 +660,15 @@ function fillHubTemplate({ epic, artifact, step, owner, domains }) {
660
660
  '## How to review (this drives the gate)',
661
661
  '- **Approve** to record your approval; **comment / request changes** to hold the gate.',
662
662
  '- This step advances when approvals are satisfied, all threads are resolved, and this PR is merged.',
663
+ '',
664
+ // Required by the hub `pr-template` gate (check_hub_body). Mirrors the Checklist block of the
665
+ // committed static template (yad-pr-template/templates/hub/<platform>/) so the generated body
666
+ // passes on the first CI run.
667
+ '## Checklist',
668
+ '- [ ] `owner` set in the artifact frontmatter (inherited from `epic.md`)',
669
+ '- [ ] Contract re-locked (`.sdlc/contract-lock.json`) if the surface changed (architecture only)',
670
+ '- [ ] Risk tags reflect the real surface touched (contract/auth/payments escalate)',
671
+ '- [ ] No secrets or tokens in the artifact or this description',
663
672
  ].join('\n');
664
673
  }
665
674
 
package/cli/manifest.mjs CHANGED
@@ -56,6 +56,7 @@ export const SKILLS = [
56
56
  'yad-timeline',
57
57
  'yad-defects',
58
58
  'yad-reconcile',
59
+ 'yad-stub',
59
60
  ];
60
61
 
61
62
  // Pre-2.0 skill names (the sdlc-* -> yad-* rename). `check`/`update` migrate any install
package/cli/next.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // `yad next` — the unified next-step driver. Read-only: it never writes state or acts. It reads the
2
2
  // file ledger and prints the ONE concrete, copy-pasteable next action (and a one-line why), so a user
3
- // never has to remember which of the 37 skills / gate commands comes next. "Guide, don't act" — the
3
+ // never has to remember which of the 38 skills / gate commands comes next. "Guide, don't act" — the
4
4
  // front half still never auto-advances. Once an epic is `ready-for-build`, it reads each story's
5
5
  // build-state and prints the next BUILD sub-step per repo (spec → tasks → implement → checks → engineer-review)
6
6
  // plus the remaining chain — so the build half is guided too, not just hinted at.
@@ -91,6 +91,10 @@ function actionLine(a, { solo } = {}) {
91
91
  }
92
92
  case 'discovery-done':
93
93
  return `invoke the ${c.bold('yad-epic')} skill ${c.dim('(seed a feature epic from roadmap.md)')}`;
94
+ case 'backfill-pending':
95
+ return `invoke the ${c.bold('yad-backfill')} skill ${c.dim('(document the code, then `yad-backfill promote` — or thread bugs now with yad-change)')}`;
96
+ case 'backfill-done':
97
+ return `invoke the ${c.bold('yad-change')} skill ${c.dim('(documented anchor — evolve it by threading a change/defect off it)')}`;
94
98
  default:
95
99
  return c.dim('nothing to do');
96
100
  }
package/cli/thread.mjs CHANGED
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import fs from 'node:fs';
7
7
  import { c, log, ok, info, warn, hand, readJSON, exists } from './lib.mjs';
8
8
  import {
9
- epicRoot, isValidEpicId, epicLineage, readFrontmatter,
9
+ epicRoot, isValidEpicId, epicLineage, readFrontmatter, isStubEpic,
10
10
  resolveThread, threadEpics, resolveCurrentArtifacts, resolveCurrentStories, THREAD_ARTIFACT_BASES,
11
11
  } from './epic-state.mjs';
12
12
 
@@ -54,6 +54,7 @@ export function threadSummary(root, threadOrEpic) {
54
54
  id, kind: lin.kind, parent: lin.parent, inherits: lin.inherits,
55
55
  currentStep: state?.currentStep || 'unseeded',
56
56
  sealed: sealedEpic(root, id),
57
+ stub: isStubEpic(root, id),
57
58
  depth: change?.depth || null,
58
59
  defect: change?.defect || null,
59
60
  brokenThread: resolveThread(root, id).broken || null,
@@ -86,7 +87,8 @@ export async function runThread(root, { epic, json = false } = {}) {
86
87
  for (const r of [...roots].sort()) {
87
88
  const s = threadSummary(root, r);
88
89
  const debt = s.openDebt.length ? c.red(` ⚠ ${s.openDebt.length} open reconcile-debt`) : '';
89
- log(` ${c.bold(r)} ${c.dim(`${s.nodes.length} epic(s)`)}${debt}`);
90
+ const stub = s.nodes[0]?.stub ? c.yellow(' [stub · backfill pending]') : '';
91
+ log(` ${c.bold(r)} ${c.dim(`${s.nodes.length} epic(s)`)}${stub}${debt}`);
90
92
  }
91
93
  log(c.dim('\n yad thread <epic> show one thread in full'));
92
94
  return;
@@ -100,8 +102,9 @@ export async function runThread(root, { epic, json = false } = {}) {
100
102
  for (const n of s.nodes) {
101
103
  const tag = KIND_TAG[n.kind] || n.kind;
102
104
  const seal = n.sealed ? c.dim(' [sealed]') : '';
105
+ const stub = n.stub ? c.yellow(' [stub · backfill pending]') : '';
103
106
  const dep = n.depth ? c.dim(` ${n.depth}`) : '';
104
- log(` • ${c.bold(n.id)} ${tag}${dep} ${c.dim('@ ' + n.currentStep)}${seal}`);
107
+ log(` • ${c.bold(n.id)} ${tag}${dep} ${c.dim('@ ' + n.currentStep)}${seal}${stub}`);
105
108
  if (n.parent) log(c.dim(` parent: ${n.parent} inherits: [${n.inherits.join(', ') || '—'}]`));
106
109
  if (n.defect) log(c.dim(` defect: ${n.defect.severity || '?'} · escaped@${n.defect.escape_stage || '?'} · ${n.defect.root_cause || ''}`));
107
110
  if (n.brokenThread) log(c.red(` ✗ ${n.brokenThread}`));
@@ -161,6 +164,8 @@ export async function runReconcile(root, { action = 'check', thread = null } = {
161
164
  log('');
162
165
  info('refresh is advisory: open a reconcile change-epic with `yad-change` (kind: change) threaded to');
163
166
  info('the affected feature, then pay any open debt (update artifacts + add a regression test).');
167
+ info('for shipped brownfield code with NO epic at all, anchor it first with `yad-stub`, then thread');
168
+ info('the change/defect off that stub (and run `yad-backfill` to make the anchor real).');
164
169
  }
165
170
  if (action === 'wire') {
166
171
  log('');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yadflow",
3
- "version": "3.5.3",
4
- "description": "Yadflow — the gated, team, multi-repo SDLC: author → review → build with a PR-driven review gate and a zero-dependency `yad` CLI (setup, gate, commit, open-pr, ship, repo, thread, reconcile). A BMAD module + 37 yad-* skills.",
3
+ "version": "3.6.1",
4
+ "description": "Yadflow — the gated, team, multi-repo SDLC: author → review → build with a PR-driven review gate and a zero-dependency `yad` CLI (setup, gate, commit, open-pr, ship, repo, thread, reconcile). A BMAD module + 38 yad-* skills.",
5
5
  "type": "module",
6
6
  "author": "AbdelRahman Nasr",
7
7
  "license": "MIT",
@@ -146,6 +146,7 @@ build:
146
146
  pack_flags: "--compress --include <globs> --include-logs --style markdown" # one feature at a time; Secretlint by default
147
147
  spec_location: "specs/backfill/<feature>/spec.md" # draft (verified: false) until a human approves
148
148
  gate_scope: touched-features # block a change only until the features it touches have approved specs
149
+ promote: yad-stub # `yad-backfill promote EP-<slug>` flips a brownfield stub epic (change.stub) to a real, verified feature epic once its spec is approved
149
150
 
150
151
  # Code context (yad-connect-repos) — the front/"brain" phases are made code-aware. Code repos are
151
152
  # connected to the product hub once (or any time a new repo is added), and an AI-readable picture of
@@ -317,6 +318,18 @@ change:
317
318
  # behaviour on a sealed epic -> the change must land in a new threaded change-epic (the front half is
318
319
  # forced to stay current; staleness is unshippable).
319
320
  seal_on: all-stories-shipped
321
+ # Brownfield stub anchor (yad-stub): an already-built feature with NO epic can't be a change parent
322
+ # (yad-change requires a real parent; lineage-check rejects a missing one). yad-stub mints the smallest
323
+ # real thread anchor — a genesis epic.md (kind:feature, thread:self) marked `stub: backfill-pending` /
324
+ # `verified: false`, with a state.json carrying `kind: stub` + `currentStep: backfill-pending`. A change
325
+ # threaded off a stub inherits only what exists (boundHash: null for the undocumented surface bases) and
326
+ # writes NO pointer-lock; change.json records `parentStub: true`. `yad-backfill promote` flips the stub
327
+ # to verified (clears the marker), at which point contract protection begins for the thread.
328
+ stub:
329
+ marker: backfill-pending # epic.md `stub:` value while the anchor is un-promoted
330
+ state_kind: stub # state.json top-level `kind` + `currentStep: backfill-pending` sentinel
331
+ allow_stub_parent: true # a change/defect MAY thread off an un-promoted stub (default on)
332
+ promote_by: yad-backfill # `yad-backfill promote EP-<slug>` documents + flips the stub to real
320
333
  hotfix:
321
334
  ship_first: true # a hotfix may run the build half BEFORE its front gates approve
322
335
  debt_blocks_next_change: true # but opens reconcile-debt.json; the next change on the thread is blocked until paid
@@ -11,7 +11,7 @@ set -euo pipefail
11
11
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
12
12
  cd "$ROOT"
13
13
 
14
- SKILLS=(yad-discovery yad-analysis yad-epic yad-architecture yad-ui yad-stories yad-test-cases yad-connect-repos yad-sync-repos yad-connect-design yad-connect-testing yad-connect-learning yad-connect-docs yad-docs yad-docs-overview yad-docs-sync yad-learn yad-spec yad-implement yad-checks yad-pr-template yad-hub-bridge yad-commit yad-open-pr yad-ship yad-engineer-review yad-backfill yad-run yad-review-gate yad-review-companion yad-pair-review yad-status yad-report yad-change yad-timeline yad-defects yad-reconcile)
14
+ SKILLS=(yad-discovery yad-analysis yad-epic yad-architecture yad-ui yad-stories yad-test-cases yad-connect-repos yad-sync-repos yad-connect-design yad-connect-testing yad-connect-learning yad-connect-docs yad-docs yad-docs-overview yad-docs-sync yad-learn yad-spec yad-implement yad-checks yad-pr-template yad-hub-bridge yad-commit yad-open-pr yad-ship yad-engineer-review yad-backfill yad-run yad-review-gate yad-review-companion yad-pair-review yad-status yad-report yad-change yad-timeline yad-defects yad-reconcile yad-stub)
15
15
 
16
16
  # Skills removed in a later release: this installer only refreshes names still in SKILLS, so a
17
17
  # rerun would otherwise leave a dropped skill sitting in the IDE dirs. Purge any lingering copy
@@ -24,7 +24,7 @@ SDLC Workflow,yad-open-pr,Open PR/MR,OP,"Build-half helper: open a code-repo tas
24
24
  SDLC Workflow,yad-ship,Commit + Open PR/MR,SP2,"Build-half helper: commit AND open the task PR/MR in one step — a thin orchestration over yad-commit then yad-open-pr. Commits the staged atomic change by the conventions, then pushes the branch and opens the PR/MR from the committed template with the roster auto-assigned. The PR step runs ONLY if the commit lands (a failed commit, tripped guard, or --dry-run stops before pushing). Drives the yad ship CLI. Never merges; never auto-advances.",,{type: feat|fix|...} {message: subject} {ai: <tool|none>} {repo: <name>} {risk: low|medium|high} {contract-change: true|false},3-build,yad-pr-template,,false,<repo>/,one commit + one PR/MR
25
25
  SDLC Workflow,yad-hub-bridge,Hub Review Bridge,HB,"The templated PR/MR bridge for the front-half review gate: when the product hub has a platform (.sdlc/hub.json), open a review PR/MR on the hub for an authored artifact, set required reviewers/labels from the routing rule, and provide the read-only gh/glab recipes yad-review-gate's sync uses to pull platform comments + approvals into the file ledger. Local-user auth, no stored tokens; file ledger stays the source of truth; degrades to file-only when no platform/CLI. Never auto-advances.",,{epic: EP-<slug>} {artifact: epic.md|architecture.md|ui-design.md|stories/} {action: open|route},1-front,yad-review-gate,yad-review-gate,false,epics/EP-<slug>/.sdlc/,hub-prs.json
26
26
  SDLC Workflow,yad-engineer-review,Engineer Review & Merge,ER,"Build-half Step E: wire an advisory AI first-pass (CodeRabbit) on the PR, record the human engineer review with the same human_approve discipline as the front gates (owner + 1 reviewer, escalating to domain owners on high risk / contract / auth / payments), and on merge record the ship in epics/<epic>/.sdlc/build-log.json and update the story state. AI review is advisory, never the authority; the human owns the merge. Never auto-advances.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {task: T0N} {repo: <repo>} {action: ai-review|approve|ship},3-build,yad-ship,,false,epics/EP-<slug>/.sdlc/,build-log.json story-status
27
- SDLC Workflow,yad-backfill,Backfill Specs,BF,"Build-half Step G: generate specs for already-built features in an existing repo. Confirm Repomix (npx repomix CLI), pack ONE feature (compress + git logs, secret-scan), feed to AI with a 'describe what exists, do not invent' prompt, write a DRAFT spec marked verified: false. Human approval (reuse yad-review-gate) makes it real. Boundary auto-proposed and human-confirmed. A change is blocked only until the features it touches have approved specs. Never auto-advances.",,{repo: <repo>} {feature: <name + globs>} {action: pack|draft|approve|gate},3-build,,,false,demo-repos/<repo>/specs/backfill/<feature>/,spec.md backfill-check.sh
27
+ SDLC Workflow,yad-backfill,Backfill Specs,BF,"Build-half Step G: generate specs for already-built features in an existing repo. Confirm Repomix (npx repomix CLI), pack ONE feature (compress + git logs, secret-scan), feed to AI with a 'describe what exists, do not invent' prompt, write a DRAFT spec marked verified: false. Human approval (reuse yad-review-gate) makes it real. Boundary auto-proposed and human-confirmed. A change is blocked only until the features it touches have approved specs. The promote action flips a brownfield stub epic (yad-stub) to a real, verified feature epic once its backfill spec is approved. Never auto-advances.",,{repo: <repo>} {feature: <name + globs>} {action: pack|draft|approve|gate|promote} {epic: EP-<slug> (promote)},3-build,,,false,demo-repos/<repo>/specs/backfill/<feature>/,spec.md backfill-check.sh
28
28
  SDLC Workflow,yad-run,Run (Automation),RN,"Phase 4 orchestrator: drive a story's back-half loop (spec→tasks→implement→checks) in one code repo, reading each step's automation dial from build-state — on machine_advance it advances on its own, on human_approve it stops for a human. Records every run in the trust log. Realizes Step B (a clean checks pass auto-advances to the engineer review when earned; any FAIL / scope overrun / contract touch HALTS). set-dial earns/reverts a step's automation (machine_advance gated by the trust threshold; front states and engineer-review refused); kill/unkill toggles the system-wide kill switch. Front states and the human merge gate never auto-advance.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {repo: <repo>} {action: run|set-dial|kill|unkill} {step: <back-step>} {to: human_approve|machine_advance},4-automate,yad-spec,yad-engineer-review,false,epics/EP-<slug>/.sdlc/,build-state/<story-id>.json trust-log.json
29
29
  SDLC Workflow,yad-status,SDLC Status,SS,"Read-only: print the full front-state chain, per-step dials, contract lock, story repo tags, and pending approvals at the active gate. For stories in the build half, also print each back-half step's automation dial and status, the trust record (runs / % approved-unchanged / earned vs gathering evidence), and the system-wide kill-switch state.",,{epic: EP-<slug>},1-front,,,false,,
30
30
  SDLC Workflow,yad-report,Report Issue,RP,"Self issue reporter: when a yad flow breaks, file a well-formed bug in the upstream yadflow repo with AUTO-SCRUBBED diagnostics (yadflow/node/os version, tool present+authenticated booleans, hub platform enum, the YadError code/hint, a path-scrubbed message, and the failing command + flag NAMES only) — never absolute paths, hostnames, git URLs, repo names, roster logins/emails, epic/story IDs, branch names, or flag values. Searches open issues first to avoid duplicates, previews the exact payload, and asks before posting to the public repo; files via an authenticated gh/glab or a prefilled issues/new URL fallback. Also offered automatically after an unexpected failure (interactive only; YAD_NO_REPORT=1 disables). Never a gate, never touches epic state.",,{message: one-line summary},,,,false,,
@@ -35,4 +35,5 @@ SDLC Workflow,yad-docs-sync,Docs Sync,DY,"Maintenance/CI: keep the generated doc
35
35
  SDLC Workflow,yad-change,Change/Defect Intake,CH,"Phase 6 post-lock change management: the INTAKE + TRIAGE step of a feature thread. Classifies the change DEPTH (defect-fix / behavioral-no-surface / contract-surface / new-capability), seeds a NEW EP-<slug> change-epic threaded to its parent (lineage frontmatter kind/parent/thread/inherits/supersedes + a state.json whose inherited steps are pre-marked done and only the changed steps run; a pointer-lock contract-lock.json when architecture is inherited), and records the intake in change.json (escape_stage + root_cause for defects). For hotfixes it records the ship-first exception and opens reconcile-debt.json. Never auto-advances — hands off to the normal authoring skills + yad-review-gate.",,{parent: EP-<slug>} {title: one-line} {kind: change|defect|hotfix} {origin: production|staging|qa|review} {severity: sev1..sev4} {description: text} {affected: artifacts},1-front,,yad-review-gate,false,epics/EP-<slug>/,epic.md state.json change.json reconcile-debt.json contract-lock.json
36
36
  SDLC Workflow,yad-timeline,Feature Timeline,TL,"Render a feature THREAD (its linked epics, genesis->changes->defects) as an evolution view (the vendored React/Vite/Tailwind shell HTML + a TIMELINE.md summary) AND resolve the inheritance chain into the authoritative current artifact set (thread-resolved.md: the winning source per artifact + the resolved contract-lock hash) — the composed source-of-truth AI/humans read for the next change. Reads frontmatter lineage + each change.json + build-log.json. An OUTPUT ENRICHMENT — never a gate; never mutates state.",,{thread: EP-<genesis>} {action: generate|deploy},,,,false,epics/EP-<genesis>/,timeline-site/ thread-resolved.md TIMELINE.md
37
37
  SDLC Workflow,yad-defects,Quality-Gap Report,DF,"Generate a per-epic AND per-thread defect/bug report (same vendored shell + DEFECTS.md). Walks the thread for every kind:defect change-epic + each change.json defect block + shipped regressions in build-log.json, aggregates by escape_stage (the SDLC gate that should have caught it) and root_cause, and visualizes WHERE quality gaps systematically come from (e.g. % of thread defects that escaped at the test-cases gate) so the team can harden the originating stage. An OUTPUT ENRICHMENT — never a gate; never mutates state.",,{epic: EP-<slug> | thread: EP-<genesis>} {action: generate|deploy},,,,false,epics/EP-<slug>/,defects-site/ DEFECTS.md
38
- SDLC Workflow,yad-reconcile,Change Reconciler,RE,"Maintenance/CI (mirrors yad-docs-sync — never a gate): detect post-lock DRIFT/ORPHANS — shipped code or a repo HEAD advance (the repos.json syncedHead-vs-current-HEAD rule) with NO owning change-epic in any thread — plus open hotfix reconcile debt, and report which thread drifted and why. refresh scaffolds a reconcile change-epic stub (hands to yad-change) — never silent; wire commits advisory CI ([skip ci] + concurrency, like yad-docs-sync). The actual merge BLOCK is the lineage-check / reconcile-debt gates; this only discovers.",,{action: check|refresh|wire} {thread: EP-<genesis>},,,,false,epics/EP-<genesis>/.sdlc/,(report) reconcile-debt.json yad-reconcile.yml
38
+ SDLC Workflow,yad-reconcile,Change Reconciler,RE,"Maintenance/CI (mirrors yad-docs-sync — never a gate): detect post-lock DRIFT/ORPHANS — shipped code or a repo HEAD advance (the repos.json syncedHead-vs-current-HEAD rule) with NO owning change-epic in any thread — plus open hotfix reconcile debt, and report which thread drifted and why. refresh points at yad-change to open a reconcile change-epic (or yad-stub first, to anchor orphan brownfield code that has no epic at all) — never silent; wire commits advisory CI ([skip ci] + concurrency, like yad-docs-sync). The actual merge BLOCK is the lineage-check / reconcile-debt gates; this only discovers.",,{action: check|refresh|wire} {thread: EP-<genesis>},,,,false,epics/EP-<genesis>/.sdlc/,(report) reconcile-debt.json yad-reconcile.yml
39
+ SDLC Workflow,yad-stub,Stub Genesis Epic,SG,"Phase 6 brownfield helper: mint a STUB genesis epic for an already-built feature that has no epic in the hub, so a defect/change can thread off it TODAY (yad-change requires a real parent and dead-ends without one). Creates the smallest real thread anchor — a tiny epic.md (kind:feature, thread:self, verified:false, stub:backfill-pending) + a seeded state.json (kind:stub / currentStep:backfill-pending) + empty ledgers — never inventing behaviour. Defects thread off it immediately (gates pass; the bug list is derived by the thread rollup); yad-backfill + its promote step later flip it into a real feature epic. Never auto-advances.",,{feature: <name>} {repos: [<repo>]} {description: one-line},1-front,,yad-change,false,epics/EP-<slug>/,epic.md state.json approvals.json comments.json
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yad-backfill
3
- description: 'Build-half Step G of the gated SDLC — backfill: generate specs for already-built features in an existing repo so new work does not break them. Confirm Repomix (the one true CLI subprocess: npx repomix), pack ONE feature at a time (compress + git logs, secret-scan), feed it to AI with a "describe what exists, do not invent" prompt, and write a DRAFT spec marked unverified. Require human approval (reuse yad-review-gate) before the spec counts as real. Boundary is auto-proposed from the project convention and human-confirmed. A change is blocked only until the features IT touches have approved specs. Use when the user says "backfill specs", "document an existing feature", or "spec the legacy code".'
3
+ description: 'Build-half Step G of the gated SDLC — backfill: generate specs for already-built features in an existing repo so new work does not break them. Confirm Repomix (the one true CLI subprocess: npx repomix), pack ONE feature at a time (compress + git logs, secret-scan), feed it to AI with a "describe what exists, do not invent" prompt, and write a DRAFT spec marked unverified. Require human approval (reuse yad-review-gate) before the spec counts as real. Boundary is auto-proposed from the project convention and human-confirmed. A change is blocked only until the features IT touches have approved specs. The `promote` action flips a brownfield stub epic (minted by yad-stub, so defects could thread off it) to a real, verified feature epic once its backfill spec is approved. Use when the user says "backfill specs", "document an existing feature", "spec the legacy code", or "promote the stub epic".'
4
4
  ---
5
5
 
6
6
  # SDLC — Backfill (existing-code specs)
@@ -26,7 +26,9 @@ have approved specs — never the whole repo at once.
26
26
 
27
27
  - `repo` — the existing code repo to backfill.
28
28
  - `feature` — the feature name (and its file globs, e.g. `src/<feature>/**`).
29
- - `action` — `pack` | `draft` | `approve` | `gate` (default `pack`).
29
+ - `action` — `pack` | `draft` | `approve` | `gate` | `promote` (default `pack`).
30
+ - `epic` — `promote` only: the stub epic `EP-<slug>` (minted by `yad-stub`) to flip to real once the
31
+ feature's backfill spec is approved.
30
32
 
31
33
  ## On Activation
32
34
 
@@ -74,9 +76,33 @@ that feature's spec is `verified: true`. It is **per touched feature** — a cha
74
76
  not blocked by an unverified feature B. Forward-spec'd features (those with their own `specs/<story>/`)
75
77
  are not this gate's concern.
76
78
 
77
- ### Step 6 — Stop (no auto-advance)
78
- Report the packed feature, the draft path (or the approval), and what is still unverified. Nothing
79
- auto-advances; a human owns the approval.
79
+ ### Step 6 — `promote` (flip a stub epic → real, once its spec is approved)
80
+ When a brownfield feature was anchored with a **stub genesis epic** (`yad-stub`) so that defects could
81
+ thread off it, `promote` is what makes that anchor real — run it once the feature's backfill spec is
82
+ `verified: true`. Given `epic EP-<slug>` (a stub: `stub: backfill-pending` / `verified: false`):
83
+ - **Verify readiness:** the matching `specs/backfill/<feature>/spec.md` must exist and be
84
+ `verified: true`. If not, STOP — approve the spec first (Step 4).
85
+ - **Clear the stub markers ATOMICALLY.** A stub is marked in two files at once — `epic.md`
86
+ (`stub: backfill-pending`) **and** `state.json` (top-level `kind: "stub"` + `currentStep:
87
+ "backfill-pending"`). Promote must clear **all** of them together, or `yad next` (reads `state.json`)
88
+ and `yad thread` / `yad-status` (read `epic.md`) will disagree about whether the epic is still a stub.
89
+ - **`epic.md`:** set `verified: true`, **remove** the `stub:` marker, and add a `backfill:` block linking
90
+ the approved spec, e.g. `backfill: { spec: specs/backfill/<feature>/spec.md, promoted: <YYYY-MM-DD> }`.
91
+ - **`state.json`:** **remove** the top-level `kind: "stub"`, and move `currentStep` off the
92
+ `backfill-pending` sentinel (per the promote flavour below).
93
+ - **Light promote (default):** the feature's documentation lives in the approved backfill spec — do NOT
94
+ wake the front chain. Set `state.json` `currentStep: "backfill-done"` (a terminal sentinel, like
95
+ `discovery-done`): the epic is now a real, verified anchor and its later evolution threads normally with
96
+ `yad-change`. `yad next` then reports it as a documented anchor, not a pending stub.
97
+ - **Full promote (opt-in):** to bring the feature fully under the front half and lock a real contract,
98
+ "wake" the state chain — set `currentStep: "epic"`, `epic` step `status: "in_progress"` — then run
99
+ `yad-epic` → `yad-architecture` → … the normal way. This re-locks a contract that subsequent thread
100
+ changes will inherit.
101
+ - Never auto-advances; a human confirms the promotion.
102
+
103
+ ### Step 7 — Stop (no auto-advance)
104
+ Report the packed feature, the draft path (or the approval / promotion), and what is still unverified.
105
+ Nothing auto-advances; a human owns the approval.
80
106
 
81
107
  ## Hard rules (build plan §G, Cross-cutting)
82
108
 
@@ -88,4 +114,6 @@ auto-advances; a human owns the approval.
88
114
  ## Reference
89
115
  - The "describe what exists" prompt, the spec shape, and the gate: `references/backfill.md`.
90
116
  - The human approval discipline reused: `../yad-review-gate/SKILL.md`.
117
+ - Minting the stub epic that `promote` later flips to real (brownfield thread anchor):
118
+ `../yad-stub/SKILL.md`.
91
119
  - Repomix flags: `RESEARCH-NOTES.md` §3.
@@ -54,6 +54,13 @@ lineage is broken (a cycle, or a `thread` cache ≠ the computed root) — fix t
54
54
  parent is a **genesis epic not yet migrated** (no `kind:`), migrate it now: add `kind: feature` and
55
55
  `thread: <its own id>` to its `epic.md` (a one-line, non-gated frontmatter add).
56
56
 
57
+ **Missing parent (brownfield) — never silent.** If the requested `parent` does **not exist at all**
58
+ because the feature was built before it had an epic, do NOT dead-end: point the user at **`yad-stub`** to
59
+ mint a stub genesis epic (a minimal `kind: feature` thread anchor) for that feature first, then re-run
60
+ this skill with that stub as the `parent`. A change MUST still thread to a real parent — `yad-stub` just
61
+ creates the smallest real one so the defect can be captured now (the `yad-reconcile` → anchor → change
62
+ discipline).
63
+
57
64
  The new epic's `thread` = the parent's thread (the genesis id). Its `parent` = the given `parent` (the
58
65
  immediate predecessor — usually the current tip; if the parent is not the tip, see "concurrent changes"
59
66
  in `references/triage.md`).
@@ -132,11 +139,25 @@ When `architecture` is **inherited**, materialize the **pointer-lock** `.sdlc/co
132
139
  There is no `contract.md` in the change-epic, so the surface cannot drift, and `contract-check` passes
133
140
  unchanged because the hash is identical. (Exact recipe + field shapes: `references/triage.md`.)
134
141
 
142
+ **Stub parent (brownfield, no locked surface yet).** When the `parent` is a **stub**
143
+ (`stub: backfill-pending` / `verified: false`, minted by `yad-stub`), it has no `architecture.md` /
144
+ `contract.md` / `ui-design.md` to inherit — the surface has not been documented yet. So for the bases the
145
+ stub lacks, mark the inherited steps `"inherited": true, "inheritedFrom": "<stub>", "boundHash": null`
146
+ (a `null` boundHash is treated as "nothing locked upstream → no drift" by the gate predicate, so the step
147
+ passes and never blocks), and write **NO** `contract-lock.json` (there is no surface to point at — the
148
+ child never touches `specs/*/contracts/**`, so `contract-check` passes trivially). Contract protection on
149
+ this thread begins only after `yad-backfill promote` documents and locks the feature. Record
150
+ `"parentStub": true` in `change.json` so it is auditable that the change threaded off an undocumented
151
+ stub. Prefer the `defect-fix` / `behavioral-no-surface` depth against a stub — a `contract-surface`
152
+ change against an undocumented feature should wait until the stub is promoted (backfilled + a real
153
+ contract locked).
154
+
135
155
  ### Step 6 — Write `.sdlc/change.json` (intake + triage record)
136
156
  Record the intake: `epicId`, `thread`, `parent`, `kind`, `depth`, `intakeBy`, `intakeDate`, `title`,
137
157
  `description`, `affectedArtifacts`, `reauthors`, `inherits`, and for a defect/hotfix the `defect` block
138
158
  (`origin`, `severity`, `escape_stage`, `root_cause`). This is what `yad-defects` reads to attribute the
139
- defect to the gate that should have caught it.
159
+ defect to the gate that should have caught it. Add `"parentStub": true` when the parent is an
160
+ un-promoted stub (Step 5) — omit it (or `false`) otherwise.
140
161
 
141
162
  ### Step 7 — Hotfix only: record the ship-first exception + open reconcile debt
142
163
  If `kind: hotfix`, the build half MAY run before these front gates approve (severity demands it). Record
@@ -165,6 +186,7 @@ Report: the new `EP-<slug>`, its thread + parent, the re-author-vs-inherit split
165
186
  - **Never auto-advances.** This skill seeds + records; humans author and approve via the normal gates.
166
187
 
167
188
  ## Reference
189
+ - Minting a stub genesis epic when the feature has no epic (brownfield): `../yad-stub/SKILL.md`.
168
190
  - Depth triage details, the exact seeding shape, the pointer-lock recipe, genesis migration, and the
169
191
  concurrent-change (re-parent) rule: `references/triage.md`.
170
192
  - The lineage frontmatter + ledger schemas: `../yad-epic/references/state-schema.md` (Phase 6).
@@ -263,6 +263,47 @@ locked `state.json` step shape. Genesis epics are backfilled once with `kind: fe
263
263
  | `severity` | `sev1` … `sev4` | **defect/hotfix only.** |
264
264
  | `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. |
265
265
  | `root_cause` | short tag | **defect/hotfix only.** e.g. `missing-negative-test`. |
266
+ | `stub` | `backfill-pending` | **stub genesis only** (`yad-stub`). A brownfield feature anchored so a change can thread off it before it is documented. Cleared by `yad-backfill promote`. |
267
+ | `verified` | `true` \| `false` | `false` on a stub genesis (not yet documented/human-authored); `yad-backfill promote` sets it `true`. Absent ⇒ treated as a normal (verified) epic. |
268
+
269
+ ## Stub genesis epics (brownfield anchors — `yad-stub`)
270
+
271
+ In a brownfield repo not every already-built feature has an epic, so a defect/change has no parent to
272
+ thread from (`yad-change` requires one; `lineage-check` rejects a missing parent). `yad-stub` mints the
273
+ smallest **real** node — a **stub genesis epic** — so the bug can be captured now and formalized later.
274
+
275
+ A stub is a normal genesis (`kind: feature`, `thread == id`, no `parent`) whose `epic.md` carries
276
+ `stub: backfill-pending` + `verified: false` and whose `state.json` uses a **sentinel**, mirroring
277
+ `EP-discovery` / `discovery-done`:
278
+ - top-level `kind: "stub"` and `currentStep: "backfill-pending"`;
279
+ - the **same 10-step front chain** as a normal epic, every step `status: "blocked"` (so `validateState`
280
+ passes and `promote` can "wake" the chain into normal authoring with no re-seed);
281
+ - empty `approvals.json` / `comments.json`; **no** `contract-lock.json` (no surface locked yet).
282
+
283
+ `nextAction` routes a stub to a `backfill-pending` action (`yad-backfill` → `yad-backfill promote`), never
284
+ to authoring. A change threaded off a stub inherits only what exists — the undocumented surface bases are
285
+ marked `inherited: true` with `boundHash: null` (the gate predicate reads `null` as "nothing locked → no
286
+ drift → pass"), no pointer-lock is written, and `change.json` records `parentStub: true`.
287
+
288
+ **The stub invariant (two files, kept in lockstep).** A stub is encoded in *both* `epic.md`
289
+ (`stub: backfill-pending`) *and* `state.json` (top-level `kind: "stub"` + `currentStep:
290
+ "backfill-pending"`), because two readers use different sources: `isStubEpic` / `yad thread` / `yad-status`
291
+ read the frontmatter, while `nextAction` / `yad next` is pure-ledger and reads `state.json`. So:
292
+
293
+ > **A stub ⟺ `epic.md stub:backfill-pending` AND `state.kind:stub` AND `currentStep:backfill-pending`.**
294
+ > Any promote MUST clear all three atomically, or the two readers disagree.
295
+
296
+ `yad-backfill promote` enforces this: it sets `epic.md` `verified: true`, removes `stub:`, links the
297
+ approved backfill spec, **and** rewrites `state.json` — removing `kind: "stub"` and moving `currentStep`
298
+ off the sentinel:
299
+ - **light promote (default)** → `currentStep: "backfill-done"`, a **terminal sentinel** (like
300
+ `discovery-done`): the feature is a real, verified anchor documented by its backfill spec; `nextAction`
301
+ reports "documented anchor — evolve it by threading a change/defect", never a pending stub, and no build
302
+ half runs directly against it;
303
+ - **full promote (opt-in)** → `currentStep: "epic"`, `epic.status: "in_progress"`, to run the normal front
304
+ half and lock a real contract.
305
+
306
+ From promotion on, the thread's contract protection is live.
266
307
 
267
308
  ## Inherited steps in `state.json`
268
309
 
@@ -324,7 +365,9 @@ Intake + triage record, one per change/defect/hotfix epic (sibling of `approvals
324
365
 
325
366
  `depth` ∈ `defect-fix | behavioral-no-surface | contract-surface | new-capability` (`config.yaml`
326
367
  `change.depths`). `defect` is `null` for a plain `change`; `hotfix` is `{ "shipFirst": true }` only for
327
- a `hotfix`. Thread-level rollups (`yad-timeline` / `yad-defects`) are **derived** walk every epic
368
+ a `hotfix`. `parentStub: true` is added when the epic threads off an un-promoted stub genesis
369
+ (`yad-stub`) — a brownfield feature not yet documented, so no contract surface is inherited yet.
370
+ Thread-level rollups (`yad-timeline` / `yad-defects`) are **derived** — walk every epic
328
371
  sharing `thread` and read each `change.json`; there is no duplicated thread registry.
329
372
 
330
373
  ## `reconcile-debt.json`
@@ -49,6 +49,12 @@ shipped, and pay any open debt (update the artifacts + add a regression test, th
49
49
  `reconcile-debt.json` entry `status: paid`). It never seeds the epic itself — opening a change-epic is a
50
50
  human, triaged act (`yad-change` Step 2).
51
51
 
52
+ **Orphan code with no epic at all (brownfield):** when the drift is shipped code that belongs to a
53
+ feature with **no owning epic in any thread**, there is nothing to thread a change off yet. Point the
54
+ human at **`yad-stub`** first — mint a stub genesis epic (a minimal `kind: feature` thread anchor) for
55
+ that feature — then thread the reconcile change off that stub (and run `yad-backfill` +
56
+ `yad-backfill promote` to make the anchor real). The reconciler never mints the stub itself.
57
+
52
58
  ### Step 3 — `wire` (advisory CI, no block)
53
59
  Commit an advisory CI job that runs `yad reconcile --check` on push, carrying `[skip ci]` on any commit
54
60
  it makes and a concurrency group — the same loop-prevention `yad-docs-sync` uses. The job **reports**;
@@ -72,4 +78,5 @@ the reconciler is advisory.
72
78
  - The drift/refresh/wire discipline this mirrors: `../yad-docs-sync/SKILL.md`.
73
79
  - The thread model + ledgers: `../yad-epic/references/state-schema.md` (Phase 6).
74
80
  - The change-epic this hands off to: `../yad-change/SKILL.md`.
81
+ - Anchoring brownfield code that has no epic (before a change can thread off it): `../yad-stub/SKILL.md`.
75
82
  - The gates that block at merge: `../yad-checks/` (lineage-check, epic-open, reconcile-debt).
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: yad-stub
3
+ description: 'Phase 6 brownfield helper — mint a STUB genesis epic for an already-built feature that has no epic in the hub, so a defect/change can thread off it TODAY. In a brownfield repo not every feature has an epic; yad-change requires a real parent and dead-ends without one. This skill creates the smallest real thread anchor — a tiny epic.md (kind:feature, thread:self, verified:false, stub:backfill-pending) + a seeded state.json + empty ledgers — never inventing behaviour. Defects thread off it immediately (gates pass, the bug list is derived by the thread rollup), and yad-backfill + its promote step later fill/flip it into a real feature epic. Never auto-advances. Use when the user says "there is no epic for this feature", "file a bug on a legacy feature", "anchor a brownfield feature", or when yad-change / yad-reconcile point here because a parent epic is missing.'
4
+ ---
5
+
6
+ # SDLC — Stub Genesis Epic (Phase 6, the brownfield thread anchor)
7
+
8
+ **Goal:** Give an **already-built feature that has no epic** the smallest node that is still *real* — a
9
+ **stub genesis epic** — so a defect or change can thread off it right now (`yad-change` needs a real
10
+ parent), the gates pass, and the list of linked bugs is **derived for free** by the thread rollup. The
11
+ stub is a **thread anchor, not a spec**: it invents no behaviour. It stays `verified: false` until
12
+ `yad-backfill` documents the feature and its `promote` step flips the stub into a real feature epic.
13
+
14
+ This is the missing connective tissue in a brownfield adoption: `yad-backfill` produces a *draft spec in
15
+ the code repo*, and `yad-reconcile` *detects* shipped code with no owning epic — but neither mints the
16
+ hub epic a defect must thread from. `yad-stub` does exactly that, and only that.
17
+
18
+ ## Conventions
19
+
20
+ - `{project-root}` resolves from the product hub. Epic artifacts live under
21
+ `{project-root}/epics/EP-<slug>/` (build plan §6), exactly like a normal genesis epic.
22
+ - **A stub is NOT a reserved-empty id.** An epic in yad *is* a directory + `epic.md`; the tooling skips
23
+ any epic dir lacking `epic.md` (`threadEpics`) and `lineage-check` rejects a parent that is not a real
24
+ `epic.md`. So the stub is a real (if minimal) genesis — that is what makes it a valid parent.
25
+ - The stub is a **genesis** (`kind: feature`, `thread == id`, no `parent`) — the root of a new thread.
26
+ Lineage frontmatter, the sentinel state, and the `stub`/`verified` fields are defined in
27
+ `../yad-epic/references/state-schema.md` (Phase 6 section).
28
+ - Speak in the configured `communication_language`; write documents in `document_output_language`.
29
+
30
+ ## Inputs
31
+
32
+ - `feature` — **required.** The already-built feature's name (→ slugged into `EP-<slug>`).
33
+ - `repos` — the code repo(s) this feature lives in (for the backfill boundary later).
34
+ - `description` — one line: what the feature is (as built). **Do not** describe design or invent
35
+ behaviour — a stub records that the feature exists, nothing more.
36
+
37
+ ## On Activation
38
+
39
+ ### Step 1 — Confirm there really is no epic (auto-propose, human-confirm)
40
+ Check `{project-root}/epics/` for an existing epic that already owns this feature (a genesis or any
41
+ thread). **If one exists, STOP** and point at `yad-change` (`--parent <that epic>`) — a stub is only for
42
+ a feature with *no* epic at all. If the code is already forward-spec'd or has a `specs/<story>/`, it is
43
+ not a stub case either. Confirm the brownfield-no-epic situation with the human before creating anything.
44
+
45
+ ### Step 2 — Derive the stub epic id (engine-assigned, never by hand)
46
+ Derive `EP-<slug>` where `slug` is **2–4 lowercase words** from the feature name (e.g.
47
+ `EP-legacy-billing`). `EP-discovery` is **reserved** — never use it. Check `epics/` for collisions;
48
+ append a distinguishing word if the slug exists. **The id is assigned once and never renamed** (a rename
49
+ breaks every downstream link) — so pick from the best-known feature name and accept it as permanent.
50
+
51
+ ### Step 3 — Open the authoring branch
52
+ Open `epic/EP-<slug>` per the shared "Authoring branches" procedure
53
+ (`../yad-epic/references/state-schema.md`): git-safe (skip with a note if `{project-root}` is not a git
54
+ work tree), check out if it exists, else create from the hub's default branch.
55
+
56
+ ### Step 4 — Write the stub `epic.md` (thread anchor — never invent behaviour)
57
+ Write `{project-root}/epics/EP-<slug>/epic.md` using EXACTLY this shape:
58
+
59
+ ```markdown
60
+ ---
61
+ id: EP-<slug>
62
+ status: draft
63
+ kind: feature # genesis / thread root — a valid parent for lineage-check + threads
64
+ thread: EP-<slug> # thread == id for a genesis
65
+ verified: false # not a real, human-authored epic yet — a stub awaiting backfill
66
+ stub: backfill-pending # the honest marker; cleared on promote
67
+ origin: brownfield
68
+ owner:
69
+ repos: [<the code repos this feature lives in>]
70
+ created: <YYYY-MM-DD>
71
+ ---
72
+
73
+ ## Feature (stub)
74
+ <!-- one line: what this already-built feature IS. No design, no invented behaviour. -->
75
+
76
+ ## Known issues
77
+ <!-- Do not hand-maintain a list. `yad thread EP-<slug>` derives every defect/change threaded off this
78
+ stub from lineage — it is always current. -->
79
+
80
+ ## Backfill
81
+ <!-- To make this real: run `yad-backfill` for <repo> (globs for this feature), get the spec approved
82
+ (verified: true), then `yad-backfill promote EP-<slug>`. -->
83
+ ```
84
+
85
+ Leave `owner` for the human to set. Set `repos` to the code repo(s) the feature lives in.
86
+
87
+ ### Step 5 — Seed the stub `state.json` (a `backfill-pending` sentinel)
88
+ Create `{project-root}/epics/EP-<slug>/.sdlc/state.json`. It carries the top-level marker
89
+ `kind: "stub"` and the sentinel `currentStep: "backfill-pending"`, and the **same 10-step front chain**
90
+ as a normal epic (`yad-epic` Step 5) but with **every step `status: "blocked"`** — so the state is valid
91
+ (`validateState` needs a non-empty `steps` + a string `currentStep`) and `promote` can later "wake" it
92
+ into normal authoring with zero re-seeding.
93
+
94
+ ```json
95
+ {
96
+ "epicId": "EP-<slug>",
97
+ "createdAt": "<YYYY-MM-DD>",
98
+ "kind": "stub",
99
+ "currentStep": "backfill-pending",
100
+ "steps": [
101
+ { "id": "epic", "type": "author", "artifact": "epic.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
102
+ { "id": "epic-review", "type": "review+approve", "artifact": "epic.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
103
+ { "id": "architecture", "type": "author", "artifact": "architecture.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
104
+ { "id": "architecture-review","type": "review+approve", "artifact": "architecture.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": ["contract"] },
105
+ { "id": "ui-design", "type": "author", "artifact": "ui-design.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
106
+ { "id": "ui-design-review", "type": "review+approve", "artifact": "ui-design.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
107
+ { "id": "stories", "type": "author", "artifact": "stories/", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
108
+ { "id": "stories-review", "type": "review+approve", "artifact": "stories/", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
109
+ { "id": "test-cases", "type": "author", "artifact": "test-cases.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] },
110
+ { "id": "test-cases-review", "type": "review+approve", "artifact": "test-cases.md", "assistance": "review", "automation": "human_approve", "locked": true, "status": "blocked", "risk_tags": [] }
111
+ ]
112
+ }
113
+ ```
114
+
115
+ Also create the empty ledgers `{.sdlc/approvals.json}` and `{.sdlc/comments.json}` (each `[]`) and the
116
+ `reviews/` directory. **Do NOT** write a `contract-lock.json` — a stub has no locked surface yet.
117
+
118
+ ### Step 6 — Stop; hand off (NO auto-advance)
119
+ Report the new `EP-<slug>`, that it is a **stub (backfill pending)**, and the two next moves:
120
+ - **File bugs now:** `yad-change` (`--parent EP-<slug>`, `kind: defect|change`) — the defect threads off
121
+ the stub, its gates pass, and `yad thread EP-<slug>` lists it. A defect off a stub inherits only what
122
+ exists (the stub `epic.md`), re-authors its own stories/test-cases, and locks no contract (there is no
123
+ surface yet — see `../yad-change/SKILL.md`).
124
+ - **Make it real later:** `yad-backfill` for the code repo, then `yad-backfill promote EP-<slug>` to flip
125
+ the stub to `verified: true`.
126
+
127
+ Front states do not auto-advance. Suggest `yad next EP-<slug>` (prints the backfill-pending guidance) and
128
+ `yad thread EP-<slug>` to see the anchor and everything threaded off it.
129
+
130
+ ## Hard rules
131
+
132
+ - **A stub is an anchor, not a spec.** Never invent behaviour in `epic.md`. It records only that the
133
+ feature exists so a change can thread off it.
134
+ - **Only for a feature with NO epic.** If any epic already owns the feature, use `yad-change` instead.
135
+ - **Never `verified: true` here.** A stub is `verified: false` / `stub: backfill-pending` until
136
+ `yad-backfill promote` flips it — approval is earned by documenting the real code, not by minting.
137
+ - **You never implement directly against a stub.** It owns no `stories/`, so there is nothing to
138
+ implement against directly — real work threads off it as a change/defect epic (which has its own
139
+ stories). (This is a convention of the empty stub, not a hard gate: `lineage-check` passes a
140
+ `kind: feature` genesis and `epic-open` treats a story-less epic as un-sealed, so neither blocks a
141
+ story mistakenly linked to the stub — keep the stub story-less.)
142
+ - **Never auto-advances.** This skill seeds the anchor and stops; humans thread changes and run backfill.
143
+
144
+ ## Reference
145
+ - The lineage frontmatter, the `stub`/`verified` fields, and the `kind: "stub"` /
146
+ `currentStep: "backfill-pending"` sentinel: `../yad-epic/references/state-schema.md` (Phase 6).
147
+ - Threading a defect/change off the stub: `../yad-change/SKILL.md`.
148
+ - Documenting the code + promoting the stub to real: `../yad-backfill/SKILL.md`.
149
+ - Detecting brownfield code with no owning epic: `../yad-reconcile/SKILL.md`.