yadflow 2.0.1 β 2.2.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 +9 -2
- package/README.md +45 -8
- package/bin/yad.mjs +11 -1
- package/cli/doctor.mjs +166 -0
- package/cli/epic-state.mjs +2 -1
- package/cli/errors.mjs +26 -0
- package/cli/gate.mjs +13 -7
- package/cli/lib.mjs +2 -1
- package/cli/manifest.mjs +10 -1
- package/cli/reconcile.mjs +1 -1
- package/cli/setup.mjs +62 -11
- package/docs/index.html +24 -10
- package/package.json +8 -2
- package/skills/sdlc/config.yaml +16 -0
- package/skills/sdlc/install.sh +1 -1
- package/skills/sdlc/module-help.csv +1 -0
- package/skills/yad-connect-design/SKILL.md +117 -0
- package/skills/yad-connect-design/references/design-context.md +64 -0
- package/skills/yad-connect-design/references/design-registry.md +54 -0
- package/skills/yad-epic/references/state-schema.md +13 -0
- package/skills/yad-ui/SKILL.md +64 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
# [2.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.1.0...v2.2.0) (2026-06-13)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* address code review on the design-tool connection ([1275146](https://github.com/abdelrahmannasr/yadflow/commit/1275146e9e24251d481d90eb26894a729e894997))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add yad-connect-design skill (Figma-first, pluggable) ([d1de46d](https://github.com/abdelrahmannasr/yadflow/commit/d1de46d2b08995db0228e60a58877649649b5cd0))
|
|
12
|
+
* materialize the feature design in yad-ui (generate or link) ([e440571](https://github.com/abdelrahmannasr/yadflow/commit/e44057131e86fed45d9a9c4151cd762eb98f24e8))
|
|
13
|
+
* wire the design-tool connection through the CLI ([1867ed4](https://github.com/abdelrahmannasr/yadflow/commit/1867ed433dd57025ae4143f5a236db32f1a99c41))
|
|
7
14
|
|
|
8
15
|
# [1.1.0](https://github.com/abdelrahmannasr/sdlc-workflow/compare/v1.0.3...v1.1.0) (2026-06-09)
|
|
9
16
|
|
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/yadflow)
|
|
4
4
|
[](https://github.com/abdelrahmannasr/yadflow/actions/workflows/ci.yml)
|
|
5
5
|
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
6
|
+
[](https://github.com/abdelrahmannasr/yadflow/blob/main/package.json)
|
|
7
|
+
[](https://github.com/abdelrahmannasr/yadflow/blob/main/SECURITY.md)
|
|
6
8
|
[](https://abdelrahmannasr.github.io/yadflow/)
|
|
7
9
|
|
|
8
10
|
> π **Start here: the [Yadflow Terminology & Workflow Structure Report](https://abdelrahmannasr.github.io/yadflow/)** β
|
|
@@ -70,12 +72,18 @@ The module ships a zero-dependency CLI, published to npm as
|
|
|
70
72
|
[`yadflow`](https://www.npmjs.com/package/yadflow). Run it
|
|
71
73
|
with `npx` from your **product hub** repo β no clone needed.
|
|
72
74
|
|
|
75
|
+
> **Platform support.** Linux and macOS are first-class β the test suite, the bash check gates, and
|
|
76
|
+
> the end-to-end harness all run on both in CI. The CLI shells out to `git` (and the bash gate
|
|
77
|
+
> scripts), so on **Windows use [WSL](https://learn.microsoft.com/windows/wsl/)**; native PowerShell
|
|
78
|
+
> is not yet supported. Requires **Node.js β₯ 18**.
|
|
79
|
+
|
|
73
80
|
| Command | What it does |
|
|
74
81
|
|---------|--------------|
|
|
75
82
|
| `npx yadflow setup` | Guided first-run wizard (the steps below). |
|
|
76
83
|
| `npx yadflow check` | Read-only report: what is **missing** / **outdated** (drifted) / **stale** (code-context) / **legacy** (pre-2.0 `sdlc-*` names) vs the bundled manifest. |
|
|
77
84
|
| `npx yadflow check --fix` | Reconcile: fill what is missing **and** update what changed β touches nothing already correct. |
|
|
78
85
|
| `npx yadflow update` | Apply drift only (alias for `check --fix --scope=changed`). Also migrates a pre-2.0 install in place: `sdlc-*` skill copies and marker-owned `sdlc-*.yml` CI files are replaced by their `yad-*` names (a same-named file *you* authored is never touched). |
|
|
86
|
+
| `npx yadflow doctor [--json]` | Environment + state health: tools on PATH and platform auth, config files parse and point at real repos, every epic ledger loads. Exit 1 on any failure; `--json` for CI and bug reports. |
|
|
79
87
|
| `yad gate open <epic> <artifact>` | Open the front-half **review PR/MR** for an artifact and mark the step `in_review`. |
|
|
80
88
|
| `yad gate sync <epic> [artifact]` | Pull the PR/MR's reviews + comment threads into the file ledger; **auto-advance** the step when approvals are satisfied, all threads are resolved, and the PR is merged. |
|
|
81
89
|
| `yad gate comments <epic> [artifact]` | Fetch the unresolved review comments to address (then reply on the PR; reviewers resolve their threads). |
|
|
@@ -114,16 +122,18 @@ a manual `yad gate sync` racing CI, or GitLab pipelines β two simultaneous syn
|
|
|
114
122
|
*commits* via the rebase retry but each works from the state it read at start, so the rarer of two
|
|
115
123
|
simultaneous advancements can be lost; the next event or scheduled sweep re-syncs and converges.
|
|
116
124
|
|
|
117
|
-
### What `setup` walks you through (
|
|
125
|
+
### What `setup` walks you through (8 steps)
|
|
118
126
|
|
|
119
127
|
1. **Preflight** β confirm the hub is a git repo (offers `git init`); check `git`/`node`/`npx`.
|
|
120
|
-
2. **Install the module** β copy all
|
|
128
|
+
2. **Install the module** β copy all 18 `yad-*` skills into the IDE skill dirs you pick
|
|
121
129
|
(`.claude/`, `.agents/`, `.zencoder/`, `.opencode/`) and register `_bmad/sdlc/`.
|
|
122
130
|
3. **Hub platform & roster** β detect GitHub/GitLab from the remote; record reviewers β `.sdlc/hub.json`.
|
|
123
|
-
4. **Connect
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
4. **Connect a design tool** β record the design tool (Figma / pencil / none) β `.sdlc/design.json` so
|
|
132
|
+
the UI step can materialize the design; the MCP itself is confirmed later by `yad-connect-design`.
|
|
133
|
+
5. **Connect code repos** β register each repo into `.sdlc/repos.json` and cache a Repomix pack.
|
|
134
|
+
6. **Wire each repo** β CI gates, PR/MR template, and review-comment scaffold.
|
|
135
|
+
7. **AI review** β optionally write `.coderabbit.yaml`.
|
|
136
|
+
8. **Done** β stamp `.sdlc/cli-version.json` and hand off the AI-only steps (code-maps; first epic).
|
|
127
137
|
|
|
128
138
|
The deterministic file work runs automatically; the AI-only steps are handed to the Claude Code skills
|
|
129
139
|
with a printed next-action. Re-run `β¦ check --fix` any time the workflow updates β it never re-asks for
|
|
@@ -143,7 +153,27 @@ provenance). See [`RELEASING.md`](RELEASING.md).
|
|
|
143
153
|
> ships the `CHANGELOG.md` in the tarball, and cuts a GitHub release. No manual `npm publish`. See
|
|
144
154
|
> [`RELEASING.md`](RELEASING.md).
|
|
145
155
|
|
|
146
|
-
|
|
156
|
+
### Troubleshooting (`yad doctor` + error codes)
|
|
157
|
+
|
|
158
|
+
When something is off, run `yad doctor` first β it checks the environment (git, gh/glab auth, node
|
|
159
|
+
version), the project state (`.sdlc/*.json` parse and point at real repos), and every epic ledger,
|
|
160
|
+
with a fix-it hint per finding. Failures carry stable, greppable codes, also printed by any failing
|
|
161
|
+
`yad` command:
|
|
162
|
+
|
|
163
|
+
| Code | Meaning | Fix |
|
|
164
|
+
|------|---------|-----|
|
|
165
|
+
| `YAD-ENV-001` | git is not installed or not on PATH | install git β every yad command needs it |
|
|
166
|
+
| `YAD-ENV-002` | platform CLI (gh/glab) missing or not authenticated | install it and authenticate β `gh auth login` (GitHub) or `glab auth login` (GitLab); the gate degrades to file-only without it |
|
|
167
|
+
| `YAD-ENV-003` | Node.js older than the supported range | install Node >= 18 |
|
|
168
|
+
| `YAD-STATE-001` | a ledger/config JSON file exists but does not parse | fix the file or restore from git β never delete a ledger blindly |
|
|
169
|
+
| `YAD-STATE-002` | a ledger/config file parses but has the wrong shape | fix the file or restore from git (the message names the field) |
|
|
170
|
+
| `YAD-STATE-003` | a registered repo path is missing or not a git repo | fix the path in `.sdlc/repos.json` or re-connect the repo |
|
|
171
|
+
| `YAD-CFG-001` | `hub.json` names an unknown platform | expected `github`, `gitlab`, or `null` β fix it or re-run `yad setup` |
|
|
172
|
+
| `YAD-CFG-002` | `design.json` names an unknown design tool | expected one of `config.yaml` `design.tools` (e.g. `figma`, `pencil`), or `none` β fix it or re-run `yad setup` |
|
|
173
|
+
|
|
174
|
+
Filing a bug? Attach `yad doctor --json` β it contains no secrets (names, paths, and check results only).
|
|
175
|
+
|
|
176
|
+
## Agent skills (all 18)
|
|
147
177
|
|
|
148
178
|
The CLI **installs and wires** the module; the skills below are the **agents you invoke by name** in your
|
|
149
179
|
AI IDE (e.g. *βrun `yad-epic`β*) to actually do the work. State lives in files you can also edit
|
|
@@ -156,6 +186,11 @@ directly. Each skill stops at a gate and never auto-advances unless a step has *
|
|
|
156
186
|
`.sdlc/repos.json`, then caches an AI-readable picture of each β a compressed Repomix pack and a
|
|
157
187
|
lightweight code-map (existing endpoints/events/data-models/modules), secret-scanned. Idempotent and
|
|
158
188
|
refreshable; staleness tracked by HEAD sha.
|
|
189
|
+
- **`yad-connect-design`** β Connects a design tool (Figma-first, pluggable) so the UI step can
|
|
190
|
+
materialize the actual feature design (mobile screens / web pages) inside it, alongside the Markdown.
|
|
191
|
+
Records the tool + project/file references in `.sdlc/design.json` (local-user / MCP-session auth, no
|
|
192
|
+
stored tokens), detecting the design-tool MCP and degrading to markdown-only when absent. Idempotent
|
|
193
|
+
and refreshable; one connection per project.
|
|
159
194
|
|
|
160
195
|
### Front half β author the "thinking" (once per epic, human-gated)
|
|
161
196
|
|
|
@@ -170,7 +205,9 @@ directly. Each skill stops at a gate and never auto-advances unless a step has *
|
|
|
170
205
|
`.sdlc/contract-lock.json`. Reads `epic.md`; escalates on the contract risk tag.
|
|
171
206
|
- **`yad-ui`** β Front state 5. With the ux-designer, author `ui-design.md` and `DESIGN.md`,
|
|
172
207
|
driving Impeccable as harness slash-commands (document/extract/craft) when installed, or authoring
|
|
173
|
-
directly when not.
|
|
208
|
+
directly when not. When a design tool is connected (`yad-connect-design`), also **materializes the
|
|
209
|
+
feature design** β mobile screens / web pages β in the tool (generate or link), recording the
|
|
210
|
+
screenβframe map in `design-links.json`; degrades to markdown-only otherwise. Reads epic + architecture.
|
|
174
211
|
- **`yad-stories`** β Front state 7. With the pm, break the approved epic into user stories, each
|
|
175
212
|
tagged with the repos that must implement it. Assigns zero-padded `EP-<slug>-S0N` IDs, one file per
|
|
176
213
|
story under `stories/`. Reads epic + architecture + contract + UI.
|
package/bin/yad.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { isValidEpicId } from '../cli/epic-state.mjs';
|
|
|
9
9
|
import { runCommit } from '../cli/commit.mjs';
|
|
10
10
|
import { runOpenPr } from '../cli/openpr.mjs';
|
|
11
11
|
import { runRepo } from '../cli/repo.mjs';
|
|
12
|
+
import { runDoctor } from '../cli/doctor.mjs';
|
|
12
13
|
|
|
13
14
|
const HELP = `${c.bold('yad')} β setup, review-gate & build helpers for the SDLC Workflow module ${c.dim('v' + VERSION)}
|
|
14
15
|
|
|
@@ -18,6 +19,8 @@ ${c.bold('Setup & maintenance')}
|
|
|
18
19
|
yad check --fix Reconcile: fill what is missing, update what changed
|
|
19
20
|
yad update Apply drift only (alias for: check --fix --scope=changed);
|
|
20
21
|
also migrates pre-2.0 sdlc-* installs to the yad-* names
|
|
22
|
+
yad doctor [--json] Environment + state health: tools/auth, config files,
|
|
23
|
+
repo paths, epic ledgers (exit 1 on any failure)
|
|
21
24
|
|
|
22
25
|
${c.bold('Review gate (front half)')}
|
|
23
26
|
yad gate open <epic> <artifact> Open the review PR/MR; mark the step in_review
|
|
@@ -62,6 +65,7 @@ function parseArgs(argv) {
|
|
|
62
65
|
else if (a === '--contract-change') o.contractChange = true;
|
|
63
66
|
else if (a === '--no-push') o.noPush = true;
|
|
64
67
|
else if (a === '--dry-run') o.dryRun = true;
|
|
68
|
+
else if (a === '--json') o.json = true;
|
|
65
69
|
else if (a === '-h' || a === '--help') o.help = true;
|
|
66
70
|
else if (a === '-v' || a === '--version') o.version = true;
|
|
67
71
|
else if (a.startsWith('--scope=')) o.scope = a.slice('--scope='.length);
|
|
@@ -96,6 +100,9 @@ async function main() {
|
|
|
96
100
|
case 'update':
|
|
97
101
|
await reconcile(o.dir, { fix: true, scope: 'changed', force: o.force, today });
|
|
98
102
|
break;
|
|
103
|
+
case 'doctor':
|
|
104
|
+
await runDoctor(o.dir, { json: o.json });
|
|
105
|
+
break;
|
|
99
106
|
case 'gate': {
|
|
100
107
|
const [, action, epic, artifact] = o._;
|
|
101
108
|
// `gate ci` takes no positionals β epic/artifact come from --branch (or a sweep of all PRs).
|
|
@@ -130,7 +137,10 @@ async function main() {
|
|
|
130
137
|
|
|
131
138
|
main()
|
|
132
139
|
.catch((err) => {
|
|
133
|
-
|
|
140
|
+
const code = err?.code && /^YAD-/.test(err.code) ? ` [${err.code}]` : '';
|
|
141
|
+
log(c.red(`\nyad failed${code}: ${err?.message || err}`));
|
|
142
|
+
if (err?.hint) log(c.yellow(` β ${err.hint}`));
|
|
143
|
+
if (code) log(c.dim(' (see README "Troubleshooting" for this code, or run `yad doctor`)'));
|
|
134
144
|
process.exitCode = 1;
|
|
135
145
|
})
|
|
136
146
|
.finally(closePrompts);
|
package/cli/doctor.mjs
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// `yad doctor` β environment + state health, the complement of `yad check` (file drift).
|
|
2
|
+
// Three sections: environment (tools on PATH, auth), project state (config files parse and
|
|
3
|
+
// point at real repos), epics (each ledger loads). Pure reporting: exit 1 on any FAIL,
|
|
4
|
+
// 0 with warnings. `--json` emits the checks for CI / bug reports.
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { c, log, ok, info, warn, fail, hand, run, has, exists, readJSON, readJSONStrict } from './lib.mjs';
|
|
8
|
+
import { VERSION, PROJECT_FILES, DESIGN_TOOLS } from './manifest.mjs';
|
|
9
|
+
import { loadLedger, epicRoot } from './epic-state.mjs';
|
|
10
|
+
import { gitHead } from './setup.mjs';
|
|
11
|
+
import { cliFor } from './platform.mjs';
|
|
12
|
+
|
|
13
|
+
const MIN_NODE = 18;
|
|
14
|
+
|
|
15
|
+
// Each check: { id, section, status: 'ok'|'warn'|'fail', message, hint? }
|
|
16
|
+
function check(checks, id, section, status, message, hint = '') {
|
|
17
|
+
checks.push({ id, section, status, message, ...(hint ? { hint } : {}) });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function envChecks(checks) {
|
|
21
|
+
const major = Number(process.versions.node.split('.')[0]);
|
|
22
|
+
if (major >= MIN_NODE) check(checks, 'node', 'environment', 'ok', `node ${process.versions.node}`);
|
|
23
|
+
else check(checks, 'node', 'environment', 'fail', `node ${process.versions.node} is below the supported range [YAD-ENV-003]`, `install Node.js >= ${MIN_NODE}`);
|
|
24
|
+
|
|
25
|
+
if (has('git')) check(checks, 'git', 'environment', 'ok', 'git present');
|
|
26
|
+
else check(checks, 'git', 'environment', 'fail', 'git not found on PATH [YAD-ENV-001]', 'install git β every yad command needs it');
|
|
27
|
+
|
|
28
|
+
for (const tool of ['npx', 'bash']) {
|
|
29
|
+
if (has(tool)) check(checks, tool, 'environment', 'ok', `${tool} present`);
|
|
30
|
+
else check(checks, tool, 'environment', 'warn', `${tool} not found on PATH`, tool === 'npx' ? 'repomix packing will be skipped' : 'the check gates are bash scripts');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function projectChecks(checks, root) {
|
|
35
|
+
const hubPath = path.join(root, PROJECT_FILES.hubConfig);
|
|
36
|
+
const regPath = path.join(root, PROJECT_FILES.reposRegistry);
|
|
37
|
+
const verPath = path.join(root, PROJECT_FILES.version);
|
|
38
|
+
if (!exists(hubPath) && !exists(regPath) && !exists(verPath)) {
|
|
39
|
+
check(checks, 'project', 'project', 'warn', 'no yad project here (.sdlc/ not initialised)', 'run `yad setup` to start one β environment checks above still apply');
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// version stamp
|
|
44
|
+
const ver = readJSON(verPath, null);
|
|
45
|
+
if (!ver) check(checks, 'cli-version', 'project', 'warn', `${PROJECT_FILES.version} missing or unreadable`, 'run `yad check --fix`');
|
|
46
|
+
else if (ver.version !== VERSION) check(checks, 'cli-version', 'project', 'warn', `project stamped v${ver.version}, CLI is v${VERSION}`, 'run `yad update` to reconcile');
|
|
47
|
+
else check(checks, 'cli-version', 'project', 'ok', `version stamp matches (v${VERSION})`);
|
|
48
|
+
|
|
49
|
+
// hub.json: parse + shape
|
|
50
|
+
let hub = null;
|
|
51
|
+
if (!exists(hubPath)) {
|
|
52
|
+
check(checks, 'hub', 'project', 'warn', `${PROJECT_FILES.hubConfig} absent β file-only gate`, 'run `yad setup` to configure a platform + roster');
|
|
53
|
+
} else {
|
|
54
|
+
let hubBroken = false;
|
|
55
|
+
try {
|
|
56
|
+
hub = readJSONStrict(hubPath, null);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
hubBroken = true;
|
|
59
|
+
check(checks, 'hub', 'project', 'fail', `${PROJECT_FILES.hubConfig} does not parse [${e.code || 'YAD-STATE-001'}]`, e.hint || 'fix the JSON or restore it from git');
|
|
60
|
+
}
|
|
61
|
+
if (hubBroken) { /* reported above */ }
|
|
62
|
+
else if (typeof hub !== 'object' || Array.isArray(hub) || hub === null) check(checks, 'hub', 'project', 'fail', `${PROJECT_FILES.hubConfig} has the wrong shape [YAD-STATE-002]`, 'expected a JSON object');
|
|
63
|
+
else if (![null, undefined, 'github', 'gitlab'].includes(hub.platform)) check(checks, 'hub', 'project', 'fail', `${PROJECT_FILES.hubConfig}: unknown platform '${hub.platform}' [YAD-CFG-001]`, 'expected github, gitlab, or null');
|
|
64
|
+
// Mirror gate.mjs's roster shape check so doctor never reports "ok" on a hub the gate would reject.
|
|
65
|
+
else if (hub.roster !== undefined && !Array.isArray(hub.roster)) check(checks, 'hub', 'project', 'fail', `${PROJECT_FILES.hubConfig}: \`roster\` must be an array [YAD-STATE-002]`, 'fix the file or re-run `yad setup`');
|
|
66
|
+
else {
|
|
67
|
+
check(checks, 'hub', 'project', 'ok', `hub: ${hub.platform || 'file-only'}, ${(hub.roster || []).length} reviewer(s)`);
|
|
68
|
+
// platform CLI + auth (best-effort; auth probing is the user's own session)
|
|
69
|
+
const cli = cliFor(hub.platform);
|
|
70
|
+
if (cli) {
|
|
71
|
+
if (!has(cli)) check(checks, 'platform-cli', 'project', 'warn', `${cli} not found on PATH [YAD-ENV-002]`, `install ${cli} β the gate degrades to file-only without it`);
|
|
72
|
+
else if (!run(cli, ['auth', 'status']).ok) check(checks, 'platform-cli', 'project', 'warn', `${cli} present but not authenticated [YAD-ENV-002]`, `run \`${cli} auth login\``);
|
|
73
|
+
else check(checks, 'platform-cli', 'project', 'ok', `${cli} present and authenticated`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// design.json: parse + shape + tool + MCP confirmation (absent is the normal markdown-only default β
|
|
79
|
+
// pre-feature projects have none, so silence rather than warn when the file does not exist).
|
|
80
|
+
const designPath = path.join(root, PROJECT_FILES.designConfig);
|
|
81
|
+
if (exists(designPath)) {
|
|
82
|
+
let design = null, designBroken = false;
|
|
83
|
+
try {
|
|
84
|
+
design = readJSONStrict(designPath, null);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
designBroken = true;
|
|
87
|
+
check(checks, 'design', 'project', 'fail', `${PROJECT_FILES.designConfig} does not parse [${e.code || 'YAD-STATE-001'}]`, e.hint || 'fix the JSON or restore it from git');
|
|
88
|
+
}
|
|
89
|
+
if (designBroken) { /* reported above */ }
|
|
90
|
+
else if (typeof design !== 'object' || Array.isArray(design) || design === null) check(checks, 'design', 'project', 'fail', `${PROJECT_FILES.designConfig} has the wrong shape [YAD-STATE-002]`, 'expected a JSON object');
|
|
91
|
+
else if (![...DESIGN_TOOLS, 'none', null, undefined].includes(design.tool)) check(checks, 'design', 'project', 'fail', `${PROJECT_FILES.designConfig}: unknown design tool '${design.tool}' [YAD-CFG-002]`, `expected one of ${DESIGN_TOOLS.join(', ')}, or none`);
|
|
92
|
+
else if (!design.tool || design.tool === 'none') check(checks, 'design', 'project', 'ok', 'design: markdown-only');
|
|
93
|
+
else if (design.source && design.source !== 'unavailable') check(checks, 'design', 'project', 'ok', `design: ${design.tool} (${design.source})`);
|
|
94
|
+
else if (design.source === 'unavailable') check(checks, 'design', 'project', 'warn', `design: ${design.tool} MCP unavailable β yad-ui runs markdown-only`, 'connect the MCP, then run `yad-connect-design` (action: refresh)');
|
|
95
|
+
else check(checks, 'design', 'project', 'warn', `design: ${design.tool} recorded but the MCP is not confirmed`, 'run `yad-connect-design` in Claude Code to detect the MCP');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// repos.json: parse + every entry is a live git repo; staleness vs syncedHead
|
|
99
|
+
let registry = { repos: [] };
|
|
100
|
+
let regBroken = false;
|
|
101
|
+
try {
|
|
102
|
+
registry = readJSONStrict(regPath, { repos: [] });
|
|
103
|
+
} catch (e) {
|
|
104
|
+
regBroken = true;
|
|
105
|
+
check(checks, 'repos', 'project', 'fail', `${PROJECT_FILES.reposRegistry} does not parse [${e.code || 'YAD-STATE-001'}]`, e.hint || 'fix the JSON or restore it from git');
|
|
106
|
+
}
|
|
107
|
+
if (regBroken) { /* reported above */ }
|
|
108
|
+
else if (!Array.isArray(registry?.repos)) check(checks, 'repos', 'project', 'fail', `${PROJECT_FILES.reposRegistry} has the wrong shape [YAD-STATE-002]`, 'expected a `repos` array');
|
|
109
|
+
else {
|
|
110
|
+
for (const repo of registry.repos) {
|
|
111
|
+
// A missing/empty path must NOT fall back to the project root (which is itself a git repo and
|
|
112
|
+
// would read as "healthy") β an entry with no path is malformed.
|
|
113
|
+
if (!repo.path) { check(checks, `repo:${repo.name || '(unnamed)'}`, 'project', 'fail', `${repo.name || '(unnamed)'}: no \`path\` in repos.json [YAD-STATE-003]`, 're-connect the repo (`yad setup`)'); continue; }
|
|
114
|
+
const repoRoot = path.resolve(root, repo.path);
|
|
115
|
+
if (!exists(repoRoot)) { check(checks, `repo:${repo.name}`, 'project', 'fail', `${repo.name}: path ${repo.path} does not exist [YAD-STATE-003]`, 'fix the path in repos.json or re-connect the repo'); continue; }
|
|
116
|
+
const head = gitHead(repoRoot);
|
|
117
|
+
if (!head) { check(checks, `repo:${repo.name}`, 'project', 'fail', `${repo.name}: ${repo.path} is not a git repository (or has no commits) [YAD-STATE-003]`, 'init/clone the repo, then re-connect it'); continue; }
|
|
118
|
+
if (repo.syncedHead && head !== repo.syncedHead) check(checks, `repo:${repo.name}`, 'project', 'warn', `${repo.name}: code-context is stale (HEAD moved since last pack)`, 'run `yad repo refresh ' + repo.name + '`');
|
|
119
|
+
else check(checks, `repo:${repo.name}`, 'project', 'ok', `${repo.name}: git repo, context fresh`);
|
|
120
|
+
}
|
|
121
|
+
if (!registry.repos.length) check(checks, 'repos', 'project', 'warn', 'no code repos registered', 'run `yad setup` to connect one');
|
|
122
|
+
}
|
|
123
|
+
return { hub, registry };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function epicChecks(checks, root) {
|
|
127
|
+
const epicsDir = path.join(root, 'epics');
|
|
128
|
+
if (!exists(epicsDir)) return;
|
|
129
|
+
for (const e of fs.readdirSync(epicsDir).sort()) {
|
|
130
|
+
if (!fs.statSync(path.join(epicsDir, e)).isDirectory()) continue;
|
|
131
|
+
try {
|
|
132
|
+
const ledger = loadLedger(epicRoot(root, e));
|
|
133
|
+
if (!ledger.state) check(checks, `epic:${e}`, 'epics', 'warn', `${e}: no state.json β epic not seeded`, 'author it via yad-epic, or remove the directory');
|
|
134
|
+
else check(checks, `epic:${e}`, 'epics', 'ok', `${e}: currentStep ${ledger.state.currentStep}`);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
check(checks, `epic:${e}`, 'epics', 'fail', `${e}: ${err.message} [${err.code || 'YAD-STATE-001'}]`, err.hint || 'fix the file or restore it from git');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function runDoctor(root, { json = false } = {}) {
|
|
142
|
+
const checks = [];
|
|
143
|
+
envChecks(checks);
|
|
144
|
+
projectChecks(checks, root);
|
|
145
|
+
epicChecks(checks, root);
|
|
146
|
+
|
|
147
|
+
const failed = checks.filter((x) => x.status === 'fail');
|
|
148
|
+
const warned = checks.filter((x) => x.status === 'warn');
|
|
149
|
+
if (json) {
|
|
150
|
+
log(JSON.stringify({ version: VERSION, ok: failed.length === 0, checks }, null, 2));
|
|
151
|
+
} else {
|
|
152
|
+
log(c.bold(`\nyad doctor ${c.dim('v' + VERSION)}`));
|
|
153
|
+
let section = '';
|
|
154
|
+
for (const x of checks) {
|
|
155
|
+
if (x.section !== section) { section = x.section; log(`\n ${c.bold(section)}`); }
|
|
156
|
+
({ ok, warn, fail })[x.status](x.message);
|
|
157
|
+
if (x.hint && x.status !== 'ok') hand(x.hint);
|
|
158
|
+
}
|
|
159
|
+
log('');
|
|
160
|
+
if (failed.length) fail(`${failed.length} problem(s) found`);
|
|
161
|
+
else if (warned.length) info(`healthy with ${warned.length} warning(s)`);
|
|
162
|
+
else ok('all clear');
|
|
163
|
+
}
|
|
164
|
+
if (failed.length) process.exitCode = 1;
|
|
165
|
+
return { ok: failed.length === 0, failed: failed.length, warned: warned.length, checks };
|
|
166
|
+
}
|
package/cli/epic-state.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import { readJSONStrict, writeJSON, fileSha } from './lib.mjs';
|
|
8
|
+
import { err } from './errors.mjs';
|
|
8
9
|
import { epicFiles } from './manifest.mjs';
|
|
9
10
|
|
|
10
11
|
const RISK_ESCALATORS = ['contract', 'auth', 'payments'];
|
|
@@ -98,7 +99,7 @@ export function artifactHash(epicDir, artifact) {
|
|
|
98
99
|
|
|
99
100
|
// Shape checks for the ledger files. Fail fast with the exact file named β a wrong-shape ledger
|
|
100
101
|
// silently treated as a default would be rewritten by the next sync, destroying the real data.
|
|
101
|
-
const badShape = (file, what) =>
|
|
102
|
+
const badShape = (file, what) => err('YAD-STATE-002', `${file}: ${what}`, 'fix the file or restore it from git');
|
|
102
103
|
function requireArray(v, file) {
|
|
103
104
|
if (!Array.isArray(v)) throw badShape(file, 'expected a JSON array');
|
|
104
105
|
return v;
|
package/cli/errors.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Structured error codes for the `yad` CLI. A YadError carries a stable code (greppable,
|
|
2
|
+
// documented in README "Troubleshooting") and a one-line recovery hint that the top-level
|
|
3
|
+
// catch in bin/yad.mjs prints after the message. Plain Errors still work everywhere; codes
|
|
4
|
+
// are reserved for the failures users actually hit and need to act on.
|
|
5
|
+
export class YadError extends Error {
|
|
6
|
+
constructor(code, message, hint = '') {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'YadError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.hint = hint;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The catalog β single source for doctor, the top-level catch, and the README table.
|
|
15
|
+
export const CODES = {
|
|
16
|
+
'YAD-ENV-001': 'git is not installed or not on PATH',
|
|
17
|
+
'YAD-ENV-002': 'the platform CLI (gh/glab) is missing or not authenticated',
|
|
18
|
+
'YAD-ENV-003': 'Node.js is older than the supported range (>=18)',
|
|
19
|
+
'YAD-STATE-001': 'a ledger/config JSON file exists but does not parse',
|
|
20
|
+
'YAD-STATE-002': 'a ledger/config JSON file parses but has the wrong shape',
|
|
21
|
+
'YAD-STATE-003': 'a registered repo path is missing or not a git repository',
|
|
22
|
+
'YAD-CFG-001': 'hub.json names an unknown platform (expected github, gitlab, or null)',
|
|
23
|
+
'YAD-CFG-002': 'design.json names an unknown design tool (expected one of config.yaml design.tools, or none)',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const err = (code, message, hint) => new YadError(code, message, hint);
|
package/cli/gate.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
artifactPaths, upsertHubPr,
|
|
15
15
|
} from './epic-state.mjs';
|
|
16
16
|
import { readPr, mapApprovers, createPr } from './platform.mjs';
|
|
17
|
+
import { err } from './errors.mjs';
|
|
17
18
|
|
|
18
19
|
// ---- tiny frontmatter reader (key: value, and `repos: [a, b]`) ----------------------------------
|
|
19
20
|
function frontmatter(file) {
|
|
@@ -64,18 +65,23 @@ function warnUnlockedContract(epicDir, artifact) {
|
|
|
64
65
|
function loadHub(root) {
|
|
65
66
|
const hubFile = path.join(root, PROJECT_FILES.hubConfig);
|
|
66
67
|
const regFile = path.join(root, PROJECT_FILES.reposRegistry);
|
|
68
|
+
// Distinguish an ABSENT hub.json (null default β fine, file-only gate) from one that exists but
|
|
69
|
+
// holds literal `null` (malformed β must not silently downgrade to file-only).
|
|
67
70
|
const hub = readJSONStrict(hubFile, null);
|
|
71
|
+
if (hub === null && fs.existsSync(hubFile)) {
|
|
72
|
+
throw err('YAD-STATE-002', `${hubFile}: contains \`null\` β expected a config object`, 'fix the file or re-run `yad setup`');
|
|
73
|
+
}
|
|
68
74
|
if (hub !== null) {
|
|
69
|
-
if (typeof hub !== 'object' || Array.isArray(hub)) throw
|
|
75
|
+
if (typeof hub !== 'object' || Array.isArray(hub)) throw err('YAD-STATE-002', `${hubFile}: expected a JSON object`, 'fix the file or re-run `yad setup`');
|
|
70
76
|
if (![null, undefined, 'github', 'gitlab'].includes(hub.platform)) {
|
|
71
|
-
throw
|
|
77
|
+
throw err('YAD-CFG-001', `${hubFile}: unknown platform '${hub.platform}'`, 'expected github, gitlab, or null β fix the file or re-run `yad setup`');
|
|
72
78
|
}
|
|
73
79
|
if (hub.roster !== undefined && !Array.isArray(hub.roster)) {
|
|
74
|
-
throw
|
|
80
|
+
throw err('YAD-STATE-002', `${hubFile}: expected \`roster\` to be an array`, 'fix the file or re-run `yad setup`');
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
const registry = readJSONStrict(regFile, { repos: [] });
|
|
78
|
-
if (!Array.isArray(registry?.repos)) throw
|
|
84
|
+
if (!Array.isArray(registry?.repos)) throw err('YAD-STATE-002', `${regFile}: expected a \`repos\` array`, 'fix the file or re-run `yad setup`');
|
|
79
85
|
return { hub, repos: registry.repos };
|
|
80
86
|
}
|
|
81
87
|
|
|
@@ -131,7 +137,7 @@ function writeComments(epicDir, base, today, blocking) {
|
|
|
131
137
|
// Upsert machine-readable participation records into the comments ledger (the counterpart to the
|
|
132
138
|
// markdown side file) so the ledger β not just reviews/*.md β reflects platform thread state. One
|
|
133
139
|
// record per (step, commenter, round); `round` is the count of prior synced rounds for the step.
|
|
134
|
-
function recordComments(comments, { artifact, stepId, today, roster,
|
|
140
|
+
function recordComments(comments, { artifact, stepId, today, roster, blocking }) {
|
|
135
141
|
if (!blocking.length) return comments;
|
|
136
142
|
const byName = (login) => (roster.find((r) => r.login === login)?.name) || login || 'reviewer';
|
|
137
143
|
const roleOf = (login) => (roster.find((r) => r.login === login)?.role) || 'reviewer';
|
|
@@ -386,8 +392,8 @@ export async function gateStatus(root, { epic } = {}) {
|
|
|
386
392
|
}
|
|
387
393
|
}
|
|
388
394
|
|
|
389
|
-
export async function gateOpen(root, { epic, artifact
|
|
390
|
-
const { hub
|
|
395
|
+
export async function gateOpen(root, { epic, artifact } = {}) {
|
|
396
|
+
const { hub } = loadHub(root);
|
|
391
397
|
const epicDir = epicRoot(root, epic);
|
|
392
398
|
const ledger = loadLedger(epicDir);
|
|
393
399
|
if (!ledger.state) { fail(`no epic state at ${epicDir}`); process.exitCode = 1; return; }
|
package/cli/lib.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Shared helpers for the `yad` CLI. Node >=18 built-ins only β no dependencies.
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
+
import { err } from './errors.mjs';
|
|
3
4
|
import { spawnSync } from 'node:child_process';
|
|
4
5
|
import * as readline from 'node:readline/promises';
|
|
5
6
|
import { stdin as input, stdout as output } from 'node:process';
|
|
@@ -108,7 +109,7 @@ export function readJSONStrict(p, def = null) {
|
|
|
108
109
|
try {
|
|
109
110
|
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
110
111
|
} catch (e) {
|
|
111
|
-
throw
|
|
112
|
+
throw err('YAD-STATE-001', `corrupt JSON in ${p}: ${e.message}`, 'fix the file or restore it from git β never delete a ledger blindly');
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
// Atomic: serialize first, write a sibling tmp file (same dir = same filesystem),
|
package/cli/manifest.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { readFileSync } from 'node:fs';
|
|
|
10
10
|
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
11
11
|
export const VERSION = version;
|
|
12
12
|
|
|
13
|
-
// The
|
|
13
|
+
// The 18 hand-authored yad-* skills (mirrors skills/sdlc/install.sh).
|
|
14
14
|
export const SKILLS = [
|
|
15
15
|
'yad-analysis',
|
|
16
16
|
'yad-epic',
|
|
@@ -18,6 +18,7 @@ export const SKILLS = [
|
|
|
18
18
|
'yad-ui',
|
|
19
19
|
'yad-stories',
|
|
20
20
|
'yad-connect-repos',
|
|
21
|
+
'yad-connect-design',
|
|
21
22
|
'yad-spec',
|
|
22
23
|
'yad-implement',
|
|
23
24
|
'yad-checks',
|
|
@@ -79,10 +80,18 @@ export const IDE_OPENCODE_DIR = '.opencode/commands'; // <skill>.md (flat SKILL.
|
|
|
79
80
|
// Module registration files copied from skills/sdlc/ into _bmad/sdlc/.
|
|
80
81
|
export const MODULE_FILES = ['config.yaml', 'module-help.csv'];
|
|
81
82
|
|
|
83
|
+
// Supported design-tool adapters (mirrors skills/sdlc/config.yaml `design.tools`); `DESIGN_PRIMARY` is
|
|
84
|
+
// the fallback `registerDesign`/setup use when an unknown tool is named, and `none` is the explicit
|
|
85
|
+
// markdown-only choice. (doctor does NOT fall back β an unknown tool there is a hard YAD-CFG-002 fail,
|
|
86
|
+
// mirroring how registerRepo falls back on platform while doctor fails on an unknown hub platform.)
|
|
87
|
+
export const DESIGN_TOOLS = ['figma', 'pencil'];
|
|
88
|
+
export const DESIGN_PRIMARY = 'figma';
|
|
89
|
+
|
|
82
90
|
// Project-level files setup produces (used by `check` to spot missing setup).
|
|
83
91
|
export const PROJECT_FILES = {
|
|
84
92
|
reposRegistry: '.sdlc/repos.json',
|
|
85
93
|
hubConfig: '.sdlc/hub.json',
|
|
94
|
+
designConfig: '.sdlc/design.json',
|
|
86
95
|
version: '.sdlc/cli-version.json',
|
|
87
96
|
};
|
|
88
97
|
|
package/cli/reconcile.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// manifest: missing setup, drifted files, stale code-context.
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {
|
|
6
|
-
c, log, ok, info, warn, hand,
|
|
6
|
+
c, log, ok, info, warn, hand, readJSON, writeJSON, exists,
|
|
7
7
|
} from './lib.mjs';
|
|
8
8
|
import { VERSION, PROJECT_FILES } from './manifest.mjs';
|
|
9
9
|
import {
|
package/cli/setup.mjs
CHANGED
|
@@ -3,9 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import {
|
|
5
5
|
c, log, step, ok, info, warn, hand, fail, ask, askYesNo, run, has,
|
|
6
|
-
exists,
|
|
6
|
+
exists, readJSON, writeJSON,
|
|
7
7
|
} from './lib.mjs';
|
|
8
|
-
import { VERSION, IDE_FOLDER_TARGETS,
|
|
8
|
+
import { VERSION, IDE_FOLDER_TARGETS, PROJECT_FILES, DESIGN_TOOLS, DESIGN_PRIMARY } from './manifest.mjs';
|
|
9
9
|
import { moduleActions, repoActions, hubActions, authorsActions } from './plan.mjs';
|
|
10
10
|
|
|
11
11
|
const ALL_IDES = [...IDE_FOLDER_TARGETS, '.opencode'];
|
|
@@ -58,6 +58,35 @@ export function registerRepo(root, registry, { name, rpath, platform, domain_own
|
|
|
58
58
|
return repo;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// Record the project's design-tool connection into .sdlc/design.json (the deterministic half of the
|
|
62
|
+
// connect loop; MCP detection itself is an AI step, handed off to `yad-connect-design`). An unknown tool
|
|
63
|
+
// falls back to the primary adapter rather than being rejected β mirrors registerRepo's platform
|
|
64
|
+
// fallback and the hub step. `none` is the explicit markdown-only choice.
|
|
65
|
+
export function registerDesign(root, { tool, project_url = null, files = null, today = null } = {}) {
|
|
66
|
+
// Idempotent re-connect: carry the original first-connect date forward (the schema defines
|
|
67
|
+
// connectedAt as "first connect"); only lastSyncedAt moves. Mirrors repo.mjs refresh.
|
|
68
|
+
const designPath = path.join(root, PROJECT_FILES.designConfig);
|
|
69
|
+
const prev = readJSON(designPath, null);
|
|
70
|
+
const connectedAt = prev && prev.connectedAt ? prev.connectedAt : today;
|
|
71
|
+
let t = (tool || '').toLowerCase();
|
|
72
|
+
if (t === 'none' || t === '') {
|
|
73
|
+
const off = { tool: 'none', provider: null, project_url: null, auth: 'user',
|
|
74
|
+
files: { web: null, mobile: null }, connectedAt, lastSyncedAt: today, source: 'unavailable' };
|
|
75
|
+
writeJSON(designPath, off);
|
|
76
|
+
return off;
|
|
77
|
+
}
|
|
78
|
+
if (!DESIGN_TOOLS.includes(t)) { warn(`unknown design tool '${tool}' β using ${DESIGN_PRIMARY}`); t = DESIGN_PRIMARY; }
|
|
79
|
+
// source stays null until `yad-connect-design` detects the MCP in the harness (AI step). doctor reports
|
|
80
|
+
// a recorded-but-unconfirmed connection as a warning pointing at that skill.
|
|
81
|
+
const design = {
|
|
82
|
+
tool: t, provider: null, project_url: project_url || null, auth: 'user',
|
|
83
|
+
files: files || { web: null, mobile: null },
|
|
84
|
+
connectedAt, lastSyncedAt: today, source: null,
|
|
85
|
+
};
|
|
86
|
+
writeJSON(designPath, design);
|
|
87
|
+
return design;
|
|
88
|
+
}
|
|
89
|
+
|
|
61
90
|
function applyActions(actions, { force = false } = {}) {
|
|
62
91
|
let changed = 0;
|
|
63
92
|
for (const a of actions) {
|
|
@@ -71,7 +100,7 @@ function applyActions(actions, { force = false } = {}) {
|
|
|
71
100
|
}
|
|
72
101
|
|
|
73
102
|
export async function runSetup(root, opts = {}) {
|
|
74
|
-
const total =
|
|
103
|
+
const total = 8;
|
|
75
104
|
log(c.bold(`\nSDLC Workflow setup ${c.dim('v' + VERSION)}`));
|
|
76
105
|
log(c.dim(`target: ${root}`));
|
|
77
106
|
|
|
@@ -131,8 +160,26 @@ export async function runSetup(root, opts = {}) {
|
|
|
131
160
|
ok(`wrote ${PROJECT_FILES.hubConfig} (${roster.length} reviewer(s))`);
|
|
132
161
|
}
|
|
133
162
|
|
|
134
|
-
// 4. Connect
|
|
135
|
-
step(4, total, 'Connect
|
|
163
|
+
// 4. Connect a design tool (Figma-first, pluggable; the UI step materializes the design here)
|
|
164
|
+
step(4, total, 'Connect a design tool (Figma / pencil / none)');
|
|
165
|
+
const designPath = path.join(root, PROJECT_FILES.designConfig);
|
|
166
|
+
if (exists(designPath) && !(await askYesNo('design.json exists β reconfigure?', false))) {
|
|
167
|
+
info('keeping existing .sdlc/design.json');
|
|
168
|
+
} else {
|
|
169
|
+
let tool = (await ask(`Design tool (${DESIGN_TOOLS.join('/')}/none)`, DESIGN_PRIMARY)).toLowerCase();
|
|
170
|
+
if (![...DESIGN_TOOLS, 'none'].includes(tool)) {
|
|
171
|
+
warn(`unknown design tool '${tool}' β using ${DESIGN_PRIMARY}`);
|
|
172
|
+
tool = DESIGN_PRIMARY;
|
|
173
|
+
}
|
|
174
|
+
const project_url = tool === 'none' ? null : (await ask(' project/file URL (blank to set later)', '')) || null;
|
|
175
|
+
registerDesign(root, { tool, project_url, today: opts.today ?? null });
|
|
176
|
+
ok(tool === 'none'
|
|
177
|
+
? `wrote ${PROJECT_FILES.designConfig} (markdown-only)`
|
|
178
|
+
: `wrote ${PROJECT_FILES.designConfig} (${tool})`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 5. Connect code repos
|
|
182
|
+
step(5, total, 'Connect code repos');
|
|
136
183
|
const regPath = path.join(root, PROJECT_FILES.reposRegistry);
|
|
137
184
|
const registry = readJSON(regPath, { repos: [] });
|
|
138
185
|
const known = new Set(registry.repos.map((r) => r.name));
|
|
@@ -155,8 +202,8 @@ export async function runSetup(root, opts = {}) {
|
|
|
155
202
|
}
|
|
156
203
|
}
|
|
157
204
|
|
|
158
|
-
//
|
|
159
|
-
step(
|
|
205
|
+
// 6. Wire each connected repo + the hub itself
|
|
206
|
+
step(6, total, 'Wire connected repos + the hub (CI gates, PR template, comment scaffold, gate-sync)');
|
|
160
207
|
if (registry.repos.length === 0) info('no repos to wire');
|
|
161
208
|
for (const repo of registry.repos) {
|
|
162
209
|
log(` ${c.bold(repo.name)} ${c.dim(`(${repo.platform})`)}`);
|
|
@@ -171,8 +218,8 @@ export async function runSetup(root, opts = {}) {
|
|
|
171
218
|
// author allowlists for the verified-commits gate (hub + every repo), from the roster emails
|
|
172
219
|
applyActions(authorsActions(root, registry.repos), { force: true });
|
|
173
220
|
|
|
174
|
-
//
|
|
175
|
-
step(
|
|
221
|
+
// 7. Optional CodeRabbit
|
|
222
|
+
step(7, total, 'AI review (CodeRabbit)');
|
|
176
223
|
for (const repo of registry.repos) {
|
|
177
224
|
const cr = path.join(path.resolve(root, repo.path), '.coderabbit.yaml');
|
|
178
225
|
if (exists(cr)) { info(`${repo.name}: .coderabbit.yaml present`); continue; }
|
|
@@ -182,13 +229,17 @@ export async function runSetup(root, opts = {}) {
|
|
|
182
229
|
}
|
|
183
230
|
}
|
|
184
231
|
|
|
185
|
-
//
|
|
186
|
-
step(
|
|
232
|
+
// 8. Summary + version stamp
|
|
233
|
+
step(8, total, 'Done');
|
|
187
234
|
writeJSON(path.join(root, PROJECT_FILES.version), { version: VERSION, ideTargets, updatedAt: opts.today ?? null });
|
|
188
235
|
ok(`stamped ${PROJECT_FILES.version} (v${VERSION})`);
|
|
189
236
|
log('');
|
|
190
237
|
log(c.bold('Next β AI-only steps (run in Claude Code):'));
|
|
191
238
|
hand('generate code-maps: run `yad-connect-repos` for each connected repo');
|
|
239
|
+
const design = readJSON(designPath, null);
|
|
240
|
+
if (design && design.tool && design.tool !== 'none') {
|
|
241
|
+
hand(`confirm the design tool: run \`yad-connect-design\` to detect the ${design.tool} MCP (or it degrades to markdown-only)`);
|
|
242
|
+
}
|
|
192
243
|
hand('author your first epic: run `yad-epic`');
|
|
193
244
|
log('');
|
|
194
245
|
log(c.dim('Re-run anytime: `yad check` (report) / `yad check --fix` (reconcile).'));
|
package/docs/index.html
CHANGED
|
@@ -284,12 +284,14 @@
|
|
|
284
284
|
<div class="lane">
|
|
285
285
|
<div class="lane-title">0 Β· One-time setup (team lead, per project)</div>
|
|
286
286
|
<div class="flow-v">
|
|
287
|
-
<div class="node plain"><strong>Install the module</strong><small><code>npx yadflow setup</code> β copies the
|
|
287
|
+
<div class="node plain"><strong>Install the module</strong><small><code>npx yadflow setup</code> β copies the 18 skills into your IDE dirs</small></div>
|
|
288
288
|
<div class="arrow-v">βΌ</div>
|
|
289
289
|
<div class="node plain"><strong>Wire each repo</strong><small><code>yad-checks</code> Β· <code>yad-pr-template</code> Β· <code>yad-review-comments</code> (CI gates, PR template, comment scaffold)</small></div>
|
|
290
290
|
<div class="arrow-v">βΌ</div>
|
|
291
291
|
<div class="node plain"><strong>Connect code repos</strong><small><code>yad-connect-repos</code> β <code>repos.json</code> + a cached code-map per repo <em>(skip if greenfield)</em></small></div>
|
|
292
292
|
<div class="arrow-v">βΌ</div>
|
|
293
|
+
<div class="node plain"><strong>Connect a design tool</strong><small><code>yad-connect-design</code> β <code>design.json</code> (Figma-first, pluggable) so <code>yad-ui</code> can materialize the screens <em>(optional β degrades to markdown-only)</em></small></div>
|
|
294
|
+
<div class="arrow-v">βΌ</div>
|
|
293
295
|
<div class="node plain"><strong>Optional: hub on a platform</strong><small>detect GitHub/GitLab + reviewer roster, so reviews run on real PRs/MRs</small></div>
|
|
294
296
|
</div>
|
|
295
297
|
</div>
|
|
@@ -311,7 +313,7 @@
|
|
|
311
313
|
<div class="arrow-v">βΌ</div>
|
|
312
314
|
<div class="node gate">gate Β· architecture review<small><strong>escalated:</strong> base rule + a domain owner for every repo in the epic</small></div>
|
|
313
315
|
<div class="arrow-v">βΌ</div>
|
|
314
|
-
<div class="node artifact"><strong>yad-ui</strong><small><code>ui-design.md</code> + <code>DESIGN.md</code
|
|
316
|
+
<div class="node artifact"><strong>yad-ui</strong><small><code>ui-design.md</code> + <code>DESIGN.md</code> + the screens in the connected design tool (<code>design-links.json</code>)</small></div>
|
|
315
317
|
<div class="arrow-v">βΌ</div>
|
|
316
318
|
<div class="node gate">gate Β· UI review<small>base rule</small></div>
|
|
317
319
|
<div class="arrow-v">βΌ</div>
|
|
@@ -367,11 +369,11 @@
|
|
|
367
369
|
epic.md the feature, agreed
|
|
368
370
|
architecture.md how it will be built
|
|
369
371
|
contract.md the LOCKED shared surface (APIs, events, data model)
|
|
370
|
-
ui-design.md + DESIGN.md
|
|
372
|
+
ui-design.md + DESIGN.md (+ the screens in the connected design tool)
|
|
371
373
|
stories/ one file per story (EP-<slug>-S01.md β¦)
|
|
372
374
|
reviews/ reviewer comments, per artifact
|
|
373
375
|
.sdlc/ state.json Β· approvals.json Β· contract-lock.json
|
|
374
|
-
build-state/ Β· trust-log.json Β· build-log.json
|
|
376
|
+
design-links.json Β· build-state/ Β· trust-log.json Β· build-log.json
|
|
375
377
|
|
|
376
378
|
each code repo/
|
|
377
379
|
specs/<story-id>/ spec, plan, tasks (+ link.md back to the story)
|
|
@@ -438,7 +440,7 @@ each code repo/
|
|
|
438
440
|
<div class="term"><strong>State machine</strong><span class="badge b-core">core</span><br>The core design: the workflow is a fixed set of <em>states</em> (steps). Each state does its work and waits at a gate. The states never change β only the <em>trigger</em> (who moves work forward) changes. That is what lets the team start fully manual and automate gradually without rebuilding anything.</div>
|
|
439
441
|
<div class="term"><strong>Product hub (product repo)</strong><span class="badge b-core">core</span><br>The git repo that holds the shared βthinkingβ: epics, architecture, the contract, UI design, stories, reviews and all state. The brain of the project. Code never lives here.</div>
|
|
440
442
|
<div class="term"><strong>Code repo</strong><span class="badge b-core">core</span><br>A separate git repo holding real application code (e.g. backend, mobile, dashboard). Each storyβs spec lives inside the code repo it belongs to, with a link back to the story in the hub.</div>
|
|
441
|
-
<div class="term"><strong>Skills source (this repo)</strong><span class="badge b-core">core</span><br>The <code>yadflow</code> repo itself β where the
|
|
443
|
+
<div class="term"><strong>Skills source (this repo)</strong><span class="badge b-core">core</span><br>The <code>yadflow</code> repo itself β where the 18 skills live and where you pull updates from. No real product work happens inside it.</div>
|
|
442
444
|
<div class="term"><strong>Front half (βdecideβ / the brain)</strong><span class="badge b-core">core</span><br>The first half of the workflow, run once per epic in the product hub: analysis (optional) β epic β architecture + contract β UI design β stories β each followed by a human review gate. Permanently human-approved.</div>
|
|
443
445
|
<div class="term"><strong>Back half / build half (βbuildβ)</strong><span class="badge b-core">core</span><br>The second half, run once per story per code repo, inside that repo: spec β implement β checks β PR β ship. These mechanical steps may earn automation over time.</div>
|
|
444
446
|
<div class="term"><strong>Gate</strong><span class="badge b-core">core</span><br>A stopping point after a step. The step writes its output and <em>waits</em>; a human must approve before the workflow moves on. βEvery step stops at a gateβ is the heart of the whole system.</div>
|
|
@@ -529,7 +531,7 @@ each code repo/
|
|
|
529
531
|
<div class="term"><strong>CodeRabbit</strong><span class="badge b-tool">tools</span><br>An AI code-review service used as the advisory first pass on PRs. Optional; never the merge authority.</div>
|
|
530
532
|
<div class="term"><strong>Graceful degradation</strong><span class="badge b-tool">tools</span><br>The rule that every optional tool (Spec Kit, Impeccable, Repomix, CodeRabbit) can be absent: the workflow falls back to doing the work directly and <em>records</em> that the tool was missing. You can start with none of them.</div>
|
|
531
533
|
<div class="term"><strong>Tool-agnostic rule</strong><span class="badge b-tool">tools</span><br>Talk to every tool through its commands and files, never through its internal code β so each tool stays swappable.</div>
|
|
532
|
-
<div class="term"><strong>Skill</strong><span class="badge b-tool">tools</span><br>One of the
|
|
534
|
+
<div class="term"><strong>Skill</strong><span class="badge b-tool">tools</span><br>One of the 18 named agents (e.g. <code>yad-epic</code>) you invoke by name in your AI IDE. Each skill does one stepβs work, writes files, and stops at its gate.</div>
|
|
533
535
|
<div class="term"><strong>The <code>yad</code> CLI</strong><span class="badge b-tool">tools</span><br>A zero-dependency command-line tool (npm: <code>yadflow</code>) that installs, wires and reconciles the workflow (<code>setup</code>, <code>check --fix</code>, <code>update</code>) and runs the deterministic pieces (<code>gate</code>, <code>commit</code>, <code>open-pr</code>, <code>repo</code>).</div>
|
|
534
536
|
|
|
535
537
|
<h3>People & AI roles</h3>
|
|
@@ -672,7 +674,7 @@ each code repo/
|
|
|
672
674
|
|
|
673
675
|
<!-- ======================================================= -->
|
|
674
676
|
<section id="skills">
|
|
675
|
-
<h2>6. The
|
|
677
|
+
<h2>6. The 18 skills β what each does, how to use it, and when</h2>
|
|
676
678
|
<p>A <strong>skill</strong> is an agent you invoke <em>by name</em> in your AI IDE β you just ask in
|
|
677
679
|
plain words, e.g. <em>βrun <code>yad-epic</code>β</em>, adding any inputs the skill needs
|
|
678
680
|
(<code>repo: backend</code>, <code>story: EP-β¦-S01</code>, <code>action: wire</code>, β¦).
|
|
@@ -682,10 +684,11 @@ each code repo/
|
|
|
682
684
|
<table>
|
|
683
685
|
<tr><th>Skill</th><th>Half</th><th>One line</th></tr>
|
|
684
686
|
<tr><td><a href="#sk-connect"><code>yad-connect-repos</code></a></td><td>Setup</td><td>Register a code repo with the hub + cache its code-map.</td></tr>
|
|
687
|
+
<tr><td><a href="#sk-connect-design"><code>yad-connect-design</code></a></td><td>Setup</td><td>Connect a design tool (Figma-first) so <code>yad-ui</code> can materialize the screens.</td></tr>
|
|
685
688
|
<tr><td><a href="#sk-analysis"><code>yad-analysis</code></a></td><td>Front</td><td><em>(Optional)</em> pressure-test an idea into <code>analysis.md</code>.</td></tr>
|
|
686
689
|
<tr><td><a href="#sk-epic"><code>yad-epic</code></a></td><td>Front</td><td>Start a feature: write <code>epic.md</code>, assign the ID.</td></tr>
|
|
687
690
|
<tr><td><a href="#sk-arch"><code>yad-architecture</code></a></td><td>Front</td><td>Author <code>architecture.md</code> + the locked <code>contract.md</code>.</td></tr>
|
|
688
|
-
<tr><td><a href="#sk-ui"><code>yad-ui</code></a></td><td>Front</td><td>Author <code>ui-design.md</code> + <code>DESIGN.md</code
|
|
691
|
+
<tr><td><a href="#sk-ui"><code>yad-ui</code></a></td><td>Front</td><td>Author <code>ui-design.md</code> + <code>DESIGN.md</code>; materialize the screens in the connected design tool.</td></tr>
|
|
689
692
|
<tr><td><a href="#sk-stories"><code>yad-stories</code></a></td><td>Front</td><td>Break the epic into repo-tagged stories.</td></tr>
|
|
690
693
|
<tr><td><a href="#sk-gate"><code>yad-review-gate</code></a></td><td>Cross-cutting</td><td>Review / comment / approve / advance <strong>any</strong> gate.</td></tr>
|
|
691
694
|
<tr><td><a href="#sk-bridge"><code>yad-hub-bridge</code></a></td><td>Cross-cutting</td><td>Run front-half reviews over real PRs/MRs and sync them back.</td></tr>
|
|
@@ -715,6 +718,17 @@ yad-connect-repos action: roster login: alice-gh name: alice role: owner # onc
|
|
|
715
718
|
<div class="row"><span class="lbl lbl-when">When</span> During one-time setup, and again any time you add a new code repo. Re-run <code>refresh</code> when a skill warns a repo is stale. <strong>Greenfield with no code yet? Skip it entirely</strong> β the brain proceeds without it.</div>
|
|
716
719
|
</div>
|
|
717
720
|
|
|
721
|
+
<div class="skillcard" id="sk-connect-design">
|
|
722
|
+
<h4><code>yad-connect-design</code></h4>
|
|
723
|
+
<div class="row"><span class="lbl lbl-what">What</span> Connects a <strong>design tool</strong> to the product hub so the UI step can materialize the <em>actual</em> feature design β mobile screens / web pages β inside it, alongside the Markdown. <strong>Figma-first but pluggable</strong> (a design-tool adapter; <code>pencil</code> is a second provider). It records the tool + project/file references in <code>design.json</code> through your own authenticated MCP session β <strong>no tokens are ever stored</strong> β and degrades to markdown-only when no design-tool MCP is available.</div>
|
|
724
|
+
<div class="row"><span class="lbl lbl-how">How</span> Run it in the product hub, once per project:</div>
|
|
725
|
+
<pre><code>yad-connect-design action: connect tool: figma project_url: https://figma.com/files/project/β¦
|
|
726
|
+
yad-connect-design action: list # show the connection + whether the MCP is available
|
|
727
|
+
yad-connect-design action: refresh # re-detect the MCP (after authenticating a session)
|
|
728
|
+
yad-connect-design action: disconnect</code></pre>
|
|
729
|
+
<div class="row"><span class="lbl lbl-when">When</span> During one-time setup (the wizard records it), and again any time you switch design tools. <strong>No design tool? Skip it</strong> β <code>yad-ui</code> simply authors the Markdown, exactly as before.</div>
|
|
730
|
+
</div>
|
|
731
|
+
|
|
718
732
|
<h3>Front half β author the thinking (in the product hub)</h3>
|
|
719
733
|
|
|
720
734
|
<div class="skillcard" id="sk-analysis">
|
|
@@ -743,7 +757,7 @@ yad-connect-repos action: roster login: alice-gh name: alice role: owner # onc
|
|
|
743
757
|
|
|
744
758
|
<div class="skillcard" id="sk-ui">
|
|
745
759
|
<h4><code>yad-ui</code></h4>
|
|
746
|
-
<div class="row"><span class="lbl lbl-what">What</span> With the ux-designer AI, authors the UI design for the epic: <code>ui-design.md</code> (this featureβs screens and flows) + <code>DESIGN.md</code> (the reusable design language). Drives Impeccableβs <code>document / extract / craft</code> commands when installed; writes the files directly when not (and records that).</div>
|
|
760
|
+
<div class="row"><span class="lbl lbl-what">What</span> With the ux-designer AI, authors the UI design for the epic: <code>ui-design.md</code> (this featureβs screens and flows) + <code>DESIGN.md</code> (the reusable design language). Drives Impeccableβs <code>document / extract / craft</code> commands when installed; writes the files directly when not (and records that). When a design tool is connected (<code>yad-connect-design</code>), it also <strong>materializes the screens in that tool</strong> β generating them from the spec or linking an existing design β and records the screenβframe map in <code>design-links.json</code>; otherwise it stays markdown-only.</div>
|
|
747
761
|
<div class="row"><span class="lbl lbl-how">How</span> In the product hub, after the architecture gate passes:</div>
|
|
748
762
|
<pre><code>run yad-ui β epic: EP-istifta-inquiries</code></pre>
|
|
749
763
|
<div class="row"><span class="lbl lbl-when">When</span> After architecture, before stories. Skip-able only if the epic genuinely has no UI. Stops at the <strong>UI review gate</strong> (base rule).</div>
|
|
@@ -883,7 +897,7 @@ yad-status EP-istifta-inquiries # one epic in detail</code></pre>
|
|
|
883
897
|
|
|
884
898
|
<div class="skillcard">
|
|
885
899
|
<h4><code>npx yadflow setup</code></h4>
|
|
886
|
-
<div class="row"><span class="lbl lbl-what">What</span> The guided first-run wizard.
|
|
900
|
+
<div class="row"><span class="lbl lbl-what">What</span> The guided first-run wizard. Eight steps: preflight (git/node check), install all 18 skills into the IDE dirs you pick, detect the hubβs platform + record the reviewer roster, connect a design tool (Figma-first; optional), connect your code repos, wire each one (CI gates, PR template, comment scaffold), optionally write the AI-review config, and stamp the installed version.</div>
|
|
887
901
|
<div class="row"><span class="lbl lbl-how">How</span></div>
|
|
888
902
|
<pre><code>cd <product-hub-repo>
|
|
889
903
|
npx yadflow setup</code></pre>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yadflow",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.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, repo). A BMAD module + 17 yad-* skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "AbdelRahman Nasr",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"bin/",
|
|
21
21
|
"cli/",
|
|
22
22
|
"!cli/test.mjs",
|
|
23
|
+
"!cli/test-checks.mjs",
|
|
23
24
|
"skills/",
|
|
24
25
|
"docs/index.html",
|
|
25
26
|
"README.md",
|
|
@@ -35,7 +36,10 @@
|
|
|
35
36
|
},
|
|
36
37
|
"scripts": {
|
|
37
38
|
"yad": "node bin/yad.mjs",
|
|
38
|
-
"
|
|
39
|
+
"lint": "eslint cli bin",
|
|
40
|
+
"test": "node --test cli/test.mjs cli/test-checks.mjs",
|
|
41
|
+
"test:e2e": "bash test/e2e/run.sh",
|
|
42
|
+
"coverage": "node --test --experimental-test-coverage --test-coverage-exclude='cli/test*.mjs' --test-coverage-lines=70 --test-coverage-branches=70 cli/test.mjs cli/test-checks.mjs",
|
|
39
43
|
"diagrams": "npx -y @mermaid-js/mermaid-cli -i docs/diagrams/sdlc-overview.mmd -o docs/diagrams/sdlc-overview.svg -b transparent && npx -y @mermaid-js/mermaid-cli -i docs/diagrams/review-loop.mmd -o docs/diagrams/review-loop.svg -b transparent",
|
|
40
44
|
"prepublishOnly": "npm test"
|
|
41
45
|
},
|
|
@@ -49,7 +53,9 @@
|
|
|
49
53
|
"spec-driven-development"
|
|
50
54
|
],
|
|
51
55
|
"devDependencies": {
|
|
56
|
+
"@eslint/js": "^9.39.4",
|
|
52
57
|
"@semantic-release/changelog": "^6.0.3",
|
|
58
|
+
"eslint": "^9.39.4",
|
|
53
59
|
"semantic-release": "^25.0.3"
|
|
54
60
|
}
|
|
55
61
|
}
|
package/skills/sdlc/config.yaml
CHANGED
|
@@ -118,6 +118,22 @@ code_context:
|
|
|
118
118
|
# works for both github and gitlab (and self-hosted), and stores NO tokens in the registry.
|
|
119
119
|
platforms: [github, gitlab]
|
|
120
120
|
|
|
121
|
+
# Design tool (yad-connect-design) β the UI design step (yad-ui) materializes the actual feature design
|
|
122
|
+
# (mobile screens / web pages) inside a connected design tool, alongside ui-design.md + DESIGN.md. The
|
|
123
|
+
# tool is reached through its MCP (a harness MCP server, like Impeccable's slash-commands β NOT a CLI),
|
|
124
|
+
# detected per provider and DEGRADING to markdown-only when absent. Figma-first but PLUGGABLE: a
|
|
125
|
+
# design-tool adapter, like the github/gitlab platform adapter. Connection is one-per-project, recorded
|
|
126
|
+
# at setup; the per-epic screen->frame map (design-links.json) is written by yad-ui per epic.
|
|
127
|
+
design:
|
|
128
|
+
registry: "{project-root}/.sdlc/design.json" # project-wide design connection (NOT per-epic)
|
|
129
|
+
tools: [figma, pencil] # supported adapters; an unknown tool falls back to `primary`
|
|
130
|
+
primary: figma # the default/named provider
|
|
131
|
+
degrade: markdown-only # no tool / no MCP => yad-ui authors ui-design.md + DESIGN.md only
|
|
132
|
+
links: "{project-root}/epics/EP-<slug>/.sdlc/design-links.json" # per-epic screen->frame map (yad-ui)
|
|
133
|
+
# Auth: `yad-connect-design` connects through the LOCAL user's own authenticated MCP session and stores
|
|
134
|
+
# NO tokens in the registry (project_url/files are plain references, never credentials).
|
|
135
|
+
auth: user
|
|
136
|
+
|
|
121
137
|
# Hub platform + front-half review bridge (yad-connect-repos `detect-hub`; yad-review-gate + yad-hub-bridge).
|
|
122
138
|
# The product hub is itself a git repo on a platform. With the bridge enabled, the front-half review/
|
|
123
139
|
# comment/approval cycle runs through a real PR/MR on the hub: a review PR is opened per artifact, reviewers
|
package/skills/sdlc/install.sh
CHANGED
|
@@ -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-analysis yad-epic yad-architecture yad-ui yad-stories yad-connect-repos yad-spec yad-implement yad-checks yad-pr-template yad-review-comments yad-hub-bridge yad-ship yad-backfill yad-run yad-review-gate yad-status)
|
|
14
|
+
SKILLS=(yad-analysis yad-epic yad-architecture yad-ui yad-stories yad-connect-repos yad-connect-design yad-spec yad-implement yad-checks yad-pr-template yad-review-comments yad-hub-bridge yad-ship yad-backfill yad-run yad-review-gate yad-status)
|
|
15
15
|
|
|
16
16
|
echo "Installing sdlc module from $ROOT/skills ..."
|
|
17
17
|
|
|
@@ -5,6 +5,7 @@ SDLC Workflow,yad-architecture,Author Architecture,AA,"Front state 3: with the a
|
|
|
5
5
|
SDLC Workflow,yad-ui,Author UI Design,AU,"Front state 5: with the ux-designer author ui-design.md and DESIGN.md, driving Impeccable slash-commands when installed. Never auto-advances.",,{epic: EP-<slug>},1-front,yad-review-gate,yad-review-gate,true,epics/EP-<slug>/,ui-design.md DESIGN.md state.json
|
|
6
6
|
SDLC Workflow,yad-stories,Author Stories,AS,"Front state 7: with the pm break the epic into repo-tagged stories with stable EP-<slug>-S0N IDs, one file each under stories/. Never auto-advances.",,{epic: EP-<slug>},1-front,yad-review-gate,yad-review-gate,true,epics/EP-<slug>/stories/,stories/EP-<slug>-S0N.md state.json
|
|
7
7
|
SDLC Workflow,yad-connect-repos,Connect Code Repos,CR,"Setup/maintenance: connect code repos to the product hub so the front/brain phases are code-aware. Registers each repo (GitHub or GitLab, local-user auth, no stored tokens) in .sdlc/repos.json and caches a Repomix pack + a lightweight code-map (existing endpoints/events/data-models/modules, secret-scanned). Idempotent and refreshable; staleness tracked by HEAD sha. Run at setup or any time a new repo is added. Not a gated state β never touches epic state or approvals.",,{action: connect|refresh|list|disconnect} {repo: <name>} {path: <path-or-absolute>} {git_url: <ssh-or-https>} {domain_owner: <who>},0-setup,,yad-epic,false,.sdlc/,repos.json code-context/<repo>/pack.md code-context/<repo>/code-map.md
|
|
8
|
+
SDLC Workflow,yad-connect-design,Connect Design Tool,CD,"Setup/maintenance: connect a design tool (Figma-first, pluggable) to the product hub so the UI design step can materialize the actual feature design (mobile screens / web pages) inside it, alongside ui-design.md + DESIGN.md. Records the tool + project/file references in .sdlc/design.json (local-user / MCP-session auth, no stored tokens), detecting whether a design-tool MCP is available and degrading to markdown-only when absent. Idempotent and refreshable; one connection per project. Not a gated state β never touches epic state or approvals.",,{action: connect|refresh|list|disconnect} {tool: figma|pencil|none} {project_url: <team/project/file url>} {files: {web,mobile}},0-setup,,yad-ui,false,.sdlc/,design.json
|
|
8
9
|
SDLC Workflow,yad-spec,Author Spec,SP,"Build-half Step A: for one ready-for-build story and one of its repos, run the heavy Spec Kit ceremony once (specifyβclarifyβplanβanalyzeβchecklistβtasks) inside that code repo, writing specs/<story-id>/ in Spec Kit's layout (drives /speckit.* when installed, else hand-authors and records speckit: not-installed). References the locked contract; never re-invents the surface. Writes link.md back to the story. Never auto-advances.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {repo: <one of story.repos>},3-build,yad-review-gate,,false,demo-repos/<repo>/specs/<story-id>/,spec.md research.md data-model.md contracts/ plan.md tasks.md link.md
|
|
9
10
|
SDLC Workflow,yad-implement,Implement Task,IM,"Build-half Step B: with the dev lens, implement ONE atomic task from a story's tasks.md as a small diff (<=3 files) on its own branch feat/<story>-<task>-<slug> in the code repo. Diff stays inside the task's declared files (flag and STOP if it grows beyond them). Commit ends with a Task: <story>-<task> trailer; add Contract-Change: yes only when the locked contract surface is touched (routes back to the architecture gate). Never auto-advances.",,{epic: EP-<slug>} {story: EP-<slug>-S0N} {repo: <one of story.repos>} {task: T0N},3-build,yad-spec,,false,demo-repos/<repo>/,branch+commit per atomic task
|
|
10
11
|
SDLC Workflow,yad-checks,Check Gates,CK,"Build-half Step C: wire and run the three production-safety CI gates on a code repo β spec-link (every change links a real story/spec via its Task trailer), contract-check (a contract-surface change without Contract-Change + an updated re-locked contract FAILS and routes back to the architecture gate), and build/test/lint. CI-agnostic bash invoked by GitHub Actions and GitLab CI. Blocking in CI; the human still owns the merge. Never auto-advances.",,{repo: <one of an epic's repos>} {action: wire|run} {base: target branch},3-build,yad-implement,,false,demo-repos/<repo>/,checks/*.sh .github/workflows/yad-checks.yml .gitlab-ci.yml
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yad-connect-design
|
|
3
|
+
description: 'Connects a design tool (Figma, or another tool β pluggable) to the product hub so the UI design step can materialize the full feature design (mobile screens / web pages) inside it, not just Markdown. Registers the tool into the project-wide .sdlc/design.json (local-user / MCP-session auth, no stored tokens), detecting whether a design-tool MCP is available and degrading to markdown-only when it is not. Run at setup or any time the design tool changes. Reusable, idempotent, refreshable. Use when the user says "connect Figma", "connect a design tool", "refresh the design connection", or "list the design connection".'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SDLC β Connect a Design Tool (make the UI step design-tool aware)
|
|
7
|
+
|
|
8
|
+
**Goal:** Let the UI design step (`yad-ui`) produce the **actual feature design** β the mobile screens
|
|
9
|
+
and/or web pages β inside a design tool such as **Figma**, alongside the Markdown artifacts
|
|
10
|
+
(`ui-design.md` / `DESIGN.md`). This skill **connects** a design tool to the product hub and records
|
|
11
|
+
*how* to reach it (the tool, the project/file references, which MCP renders it) β never a credential.
|
|
12
|
+
|
|
13
|
+
This is **setup/maintenance**, not a gated front state β it never touches `.sdlc/state.json` or any
|
|
14
|
+
epic's approvals. It only writes the project-wide design registry. `yad-ui` consumes it: when a tool is
|
|
15
|
+
connected and its MCP is available, the `ux-designer` lens **generates** screens into the tool (or
|
|
16
|
+
**links** an existing human-made design and reads it back); when nothing is connected, `yad-ui` runs
|
|
17
|
+
markdown-only exactly as before.
|
|
18
|
+
|
|
19
|
+
## Conventions
|
|
20
|
+
|
|
21
|
+
- `{project-root}` resolves from the project working directory (the **product hub**).
|
|
22
|
+
- The integration is **Figma-first but pluggable** (`config.yaml` `design.tools`): a design-tool
|
|
23
|
+
*adapter*, like the `github`/`gitlab` platform adapter. Figma is the primary provider; `pencil`
|
|
24
|
+
(the `.pen` web/mobile editor) is a second, write-capable provider; `none` β markdown-only.
|
|
25
|
+
- **The design tool is reached through its MCP** (a harness MCP server), NOT a subprocess CLI β the same
|
|
26
|
+
shape as Impeccable's slash-commands, not Repomix's `npx`. The skill detects the MCP and degrades when
|
|
27
|
+
it is absent; it never installs an MCP server.
|
|
28
|
+
- Registry: `{project-root}/.sdlc/design.json` (project-wide, shared across all epics β NOT per-epic),
|
|
29
|
+
the sibling of `.sdlc/repos.json` and `.sdlc/hub.json`.
|
|
30
|
+
- Per-epic screenβframe links are written later by `yad-ui` (`epics/EP-<slug>/.sdlc/design-links.json`),
|
|
31
|
+
not here.
|
|
32
|
+
- Speak in the configured `communication_language`; write documents in `document_output_language`.
|
|
33
|
+
|
|
34
|
+
## Inputs
|
|
35
|
+
|
|
36
|
+
- `action` β `connect` (default) | `refresh` | `list` | `disconnect`.
|
|
37
|
+
- `tool` β `figma` | `pencil` | another adapter id (`config.yaml` `design.tools`). `none` records a
|
|
38
|
+
deliberate markdown-only project.
|
|
39
|
+
- `project_url` β the design tool's team/project/file reference (e.g. a Figma project or file URL).
|
|
40
|
+
Optional β a connection with no file yet is valid; `yad-ui` can create one on first generate.
|
|
41
|
+
- `files` β optional default file mapping per platform (`{ web: <ref>, mobile: <ref> }`).
|
|
42
|
+
|
|
43
|
+
## On Activation
|
|
44
|
+
|
|
45
|
+
### Step 1 β Resolve the tool and its MCP (the design-tool adapter)
|
|
46
|
+
Determine which tool is being connected from `tool` (default `figma`); reject a `tool` not in
|
|
47
|
+
`config.yaml` `design.tools` (fall back to the configured `design.primary` with a warning, the same way
|
|
48
|
+
`registerRepo` falls back on an unknown platform). Then **detect the tool's MCP** in this harness:
|
|
49
|
+
|
|
50
|
+
- **figma** β a Figma MCP server (Dev Mode MCP for read/link; html.to.design for HTMLβFigma *generate*).
|
|
51
|
+
- **pencil** β the `pencil` MCP (`batch_design` writes `.pen` web/mobile screens β generate-capable).
|
|
52
|
+
- another adapter β its named MCP.
|
|
53
|
+
|
|
54
|
+
Record `provider` (the concrete MCP, e.g. `figma-mcp` | `html-to-design` | `pencil-mcp`) and whether it
|
|
55
|
+
is available. **Auth is the local user's own** β the user's authenticated MCP session. The skill
|
|
56
|
+
**stores no tokens**; `project_url`/`files` are plain references, never credentials.
|
|
57
|
+
|
|
58
|
+
**Graceful degradation:** if no design-tool MCP is available, record `source: "unavailable"` and report
|
|
59
|
+
that `yad-ui` will run **markdown-only** until an MCP is connected (no error β the design tool is purely
|
|
60
|
+
additive, exactly like Impeccable being absent). Do **not** install an MCP server as part of this step.
|
|
61
|
+
|
|
62
|
+
### Step 2 β Record the connection in the registry
|
|
63
|
+
Upsert into `{project-root}/.sdlc/design.json` (create the file + parent `.sdlc/` if absent):
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"tool": "figma",
|
|
68
|
+
"provider": "figma-mcp",
|
|
69
|
+
"project_url": "https://www.figma.com/files/project/<id>/<name>",
|
|
70
|
+
"auth": "user",
|
|
71
|
+
"files": { "web": null, "mobile": null },
|
|
72
|
+
"connectedAt": "<YYYY-MM-DD>",
|
|
73
|
+
"lastSyncedAt": "<YYYY-MM-DD>",
|
|
74
|
+
"source": "figma-mcp"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `tool: "none"` records a deliberate markdown-only project: `{ "tool": "none", "provider": null,
|
|
79
|
+
"source": "unavailable", ... }`.
|
|
80
|
+
- `connect` is **idempotent** β re-running it overwrites the single connection in place (a project has
|
|
81
|
+
one design tool at a time; switching tools is just another `connect`).
|
|
82
|
+
|
|
83
|
+
### Step 3 β Report (never auto-advance)
|
|
84
|
+
Report the connected `tool`, its `provider`, whether the MCP is available (or that `yad-ui` will degrade
|
|
85
|
+
to markdown-only), the `project_url`, and that **`yad-ui` will now generate/link the design here**.
|
|
86
|
+
Nothing auto-advances; this is setup.
|
|
87
|
+
|
|
88
|
+
## Other actions
|
|
89
|
+
|
|
90
|
+
- **`refresh`** β re-detect the MCP and update `lastSyncedAt` (after the user authenticates a session or
|
|
91
|
+
changes tools). Same machinery as `connect`. Re-detection may flip `source` between an MCP id and
|
|
92
|
+
`unavailable` β report the change.
|
|
93
|
+
- **`list`** β print the current connection: `tool`, `provider`, `project_url`, the file mapping, and a
|
|
94
|
+
**available/unavailable** flag for the MCP (best-effort, the user's own session). No design tool
|
|
95
|
+
connected β "markdown-only".
|
|
96
|
+
- **`disconnect`** β remove the registry file (or set `tool: "none"`). The design tool's own
|
|
97
|
+
project/files are **never touched** β only the hub's record of them.
|
|
98
|
+
|
|
99
|
+
## Hard rules
|
|
100
|
+
|
|
101
|
+
- **Local-user / MCP-session auth only; store no tokens.** Connect through the user's authenticated MCP
|
|
102
|
+
session; never embed a Figma PAT or any credential in the registry. `project_url`/`files` are plain
|
|
103
|
+
references.
|
|
104
|
+
- **Degrade gracefully.** No design tool / no MCP β `yad-ui` runs markdown-only with no error. The design
|
|
105
|
+
tool is additive, never a blocker β the same discipline as Impeccable and the `gh`/`glab` bridge.
|
|
106
|
+
- **Setup, not a gate.** Never touch `.sdlc/state.json`, approvals, or the contract lock from here.
|
|
107
|
+
- **Idempotent + refreshable.** `connect`/`refresh` are safe to re-run; a project carries one design
|
|
108
|
+
connection at a time.
|
|
109
|
+
- **Describe the connection; do not design here.** This skill records *how to reach* the tool. The actual
|
|
110
|
+
screens are generated/linked by `yad-ui`, per epic.
|
|
111
|
+
|
|
112
|
+
## Reference
|
|
113
|
+
- Registry schema + freshness rule: `references/design-registry.md`.
|
|
114
|
+
- MCP detection per provider, the generate-vs-link recipes, the degrade path, and the honest
|
|
115
|
+
write-vs-read-only MCP capability note: `references/design-context.md`.
|
|
116
|
+
- The connect pattern this mirrors (code repos): `../yad-connect-repos/SKILL.md`.
|
|
117
|
+
- The consumer β how `yad-ui` generates/links and writes `design-links.json`: `../yad-ui/SKILL.md`.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Design context β MCP detection, generate vs link, and the degrade path
|
|
2
|
+
|
|
3
|
+
How a connected design tool turns into the actual feature design the UI step materializes. The design
|
|
4
|
+
tool is reached through its **MCP** (a harness MCP server), the same shape as Impeccable's
|
|
5
|
+
slash-commands β detect it, use it when present, degrade cleanly when absent. This is the design-side
|
|
6
|
+
analogue of `yad-connect-repos`'s code-context.
|
|
7
|
+
|
|
8
|
+
## Provider detection
|
|
9
|
+
|
|
10
|
+
`connect`/`refresh` records `provider` (the concrete MCP) and `source` (the MCP id, or `unavailable`).
|
|
11
|
+
Detection is best-effort against the user's own authenticated MCP session:
|
|
12
|
+
|
|
13
|
+
| `tool` | MCP / provider | Capability |
|
|
14
|
+
|--------|----------------|------------|
|
|
15
|
+
| `figma` | a Figma Dev Mode MCP | **read/link** β reference a file, read frames back into `ui-design.md` |
|
|
16
|
+
| `figma` | html.to.design MCP (`import-html`) | **generate** β render HTML/CSS screens into a Figma file |
|
|
17
|
+
| `pencil` | the `pencil` MCP (`batch_design`) | **generate** β author `.pen` web/mobile screens directly |
|
|
18
|
+
| other | the adapter's named MCP | per that adapter |
|
|
19
|
+
|
|
20
|
+
**Honest capability note:** not every design-tool MCP can *write*. A read-only Figma Dev Mode MCP
|
|
21
|
+
supports **link + read-back** only; *generate* needs a write-capable provider (html.to.design for Figma,
|
|
22
|
+
or `pencil`). `yad-ui` picks the direction the connected provider actually supports and records which one
|
|
23
|
+
it used (`direction: generated | linked`). It never claims to have generated screens a read-only MCP
|
|
24
|
+
cannot produce.
|
|
25
|
+
|
|
26
|
+
## Generate (push screens into the tool)
|
|
27
|
+
|
|
28
|
+
When the connected provider is write-capable, the `ux-designer` lens produces the epic's screens in the
|
|
29
|
+
tool, covering the screens/states `ui-design.md` enumerates and the user flows the architecture defines:
|
|
30
|
+
|
|
31
|
+
- **Figma via html.to.design** β the lens drafts each screen as HTML/CSS (reusing `DESIGN.md` tokens),
|
|
32
|
+
then imports it into the Figma file via the MCP's `import-html`, one frame per screen.
|
|
33
|
+
- **pencil** β the lens calls `batch_design` to author the screens as `.pen` frames directly (mobile
|
|
34
|
+
and/or web per the epic).
|
|
35
|
+
|
|
36
|
+
Reuse what already exists: load the connected code repos' code-maps (`yad-ui` Step 2b) and any
|
|
37
|
+
Impeccable `DESIGN.md` tokens so generated screens match built components, not invented ones.
|
|
38
|
+
|
|
39
|
+
## Link (reference a human-made design)
|
|
40
|
+
|
|
41
|
+
When a designer has already built the screens (or the provider is read-only), point `yad-ui` at the
|
|
42
|
+
existing file and **read the frames back** so `ui-design.md` reflects the real design: list each frame as
|
|
43
|
+
a screen, capture its node id + URL, and map components/tokens into `DESIGN.md`.
|
|
44
|
+
|
|
45
|
+
## Write back the linkage (done by `yad-ui`, per epic)
|
|
46
|
+
|
|
47
|
+
Either direction ends by writing `epics/EP-<slug>/.sdlc/design-links.json` β the machine-readable
|
|
48
|
+
screenβframe map β and a `## Design (<tool>)` section in `ui-design.md` linking each screen to its frame
|
|
49
|
+
URL. The design itself lives in the tool; the hub keeps the *links* and the Markdown spec beside the
|
|
50
|
+
other epic artifacts.
|
|
51
|
+
|
|
52
|
+
## Degrade path (no MCP / no tool)
|
|
53
|
+
|
|
54
|
+
If `design.json` is absent, `tool: "none"`, or `source: "unavailable"`, `yad-ui` runs **markdown-only**:
|
|
55
|
+
it authors `ui-design.md` / `DESIGN.md` exactly as before and records `design: none` in the frontmatter
|
|
56
|
+
with a one-line note (mirroring the `impeccable: not-installed` degrade). No error β the design tool is
|
|
57
|
+
purely additive.
|
|
58
|
+
|
|
59
|
+
## Staleness / refresh
|
|
60
|
+
|
|
61
|
+
A re-generated or designer-edited file is like a moved code repo: `yad-ui` **flags** a divergence and
|
|
62
|
+
lets a human decide (re-run the step, or `yad-connect-design` action: refresh). It never silently
|
|
63
|
+
overwrites a designer's frames β refreshing the design is a human decision, the same discipline as
|
|
64
|
+
`code_context.refresh: human`.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Design registry β schema + freshness rule
|
|
2
|
+
|
|
3
|
+
The registry is the product hub's record of which design tool is connected and how to reach it. It is
|
|
4
|
+
**project-wide** (one design tool per project, shared across every epic), so it lives at the product
|
|
5
|
+
root, not under any `epics/EP-<slug>/.sdlc/`.
|
|
6
|
+
|
|
7
|
+
## Location
|
|
8
|
+
|
|
9
|
+
`{project-root}/.sdlc/design.json`
|
|
10
|
+
|
|
11
|
+
(`config.yaml` `design.registry`.) Create the file and its parent `.sdlc/` on the first `connect`.
|
|
12
|
+
|
|
13
|
+
## Schema
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"tool": "figma", // figma | pencil | <adapter id> | none (markdown-only)
|
|
18
|
+
"provider": "figma-mcp", // the concrete MCP: figma-mcp | html-to-design | pencil-mcp | null
|
|
19
|
+
"project_url": "https://www.figma.com/files/project/123/feature", // team/project/file reference; null if none yet
|
|
20
|
+
"auth": "user", // ALWAYS the user's own MCP session β never a token
|
|
21
|
+
"files": { "web": null, "mobile": null }, // optional default file refs per platform
|
|
22
|
+
"connectedAt": "2026-06-13", // first connect (YYYY-MM-DD)
|
|
23
|
+
"lastSyncedAt": "2026-06-13", // last connect/refresh
|
|
24
|
+
"source": "figma-mcp" // the MCP detected at connect | unavailable (degraded)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
- **`tool`** selects the adapter; it MUST be one of `config.yaml` `design.tools` (or `none`). An unknown
|
|
31
|
+
tool falls back to `design.primary` with a warning β never silently accepted.
|
|
32
|
+
- **Auth is never stored.** No Figma PAT, OAuth token, or any credential in the registry. `project_url`
|
|
33
|
+
and `files` are plain references; `connect` reaches the tool through the user's authenticated MCP
|
|
34
|
+
session.
|
|
35
|
+
- **`connect` overwrites in place** β a project carries exactly one design connection at a time;
|
|
36
|
+
switching tools is just another `connect`. There is no array (unlike `repos.json`).
|
|
37
|
+
- **`source`** is the authority for availability: an MCP id (`figma-mcp` / `pencil-mcp` / β¦) means
|
|
38
|
+
`yad-ui` can generate/link; `unavailable` means `yad-ui` degrades to markdown-only. `refresh`
|
|
39
|
+
re-detects and may flip it.
|
|
40
|
+
- **`tool: "none"`** is a valid, deliberate state: a project that has chosen markdown-only. `yad-ui`
|
|
41
|
+
treats it exactly like an absent registry.
|
|
42
|
+
- **`disconnect`** removes the file (or sets `tool: "none"`). The design tool's own project/files are
|
|
43
|
+
never touched.
|
|
44
|
+
|
|
45
|
+
## Git tracking
|
|
46
|
+
|
|
47
|
+
Commit the **registry** (`design.json`) β it is small, reviewable, and holds no secrets (references
|
|
48
|
+
only). This mirrors how `repos.json` and `hub.json` are committed.
|
|
49
|
+
|
|
50
|
+
## Greenfield
|
|
51
|
+
|
|
52
|
+
A brand-new product hub has no `design.json`. That is valid β `yad-ui` treats "no design tool connected"
|
|
53
|
+
the same as `tool: "none"` and produces the Markdown artifacts only. The registry appears the first time
|
|
54
|
+
`connect` runs.
|
|
@@ -86,6 +86,19 @@ PR/MR opened on the hub (sibling of `approvals.json`, so the locked `state.json`
|
|
|
86
86
|
{ "step": "<review step id>", "artifact": "<artifact>", "platform": "github|gitlab", "number": <n>, "url": "<pr/mr url>", "branch": "review/EP-<slug>/<artifact-base>", "lastSyncedAt": "<YYYY-MM-DD or null>" }
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
## `design-links.json`
|
|
90
|
+
Present only when the `ui-design` step materialized the design in a connected design tool
|
|
91
|
+
(`yad-connect-design` β `.sdlc/design.json`). Written by `yad-ui`, the machine-readable screenβframe map
|
|
92
|
+
(sibling of `contract-lock.json`; the locked `state.json` step shape is untouched). The `ui-design` step
|
|
93
|
+
chain is unchanged β this is an *output enrichment*, mirrored by the `design:` frontmatter block and the
|
|
94
|
+
`## Design (<tool>)` section in `ui-design.md`. Absent when the step ran markdown-only (`design: none`).
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{ "tool": "figma", "fileUrl": "<url>", "generatedAt": "<YYYY-MM-DD>", "direction": "generated|linked",
|
|
98
|
+
"screens": [ { "name": "<screen>", "platform": "mobile|web", "nodeId": "<id>", "url": "<frame url>" } ],
|
|
99
|
+
"source": "<mcp id>" }
|
|
100
|
+
```
|
|
101
|
+
|
|
89
102
|
## `reviews/`
|
|
90
103
|
Human-readable review records, one file per round:
|
|
91
104
|
`reviews/<artifact-base>--<YYYY-MM-DD>--<status>.md` where `status` β `comments` | `approved`
|
package/skills/yad-ui/SKILL.md
CHANGED
|
@@ -6,18 +6,28 @@ description: 'Front state 5 of the gated SDLC. With the ux-designer, author ui-d
|
|
|
6
6
|
# SDLC β Author UI Design (front state 5)
|
|
7
7
|
|
|
8
8
|
**Goal:** Produce a human-authored, AI-assisted `ui-design.md` and `DESIGN.md` for an approved
|
|
9
|
-
architecture
|
|
10
|
-
|
|
9
|
+
architecture **and**, when a design tool is connected, the **actual feature design** β the mobile
|
|
10
|
+
screens and/or web pages β inside that tool (e.g. Figma), linked back from the artifacts. This is a
|
|
11
|
+
**front state**: human-authored with AI assist, **never auto-advances**. When the UI is drafted, control
|
|
12
|
+
passes to `yad-review-gate` (base rule: owner + 1 reviewer).
|
|
11
13
|
|
|
12
14
|
UI work is shaped by **Impeccable**, invoked as **harness slash-commands** (not a subprocess CLI) per
|
|
13
15
|
the Phase 0 deviation. If Impeccable is not installed, the `ux-designer` lens authors the same outputs
|
|
14
16
|
directly β the workflow does not block on the tool.
|
|
15
17
|
|
|
18
|
+
The visual design is materialized in the **design tool connected via `yad-connect-design`**
|
|
19
|
+
(`.sdlc/design.json`), reached through its MCP. When a tool is connected the `ux-designer` lens
|
|
20
|
+
**generates** screens into it (or **links** an existing human-made design and reads it back); when none
|
|
21
|
+
is connected, the step degrades to the Markdown artifacts only β the design tool is additive, exactly
|
|
22
|
+
like Impeccable.
|
|
23
|
+
|
|
16
24
|
## Conventions
|
|
17
25
|
|
|
18
26
|
- `{project-root}` resolves from the project working directory.
|
|
19
27
|
- Artifacts live under `{project-root}/epics/EP-<slug>/` (build plan Β§6).
|
|
20
28
|
- `DESIGN.md` is Impeccable's conventional root design-system file (RESEARCH-NOTES Β§4).
|
|
29
|
+
- The connected design tool is recorded in `{project-root}/.sdlc/design.json` (`config.yaml` `design`),
|
|
30
|
+
written by `yad-connect-design`. The per-epic screenβframe map is `design-links.json` (Step 4b).
|
|
21
31
|
- Speak in the configured `communication_language`; write documents in `document_output_language`.
|
|
22
32
|
|
|
23
33
|
## On Activation
|
|
@@ -66,6 +76,25 @@ components/tokens into the design system; `/impeccable craft` is shape-then-buil
|
|
|
66
76
|
that Impeccable was not used**. Do not run `npx impeccable skills install` as part of this step β tool
|
|
67
77
|
installation is out of scope for the front half.
|
|
68
78
|
|
|
79
|
+
### Step 3b β Materialize the design in the connected tool (generate or link)
|
|
80
|
+
Read `{project-root}/.sdlc/design.json` (`config.yaml` `design.registry`). Decide the path:
|
|
81
|
+
|
|
82
|
+
- **No tool / `tool: "none"` / `source: "unavailable"`** (or the file is absent): **degrade** β
|
|
83
|
+
author the Markdown artifacts only and record `design: none` in the frontmatter with a one-line note
|
|
84
|
+
(mirrors the `impeccable: not-installed` degrade). Skip to Step 4.
|
|
85
|
+
- **A tool is connected and its MCP is available:** adopt the `ux-designer` lens and, using the provider
|
|
86
|
+
recorded in `design.json` (Figma via a Figma/html.to.design MCP, `pencil` via its MCP, etc.):
|
|
87
|
+
- **Generate** β when the provider is write-capable, produce one frame per screen the design covers,
|
|
88
|
+
for the platforms in `epic.repos` (mobile and/or web), reusing the code-maps (Step 2b) and
|
|
89
|
+
`DESIGN.md` tokens so screens match built components rather than inventing parallel ones.
|
|
90
|
+
- **Link** β when the user points at an existing design file (or the provider is read-only), reference
|
|
91
|
+
it and **read the frames back** so `ui-design.md` reflects the real design.
|
|
92
|
+
|
|
93
|
+
Capture each screen's `name`, `platform`, `nodeId`, and `url`. Record which direction was used
|
|
94
|
+
(`generated | linked`). Honest-capability rule: a read-only MCP supports **link** only β never claim a
|
|
95
|
+
screen was generated that the provider cannot produce. See
|
|
96
|
+
`../yad-connect-design/references/design-context.md`.
|
|
97
|
+
|
|
69
98
|
### Step 4 β Write the UI artifacts
|
|
70
99
|
Write `{project-root}/epics/EP-<slug>/ui-design.md` using EXACTLY this template:
|
|
71
100
|
|
|
@@ -78,6 +107,7 @@ owner: <inherit from epic.md owner> # the epic owner carries through; not rety
|
|
|
78
107
|
repos: [<inherit from epic>]
|
|
79
108
|
impeccable: <used | not-installed>
|
|
80
109
|
code-context: { repos: [], loaded: <YYYY-MM-DD or none> } # code-maps that informed component reuse (Step 2b)
|
|
110
|
+
design: <none | { tool: <figma|pencil|β¦>, direction: <generated|linked>, file: <url>, screens: <N> }> # the connected design tool (Step 3b)
|
|
81
111
|
---
|
|
82
112
|
|
|
83
113
|
## Screens & states
|
|
@@ -91,23 +121,52 @@ code-context: { repos: [], loaded: <YYYY-MM-DD or none> } # code-maps that inf
|
|
|
91
121
|
|
|
92
122
|
## Accessibility & responsiveness
|
|
93
123
|
<!-- a11y notes; breakpoints/viewports covered -->
|
|
124
|
+
|
|
125
|
+
## Design (<tool>)
|
|
126
|
+
<!-- omit this section when design: none. one row per screen, linking to its frame in the tool.
|
|
127
|
+
mirrors design-links.json (Step 4b). -->
|
|
128
|
+
<!-- - <Screen name> (<mobile|web>) β <frame url> -->
|
|
94
129
|
```
|
|
95
130
|
|
|
96
131
|
Also create/update `{project-root}/epics/EP-<slug>/DESIGN.md` (Impeccable's design-system file, or a
|
|
97
132
|
hand-authored equivalent when degraded) capturing the design tokens/components the screens rely on.
|
|
98
133
|
|
|
134
|
+
### Step 4b β Write the design-links map (when a tool was used)
|
|
135
|
+
When Step 3b generated or linked a design, write the machine-readable screenβframe map to
|
|
136
|
+
`{project-root}/epics/EP-<slug>/.sdlc/design-links.json` (sibling of `contract-lock.json`):
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"tool": "figma",
|
|
141
|
+
"fileUrl": "https://www.figma.com/file/<key>/<name>",
|
|
142
|
+
"generatedAt": "<YYYY-MM-DD>",
|
|
143
|
+
"direction": "generated | linked",
|
|
144
|
+
"screens": [
|
|
145
|
+
{ "name": "Submit Inquiry", "platform": "mobile",
|
|
146
|
+
"nodeId": "123:45", "url": "https://www.figma.com/file/<key>/?node-id=123-45" }
|
|
147
|
+
],
|
|
148
|
+
"source": "figma-mcp"
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Keep the `## Design (<tool>)` section of `ui-design.md` in step with this file. When Step 3b degraded
|
|
153
|
+
(`design: none`), do **not** write `design-links.json`.
|
|
154
|
+
|
|
99
155
|
### Step 5 β Advance the authoring step (NOT the gate)
|
|
100
156
|
In `state.json`: set `ui-design.status: "done"`, set `ui-design-review.status: "in_review"`, and set
|
|
101
157
|
`currentStep: "ui-design-review"`. Write `state.json`. Do **not** touch `approvals.json`.
|
|
102
158
|
|
|
103
159
|
### Step 6 β Stop at the gate (do NOT advance)
|
|
104
|
-
Report: the paths to `ui-design.md` and `DESIGN.md`, whether Impeccable was used,
|
|
105
|
-
|
|
106
|
-
|
|
160
|
+
Report: the paths to `ui-design.md` and `DESIGN.md`, whether Impeccable was used, the connected design
|
|
161
|
+
tool and what it produced (e.g. "Figma β 4 screens generated", the file URL + `design-links.json` path,
|
|
162
|
+
or "no design tool β markdown-only"), and that the next action is **review** via `yad-review-gate` (base
|
|
163
|
+
rule: owner + 1 reviewer). **Never record approval here.** Front states do not auto-advance. When the hub has a platform, the gate opens a review PR on the
|
|
107
164
|
hub (via `yad-hub-bridge`) and `yad-review-gate action: sync` pulls platform approvals/comments into
|
|
108
165
|
the ledger; otherwise the review is recorded file-only.
|
|
109
166
|
|
|
110
167
|
## Reference
|
|
111
168
|
- Impeccable commands and the slash-command-vs-CLI deviation: `RESEARCH-NOTES.md` Β§4 + Deviation 3.
|
|
112
169
|
- State schema and field meanings: `../yad-epic/references/state-schema.md`.
|
|
170
|
+
- Connecting a design tool (Figma, pluggable) + generate-vs-link recipes and the degrade path:
|
|
171
|
+
`../yad-connect-design/SKILL.md` (+ `references/design-context.md`).
|
|
113
172
|
- Connecting code repos + the code-context the brain reads: `../yad-connect-repos/SKILL.md`.
|