yadflow 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -8
- package/cli/doctor.mjs +34 -0
- package/cli/setup.mjs +28 -1
- package/package.json +1 -1
- package/skills/yad-checks/SKILL.md +7 -0
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +8 -0
- package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +5 -0
- package/skills/yad-hub-bridge/SKILL.md +8 -0
- package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
## [2.4.2](https://github.com/abdelrahmannasr/yadflow/compare/v2.4.1...v2.4.2) (2026-06-14)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
### Features
|
|
10
|
-
|
|
11
|
-
* add DeepTutor learning layer across all SDLC stages ([bd8d4ea](https://github.com/abdelrahmannasr/yadflow/commit/bd8d4eaaa0258242a62ed1b131f7e3f74506af64))
|
|
12
|
-
* make learning-layer output local-only (never committed or pushed) ([aa8f74e](https://github.com/abdelrahmannasr/yadflow/commit/aa8f74eb61855d3a663810a0c68cf8e37fbedd66))
|
|
6
|
+
* route GitLab CI gate jobs to tag-locked runners via $YAD_RUNNER_TAGS ([a0311c5](https://github.com/abdelrahmannasr/yadflow/commit/a0311c5af647f63968f3f34e8e6e6fa48b7423d8)), closes [#50](https://github.com/abdelrahmannasr/yadflow/issues/50)
|
|
13
7
|
|
|
14
8
|
# [2.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.1.0...v2.2.0) (2026-06-14)
|
|
15
9
|
|
package/cli/doctor.mjs
CHANGED
|
@@ -162,9 +162,43 @@ export function projectChecks(checks, root) {
|
|
|
162
162
|
}
|
|
163
163
|
if (!registry.repos.length) check(checks, 'repos', 'project', 'warn', 'no code repos registered', 'run `yad setup` to connect one');
|
|
164
164
|
}
|
|
165
|
+
|
|
166
|
+
ciTagsChecks(checks, root, hub, registry);
|
|
165
167
|
return { hub, registry };
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
// GitLab CI runner tags: the wired fragments run docker-image jobs. On instances whose runners are
|
|
171
|
+
// all tag-locked (run_untagged: false), an untagged image job matches no runner and sits `pending`
|
|
172
|
+
// forever — silently blocking the gates (issue #50). A current fragment carries
|
|
173
|
+
// `tags: [$YAD_RUNNER_TAGS]`; warn on any wired GitLab fragment that sets an `image:` but has no
|
|
174
|
+
// `tags:` (an old install, or one hand-reverted by a sync). Pure local read — no API calls.
|
|
175
|
+
export function ciTagsChecks(checks, root, hub, registry) {
|
|
176
|
+
const untagged = (p) => {
|
|
177
|
+
try {
|
|
178
|
+
const txt = fs.readFileSync(p, 'utf8');
|
|
179
|
+
return /^\s*image:/m.test(txt) && !/^\s*tags:/m.test(txt);
|
|
180
|
+
} catch { return false; } // absent fragment is not this check's concern
|
|
181
|
+
};
|
|
182
|
+
const fragments = [];
|
|
183
|
+
if (hub?.platform === 'gitlab' && (hub.bridge_enabled === true || hub.bridge === true)) {
|
|
184
|
+
fragments.push(
|
|
185
|
+
{ scope: 'hub', file: '.gitlab/ci/yad-gate-sync.yml', path: path.join(root, '.gitlab/ci/yad-gate-sync.yml') },
|
|
186
|
+
{ scope: 'hub', file: '.gitlab/ci/yad-verified-commits.yml', path: path.join(root, '.gitlab/ci/yad-verified-commits.yml') },
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
for (const repo of registry?.repos || []) {
|
|
190
|
+
if (repo.platform !== 'gitlab' || !repo.path) continue;
|
|
191
|
+
fragments.push({ scope: repo.name, file: '.gitlab/ci/yad-checks.yml', path: path.join(path.resolve(root, repo.path), '.gitlab/ci/yad-checks.yml') });
|
|
192
|
+
}
|
|
193
|
+
for (const f of fragments) {
|
|
194
|
+
if (untagged(f.path)) {
|
|
195
|
+
check(checks, `ci-tags:${f.scope}`, 'project', 'warn',
|
|
196
|
+
`${f.scope}: ${f.file} runs a docker job with no \`tags:\` [YAD-CI-001]`,
|
|
197
|
+
'tag-locked runners (run_untagged: false) will strand it at `pending` — run `yad update`, then set the `YAD_RUNNER_TAGS` CI/CD variable');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
168
202
|
export function epicChecks(checks, root) {
|
|
169
203
|
const epicsDir = path.join(root, 'epics');
|
|
170
204
|
if (!exists(epicsDir)) return;
|
package/cli/setup.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// `yad setup` — the guided, idempotent first-run wizard.
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
4
5
|
import {
|
|
5
6
|
c, log, step, ok, info, warn, hand, fail, ask, askYesNo, run, has,
|
|
6
7
|
exists, readJSON, writeJSON,
|
|
7
8
|
} from './lib.mjs';
|
|
8
9
|
import { VERSION, IDE_FOLDER_TARGETS, PROJECT_FILES, DESIGN_TOOLS, DESIGN_PRIMARY, TESTING_TOOLS, TESTING_PRIMARY, LEARNING_TOOLS, LEARNING_PRIMARY } from './manifest.mjs';
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
moduleActions, repoActions, hubActions, authorsActions,
|
|
12
|
+
legacyModuleActions, legacyRepoActions, legacyHubActions,
|
|
13
|
+
} from './plan.mjs';
|
|
10
14
|
|
|
11
15
|
const ALL_IDES = [...IDE_FOLDER_TARGETS, '.opencode'];
|
|
12
16
|
|
|
@@ -181,8 +185,27 @@ export async function runSetup(root, opts = {}) {
|
|
|
181
185
|
ideTargets = answer.split(',').map((s) => s.trim()).filter(Boolean);
|
|
182
186
|
}
|
|
183
187
|
applyActions(moduleActions(root, ideTargets), { force: true });
|
|
188
|
+
// Migrate any pre-2.0 install in place: remove the old sdlc-* skill copies in the project's
|
|
189
|
+
// IDE targets and install their yad-* renames. Without this, setup only ADDED yad-* and left
|
|
190
|
+
// stale sdlc-* sitting next to them (the rename only ran under `yad update` / `yad check --fix`).
|
|
191
|
+
applyActions(legacyModuleActions(root, ideTargets), { force: true });
|
|
184
192
|
ok(`module installed into: ${ideTargets.join(', ')}`);
|
|
185
193
|
|
|
194
|
+
// Global leftovers: a pre-2.0 install may have put sdlc-* skills in the user's global
|
|
195
|
+
// ~/.claude/skills (path.join(homedir, '.claude', 'skills', <old>)). The CLI is project-scoped,
|
|
196
|
+
// so touching the home dir requires an interactive yes — never auto-fire it in SDLC_NONINTERACTIVE
|
|
197
|
+
// (scripted/CI) mode, where the prompt would otherwise return its default. Silent when there is
|
|
198
|
+
// nothing to migrate.
|
|
199
|
+
const globalLegacy = process.env.SDLC_NONINTERACTIVE ? [] : legacyModuleActions(os.homedir(), ['.claude']);
|
|
200
|
+
if (globalLegacy.length) {
|
|
201
|
+
if (await askYesNo(`Found ${globalLegacy.length} legacy sdlc-* skill(s) in your global ~/.claude/skills (pre-2.0 install). Migrate them to yad-*?`, true)) {
|
|
202
|
+
applyActions(globalLegacy, { force: true });
|
|
203
|
+
ok('migrated global ~/.claude/skills to yad-*');
|
|
204
|
+
} else {
|
|
205
|
+
info('left global ~/.claude/skills untouched — re-run `yad setup` or migrate later with `yad update`');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
186
209
|
// 3. Detect hub platform + roster
|
|
187
210
|
step(3, total, 'Hub platform & reviewer roster');
|
|
188
211
|
const hubPath = path.join(root, PROJECT_FILES.hubConfig);
|
|
@@ -299,6 +322,9 @@ export async function runSetup(root, opts = {}) {
|
|
|
299
322
|
for (const repo of registry.repos) {
|
|
300
323
|
log(` ${c.bold(repo.name)} ${c.dim(`(${repo.platform})`)}`);
|
|
301
324
|
applyActions(repoActions(root, repo), { force: true });
|
|
325
|
+
// Migrate pre-2.0 wired CI (marker-owned sdlc-*.yml -> yad-*.yml); a user-authored
|
|
326
|
+
// same-named file is never touched.
|
|
327
|
+
applyActions(legacyRepoActions(root, repo), { force: true });
|
|
302
328
|
}
|
|
303
329
|
// the hub: event-driven gate-sync CI, so platform approvals/merges drive `yad gate ci`
|
|
304
330
|
const hubWiring = hubActions(root);
|
|
@@ -306,6 +332,7 @@ export async function runSetup(root, opts = {}) {
|
|
|
306
332
|
log(` ${c.bold('hub')} ${c.dim('(gate-sync + verified-commits CI)')}`);
|
|
307
333
|
applyActions(hubWiring, { force: true });
|
|
308
334
|
}
|
|
335
|
+
applyActions(legacyHubActions(root), { force: true });
|
|
309
336
|
// author allowlists for the verified-commits gate (hub + every repo), from the roster emails
|
|
310
337
|
applyActions(authorsActions(root, registry.repos), { force: true });
|
|
311
338
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yadflow",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "Yadflow — the gated, team, multi-repo SDLC: author → review → build with a PR-driven review gate and a zero-dependency `yad` CLI (setup, gate, commit, open-pr, repo). A BMAD module + 22 yad-* skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "AbdelRahman Nasr",
|
|
@@ -88,6 +88,13 @@ Copy from this skill's `templates/`:
|
|
|
88
88
|
human to paste (graceful degradation — never guess-edit a pipeline you cannot parse).
|
|
89
89
|
- The legacy standalone `templates/gitlab/.gitlab-ci.yml` is retained only for a clean greenfield repo
|
|
90
90
|
that prefers a single self-contained file; the include path above is the default.
|
|
91
|
+
- **Tag-locked runners** (`run_untagged: false`, common on self-hosted): the fragment's jobs run a
|
|
92
|
+
docker image, so set a `YAD_RUNNER_TAGS` CI/CD variable (e.g. `dind_runner`) to route them — the
|
|
93
|
+
`default:` block emits `tags: [$YAD_RUNNER_TAGS]`. When unset, current GitLab (≥15) drops the
|
|
94
|
+
empty-expanded tag so the jobs run untagged (gitlab.com shared runners); older versions may
|
|
95
|
+
strand them `pending`, in which case set the variable to a real tag. The variable lives in
|
|
96
|
+
project settings, so it survives every `yad` sync. Single value only — `tags: [$VAR]` is one tag
|
|
97
|
+
equal to the whole variable, not a comma-split.
|
|
91
98
|
|
|
92
99
|
- Ensure `<repo>/package.json` defines `lint`, `build`, `test` scripts (see `references/check-gates.md`
|
|
93
100
|
for the canonical scripts). **Only ADD a missing script; never overwrite an existing one.**
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
# colliding with the host project's own job names.
|
|
12
12
|
default:
|
|
13
13
|
image: node:20
|
|
14
|
+
# Runner selection. These jobs need a docker/dind executor; on instances whose runners are all
|
|
15
|
+
# tag-locked (run_untagged: false) an untagged image job never starts. Set the YAD_RUNNER_TAGS
|
|
16
|
+
# CI/CD variable (Settings → CI/CD → Variables, e.g. `dind_runner`) to route them — it lives in
|
|
17
|
+
# project settings, so it survives every `yad` sync. When unset, current GitLab (>=15) drops the
|
|
18
|
+
# empty-expanded tag so the job runs untagged (gitlab.com shared runners); some older versions
|
|
19
|
+
# instead strand it `pending` — if so, set the variable to a real tag. Single value only:
|
|
20
|
+
# `tags: [$VAR]` is one tag equal to the whole variable, not a comma-split.
|
|
21
|
+
tags: [$YAD_RUNNER_TAGS]
|
|
14
22
|
|
|
15
23
|
.sdlc_mr_only:
|
|
16
24
|
rules:
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
# Job name is yad-hub-prefixed so it can coexist with the code-repo fragment in one pipeline.
|
|
13
13
|
yad-hub-verified-commits:
|
|
14
14
|
needs: []
|
|
15
|
+
# Runner selection: set the YAD_RUNNER_TAGS CI/CD variable (e.g. `dind_runner`) to route this
|
|
16
|
+
# docker job on instances whose runners are tag-locked (run_untagged: false) — it lives in
|
|
17
|
+
# project Settings → CI/CD → Variables, so it survives every `yad` sync. When unset, current
|
|
18
|
+
# GitLab (>=15) runs the job untagged; older versions may strand it — set a real tag if so.
|
|
19
|
+
tags: [$YAD_RUNNER_TAGS]
|
|
15
20
|
image: node:20
|
|
16
21
|
variables:
|
|
17
22
|
GIT_DEPTH: "0" # full history so the gate can diff against the target branch
|
|
@@ -89,6 +89,14 @@ remains valid and is the fallback whenever CI cannot push.
|
|
|
89
89
|
`SDLC_GATE_TOKEN` project-access-token variable (`read_api` + `write_repository`). GitLab fires no
|
|
90
90
|
pipeline on an approval alone, so the schedule is the path that picks approvals up (≤ ~15 min
|
|
91
91
|
latency); MR events and the merge are near-immediate.
|
|
92
|
+
- **If your runners are tag-locked** (`run_untagged: false`, common on self-hosted): set a
|
|
93
|
+
`YAD_RUNNER_TAGS` CI/CD variable (e.g. `dind_runner`) so the docker-image `yad-gate-sync` job
|
|
94
|
+
is routed to a runner — otherwise it sits `pending` forever. The fragment emits
|
|
95
|
+
`tags: [$YAD_RUNNER_TAGS]`. When unset, current GitLab (≥15) drops the empty-expanded tag so the
|
|
96
|
+
job runs untagged (gitlab.com shared runners); older versions may strand it `pending`, in which
|
|
97
|
+
case set the variable to a real tag. The variable lives in project settings, so it survives
|
|
98
|
+
every `yad` sync. Single value only — `tags: [$VAR]` is one tag equal to the whole variable,
|
|
99
|
+
not a comma-split.
|
|
92
100
|
3. Commit the workflow to the hub. GitHub needs nothing else — the ephemeral `github.token` reads the
|
|
93
101
|
PR and pushes the ledger. If the default branch is protected, see the workflow header for the
|
|
94
102
|
bypass / PAT options; until then the run fails visibly and manual `yad gate sync` still works.
|
|
@@ -31,6 +31,11 @@ variables:
|
|
|
31
31
|
|
|
32
32
|
yad-gate-sync:
|
|
33
33
|
needs: []
|
|
34
|
+
# Runner selection: set the YAD_RUNNER_TAGS CI/CD variable (e.g. `dind_runner`) to route this
|
|
35
|
+
# docker job on instances whose runners are tag-locked (run_untagged: false) — it lives in
|
|
36
|
+
# project Settings → CI/CD → Variables, so it survives every `yad` sync. When unset, current
|
|
37
|
+
# GitLab (>=15) runs the job untagged; older versions may strand it — set a real tag if so.
|
|
38
|
+
tags: [$YAD_RUNNER_TAGS]
|
|
34
39
|
image: node:20
|
|
35
40
|
rules:
|
|
36
41
|
# MR-event path: fires on MR open / push to the review branch — NOT on approval (see header).
|