yadflow 3.18.1 → 4.0.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +355 -0
- package/README.md +79 -26
- package/bin/commands.mjs +41 -0
- package/bin/yad.mjs +437 -124
- package/cli/artifact-status.mjs +34 -15
- package/cli/checkpoint.mjs +69 -49
- package/cli/codeowners-command.mjs +170 -0
- package/cli/codeowners.mjs +397 -0
- package/cli/commit.mjs +13 -9
- package/cli/companion.mjs +2 -2
- package/cli/dial.mjs +183 -0
- package/cli/docs.mjs +88 -32
- package/cli/doctor.mjs +1472 -97
- package/cli/epic-state.mjs +3478 -232
- package/cli/epic.mjs +506 -0
- package/cli/errors.mjs +4 -1
- package/cli/gate.mjs +1002 -209
- package/cli/history.mjs +556 -0
- package/cli/hook.mjs +266 -55
- package/cli/hubcommit.mjs +6 -17
- package/cli/index-command.mjs +87 -0
- package/cli/ledger.mjs +57 -7
- package/cli/lib.mjs +184 -18
- package/cli/manifest.mjs +367 -56
- package/cli/migrate.mjs +726 -53
- package/cli/mode.mjs +170 -0
- package/cli/next.mjs +349 -90
- package/cli/openpr.mjs +191 -39
- package/cli/people.mjs +654 -0
- package/cli/plan.mjs +417 -132
- package/cli/platform.mjs +110 -129
- package/cli/product-index.mjs +287 -0
- package/cli/protection.mjs +706 -0
- package/cli/reconcile.mjs +38 -12
- package/cli/repo-publish.mjs +24 -26
- package/cli/repo.mjs +23 -14
- package/cli/report.mjs +21 -15
- package/cli/review.mjs +24 -27
- package/cli/riskmap-command.mjs +289 -0
- package/cli/riskmap.mjs +373 -0
- package/cli/setup.mjs +139 -287
- package/cli/ship.mjs +7 -6
- package/cli/skill.mjs +180 -0
- package/cli/skip.mjs +211 -30
- package/cli/thread.mjs +42 -17
- package/cli/tidy.mjs +20 -20
- package/cli/update-commit.mjs +22 -22
- package/cli/usage.mjs +115 -109
- package/package.json +3 -3
- package/skills/sdlc/config.yaml +166 -87
- package/skills/sdlc/module-help.csv +35 -35
- package/skills/yad-analysis/SKILL.md +125 -65
- package/skills/yad-architecture/SKILL.md +34 -23
- package/skills/yad-architecture/references/contract-format.md +10 -8
- package/skills/yad-backfill/SKILL.md +14 -8
- package/skills/yad-backfill/references/backfill.md +1 -1
- package/skills/yad-change/SKILL.md +127 -52
- package/skills/yad-change/references/triage.md +42 -28
- package/skills/yad-checks/SKILL.md +89 -45
- package/skills/yad-checks/references/check-gates.md +315 -92
- package/skills/yad-checks/templates/checks/build-test-lint.sh +25 -7
- package/skills/yad-checks/templates/checks/commit-message.sh +17 -3
- package/skills/yad-checks/templates/checks/contract-check.sh +58 -2
- package/skills/yad-checks/templates/checks/epic-open.sh +3 -3
- package/skills/yad-checks/templates/checks/install-deps.sh +46 -0
- package/skills/yad-checks/templates/checks/ledger-guard.sh +94 -18
- package/skills/yad-checks/templates/checks/lineage-check.sh +23 -9
- package/skills/yad-checks/templates/checks/package-manager.sh +140 -0
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +4 -4
- package/skills/yad-checks/templates/checks/risk-map-check.sh +438 -0
- package/skills/yad-checks/templates/checks/verified-commits.sh +20 -46
- package/skills/yad-checks/templates/github/yad-checks.yml +37 -5
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +5 -5
- package/skills/yad-checks/templates/github/yad-update-guard.yml +3 -4
- package/skills/yad-checks/templates/github/yad-verified-commits.yml +4 -4
- package/skills/yad-checks/templates/gitlab/.gitlab-ci.yml +7 -1
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +22 -4
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +5 -5
- package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +4 -4
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +91 -0
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +38 -7
- package/skills/yad-commit/SKILL.md +6 -6
- package/skills/yad-connect-design/SKILL.md +6 -6
- package/skills/yad-connect-design/references/design-context.md +1 -1
- package/skills/yad-connect-design/references/design-registry.md +2 -2
- package/skills/yad-connect-docs/SKILL.md +12 -12
- package/skills/yad-connect-docs/references/docs-registry.md +1 -1
- package/skills/yad-connect-learning/SKILL.md +5 -5
- package/skills/yad-connect-learning/references/learning-registry.md +2 -2
- package/skills/yad-connect-repos/SKILL.md +92 -54
- package/skills/yad-connect-repos/references/code-context.md +6 -6
- package/skills/yad-connect-repos/references/hub-config.md +68 -58
- package/skills/yad-connect-repos/references/repos-registry.md +10 -9
- package/skills/yad-connect-repos/references/risk-map.md +81 -0
- package/skills/yad-connect-testing/SKILL.md +6 -6
- package/skills/yad-connect-testing/references/testing-context.md +3 -4
- package/skills/yad-connect-testing/references/testing-registry.md +2 -2
- package/skills/yad-defects/SKILL.md +8 -8
- package/skills/yad-discovery/SKILL.md +130 -94
- package/skills/yad-discovery/references/discovery-schema.md +23 -7
- package/skills/yad-discovery/references/foundation-schema.md +374 -0
- package/skills/yad-docs/SKILL.md +16 -11
- package/skills/yad-docs/references/data-mapping.md +9 -7
- package/skills/yad-docs/templates/app/package-lock.json +3 -3
- package/skills/yad-docs-overview/SKILL.md +32 -17
- package/skills/yad-docs-overview/references/pipeline-model.md +47 -28
- package/skills/yad-docs-sync/SKILL.md +10 -5
- package/skills/yad-docs-sync/references/staleness.md +8 -7
- package/skills/yad-engineer-review/SKILL.md +88 -24
- package/skills/yad-engineer-review/references/ship-and-record.md +25 -16
- package/skills/yad-epic/SKILL.md +178 -100
- package/skills/yad-epic/references/state-schema.md +626 -117
- package/skills/yad-hub-bridge/SKILL.md +66 -48
- package/skills/yad-hub-bridge/references/bridge.md +110 -83
- package/skills/yad-hub-bridge/references/login-roster.md +163 -70
- package/skills/yad-hub-bridge/templates/checks/hub-route.sh +22 -19
- package/skills/yad-hub-bridge/templates/github/yad-gate-sync.yml +34 -14
- package/skills/yad-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +2 -2
- package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +22 -12
- package/skills/yad-implement/SKILL.md +29 -15
- package/skills/yad-implement/references/implement-conventions.md +2 -2
- package/skills/yad-learn/SKILL.md +9 -9
- package/skills/yad-learn/references/learning-state.md +2 -2
- package/skills/yad-open-pr/SKILL.md +64 -29
- package/skills/yad-pair-review/SKILL.md +18 -16
- package/skills/yad-pair-review/references/session-state.md +4 -4
- package/skills/yad-pr-template/SKILL.md +48 -27
- package/skills/yad-pr-template/references/risk-routing.md +97 -24
- package/skills/yad-pr-template/templates/checks/pr-template.sh +37 -15
- package/skills/yad-pr-template/templates/checks/pr-title.sh +27 -13
- package/skills/yad-pr-template/templates/checks/risk-route.sh +107 -14
- package/skills/yad-pr-template/templates/github/pull_request_template.md +7 -5
- package/skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md +7 -5
- package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +15 -14
- package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +15 -13
- package/skills/yad-reconcile/SKILL.md +3 -3
- package/skills/yad-report/SKILL.md +5 -5
- package/skills/yad-review-companion/SKILL.md +12 -9
- package/skills/yad-review-gate/SKILL.md +198 -79
- package/skills/yad-review-gate/references/gating.md +230 -54
- package/skills/yad-run/SKILL.md +86 -56
- package/skills/yad-run/references/run-loop.md +67 -45
- package/skills/yad-ship/SKILL.md +18 -14
- package/skills/yad-spec/SKILL.md +31 -17
- package/skills/yad-spec/references/spec-handoff.md +17 -5
- package/skills/yad-status/SKILL.md +114 -56
- package/skills/yad-stories/SKILL.md +42 -27
- package/skills/yad-stories/references/story-schema.md +10 -9
- package/skills/yad-stub/SKILL.md +59 -48
- package/skills/yad-sync-repos/SKILL.md +3 -3
- package/skills/yad-test-cases/SKILL.md +37 -30
- package/skills/yad-test-cases/references/test-cases-schema.md +8 -5
- package/skills/yad-timeline/SKILL.md +8 -7
- package/skills/yad-ui/SKILL.md +46 -25
- package/cli/roster.mjs +0 -164
- package/skills/sdlc/install.sh +0 -68
|
@@ -5,7 +5,7 @@ description: 'Brings every connected code repo up to date in one shot: switches
|
|
|
5
5
|
|
|
6
6
|
# SDLC — Sync Connected Repos (one command, every repo on its default branch)
|
|
7
7
|
|
|
8
|
-
**Goal:** Before
|
|
8
|
+
**Goal:** Before Shape/Build work starts, get every connected code repo onto its default branch at the
|
|
9
9
|
latest commit, so nobody implements on a stale or wrong branch. This is the deterministic counterpart to
|
|
10
10
|
the `sync-before-implementation` discipline: one command instead of N manual `git checkout && git pull`.
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ tree (branch + fast-forward). It writes **nothing** back to the registry.
|
|
|
15
15
|
|
|
16
16
|
## Conventions
|
|
17
17
|
|
|
18
|
-
- `{project-root}` resolves from the project working directory (the **
|
|
18
|
+
- `{project-root}` resolves from the project working directory (the **Product**).
|
|
19
19
|
- Registry: `{project-root}/.sdlc/repos.json` (project-wide; the same file `yad-connect-repos` writes).
|
|
20
20
|
Each repo entry supplies `path` (local path, relative to `{project-root}` or absolute) and
|
|
21
21
|
`default_branch` (the branch to land on).
|
|
@@ -64,7 +64,7 @@ Per repo: `switched to <branch>, pulled (ff)` / `already current` / `SKIPPED (..
|
|
|
64
64
|
so any repo whose `HEAD` now differs from its registry `syncedHead` has a **stale code-context pack** —
|
|
65
65
|
the command ends by pointing the human at `yad repo refresh` to repack (that is a separate human
|
|
66
66
|
decision; this skill never repacks or writes the registry). After the repack + AI code-map
|
|
67
|
-
regeneration, `yad repo refresh --push` publishes the refreshed code-maps + registry to the
|
|
67
|
+
regeneration, `yad repo refresh --push` publishes the refreshed code-maps + registry to the Product's
|
|
68
68
|
default branch as a `chore(hub): sync code-context … [skip ci]` audit commit.
|
|
69
69
|
|
|
70
70
|
## Hard rules
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-test-cases
|
|
3
|
-
description: '
|
|
3
|
+
description: 'Shape step 9 of the gated SDLC — a PARALLEL, non-blocking track. Opens when the stories gate passes (the epic is already ready-for-build, so Build can start at the same time) and runs alongside implementation. With the test architect, author test-cases.md for the approved stories, and — when a testing tool is connected — generate/link the actual automation tests in it; otherwise produce the test-case artifact only. Reads epic + architecture + contract + UI + stories as input. Never auto-advances — hands off to the team review gate. Use when the user says "author the test cases" or after the stories gate passes.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# SDLC — Author Test Cases (
|
|
6
|
+
# SDLC — Author Test Cases (Shape step 9 — parallel, non-blocking)
|
|
7
7
|
|
|
8
8
|
**Goal:** Produce a human-authored, AI-assisted `test-cases.md` for an approved epic — the risk-based
|
|
9
9
|
test cases that cover the stories' acceptance criteria — **and**, when a testing tool is connected, the
|
|
10
10
|
**actual automation tests** inside the connected code repo(s), linked back from the artifact. This is a
|
|
11
|
-
**
|
|
12
|
-
drafted, control passes to `yad-review-gate` (base rule:
|
|
11
|
+
**Shape step**: human-authored with AI assist, **never auto-advances**. When the test cases are
|
|
12
|
+
drafted, control passes to `yad-review-gate` (base rule: 1 distinct approver, who should not be the author).
|
|
13
13
|
|
|
14
|
-
**This step does NOT block
|
|
14
|
+
**This step does NOT block Build.** It opens when the **stories** gate passes — at which point
|
|
15
15
|
the epic is already `ready-for-build`, so implementation (`yad-spec` → `yad-implement` → …) can start
|
|
16
16
|
**at the same time** the tester works here. The test-cases track is driven by its own step `status`
|
|
17
17
|
(it opens to `in_progress` when `stories-review` passes) and its review **never moves `currentStep`
|
|
18
18
|
away from `ready-for-build`**, so the two run in parallel.
|
|
19
19
|
|
|
20
|
-
Test work is shaped by the **test architect** lens
|
|
21
|
-
`bmad-testarch-test-design` for the cases and `bmad-testarch-automate` for the automation. The
|
|
20
|
+
Test work is shaped by the **test architect** lens, which designs the cases and writes the automation. The
|
|
22
21
|
automation is materialized in the **testing tool connected via `yad-connect-testing`**
|
|
23
22
|
(`.sdlc/testing.json`), reached through its MCP. When a tool is connected the lens **generates** tests
|
|
24
23
|
into it (or **links** an existing suite and reads it back); when none is connected, the step degrades to
|
|
@@ -31,7 +30,7 @@ the Markdown artifact only — the testing tool is additive, exactly like the de
|
|
|
31
30
|
- The connected testing tool is recorded in `{project-root}/.sdlc/testing.json` (`config.yaml`
|
|
32
31
|
`testing`), written by `yad-connect-testing`. The per-epic case→test map is `test-links.json`
|
|
33
32
|
(Step 4b).
|
|
34
|
-
- Speak in the
|
|
33
|
+
- Speak in the `communication_language` set in `{project-root}/.sdlc/config.yaml`; write documents in `document_output_language`.
|
|
35
34
|
|
|
36
35
|
## On Activation
|
|
37
36
|
|
|
@@ -44,14 +43,14 @@ it passes. The check is track-aware: it keys off the `test-cases` step's predece
|
|
|
44
43
|
`currentStep` (this is a parallel track — `currentStep` stays at `ready-for-build`).
|
|
45
44
|
|
|
46
45
|
This passes once the `test-cases` step is runnable — its predecessor `stories-review` is `done` (so the
|
|
47
|
-
step has opened to `in_progress`) and `test-cases` is not already `done`. While it is still `
|
|
48
|
-
the stories review has not passed.
|
|
46
|
+
step has opened to `in_progress`) and `test-cases` is not already `done`. While it is still `todo` (or
|
|
47
|
+
`blocked`, in a file written before shape 7), the stories review has not passed.
|
|
49
48
|
|
|
50
49
|
### Step 1b — Open the authoring branch
|
|
51
50
|
Open the test-cases authoring branch `test-cases/EP-<slug>` per the shared procedure
|
|
52
51
|
(`../yad-epic/references/state-schema.md` → "Authoring branches"): git-safe (skip with a note if
|
|
53
52
|
`{project-root}` is not a git work tree), check out the branch if it exists, else create it from the
|
|
54
|
-
|
|
53
|
+
Product's default branch. Author and commit `test-cases.md` on it. This is **distinct** from the verified ledger's
|
|
55
54
|
`review/…` branch.
|
|
56
55
|
|
|
57
56
|
### Step 2 — Read inputs
|
|
@@ -62,7 +61,9 @@ for the cases — the test cases must collectively verify every story's criteria
|
|
|
62
61
|
|
|
63
62
|
- **UI-optional-safe:** `ui-design.md` may be absent — the `ui-design` step is optional and can be
|
|
64
63
|
marked N/A for a UI-less epic (state.json shows it `skipped`). When there is no UI design, there are
|
|
65
|
-
simply no screens to exercise; the stories' acceptance criteria remain the source of truth.
|
|
64
|
+
simply no screens to exercise; the stories' acceptance criteria remain the source of truth. If it
|
|
65
|
+
shows `deferred`, the screens are still to be designed: cover the stories' criteria now, and leave the
|
|
66
|
+
screen-level cases for when the UI design lands.
|
|
66
67
|
|
|
67
68
|
### Step 2b — Load existing-code context (make the brain code-aware)
|
|
68
69
|
Read the registry `{project-root}/.sdlc/repos.json` (`config.yaml` `code_context`). For **each repo in
|
|
@@ -77,11 +78,11 @@ inventing parallel ones.
|
|
|
77
78
|
- **Traceability:** record the loaded maps in the `test-cases.md` `code-context:` frontmatter field.
|
|
78
79
|
|
|
79
80
|
### Step 3 — Author the test cases (assist: test architect)
|
|
80
|
-
Adopt the **test architect** lens
|
|
81
|
+
Adopt the **test architect** lens. For each
|
|
81
82
|
story / user flow shape risk-based test cases:
|
|
82
83
|
|
|
83
84
|
- **Risk assessment** — categorize and score what can fail (probability × impact); depth scales with
|
|
84
|
-
impact (
|
|
85
|
+
impact (risk-based testing).
|
|
85
86
|
- **Coverage plan** — assign each case a priority **P0 (critical) / P1 (high) / P2 (medium) / P3 (low)**
|
|
86
87
|
and a level, preferring the lowest useful level (unit > integration > E2E).
|
|
87
88
|
- **Entry/exit criteria** and any **NFR** thresholds (security, performance, reliability) in scope.
|
|
@@ -95,8 +96,8 @@ Read `{project-root}/.sdlc/testing.json` (`config.yaml` `testing.registry`). Dec
|
|
|
95
96
|
(mirrors the `design: none` degrade). Skip to Step 4.
|
|
96
97
|
- **A tool is connected and its MCP is available:** adopt the `test architect` lens and, using the
|
|
97
98
|
provider recorded in `testing.json` (Playwright via a Playwright MCP, Cypress/pytest/Maestro via
|
|
98
|
-
theirs — Maestro authors mobile flows where Playwright has no reach)
|
|
99
|
-
|
|
99
|
+
theirs — Maestro authors mobile flows where Playwright has no reach) write the
|
|
100
|
+
automation:
|
|
100
101
|
- **Generate** — when the provider is write-capable, author one automation test per high-priority
|
|
101
102
|
(P0/P1) case into the connected code repo(s) for the repos in `epic.repos`, reusing the code-maps
|
|
102
103
|
(Step 2b) and existing fixtures so tests target built endpoints/components, then run them via the MCP
|
|
@@ -163,10 +164,10 @@ degraded (`testing: none`), do **not** write `test-links.json`.
|
|
|
163
164
|
|
|
164
165
|
### Step 5 — Advance the authoring step (NOT the gate)
|
|
165
166
|
**Check the mode first — the two modes have opposite instructions here.** Read `.sdlc/hub.json`:
|
|
166
|
-
**
|
|
167
|
+
**verified mode** is `platform` set AND `ledger: "verified"` — or, on a project that has not run `yad migrate` yet, `bridge_enabled` (or legacy `bridge`) `true`. `ledger` wins whenever it is present.
|
|
167
168
|
|
|
168
|
-
**
|
|
169
|
-
any non-bot commit touching `epics/*/.sdlc/{state,approvals,comments,hub-prs}.json` or
|
|
169
|
+
**verified mode — do NOT write `state.json`.** The ledger is CI-owned: the `ledger-guard` check rejects
|
|
170
|
+
any non-bot commit touching `epics/*/.sdlc/{state,approvals,comments,product-prs,hub-prs}.json` or
|
|
170
171
|
`epics/*/reviews/*.md`, `yad gate open` deliberately skips this write for the same reason, and
|
|
171
172
|
`yad gate ci --merged` performs the whole transition when the review PR merges. Making the edit here
|
|
172
173
|
fails the gate if it rides the review PR, and desynchronises the ledger CI is about to rewrite if it
|
|
@@ -174,21 +175,27 @@ is pushed around the gate. Commit the artifact set — **`test-cases.md` and, wh
|
|
|
174
175
|
tool was used, `.sdlc/test-links.json`** (artifact-side, not ledger; generated tests live in their
|
|
175
176
|
own code repo, not here) — then hand off to `yad-review-gate`.
|
|
176
177
|
|
|
177
|
-
**Otherwise —
|
|
178
|
-
`test-cases.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
**Otherwise — local, or a platform with no gate-sync CI — the engine makes this edit, not you.**
|
|
179
|
+
`yad gate open <epic> test-cases.md` marks `test-cases-review` `in_review`, closes `test-cases` as `done`, and **leaves `currentStep` at `ready-for-build`** — this is the parallel track, and moving it would pull the epic back from Build
|
|
180
|
+
— the same transition, from the one function that owns it (`markInReview`, `cli/epic-state.mjs`).
|
|
181
|
+
`yad-review-gate action: open` runs that command; hand off to it rather than editing the ledger here.
|
|
182
|
+
|
|
183
|
+
**With no platform configured** it writes the ledger and simply opens no PR, so this works offline.
|
|
184
|
+
**With a platform** the `review/EP-<slug>/test-cases` branch must already be **on origin** — the command
|
|
185
|
+
refuses and writes nothing otherwise. Cut it from the authoring branch and push it before handing off
|
|
186
|
+
(or run `yad open-pr` from that branch: it pushes the branch, then delegates).
|
|
187
|
+
|
|
188
|
+
Do **not** hand-edit `state.json`, and do **not** touch `approvals.json` — only real reviewers approve,
|
|
189
|
+
through the gate.
|
|
183
190
|
|
|
184
191
|
### Step 6 — Stop at the gate (do NOT advance)
|
|
185
192
|
Report: the path to `test-cases.md`, the connected testing tool and what it produced (e.g. "Playwright —
|
|
186
193
|
6 tests generated", the suite path + `test-links.json` path, or "no testing tool — artifacts-only"), that
|
|
187
|
-
|
|
188
|
-
`yad-review-gate` (base rule:
|
|
189
|
-
auto-advance. When the
|
|
190
|
-
PR on the
|
|
191
|
-
into the ledger; otherwise the review is recorded
|
|
194
|
+
Build may already be underway in parallel, and that the next action is **review** via
|
|
195
|
+
`yad-review-gate` (base rule: 1 distinct approver, who should not be the author). **Never record approval here.** Shape steps do not
|
|
196
|
+
auto-advance. When the Product has a platform, the gate opens a review
|
|
197
|
+
PR on the Product (via `yad-hub-bridge`) and `yad-review-gate action: sync` pulls platform approvals/comments
|
|
198
|
+
into the ledger; otherwise the review is recorded local.
|
|
192
199
|
|
|
193
200
|
## Reference
|
|
194
201
|
- Test-cases frontmatter, body template, and the `test-links.json` schema: `references/test-cases-schema.md`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Test-cases schema
|
|
2
2
|
|
|
3
|
-
The test cases authored at
|
|
3
|
+
The test cases authored at Shape step 9 are one Markdown file under `epics/EP-<slug>/`, named
|
|
4
4
|
`test-cases.md` (one per epic — mirrors `ui-design.md`, not a per-story folder). The optional automation
|
|
5
5
|
linkage is a sibling `test-links.json`.
|
|
6
6
|
|
|
@@ -56,15 +56,18 @@ Written by `yad-test-cases` Step 4b. Absent when the step ran artifacts-only (`t
|
|
|
56
56
|
- **Cover every story.** The "Coverage & traceability" section must map each story's acceptance criteria
|
|
57
57
|
to at least one case — an unmapped criterion is a gap the reviewer should catch.
|
|
58
58
|
- **Priorities are risk-based.** P0 = critical (run on every commit) … P3 = low (on-demand); depth
|
|
59
|
-
scales with impact
|
|
59
|
+
scales with impact.
|
|
60
60
|
- **Prefer the lowest useful level.** unit > integration > E2E when a case can be verified lower.
|
|
61
61
|
- **`repos` is a subset of `epic.repos`.** Automation cannot target a repo the epic does not declare.
|
|
62
62
|
- **Single-file, not a folder.** `test-cases.md` resolves through the gate's default content-hash path
|
|
63
|
-
(the file
|
|
63
|
+
(the file without its frontmatter `status:` line), so the review gate, revoke-on-change, and review-PR machinery work unchanged.
|
|
64
64
|
- **Automation is additive.** When no testing tool is connected, the step still produces a complete
|
|
65
65
|
`test-cases.md`; `testing: none` and no `test-links.json`.
|
|
66
66
|
|
|
67
67
|
## Review routing (the test-cases gate)
|
|
68
68
|
|
|
69
|
-
The test-cases gate uses the **base rule
|
|
70
|
-
|
|
69
|
+
The test-cases gate uses the **base rule**: at least 1 distinct approver, who should not be the author.
|
|
70
|
+
The step carries no risk tags, so its full count is also 1. The natural reviewer is the test architect /
|
|
71
|
+
QA owner, but the gate names no role — any approver counts. yadflow does not itself check that the
|
|
72
|
+
approver is not the author (see `yad-review-gate`). The review PR requests no reviewers; the team asks
|
|
73
|
+
them on the PR itself.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-timeline
|
|
3
|
-
description: 'Phase 6 output enrichment (never a gate) — render a feature THREAD as an evolution view AND resolve its current truth. Walks the thread (genesis -> changes -> defects, linked by parent: frontmatter), renders the evolution as the vendored React/Vite/Tailwind shell HTML + a TIMELINE.md summary (each node:
|
|
3
|
+
description: 'Phase 6 output enrichment (never a gate) — render a feature THREAD as an evolution view AND resolve its current truth. Walks the thread (genesis -> changes -> defects, linked by parent: frontmatter), renders the evolution as the vendored React/Vite/Tailwind shell HTML + a TIMELINE.md summary (each node: type, what it re-authored vs inherited, ship events, contract re-lock history, open debt), and emits thread-resolved.md — the composed CURRENT-TRUTH map (the latest epic that owns each artifact + the resolved contract-lock hash). That resolved map is the source-of-truth AI/humans read to plan the next change, instead of a stale genesis epic. Degrades to markdown-only when no docs target is connected. Use when the user says "show the feature timeline", "how did this feature evolve", "resolve the current truth", or "render the thread".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDLC — Feature Timeline + Current-Truth Resolver (Phase 6, output enrichment)
|
|
@@ -14,9 +14,9 @@ It is an **output enrichment**, exactly like `yad-docs` — **never a gate**: it
|
|
|
14
14
|
|
|
15
15
|
## Conventions
|
|
16
16
|
|
|
17
|
-
- `{project-root}` resolves from the
|
|
17
|
+
- `{project-root}` resolves from the Product.
|
|
18
18
|
- Reuses the **`yad-docs` shell** verbatim (`../yad-docs/templates/app/`) — generated `src/data/*.ts`,
|
|
19
|
-
themed
|
|
19
|
+
themed; build-only / markdown-only (`yad docs deploy` does not build `timeline-site/` yet)
|
|
20
20
|
(`.sdlc/docs.json`). The resolver part drives the **`yad thread` CLI** (`cli/thread.mjs`).
|
|
21
21
|
- The thread is **derived** from `parent:` frontmatter (no registry). The thread report lives under the
|
|
22
22
|
**genesis** epic (`thread == genesis id`).
|
|
@@ -32,7 +32,8 @@ It is an **output enrichment**, exactly like `yad-docs` — **never a gate**: it
|
|
|
32
32
|
|
|
33
33
|
### Step 1 — Resolve the thread
|
|
34
34
|
Run `yad thread <thread> --json` (`resolveThread` + `threadEpics` + `resolveCurrentArtifacts`). This
|
|
35
|
-
gives the genesis-first chain, each epic's lineage (`
|
|
35
|
+
gives the genesis-first chain, each epic's lineage (`type`, and `kind` holding the same value under
|
|
36
|
+
the older name, plus `parent` and `inherits`), the resolved
|
|
36
37
|
current-truth map, and any open reconcile debt. **STOP** and report if the lineage is broken (point at
|
|
37
38
|
`yad doctor` / `yad-reconcile`).
|
|
38
39
|
|
|
@@ -54,9 +55,9 @@ deterministically; theme from the design system). The thread maps onto the shell
|
|
|
54
55
|
= what it re-authored, its side-effects = the ships it produced + any contract re-lock.
|
|
55
56
|
- **System components** = the artifacts (epic/architecture/contract/ui/stories/test-cases), each labelled
|
|
56
57
|
with the epic that currently **owns** it (from the resolved map).
|
|
57
|
-
- Label and colour nodes by `
|
|
58
|
+
- Label and colour nodes by `type` — render each node's type noun (**Change request** / **Defect** /
|
|
58
59
|
**Hotfix** / **Epic** for feature) alongside its id, not the generic word "epic"; mark sealed epics and
|
|
59
|
-
open debt. (A bug is a defect — `
|
|
60
|
+
open debt. (A bug is a defect — type `defect`. Presentation only; every node is still an epic.)
|
|
60
61
|
|
|
61
62
|
### Step 4 — Emit `thread-resolved.md` (the current-truth map — derived, non-authoritative)
|
|
62
63
|
Write `epics/<thread>/thread-resolved.md`: for each artifact base, the **owning epic** (the latest in the
|
|
@@ -66,7 +67,7 @@ is the file the next `yad-change` / `yad-epic` reads as "the feature's current t
|
|
|
66
67
|
|
|
67
68
|
### Step 5 — Emit `TIMELINE.md` + (optional) deploy
|
|
68
69
|
Write a short `epics/<thread>/TIMELINE.md` (the chain, what each node changed, ships, open debt) for a
|
|
69
|
-
plain-text read — head each node with its
|
|
70
|
+
plain-text read — head each node with its type noun (Change request / Defect / Hotfix / Chore / Epic) + id. On `action: deploy`, note that `yad docs deploy` does not build this folder yet (it builds only the epic `docs-site/` and the overview) — build it with `npm ci` (or `npm install` when it has no lockfile) and `npm run build` inside the folder, and report it as build-only; a failed npm build is reported as a failure, never as a deploy.
|
|
70
71
|
|
|
71
72
|
## Hard rules
|
|
72
73
|
|
package/skills/yad-ui/SKILL.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-ui
|
|
3
|
-
description: '
|
|
3
|
+
description: 'Shape step 5 of the gated SDLC. With the ux-designer, author ui-design.md and DESIGN.md for an approved architecture, driving Impeccable as harness slash-commands (document/extract/craft) when installed, or authoring directly when not. Reads epic + architecture as input. Never auto-advances — hands off to the team review gate. Use when the user says "author the UI design" or after the architecture gate passes.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# SDLC — Author UI Design (
|
|
6
|
+
# SDLC — Author UI Design (Shape step 5)
|
|
7
7
|
|
|
8
8
|
**Goal:** Produce a human-authored, AI-assisted `ui-design.md` and `DESIGN.md` for an approved
|
|
9
9
|
architecture **and**, when a design tool is connected, the **actual feature design** — the mobile
|
|
10
10
|
screens and/or web pages — inside that tool (e.g. Figma), linked back from the artifacts. This is a
|
|
11
|
-
**
|
|
12
|
-
passes to `yad-review-gate` (base rule:
|
|
11
|
+
**Shape step**: human-authored with AI assist, **never auto-advances**. When the UI is drafted, control
|
|
12
|
+
passes to `yad-review-gate` (base rule: 1 distinct approver, who should not be the author).
|
|
13
13
|
|
|
14
14
|
UI work is shaped by **Impeccable**, invoked as **harness slash-commands** (not a subprocess CLI) per
|
|
15
15
|
the Phase 0 deviation. If Impeccable is not installed, the `ux-designer` lens authors the same outputs
|
|
@@ -28,7 +28,7 @@ like Impeccable.
|
|
|
28
28
|
- `DESIGN.md` is Impeccable's conventional root design-system file (RESEARCH-NOTES §4).
|
|
29
29
|
- The connected design tool is recorded in `{project-root}/.sdlc/design.json` (`config.yaml` `design`),
|
|
30
30
|
written by `yad-connect-design`. The per-epic screen→frame map is `design-links.json` (Step 4b).
|
|
31
|
-
- Speak in the
|
|
31
|
+
- Speak in the `communication_language` set in `{project-root}/.sdlc/config.yaml`; write documents in `document_output_language`.
|
|
32
32
|
|
|
33
33
|
## On Activation
|
|
34
34
|
|
|
@@ -45,22 +45,34 @@ This passes when `ui-design` is the next runnable step per the state sequence
|
|
|
45
45
|
**This step is OPTIONAL.** If the epic has no user-facing surface (a backend/API service, a data
|
|
46
46
|
pipeline, infra work), it does not need a UI design. Two signals to watch for:
|
|
47
47
|
|
|
48
|
-
- **Already skipped:** if `ui-design.status` is `
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
- **Already skipped or deferred:** if `ui-design.status` is `skipped` in `state.json` — or, in a file
|
|
49
|
+
from before shape 7, `done` with `skipped: true` beside it — the step was marked N/A. If it is
|
|
50
|
+
`deferred`, the team set it aside to come back to later. Either way the `--check` above refuses it
|
|
51
|
+
(`ui-design is already skipped`, or `already deferred`). **STOP**, there is nothing to author now;
|
|
52
|
+
point the user at `yad next EP-<slug>` (the next step is `stories`). To pick a deferred UI back up,
|
|
53
|
+
run `yad undefer EP-<slug> ui-design` first.
|
|
51
54
|
- **Should be skipped:** if you reach this step and the epic clearly produces no screens, do **not**
|
|
52
55
|
invent a hollow UI artifact. Offer to mark it N/A instead:
|
|
53
|
-
`yad skip EP-<slug> ui-design --reason "<why, e.g. backend-only service>"`. That
|
|
54
|
-
`ui-design` and `ui-design-review` steps `
|
|
55
|
-
gate, and advances to `stories`. It is reversible with `yad
|
|
56
|
+
`yad skip EP-<slug> ui-design --reason "<why, e.g. backend-only service>"`. That marks both the
|
|
57
|
+
`ui-design` and `ui-design-review` steps `skipped` (recorded reason + actor), short-circuits the review
|
|
58
|
+
gate, and advances to `stories`. It is reversible with `yad unskip EP-<slug> ui-design` until the
|
|
56
59
|
stories review opens. See `../yad-epic/references/state-schema.md` → "ui-design is optional".
|
|
60
|
+
- **Will have screens, just not yet:** if the epic does need a UI but the team wants to design it later,
|
|
61
|
+
offer `yad defer EP-<slug> ui-design --reason "<why, and who is waiting for it>"` instead of a skip.
|
|
62
|
+
It passes the step for now and keeps it visibly owed. If the team is setting it aside under pressure
|
|
63
|
+
and owes it back, add `--debt`: `yad next` and `yad doctor` then remind them until its review passes.
|
|
64
|
+
It can be picked back up with `yad undefer EP-<slug> ui-design` at any time — after the stories are
|
|
65
|
+
finished, the UI re-opens beside them and the stories stay done.
|
|
66
|
+
- **On a verified Product (the mode check below), none of these verbs works here:** by this step the
|
|
67
|
+
epic's ledger is on the default branch, so `yad skip`, `yad defer` and `yad undefer` refuse (CI owns
|
|
68
|
+
`state.json`, and has no step for them yet). Tell the user that, and do not edit the ledger by hand.
|
|
57
69
|
|
|
58
70
|
### Step 1b — Open the authoring branch
|
|
59
71
|
Open the UI authoring branch `ui-design/EP-<slug>` per the shared procedure
|
|
60
72
|
(`../yad-epic/references/state-schema.md` → "Authoring branches"): git-safe (skip with a note
|
|
61
73
|
if `{project-root}` is not a git work tree), check out the branch if it exists, else create it from the
|
|
62
|
-
|
|
63
|
-
the
|
|
74
|
+
Product's default branch. Author and commit `ui-design.md` / `DESIGN.md` on it. This is **distinct** from
|
|
75
|
+
the verified ledger's `review/…` branch.
|
|
64
76
|
|
|
65
77
|
### Step 2 — Read inputs
|
|
66
78
|
Read `epic.md` (user-level acceptance signals, scope) and `architecture.md` (flows, components by
|
|
@@ -80,7 +92,7 @@ Impeccable is absent, the code-map is the brain's view of what UI/components alr
|
|
|
80
92
|
- **Traceability:** record the loaded maps in the `ui-design.md` `code-context:` frontmatter field.
|
|
81
93
|
|
|
82
94
|
### Step 3 — Shape the UI (assist: ux-designer + Impeccable slash-commands)
|
|
83
|
-
Adopt the **ux-designer** lens
|
|
95
|
+
Adopt the **ux-designer** lens. Drive Impeccable as slash-commands:
|
|
84
96
|
|
|
85
97
|
- **Existing project** (a codebase/design system already exists): `/impeccable document` → then
|
|
86
98
|
`/impeccable extract` → then `/impeccable craft`.
|
|
@@ -92,7 +104,7 @@ components/tokens into the design system; `/impeccable craft` is shape-then-buil
|
|
|
92
104
|
**Graceful degradation:** if Impeccable is not installed (no `/impeccable …` commands available), the
|
|
93
105
|
`ux-designer` lens authors `ui-design.md` and `DESIGN.md` directly, and you **note in `ui-design.md`
|
|
94
106
|
that Impeccable was not used**. Do not run `npx impeccable skills install` as part of this step — tool
|
|
95
|
-
installation is out of scope for
|
|
107
|
+
installation is out of scope for Shape.
|
|
96
108
|
|
|
97
109
|
### Step 3b — Materialize the design in the connected tool (generate or link)
|
|
98
110
|
Read `{project-root}/.sdlc/design.json` (`config.yaml` `design.registry`). Decide the path:
|
|
@@ -172,10 +184,10 @@ Keep the `## Design (<tool>)` section of `ui-design.md` in step with this file.
|
|
|
172
184
|
|
|
173
185
|
### Step 5 — Advance the authoring step (NOT the gate)
|
|
174
186
|
**Check the mode first — the two modes have opposite instructions here.** Read `.sdlc/hub.json`:
|
|
175
|
-
**
|
|
187
|
+
**verified mode** is `platform` set AND `ledger: "verified"` — or, on a project that has not run `yad migrate` yet, `bridge_enabled` (or legacy `bridge`) `true`. `ledger` wins whenever it is present.
|
|
176
188
|
|
|
177
|
-
**
|
|
178
|
-
any non-bot commit touching `epics/*/.sdlc/{state,approvals,comments,hub-prs}.json` or
|
|
189
|
+
**verified mode — do NOT write `state.json`.** The ledger is CI-owned: the `ledger-guard` check rejects
|
|
190
|
+
any non-bot commit touching `epics/*/.sdlc/{state,approvals,comments,product-prs,hub-prs}.json` or
|
|
179
191
|
`epics/*/reviews/*.md`, `yad gate open` deliberately skips this write for the same reason, and
|
|
180
192
|
`yad gate ci --merged` performs the whole transition when the review PR merges. Making the edit here
|
|
181
193
|
fails the gate if it rides the review PR, and desynchronises the ledger CI is about to rewrite if it
|
|
@@ -183,18 +195,27 @@ is pushed around the gate. Commit the artifact set — **`ui-design.md`, `DESIGN
|
|
|
183
195
|
design tool was used, `.sdlc/design-links.json`** (artifact-side, not ledger) — then hand off to
|
|
184
196
|
`yad-review-gate`.
|
|
185
197
|
|
|
186
|
-
**Otherwise —
|
|
187
|
-
`ui-design.
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
**Otherwise — local, or a platform with no gate-sync CI — the engine makes this edit, not you.**
|
|
199
|
+
`yad gate open <epic> ui-design.md` marks `ui-design-review` `in_review`, closes `ui-design` as `done`, and moves `currentStep` to the gate
|
|
200
|
+
— unless the chain is already past it: a `ui-design` re-opened with a late `yad undefer` runs beside the
|
|
201
|
+
finished stories, and opening its review leaves `currentStep` where it is — the same transition, from the one function that owns it (`markInReview`, `cli/epic-state.mjs`).
|
|
202
|
+
`yad-review-gate action: open` runs that command; hand off to it rather than editing the ledger here.
|
|
203
|
+
|
|
204
|
+
**With no platform configured** it writes the ledger and simply opens no PR, so this works offline.
|
|
205
|
+
**With a platform** the `review/EP-<slug>/ui-design` branch must already be **on origin** — the command
|
|
206
|
+
refuses and writes nothing otherwise. Cut it from the authoring branch and push it before handing off
|
|
207
|
+
(or run `yad open-pr` from that branch: it pushes the branch, then delegates).
|
|
208
|
+
|
|
209
|
+
Do **not** hand-edit `state.json`, and do **not** touch `approvals.json` — only real reviewers approve,
|
|
210
|
+
through the gate.
|
|
190
211
|
|
|
191
212
|
### Step 6 — Stop at the gate (do NOT advance)
|
|
192
213
|
Report: the paths to `ui-design.md` and `DESIGN.md`, whether Impeccable was used, the connected design
|
|
193
214
|
tool and what it produced (e.g. "Figma — 4 screens generated", the file URL + `design-links.json` path,
|
|
194
215
|
or "no design tool — markdown-only"), and that the next action is **review** via `yad-review-gate` (base
|
|
195
|
-
rule:
|
|
196
|
-
|
|
197
|
-
the ledger; otherwise the review is recorded
|
|
216
|
+
rule: 1 distinct approver, who should not be the author). **Never record approval here.** Shape steps do not auto-advance. When the Product has a platform, the gate opens a review PR on the
|
|
217
|
+
Product (via `yad-hub-bridge`) and `yad-review-gate action: sync` pulls platform approvals/comments into
|
|
218
|
+
the ledger; otherwise the review is recorded local.
|
|
198
219
|
|
|
199
220
|
## Reference
|
|
200
221
|
- Impeccable commands and the slash-command-vs-CLI deviation: `RESEARCH-NOTES.md` §4 + Deviation 3.
|
package/cli/roster.mjs
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
// `yad roster` — manage the reviewer roster (.sdlc/hub.json) and per-repo roles at any time.
|
|
2
|
-
// The roster maps a platform login -> SDLC name + a per-scope roles map; it is the only thing that lets
|
|
3
|
-
// the review gate attribute approvals and route per-repo domain-owner reviewers. This is the standalone
|
|
4
|
-
// counterpart of the `yad setup` roster step and the `yad-connect-repos action: roster` skill action.
|
|
5
|
-
// Repo-driven: `add`/`edit` walks the connected repos from repos.json so roles are assigned against what
|
|
6
|
-
// is actually connected, not against repo names the user has to remember. Granting/revoking a
|
|
7
|
-
// `domain-owner` keeps repos.json `domain_owners` in sync so the gate never drifts from the roster.
|
|
8
|
-
import path from 'node:path';
|
|
9
|
-
import { c, log, ok, info, warn, hand, fail, ask, askYesNo, readJSON, writeJSON } from './lib.mjs';
|
|
10
|
-
import { PROJECT_FILES } from './manifest.mjs';
|
|
11
|
-
import { rolesForScope } from './platform.mjs';
|
|
12
|
-
import { parseRolesSpec, upsertRosterEntry, addRepoRoles, removeRepoRole, setRepoDomainOwners, reconcileRepoRoles } from './setup.mjs';
|
|
13
|
-
|
|
14
|
-
const ROLES = ['owner', 'reviewer', 'domain-owner'];
|
|
15
|
-
|
|
16
|
-
const loadHub = (root) => readJSON(path.join(root, PROJECT_FILES.hubConfig), null);
|
|
17
|
-
const loadRepos = (root) => readJSON(path.join(root, PROJECT_FILES.reposRegistry), { repos: [] }).repos || [];
|
|
18
|
-
const ownersOf = (repo) => (Array.isArray(repo.domain_owners) ? repo.domain_owners : (repo.domain_owner ? [repo.domain_owner] : []));
|
|
19
|
-
|
|
20
|
-
// `list` — every member with their per-scope roles, plus a drift check between hub.json roles and
|
|
21
|
-
// repos.json domain_owners (the two should agree; the sync on grant/revoke keeps them aligned).
|
|
22
|
-
function rosterList(root) {
|
|
23
|
-
const hub = loadHub(root);
|
|
24
|
-
if (!hub || !Array.isArray(hub.roster) || !hub.roster.length) {
|
|
25
|
-
warn('no roster yet (.sdlc/hub.json) — add one with `yad roster add <login>` or `yad setup`');
|
|
26
|
-
return { members: 0 };
|
|
27
|
-
}
|
|
28
|
-
const repos = loadRepos(root);
|
|
29
|
-
log(c.bold('\nreviewer roster'));
|
|
30
|
-
for (const e of hub.roster) {
|
|
31
|
-
const flag = e.unverified ? c.yellow(' (unverified)') : '';
|
|
32
|
-
log(` ${c.bold(e.name || e.login)} ${c.dim(`@${e.login}`)}${e.email ? c.dim(` <${e.email}>`) : ''}${flag}`);
|
|
33
|
-
const hubRoles = rolesForScope(e, 'hub');
|
|
34
|
-
log(` hub: ${hubRoles.length ? hubRoles.join(', ') : c.dim('—')}`);
|
|
35
|
-
for (const r of repos) {
|
|
36
|
-
const rr = rolesForScope(e, r.name);
|
|
37
|
-
if (rr.length) log(` ${r.name}: ${rr.join(', ')}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const drift = [];
|
|
41
|
-
for (const r of repos) {
|
|
42
|
-
const owners = ownersOf(r);
|
|
43
|
-
for (const nm of owners) {
|
|
44
|
-
const e = hub.roster.find((x) => x.name === nm);
|
|
45
|
-
if (!e || !rolesForScope(e, r.name).includes('domain-owner')) {
|
|
46
|
-
drift.push(`${nm} owns ${r.name} in repos.json but has no domain-owner role in hub.json`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
for (const e of hub.roster) {
|
|
50
|
-
if (rolesForScope(e, r.name).includes('domain-owner') && !owners.includes(e.name)) {
|
|
51
|
-
drift.push(`${e.name} has domain-owner for ${r.name} in hub.json but is missing from repos.json domain_owners`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (drift.length) { log(''); for (const d of drift) warn(d); }
|
|
56
|
-
return { members: hub.roster.length, repos: repos.length, drift: drift.length };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// The repo-driven walk: for each connected repo show the member's current role and offer to set it.
|
|
60
|
-
async function repoWalk(root, entry) {
|
|
61
|
-
const repos = loadRepos(root);
|
|
62
|
-
if (!repos.length) { info('no connected repos to assign roles for (.sdlc/repos.json) — connect one with `yad setup`'); return; }
|
|
63
|
-
for (const r of repos) {
|
|
64
|
-
const cur = rolesForScope(entry, r.name);
|
|
65
|
-
if (!(await askYesNo(` set ${entry.name}'s role on ${r.name}? (current: ${cur.length ? cur.join(', ') : 'none'})`, false))) continue;
|
|
66
|
-
const input = await ask(' roles (domain-owner/reviewer/owner, space-separated; blank = clear)', cur.join(' '));
|
|
67
|
-
const want = input.split(/\s+/).map((x) => x.trim()).filter(Boolean);
|
|
68
|
-
const invalid = want.filter((x) => !ROLES.includes(x));
|
|
69
|
-
if (invalid.length) warn(`ignoring unknown role(s): ${invalid.join(', ')} (allowed: ${ROLES.join(', ')})`);
|
|
70
|
-
reconcileRepoRoles(root, entry.name, r.name, cur, want.filter((x) => ROLES.includes(x)));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Mirror any domain-owner scopes from a non-interactive `--roles` upsert into repos.json.
|
|
75
|
-
function syncDomainOwners(root, entry) {
|
|
76
|
-
for (const [scope, list] of Object.entries(entry.roles || {})) {
|
|
77
|
-
if (scope !== 'hub' && Array.isArray(list) && list.includes('domain-owner')) setRepoDomainOwners(root, scope, entry.name, { add: true });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// `add`/`edit <login>` — upsert by login (from flags or interactive prompts), then either apply a
|
|
82
|
-
// `--roles` spec directly (scriptable) or run the repo-driven walk (interactive default).
|
|
83
|
-
async function rosterAdd(root, login, { name, email, roles } = {}) {
|
|
84
|
-
if (!login) { fail('usage: yad roster add <login> [--name N] [--email E] [--roles "hub=owner,reviewer backend=domain-owner"]'); process.exitCode = 1; return {}; }
|
|
85
|
-
const hub = loadHub(root);
|
|
86
|
-
const platform = hub ? hub.platform : null;
|
|
87
|
-
const existing = hub && Array.isArray(hub.roster) ? hub.roster.find((e) => e.login === login) : null;
|
|
88
|
-
const scripted = !!roles;
|
|
89
|
-
let nm = name;
|
|
90
|
-
let em = email;
|
|
91
|
-
let rolesMap;
|
|
92
|
-
if (scripted) {
|
|
93
|
-
rolesMap = parseRolesSpec(roles);
|
|
94
|
-
} else {
|
|
95
|
-
nm = nm || await ask(' yad name', (existing && existing.name) || login);
|
|
96
|
-
em = em || await ask(' commit email (blank to skip)', (existing && existing.email) || '');
|
|
97
|
-
const def = rolesForScope(existing, 'hub').join(' ') || 'reviewer';
|
|
98
|
-
const hubRoles = (await ask(' hub roles (owner/reviewer, space-separated)', def)).split(/\s+/).filter(Boolean);
|
|
99
|
-
rolesMap = hubRoles.length ? { hub: hubRoles } : {};
|
|
100
|
-
}
|
|
101
|
-
const { entry, created } = upsertRosterEntry(root, { login, name: nm, email: em || undefined, roles: rolesMap, platform });
|
|
102
|
-
if (!entry) return {};
|
|
103
|
-
ok(`${created ? 'added' : 'updated'} ${entry.name} (@${login})`);
|
|
104
|
-
if (scripted) syncDomainOwners(root, entry);
|
|
105
|
-
else await repoWalk(root, entry);
|
|
106
|
-
return { entry: entry.name, created };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// `grant <name> <repo> <role...>` — scriptable per-repo grant (member must already be in the roster).
|
|
110
|
-
function rosterGrant(root, [name, repo, ...roles]) {
|
|
111
|
-
if (!name || !repo || !roles.length) { fail('usage: yad roster grant <name> <repo> <role...>'); process.exitCode = 1; return {}; }
|
|
112
|
-
const invalid = roles.filter((r) => !ROLES.includes(r));
|
|
113
|
-
if (invalid.length) { fail(`unknown role(s): ${invalid.join(', ')} (allowed: ${ROLES.join(', ')})`); process.exitCode = 1; return {}; }
|
|
114
|
-
const hub = loadHub(root);
|
|
115
|
-
if (!hub || !Array.isArray(hub.roster) || !hub.roster.some((e) => e.name === name)) {
|
|
116
|
-
fail(`'${name}' is not in the roster — add them first with \`yad roster add <login>\``); process.exitCode = 1; return {};
|
|
117
|
-
}
|
|
118
|
-
if (!loadRepos(root).some((r) => r.name === repo)) warn(`repo '${repo}' is not registered — the role is recorded and applies once it is connected`);
|
|
119
|
-
addRepoRoles(root, repo, Object.fromEntries(roles.map((r) => [r, [name]])));
|
|
120
|
-
if (roles.includes('domain-owner')) setRepoDomainOwners(root, repo, name, { add: true });
|
|
121
|
-
ok(`granted ${name} ${roles.join(', ')} on ${repo}`);
|
|
122
|
-
return { name, repo, roles };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// `revoke <name> <repo> <role...>` — scriptable per-repo revoke.
|
|
126
|
-
function rosterRevoke(root, [name, repo, ...roles]) {
|
|
127
|
-
if (!name || !repo || !roles.length) { fail('usage: yad roster revoke <name> <repo> <role...>'); process.exitCode = 1; return {}; }
|
|
128
|
-
removeRepoRole(root, name, repo, roles);
|
|
129
|
-
if (roles.includes('domain-owner')) setRepoDomainOwners(root, repo, name, { add: false });
|
|
130
|
-
ok(`revoked ${name} ${roles.join(', ')} on ${repo}`);
|
|
131
|
-
return { name, repo, roles };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// `remove <login>` — delete a member; warn (do not cascade) if still a domain owner in repos.json.
|
|
135
|
-
function rosterRemove(root, login) {
|
|
136
|
-
if (!login) { fail('usage: yad roster remove <login>'); process.exitCode = 1; return {}; }
|
|
137
|
-
const hubPath = path.join(root, PROJECT_FILES.hubConfig);
|
|
138
|
-
const hub = readJSON(hubPath, null);
|
|
139
|
-
if (!hub || !Array.isArray(hub.roster)) { warn('no roster to remove from (.sdlc/hub.json)'); return { removed: 0 }; }
|
|
140
|
-
const idx = hub.roster.findIndex((e) => e.login === login);
|
|
141
|
-
if (idx < 0) { warn(`no roster member with login '${login}'`); return { removed: 0 }; }
|
|
142
|
-
const [removed] = hub.roster.splice(idx, 1);
|
|
143
|
-
writeJSON(hubPath, hub);
|
|
144
|
-
ok(`removed ${removed.name} (@${login})`);
|
|
145
|
-
const refs = loadRepos(root).filter((r) => ownersOf(r).includes(removed.name)).map((r) => r.name);
|
|
146
|
-
if (refs.length) hand(`'${removed.name}' is still a domain owner in repos.json for: ${refs.join(', ')} — revoke with \`yad roster revoke ${removed.name} <repo> domain-owner\``);
|
|
147
|
-
return { removed: 1 };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export async function runRoster(root, { action = 'list', args = [], name, email, roles } = {}) {
|
|
151
|
-
switch (action) {
|
|
152
|
-
case 'list': return rosterList(root);
|
|
153
|
-
case 'add':
|
|
154
|
-
case 'edit': return rosterAdd(root, args[0], { name, email, roles });
|
|
155
|
-
case 'grant': return rosterGrant(root, args);
|
|
156
|
-
case 'revoke': return rosterRevoke(root, args);
|
|
157
|
-
case 'remove':
|
|
158
|
-
case 'rm': return rosterRemove(root, args[0]);
|
|
159
|
-
default:
|
|
160
|
-
fail(`unknown roster action: ${action} (list | add | edit | grant | revoke | remove)`);
|
|
161
|
-
process.exitCode = 1;
|
|
162
|
-
return {};
|
|
163
|
-
}
|
|
164
|
-
}
|