yadflow 3.16.1 → 3.16.2
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 +9 -0
- package/cli/checkpoint.mjs +41 -11
- package/package.json +1 -1
- package/skills/sdlc/config.yaml +6 -1
- package/skills/sdlc/module-help.csv +2 -2
- package/skills/yad-defects/SKILL.md +11 -3
- package/skills/yad-docs-overview/references/pipeline-model.md +6 -1
- package/skills/yad-engineer-review/references/ship-and-record.md +13 -5
- package/skills/yad-epic/references/state-schema.md +10 -4
- package/skills/yad-reconcile/SKILL.md +9 -1
- package/skills/yad-timeline/SKILL.md +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [3.16.2](https://github.com/abdelrahmannasr/yadflow/compare/v3.16.1...v3.16.2) (2026-08-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **checkpoint:** make a --retro-ship dry run honest and side-effect free ([b03704f](https://github.com/abdelrahmannasr/yadflow/commit/b03704fb0564b1510dca121a24853758431f9374)), closes [112/#142](https://github.com/abdelrahmannasr/yadflow/issues/142) [#167](https://github.com/abdelrahmannasr/yadflow/issues/167)
|
|
7
|
+
* **checkpoint:** name the shard path and the fold step after --retro-ship ([64c33b3](https://github.com/abdelrahmannasr/yadflow/commit/64c33b37171ee1cebf540b7a66c35ee56cdc61a7)), closes [#167](https://github.com/abdelrahmannasr/yadflow/issues/167) [#167](https://github.com/abdelrahmannasr/yadflow/issues/167)
|
|
8
|
+
* **skills:** read build-log as the folded + shard union ([4302de2](https://github.com/abdelrahmannasr/yadflow/commit/4302de2e5f569989e8fc51c0a165aaae5abe1625)), closes [#167](https://github.com/abdelrahmannasr/yadflow/issues/167)
|
|
9
|
+
|
|
1
10
|
## [3.16.1](https://github.com/abdelrahmannasr/yadflow/compare/v3.16.0...v3.16.1) (2026-08-12)
|
|
2
11
|
|
|
3
12
|
|
package/cli/checkpoint.mjs
CHANGED
|
@@ -191,7 +191,7 @@ export function retroShipRepos(root, storyFile) {
|
|
|
191
191
|
return { names, source: names.length ? 'registry' : 'none' };
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
export function recordRetroShip(root, { epic, story, repo, task, mergeCommit, today }) {
|
|
194
|
+
export function recordRetroShip(root, { epic, story, repo, task, mergeCommit, today, dryRun = false }) {
|
|
195
195
|
if (!epic || !story) { fail('--retro-ship needs <epic>/<story> (e.g. --retro-ship EP-foo/EP-foo-S01)'); return { ok: false }; }
|
|
196
196
|
if (!repo) { fail('--retro-ship needs --repo <name> (the repo the story shipped in)'); return { ok: false }; }
|
|
197
197
|
const epicDir = path.join(root, 'epics', epic);
|
|
@@ -203,9 +203,10 @@ export function recordRetroShip(root, { epic, story, repo, task, mergeCommit, to
|
|
|
203
203
|
// Evidence and the flip must land TOGETHER — the #112 no-drift invariant. Refuse unless the human has
|
|
204
204
|
// already flipped the story frontmatter to a back-half status in the working tree; otherwise the ship
|
|
205
205
|
// shard would commit while the artifact still says e.g. `approved` — the very drift #112 prevents.
|
|
206
|
-
|
|
206
|
+
const storyStatus = readFrontmatter(storyFile).status;
|
|
207
|
+
if (!BACK_HALF_STATUSES.has(storyStatus)) {
|
|
207
208
|
fail(`${story} frontmatter is not at in-build|shipped`);
|
|
208
|
-
hand(`set \`status: shipped\` in ${storyRel} first, then re-run — the ship and the flip land in one commit`);
|
|
209
|
+
hand(`set \`status: in-build\` or \`status: shipped\` in ${storyRel} first, then re-run — the ship and the flip land in one commit`);
|
|
209
210
|
return { ok: false };
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -238,7 +239,12 @@ export function recordRetroShip(root, { epic, story, repo, task, mergeCommit, to
|
|
|
238
239
|
if (res.reason === 'collision') {
|
|
239
240
|
// Distinct names, ONE shard file (`buildShardName` sanitizes each component) — recording this one
|
|
240
241
|
// would overwrite the other repo's ship record, so it is refused rather than silently clobbered.
|
|
241
|
-
|
|
242
|
+
// `writeRetroShip` reports a clash EITHER as `repo` (a ship readShips can see) OR as `file` (a shard
|
|
243
|
+
// on disk it cannot parse) — and a malformed shard can carry a blank `repo`, satisfying neither. Name
|
|
244
|
+
// whichever it gave us; never index into the one it did not, or the refusal becomes a stack trace.
|
|
245
|
+
const clashedWith = res.repo ? `the already-recorded ${res.repo}`
|
|
246
|
+
: res.file ? `an existing shard (${path.basename(res.file)})` : 'an existing ship record';
|
|
247
|
+
fail(`${repo} cannot be recorded: it shares a build-log shard name with ${clashedWith}`);
|
|
242
248
|
hand('recording it would overwrite that ship record — rename one of the repos in the registry, or record this ship through the normal ship/checkpoint flow');
|
|
243
249
|
return { ok: false };
|
|
244
250
|
}
|
|
@@ -249,11 +255,26 @@ export function recordRetroShip(root, { epic, story, repo, task, mergeCommit, to
|
|
|
249
255
|
if (left.length) hand(`still unrecorded for ${story}: ${left.join(', ')} — re-run with \`--repo <name>\` for each`);
|
|
250
256
|
return { ok: false };
|
|
251
257
|
}
|
|
252
|
-
|
|
258
|
+
// A dry run WROTE this shard only so the flip could be previewed; it is rolled back before the command
|
|
259
|
+
// returns. Every line below must therefore speak in the conditional — a past-tense "recorded" would tell
|
|
260
|
+
// the operator the backfill landed, they would never re-run for real, and the story would keep
|
|
261
|
+
// `status: shipped` with no ship behind it: exactly the #112/#142 drift this command exists to remove.
|
|
262
|
+
ok(`${dryRun ? 'would record' : 'recorded'} retroactive ship for ${story} (${repo})${mergeCommit ? ` @ ${mergeCommit}` : ''}`);
|
|
263
|
+
// Name WHERE the record landed. The ledger is shard-then-fold, so this ship is one loose shard and the
|
|
264
|
+
// folded `build-log.json` is untouched until `yad tidy up` runs — an operator who opens build-log.json,
|
|
265
|
+
// finds nothing, and concludes the write was lost is reading half the ledger (#167). Every reader unions
|
|
266
|
+
// the two; say so here, while they are looking. `tidy up` only folds a story whose frontmatter is
|
|
267
|
+
// `shipped`, so an `in-build` story's shard stays loose (and still readable) by design — mention the
|
|
268
|
+
// `shipped` precondition only when it is actually still outstanding.
|
|
269
|
+
const rel = path.relative(root, res.file).split(path.sep).join('/');
|
|
270
|
+
info(`${dryRun ? 'would land at' : 'landed at'} ${rel}`);
|
|
271
|
+
info(`readers union build-log/ shards with the folded build-log.json; \`yad tidy up\` folds it in${storyStatus === 'shipped' ? '' : ` once ${story} is \`shipped\``}`);
|
|
253
272
|
// A multi-repo story is only half-reconciled until every declared repo has evidence, and nothing
|
|
254
273
|
// downstream reports the gap (the story already reads `shipped`) — so say it here, while the
|
|
255
|
-
// operator is running the backfill.
|
|
256
|
-
|
|
274
|
+
// operator is running the backfill. `left` excludes the repo just recorded; in a dry run that record is
|
|
275
|
+
// rolled back, so it is still unrecorded and belongs back in the list.
|
|
276
|
+
const stillLeft = dryRun && left.length ? [repo, ...left] : left;
|
|
277
|
+
if (stillLeft.length) hand(`${story} declares ${names.length} repos — still unrecorded: ${stillLeft.join(', ')}; re-run with \`--repo <name>\` for each`);
|
|
257
278
|
return { ok: true, file: res.file };
|
|
258
279
|
}
|
|
259
280
|
|
|
@@ -288,22 +309,30 @@ export async function runCheckpoint(root, opts = {}) {
|
|
|
288
309
|
// already wrote is then carried by the normal storyStatusPathspecs path below — no raw git needed.
|
|
289
310
|
let retroFile;
|
|
290
311
|
if (opts.retroShip) {
|
|
291
|
-
const r = recordRetroShip(root, opts.retroShip);
|
|
312
|
+
const r = recordRetroShip(root, { ...opts.retroShip, dryRun: opts.dryRun });
|
|
292
313
|
if (!r.ok) { process.exitCode = 1; return; }
|
|
293
314
|
retroFile = r.file;
|
|
294
315
|
}
|
|
295
316
|
|
|
317
|
+
// A --retro-ship DRY RUN wrote a real shard so the flip could be previewed, and it must be undone on
|
|
318
|
+
// EVERY exit path below, not just the happy one. An early return that skipped the rollback (git add
|
|
319
|
+
// failed, nothing staged) would leave an untracked retro shard behind — which then refuses the
|
|
320
|
+
// operator's next REAL backfill ("already has a build-log ship in <repo>") and rides into the next
|
|
321
|
+
// plain `yad checkpoint` as permanent `retroactive: true` audit evidence nobody chose to record.
|
|
322
|
+
// A real run deliberately keeps its shard on a failure (see the commit-failed path below).
|
|
323
|
+
const rollbackRetro = () => { if (opts.dryRun && retroFile) cleanupRetroShard(retroFile); };
|
|
324
|
+
|
|
296
325
|
// The machine ledgers PLUS any build-log-backed story `status:` flip (#112) — one commit records
|
|
297
326
|
// both, so the story artifact never drifts from build-log and no raw git-to-main push is needed.
|
|
298
327
|
const pathspecs = [...backHalfPathspecs(root), ...storyStatusPathspecs(root)];
|
|
299
|
-
if (!pathspecs.length) { info('no back-half ledgers found — nothing to checkpoint'); return; }
|
|
328
|
+
if (!pathspecs.length) { rollbackRetro(); info('no back-half ledgers found — nothing to checkpoint'); return; }
|
|
300
329
|
|
|
301
330
|
// Stage the allowlist. `git add -- <spec>` picks up new + modified files, and deletions of tracked
|
|
302
331
|
// files WITHIN a still-present spec (e.g. a removed build-state/<story>.json). A wholesale-deleted
|
|
303
332
|
// top-level ledger is intentionally NOT staged (its spec drops out on the existence check) — an
|
|
304
333
|
// append-only audit ledger vanishing is an anomaly a human should see, not something to auto-commit.
|
|
305
334
|
const add = git('add', '--', ...pathspecs);
|
|
306
|
-
if (!add.ok) { fail(`git add failed — ${add.stderr.split('\n')[0] || add.code}`); process.exitCode = 1; return; }
|
|
335
|
+
if (!add.ok) { rollbackRetro(); fail(`git add failed — ${add.stderr.split('\n')[0] || add.code}`); process.exitCode = 1; return; }
|
|
307
336
|
|
|
308
337
|
// #112 review-bypass guard: a story is only carried when its staged change is the `status:` line
|
|
309
338
|
// ALONE. Unstage any candidate whose working tree also touched prose/other frontmatter — those
|
|
@@ -318,6 +347,7 @@ export async function runCheckpoint(root, opts = {}) {
|
|
|
318
347
|
}
|
|
319
348
|
|
|
320
349
|
if (git('diff', '--cached', '--quiet', '--', ...pathspecs).ok) {
|
|
350
|
+
rollbackRetro();
|
|
321
351
|
info('back-half state unchanged — nothing to commit');
|
|
322
352
|
return;
|
|
323
353
|
}
|
|
@@ -336,7 +366,7 @@ export async function runCheckpoint(root, opts = {}) {
|
|
|
336
366
|
git('reset', '-q', '--', ...pathspecs); // restore the index — a dry run must not leave things staged
|
|
337
367
|
// A --retro-ship dry run wrote a shard so the flip could be PREVIEWED above; undo it now so the dry
|
|
338
368
|
// run leaves no side effect on disk (git reset only unstaged it, back to untracked).
|
|
339
|
-
|
|
369
|
+
rollbackRetro();
|
|
340
370
|
info('dry run — not committed');
|
|
341
371
|
return { message };
|
|
342
372
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yadflow",
|
|
3
|
-
"version": "3.16.
|
|
3
|
+
"version": "3.16.2",
|
|
4
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",
|
package/skills/sdlc/config.yaml
CHANGED
|
@@ -139,7 +139,12 @@ build:
|
|
|
139
139
|
risk_levels: [low, medium, high] # high (or a contract/auth/payments surface) routes to domain owners (yad-review-gate escalation)
|
|
140
140
|
# Step E (yad-engineer-review) — AI review (advisory) + engineer review (the human gate) + merge.
|
|
141
141
|
ai_review: coderabbit # advisory first pass; never the authority (.coderabbit.yaml)
|
|
142
|
-
|
|
142
|
+
# Append-only ship ledger (back-half analogue of approvals.json), stored shard-then-fold: writers add one
|
|
143
|
+
# shard per ship under build_log_dir; `yad tidy up` folds a SHIPPED story's shards into build_log.
|
|
144
|
+
# READERS MUST UNION the two (dedupe by (story, task, repo); a shard wins) — build_log alone omits every
|
|
145
|
+
# unfolded ship, e.g. a `yad checkpoint --retro-ship` backfill or any ship on an `in-build` story.
|
|
146
|
+
build_log: "epics/EP-<slug>/.sdlc/build-log.json" # folded ships
|
|
147
|
+
build_log_dir: "epics/EP-<slug>/.sdlc/build-log/" # one <story>-<task>-<repo>.json shard per ship
|
|
143
148
|
story_build_states: [in-build, shipped] # in-build = some tasks shipped; shipped = all tasks in tasks.md shipped
|
|
144
149
|
# Backfill (yad-backfill) — specs for already-built features in an existing repo.
|
|
145
150
|
backfill:
|
|
@@ -33,7 +33,7 @@ SDLC Workflow,yad-docs,Author Docs Site,DS,"Generate the per-epic interactive do
|
|
|
33
33
|
SDLC Workflow,yad-docs-overview,Docs Overview Site,DO,"Generate the project SDLC-overview interactive site (docs/sdlc-site/) — every stage from setup to ship modeled as flow paths, system components, and stakeholder roles — reusing the same vendored shell, themed with yadflow's brand palette. Reads config.yaml + module-help.csv + the overview diagram as the pipeline source. Folds the hand-maintained docs/index.html report into the site as report.html (linked from the nav). Not a gate — a project-level enrichment that regenerates whenever the skill set / pipeline changes.",,{action: generate|deploy},,,,false,docs/sdlc-site/,sdlc-site/ .docs-build.json
|
|
34
34
|
SDLC Workflow,yad-docs-sync,Docs Sync,DY,"Maintenance/CI: keep the generated doc sites fresh. Detect staleness (a content hash of the approved artifacts + the connected repos' HEAD shas + the doc-shell version vs each site's build manifest), report which sites drifted and why, regenerate + redeploy the stale ones, and wire a CI job that rebuilds on push (carrying [skip ci] + a concurrency group to prevent deploy loops). Generalizes the rule that feature work must hand-update docs/index.html + diagrams + skill counts. Refresh is always a human/CI decision; never a gate.",,{action: check|refresh|wire} {epic: EP-<slug>},,,,false,epics/EP-<slug>/.sdlc/,docs-build.json yad-docs.yml
|
|
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
|
-
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
|
-
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
|
|
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 + the build ledger (folded build-log.json UNIONed with every build-log/ shard). 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
|
+
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 the build ledger (folded build-log.json UNIONed with every build-log/ shard), 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
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
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-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".'
|
|
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 the build ledger (the folded build-log.json unioned with every build-log/ shard) 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
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDLC — Quality-Gap Report (Phase 6, output enrichment)
|
|
@@ -32,10 +32,16 @@ risk), not just the symptom. It is an **output enrichment**, exactly like `yad-d
|
|
|
32
32
|
Resolve the scope (`yad thread <id> --json` for a thread). Collect, across the scoped epic(s):
|
|
33
33
|
- every `kind: defect` (and `kind: hotfix`) change-epic + its `.sdlc/change.json` `defect` block
|
|
34
34
|
(`origin`, `severity`, `escape_stage`, `root_cause`);
|
|
35
|
-
- the shipped regression fixes from each
|
|
35
|
+
- the shipped regression fixes from each epic's build ledger (the fix that closed the defect, linking
|
|
36
36
|
the change-epic → its regression story/test);
|
|
37
37
|
- open reconcile debt (a hotfix whose front truth is not yet restored).
|
|
38
38
|
|
|
39
|
+
The build ledger is **shard-then-fold**: read it as the **union** of the folded `.sdlc/build-log.json`
|
|
40
|
+
`ships` PLUS every loose `.sdlc/build-log/` shard, deduped by `(story, task, repo)` — a shard WINS over a
|
|
41
|
+
folded ship of the same key. Reading `build-log.json` alone drops every ship not yet folded by
|
|
42
|
+
`yad tidy up` (including every `yad checkpoint --retro-ship` backfill, and every ship on a story still at
|
|
43
|
+
`in-build`, which `tidy up` never folds), which would under-count the very fixes this report attributes.
|
|
44
|
+
|
|
39
45
|
### Step 2 — Attribute each defect to the gate that should have caught it
|
|
40
46
|
A defect is attributed to its **earliest** responsible SDLC stage. Use `change.json.escape_stage`
|
|
41
47
|
(human-set at intake), cross-checked against the fix's shape: a missing negative test → `test-cases`; a
|
|
@@ -73,7 +79,9 @@ Also write a plain `epics/<scope>/DEFECTS.md` mirror. On `action: deploy`, `yad
|
|
|
73
79
|
- **Degrade gracefully.** No docs target → `DEFECTS.md` only; never fail because a tool is absent.
|
|
74
80
|
|
|
75
81
|
## Reference
|
|
76
|
-
- The defect data: `.sdlc/change.json` `defect` blocks +
|
|
82
|
+
- The defect data: `.sdlc/change.json` `defect` blocks + the build ledger, read as the folded
|
|
83
|
+
`build-log.json` unioned with every `.sdlc/build-log/` shard
|
|
84
|
+
(`../yad-epic/references/state-schema.md`, Phase 6; `../yad-engineer-review/references/ship-and-record.md`).
|
|
77
85
|
- The shell + deterministic generation it reuses: `../yad-docs/SKILL.md`, `../yad-docs/references/data-mapping.md`.
|
|
78
86
|
- The companion evolution view: `../yad-timeline/SKILL.md`.
|
|
79
87
|
- The intake that records `escape_stage` + `root_cause`: `../yad-change/SKILL.md`.
|
|
@@ -69,7 +69,7 @@ Per-story, per-repo: `spec → tasks → implement → checks → engineer-revie
|
|
|
69
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`, plus `yad-update-guard` (push-on-default: re-checks any direct-to-default commit with `verified-commits · commit-message`) |
|
|
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
|
-
| `yad-engineer-review` | engineer review + ship recorded
|
|
72
|
+
| `yad-engineer-review` | engineer review + ship recorded as a `build-log/` shard |
|
|
73
73
|
| `yad-backfill` | DRAFT specs for legacy features |
|
|
74
74
|
|
|
75
75
|
### Path: Automation (the second dial + observation)
|
|
@@ -110,6 +110,11 @@ and the change-thread ledgers `change.json`, `reconcile-debt.json`, `build-log.j
|
|
|
110
110
|
the **connected code repos**; the **design / testing / learning tools**; and the **platform**
|
|
111
111
|
(GitHub/GitLab + Pages). A skill's `sideEffects` link its step to the component it writes.
|
|
112
112
|
|
|
113
|
+
`trust-log.json` and `build-log.json` are the *folded* halves of two **shard-then-fold** ledgers — each
|
|
114
|
+
also has a shard dir (`.sdlc/trust-log/`, `.sdlc/build-log/`) holding the entries `yad tidy up` has not
|
|
115
|
+
folded yet. They render as one component each, but anything READING them must union the folded file with
|
|
116
|
+
its shards (`../../yad-engineer-review/references/ship-and-record.md`).
|
|
117
|
+
|
|
113
118
|
## Roles = the lenses
|
|
114
119
|
|
|
115
120
|
The eight yadflow lenses, each to its relevant phase sections + paths:
|
|
@@ -83,8 +83,15 @@ It writes ONE minimal ship shard marked `retroactive: true` (`task` defaults to
|
|
|
83
83
|
the normal checkpoint so the story's already-made `status:` flip rides along in the **same** commit. It
|
|
84
84
|
refuses when the story already has a ship **in that repo** (then it isn't pre-tracking there — use the
|
|
85
85
|
normal flow). It does **not** author the story frontmatter — and to keep evidence and the flip atomic (the no-drift
|
|
86
|
-
invariant), it **refuses** unless you have already set `status
|
|
87
|
-
ship shard is never committed while the artifact still says `approved`.
|
|
86
|
+
invariant), it **refuses** unless you have already set a back-half `status:` (`in-build` or `shipped`) in
|
|
87
|
+
`stories/<story>.md`, so a ship shard is never committed while the artifact still says `approved`.
|
|
88
|
+
|
|
89
|
+
**Where the record lands — it is a shard, not an append to `build-log.json`.** Like every other ship, a
|
|
90
|
+
retroactive one is written to `.sdlc/build-log/` and the folded `build-log.json` is left untouched until
|
|
91
|
+
`yad tidy up` folds it. Opening `build-log.json` and finding nothing does **not** mean the write was lost:
|
|
92
|
+
read the ledger by the union rule above and the ship is there (#167). Note `tidy up` only folds a story
|
|
93
|
+
whose frontmatter is `shipped`, so a backfill recorded against an `in-build` story stays a loose shard
|
|
94
|
+
indefinitely — which is precisely why reading the folded file alone is never sufficient.
|
|
88
95
|
|
|
89
96
|
**One repo per run (#166).** A ship is recorded per `(story, task, repo)`, so a story that shipped in
|
|
90
97
|
several repos needs one retroactive shard **per repo** — the guard is keyed on `(story, repo)`, not on
|
|
@@ -138,11 +145,12 @@ The story frontmatter `status` reflects build progress:
|
|
|
138
145
|
You write this flip into `stories/<story>.md`, but **do not hand-commit it** — the next
|
|
139
146
|
`yad checkpoint --push` carries it in the same `chore(hub)` commit as the ledgers (the story now has a
|
|
140
147
|
build-log ship, so checkpoint stages it; #112). This is what keeps the story artifact from drifting
|
|
141
|
-
from
|
|
148
|
+
from the build ledger, so there is never a reason to fall back to a raw `git push origin main`.
|
|
142
149
|
|
|
143
150
|
So the chain is traceable both ways: from the epic down (`epic.md` → `stories/<story>.md` →
|
|
144
|
-
`tasks.md` →
|
|
145
|
-
trailer → story → epic).
|
|
151
|
+
`tasks.md` → the build-ledger ship → `mergeCommit`) and from a merge commit back up (its `Task:`
|
|
152
|
+
trailer → story → epic). Resolve that ship by the union rule, not from `build-log.json` alone — until
|
|
153
|
+
`yad tidy up` folds it, the ship lives only in its `build-log/` shard and the chain looks broken.
|
|
146
154
|
|
|
147
155
|
## Preconditions for ship (all required)
|
|
148
156
|
|
|
@@ -325,11 +325,17 @@ storage layout is noted here (it mirrors `trust-log.json`):
|
|
|
325
325
|
ship object. `(story, task, repo)` is already a natural unique key, so no `uid` is needed.
|
|
326
326
|
- **Folded file:** `epics/<epic>/.sdlc/build-log.json` = `{ "epic": "<id>", "ships": [ <ship>, … ] }`
|
|
327
327
|
(also the legacy single-file layout, and the output of `yad tidy up`).
|
|
328
|
-
- **Union-read rule
|
|
329
|
-
`(story, task, repo)`** — a shard WINS over a stale
|
|
330
|
-
ship's shard is authoritative until it is folded).
|
|
328
|
+
- **Union-read rule (binding on every reader — never read the folded file alone):** union the folded
|
|
329
|
+
`ships` with every `build-log/` shard, **deduping by `(story, task, repo)`** — a shard WINS over a stale
|
|
330
|
+
folded ship (so a `yad review reconcile` edit to a ship's shard is authoritative until it is folded).
|
|
331
|
+
`build-log.json` on its own is only the *folded* half of the ledger: it omits every ship `yad tidy up`
|
|
332
|
+
has not folded, including every `yad checkpoint --retro-ship` backfill, and — because `tidy up` folds
|
|
333
|
+
only a story whose frontmatter is `shipped` — every ship on a story still at `in-build`, indefinitely.
|
|
334
|
+
A reader that skips the union silently under-reports what shipped (#167).
|
|
331
335
|
- `yad checkpoint` commits the shard dir; `yad tidy up` folds a shipped story's finished shards into the
|
|
332
|
-
folded file (loose objects + `git gc`).
|
|
336
|
+
folded file (loose objects + `git gc`). No **ship** path ever appends to the folded file — that is what
|
|
337
|
+
keeps concurrent shippers conflict-free. (`yad review reconcile` does write it, but only to stamp a ship
|
|
338
|
+
already folded there; see `updateShip`.)
|
|
333
339
|
|
|
334
340
|
---
|
|
335
341
|
|
|
@@ -34,7 +34,7 @@ Run `yad reconcile check` (optionally `--thread EP-<genesis>`). For each thread
|
|
|
34
34
|
`yad check` drift style, any of:
|
|
35
35
|
- **Broken lineage** — a change-epic whose `parent` is missing, a cycle, or a `thread` cache that
|
|
36
36
|
disagrees with the computed root (the same signal `yad doctor` reports).
|
|
37
|
-
- **Orphan / drift** — code shipped (
|
|
37
|
+
- **Orphan / drift** — code shipped (the build ledger) or a touched repo's HEAD advanced past its
|
|
38
38
|
`repos.json` `syncedHead` with **no owning change-epic** in any thread — i.e. behaviour reached
|
|
39
39
|
production that no epic in the thread describes. Name the repo (`<repo>: <old>→<new>`).
|
|
40
40
|
- **Open hotfix debt** — a `reconcile-debt.json` entry still `open`; the next normal change on that
|
|
@@ -42,6 +42,14 @@ Run `yad reconcile check` (optionally `--thread EP-<genesis>`). For each thread
|
|
|
42
42
|
|
|
43
43
|
Writes nothing. This is the read-only sweep a human (or CI) runs to see the picture.
|
|
44
44
|
|
|
45
|
+
> **Read the build ledger as a union — a missed ship reads as "no drift".** It is **shard-then-fold**:
|
|
46
|
+
> union the folded `.sdlc/build-log.json` `ships` with every loose `.sdlc/build-log/` shard, deduped by
|
|
47
|
+
> `(story, task, repo)` (a shard WINS over a folded ship of the same key). Reading `build-log.json` alone
|
|
48
|
+
> drops every ship not yet folded by `yad tidy up` — including every `yad checkpoint --retro-ship` backfill,
|
|
49
|
+
> and every ship on a story still at `in-build`, which `tidy up` never folds. Those are exactly the ships
|
|
50
|
+
> most likely to be orphaned, so missing them turns this check into a false all-clear. See
|
|
51
|
+
> `../yad-engineer-review/references/ship-and-record.md`.
|
|
52
|
+
|
|
45
53
|
### Step 2 — `refresh` (advisory, never silent)
|
|
46
54
|
For each flagged thread, **point the human at the fix** — open a reconcile change-epic with `yad-change`
|
|
47
55
|
(`kind: change`, threaded to the affected feature) to bring the front artifacts back in step with what
|
|
@@ -38,9 +38,15 @@ current-truth map, and any open reconcile debt. **STOP** and report if the linea
|
|
|
38
38
|
|
|
39
39
|
### Step 2 — Read each node's evolution facts
|
|
40
40
|
For each epic in the chain read `epic.md` (lineage + the change brief), `.sdlc/change.json` (depth,
|
|
41
|
-
defect block),
|
|
41
|
+
defect block), the build ledger (ship events), and the contract-lock (a real lock = a re-lock
|
|
42
42
|
event; a pointer-lock = inherited). Greenfield-safe: an absent input degrades its part of the view.
|
|
43
43
|
|
|
44
|
+
The build ledger is **shard-then-fold**, so read it as the **union** of the folded `.sdlc/build-log.json`
|
|
45
|
+
`ships` PLUS every loose `.sdlc/build-log/` shard, deduped by `(story, task, repo)` — a shard WINS over a
|
|
46
|
+
folded ship of the same key. Reading `build-log.json` alone silently drops every ship not yet folded by
|
|
47
|
+
`yad tidy up` (including every `yad checkpoint --retro-ship` backfill, and every ship on a story still at
|
|
48
|
+
`in-build`, which `tidy up` never folds). See `../yad-engineer-review/references/ship-and-record.md`.
|
|
49
|
+
|
|
44
50
|
### Step 3 — Render the evolution view (yad-docs shell)
|
|
45
51
|
Generate the site into `epics/<thread>/timeline-site/` (copy the shell verbatim; generate `src/data/*.ts`
|
|
46
52
|
deterministically; theme from the design system). The thread maps onto the shell primitives:
|