yadflow 3.11.1 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/cli/epic-state.mjs +7 -0
- package/cli/next.mjs +14 -5
- package/cli/thread.mjs +6 -3
- package/package.json +1 -1
- package/skills/yad-status/SKILL.md +5 -2
- package/skills/yad-timeline/SKILL.md +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [3.12.0](https://github.com/abdelrahmannasr/yadflow/compare/v3.11.1...v3.12.0) (2026-07-11)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* render an epic's kind as its noun in next/thread/status ([42e80e1](https://github.com/abdelrahmannasr/yadflow/commit/42e80e19e20e129a2a3941c85db6777a66ab00cc))
|
|
7
|
+
|
|
1
8
|
## [3.11.1](https://github.com/abdelrahmannasr/yadflow/compare/v3.11.0...v3.11.1) (2026-07-11)
|
|
2
9
|
|
|
3
10
|
|
package/cli/epic-state.mjs
CHANGED
|
@@ -755,6 +755,13 @@ export function readFrontmatter(file) {
|
|
|
755
755
|
|
|
756
756
|
const asList = (v) => (Array.isArray(v) ? v : v ? [v] : []);
|
|
757
757
|
|
|
758
|
+
// The human-facing noun for a lineage kind. Presentation only — the artifact is always an epic
|
|
759
|
+
// (`EP-<slug>`); this just renders WHAT KIND of work it is so `yad next`/`yad thread`/`yad status`
|
|
760
|
+
// read as "Defect EP-…" / "Change request EP-…" instead of a generic "Epic". `feature` (and any
|
|
761
|
+
// unknown/absent kind) falls back to "Epic". A bug is a defect (kind:defect) — no separate noun.
|
|
762
|
+
export const KIND_NOUN = { feature: 'Epic', change: 'Change request', defect: 'Defect', hotfix: 'Hotfix' };
|
|
763
|
+
export const kindNoun = (kind) => KIND_NOUN[kind] || 'Epic';
|
|
764
|
+
|
|
758
765
|
// The lineage of an epic from epic.md frontmatter. `kind` defaults to `feature` (genesis) when absent,
|
|
759
766
|
// so an un-migrated genesis epic behaves as the thread root. Greenfield/missing-safe.
|
|
760
767
|
export function epicLineage(root, epic) {
|
package/cli/next.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import fs from 'node:fs';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { c, log, ok, info, warn, hand, fail, readJSON, exists } from './lib.mjs';
|
|
15
15
|
import { PROJECT_FILES } from './manifest.mjs';
|
|
16
|
-
import { epicRoot, loadLedger, nextAction, preconditionsMet, isValidEpicId, DISCOVERY_EPIC } from './epic-state.mjs';
|
|
16
|
+
import { epicRoot, loadLedger, nextAction, preconditionsMet, isValidEpicId, epicLineage, kindNoun, DISCOVERY_EPIC } from './epic-state.mjs';
|
|
17
17
|
|
|
18
18
|
// Is solo mode on? Persisted in hub.json by setup (Phase C/D); default false. Read defensively so a
|
|
19
19
|
// missing/old hub.json never breaks the driver.
|
|
@@ -102,7 +102,10 @@ function actionLine(a, { solo } = {}) {
|
|
|
102
102
|
|
|
103
103
|
// Full, friendly printout for a single epic.
|
|
104
104
|
function printAction(a, { solo } = {}) {
|
|
105
|
-
|
|
105
|
+
// Prefix the id with the kind noun (Defect / Change request / Hotfix / Epic) so a glance says what
|
|
106
|
+
// kind of work this is. The discovery front-zero is not a feature epic — leave it un-prefixed.
|
|
107
|
+
const noun = a.lineageKind && a.epicId !== DISCOVERY_EPIC ? `${kindNoun(a.lineageKind)} ` : '';
|
|
108
|
+
log(`\n ${c.bold(`${noun}${a.epicId || '(epic)'}`)} ${c.dim(`— ${a.why}`)}`);
|
|
106
109
|
// In the build half with live lanes, print each story/repo's next sub-step + remaining chain instead
|
|
107
110
|
// of the single static hint; otherwise the one actionable line.
|
|
108
111
|
if (a.kind === 'build' && a.builds?.length) printBuildLanes(a.builds);
|
|
@@ -139,7 +142,10 @@ function generalNext(root, { all } = {}) {
|
|
|
139
142
|
return;
|
|
140
143
|
}
|
|
141
144
|
|
|
142
|
-
const actions = featureEpics.map((id) =>
|
|
145
|
+
const actions = featureEpics.map((id) => ({
|
|
146
|
+
...nextAction(loadLedger(epicRoot(root, id)), { epic: id }),
|
|
147
|
+
lineageKind: epicLineage(root, id).kind,
|
|
148
|
+
}));
|
|
143
149
|
if (discoveryOpen) printAction(discoveryAction, { solo }); // an unfinished discovery comes first
|
|
144
150
|
|
|
145
151
|
if (featureEpics.length === 1 || all) {
|
|
@@ -148,7 +154,7 @@ function generalNext(root, { all } = {}) {
|
|
|
148
154
|
}
|
|
149
155
|
// Several epics — list each with a one-liner, then point at the per-epic / --all views.
|
|
150
156
|
log(`\n ${c.bold(`${featureEpics.length} epics`)} ${c.dim('— next action each:')}`);
|
|
151
|
-
for (const a of actions) log(` ${c.cyan(a.epicId)} ${actionLine(a, { solo })}`);
|
|
157
|
+
for (const a of actions) log(` ${c.cyan(`${kindNoun(a.lineageKind)} ${a.epicId}`)} ${actionLine(a, { solo })}`);
|
|
152
158
|
info(c.dim(`detail: ${c.bold('yad next <epic>')} • all at once: ${c.bold('yad next --all')}`));
|
|
153
159
|
}
|
|
154
160
|
|
|
@@ -183,5 +189,8 @@ export async function runNext(root, { epic, check, all } = {}) {
|
|
|
183
189
|
process.exitCode = 1;
|
|
184
190
|
return;
|
|
185
191
|
}
|
|
186
|
-
printAction(
|
|
192
|
+
printAction(
|
|
193
|
+
{ ...nextAction(loadLedger(epicDir), { epic }), lineageKind: epicLineage(root, epic).kind },
|
|
194
|
+
{ solo: isSolo(root) },
|
|
195
|
+
);
|
|
187
196
|
}
|
package/cli/thread.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import fs from 'node:fs';
|
|
|
7
7
|
import { c, log, ok, info, warn, hand, readJSON, exists } from './lib.mjs';
|
|
8
8
|
import { readShips } from './ledger.mjs';
|
|
9
9
|
import {
|
|
10
|
-
epicRoot, isValidEpicId, epicLineage, readFrontmatter, isStubEpic,
|
|
10
|
+
epicRoot, isValidEpicId, epicLineage, readFrontmatter, isStubEpic, kindNoun,
|
|
11
11
|
resolveThread, threadEpics, resolveCurrentArtifacts, resolveCurrentStories, THREAD_ARTIFACT_BASES,
|
|
12
12
|
} from './epic-state.mjs';
|
|
13
13
|
|
|
@@ -73,7 +73,10 @@ export function threadSummary(root, threadOrEpic) {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
// Colour a node's kind noun for the tree render. The noun words live in one place (`kindNoun`); this
|
|
77
|
+
// only layers the per-kind colour on top, so the two never drift. Unknown kind → uncoloured noun.
|
|
78
|
+
const KIND_COLOR = { feature: c.green, change: c.cyan, defect: c.yellow, hotfix: c.red };
|
|
79
|
+
const kindTag = (kind) => (KIND_COLOR[kind] || ((s) => s))(kindNoun(kind));
|
|
77
80
|
|
|
78
81
|
export async function runThread(root, { epic, json = false } = {}) {
|
|
79
82
|
if (!epic) {
|
|
@@ -103,7 +106,7 @@ export async function runThread(root, { epic, json = false } = {}) {
|
|
|
103
106
|
log(c.bold(`\nThread ${s.thread}`) + c.dim(' (genesis → tip)'));
|
|
104
107
|
if (s.broken) log(c.red(` ✗ broken lineage: ${s.broken}`));
|
|
105
108
|
for (const n of s.nodes) {
|
|
106
|
-
const tag =
|
|
109
|
+
const tag = kindTag(n.kind);
|
|
107
110
|
const seal = n.sealed ? c.dim(' [sealed]') : '';
|
|
108
111
|
const stub = n.stub ? c.yellow(' [stub · backfill pending]') : '';
|
|
109
112
|
const dep = n.depth ? c.dim(` ${n.depth}`) : '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yadflow",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
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",
|
|
@@ -35,8 +35,11 @@ Do not modify any of them.
|
|
|
35
35
|
### Step 3 — Report
|
|
36
36
|
Print, in this order:
|
|
37
37
|
|
|
38
|
-
1. **
|
|
39
|
-
|
|
38
|
+
1. **Header:** render the kind noun from `epic.md` frontmatter `kind` — **Change request** (`change`),
|
|
39
|
+
**Defect** (`defect`), **Hotfix** (`hotfix`), or **Epic** (`feature`, and the default when `kind` is
|
|
40
|
+
absent) — followed by `epicId`, then `status` from `epic.md` frontmatter, `currentStep`, and `repos`
|
|
41
|
+
(the touched domains). Example: `Defect EP-istifta-queue-filter — draft @ stories`. A bug is a defect
|
|
42
|
+
(`kind: defect`) — there is no separate noun. This is presentation only; the artifact is still an epic.
|
|
40
43
|
2. **Steps table** — for every front step in `steps[]` order (10, or 12 when the optional analysis step
|
|
41
44
|
was run): `id`, `type`, `status`, `assistance`, `automation`, `locked`, and `risk_tags`. Mark the
|
|
42
45
|
`currentStep` with `→`. The gating chain is `[analysis → analysis-review →] epic → epic-review →
|
|
@@ -48,7 +48,9 @@ deterministically; theme from the design system). The thread maps onto the shell
|
|
|
48
48
|
= what it re-authored, its side-effects = the ships it produced + any contract re-lock.
|
|
49
49
|
- **System components** = the artifacts (epic/architecture/contract/ui/stories/test-cases), each labelled
|
|
50
50
|
with the epic that currently **owns** it (from the resolved map).
|
|
51
|
-
-
|
|
51
|
+
- Label and colour nodes by `kind` — render each node's kind noun (**Change request** / **Defect** /
|
|
52
|
+
**Hotfix** / **Epic** for feature) alongside its id, not the generic word "epic"; mark sealed epics and
|
|
53
|
+
open debt. (A bug is a defect — `kind: defect`. Presentation only; every node is still an epic.)
|
|
52
54
|
|
|
53
55
|
### Step 4 — Emit `thread-resolved.md` (the current-truth map — derived, non-authoritative)
|
|
54
56
|
Write `epics/<thread>/thread-resolved.md`: for each artifact base, the **owning epic** (the latest in the
|
|
@@ -58,7 +60,7 @@ is the file the next `yad-change` / `yad-epic` reads as "the feature's current t
|
|
|
58
60
|
|
|
59
61
|
### Step 5 — Emit `TIMELINE.md` + (optional) deploy
|
|
60
62
|
Write a short `epics/<thread>/TIMELINE.md` (the chain, what each node changed, ships, open debt) for a
|
|
61
|
-
plain-text read. On `action: deploy`, `yad docs deploy` the site (build-only when no target).
|
|
63
|
+
plain-text read — head each node with its kind noun (Change request / Defect / Hotfix / Epic) + id. On `action: deploy`, `yad docs deploy` the site (build-only when no target).
|
|
62
64
|
|
|
63
65
|
## Hard rules
|
|
64
66
|
|