yadflow 4.3.0 → 4.5.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 +63 -0
- package/README.md +4 -2
- package/bin/commands.mjs +3 -1
- package/bin/yad.mjs +80 -14
- package/cli/claims.mjs +16 -13
- package/cli/detect.mjs +2 -2
- package/cli/docs.mjs +1 -1
- package/cli/doctor.mjs +108 -3
- package/cli/epic-state.mjs +121 -24
- package/cli/epic.mjs +7 -5
- package/cli/manifest.mjs +3 -0
- package/cli/members.mjs +822 -0
- package/cli/next.mjs +21 -9
- package/cli/people.mjs +13 -3
- package/cli/platform.mjs +1 -1
- package/cli/pr-list.mjs +125 -0
- package/cli/protection.mjs +4 -4
- package/cli/recommend.mjs +301 -0
- package/cli/setup.mjs +3 -2
- package/cli/skill.mjs +263 -63
- package/cli/standup.mjs +709 -0
- package/cli/toolbox.mjs +433 -11
- package/cli/usage.mjs +18 -11
- package/cli/workspace.mjs +14 -5
- package/package.json +5 -3
- package/skills/sdlc/config.yaml +1 -1
- package/skills/sdlc/module-help.csv +1 -1
- package/skills/yad-backfill/SKILL.md +40 -7
- package/skills/yad-backfill/references/backfill.md +5 -4
- package/skills/yad-change/SKILL.md +1 -1
- package/skills/yad-checks/templates/checks/member-check.mjs +197 -0
- package/skills/yad-checks/templates/github/yad-product-checks.yml +15 -0
- package/skills/yad-checks/templates/gitlab/yad-product-checks.gitlab-ci.yml +9 -0
- package/skills/yad-connect-design/SKILL.md +16 -0
- package/skills/yad-connect-learning/SKILL.md +14 -0
- package/skills/yad-connect-repos/SKILL.md +34 -5
- package/skills/yad-connect-repos/references/code-context.md +5 -5
- package/skills/yad-connect-testing/SKILL.md +20 -0
- package/skills/yad-docs/SKILL.md +1 -1
- package/skills/yad-epic/references/state-schema.md +10 -2
- package/skills/yad-learn/SKILL.md +14 -0
- package/skills/yad-run/SKILL.md +7 -4
- package/skills/yad-run/references/run-loop.md +2 -1
- package/skills/yad-spec/SKILL.md +37 -6
- package/skills/yad-spec/references/spec-handoff.md +2 -2
- package/skills/yad-status/SKILL.md +3 -0
- package/skills/yad-test-cases/SKILL.md +20 -0
- package/skills/yad-ui/SKILL.md +36 -3
package/cli/epic-state.mjs
CHANGED
|
@@ -2297,8 +2297,8 @@ export function markInReview(state, step, close = null) {
|
|
|
2297
2297
|
// E108 — is `gateRuleFor` and `gateCapFor` at the top of this file (E7, E62, E72), which read
|
|
2298
2298
|
// the `risk_tags` a seed copies from the row below into the epic's own `state.json`;
|
|
2299
2299
|
// the fuller step-state model is E38. The `skill` column stays here as the shipped DEFAULT, and a
|
|
2300
|
-
// project overrides it in `.sdlc/skills.json` (E6, below)
|
|
2301
|
-
//
|
|
2300
|
+
// project overrides it in `.sdlc/skills.json` (E6, below), for every epic or for one route only
|
|
2301
|
+
// (E51: `profiles.<id>.steps`, which wins over the project-wide line). Three of the five
|
|
2302
2302
|
// skills that used to hand-write a seed now run `yad epic new` instead (E17b), `yad-discovery` runs
|
|
2303
2303
|
// `yad foundation new` (E75), and `yad-change` runs `yad epic new --parent` for a threaded change-epic
|
|
2304
2304
|
// (E42).
|
|
@@ -2443,9 +2443,9 @@ export const STEPS = [
|
|
|
2443
2443
|
// `yad epic new` (E17, cli/epic.mjs); shape 6 is where an epic first RECORDS which profile it is on,
|
|
2444
2444
|
// and an epic seeded before that field existed still has its route derived by matching its chain
|
|
2445
2445
|
// (`matchLifecycleProfile`). Which steps are optional per route already lives here and is read per
|
|
2446
|
-
// EPIC (E35, `optionalStepsFor`); the short chore and spike lanes are E40.
|
|
2447
|
-
//
|
|
2448
|
-
//
|
|
2446
|
+
// EPIC (E35, `optionalStepsFor`); the short chore and spike lanes are E40. Which skill runs a step
|
|
2447
|
+
// is bound per project (E6, below) and, more specifically, per route (E51) — both in
|
|
2448
|
+
// `.sdlc/skills.json`, never here, so two routes can run different skills for the same step.
|
|
2449
2449
|
export const LIFECYCLE_PROFILES = [
|
|
2450
2450
|
{
|
|
2451
2451
|
id: 'classic',
|
|
@@ -2877,8 +2877,9 @@ const BUILD_STEP_ORDER = STEPS.filter((s) => s.phase === 'build').map((s) => s.i
|
|
|
2877
2877
|
// THE FILE WINS, AND THE DOCTOR ONLY REPORTS. Same discipline as `workItemType` and the lifecycle
|
|
2878
2878
|
// profile: a project may bind a skill this engine has never heard of — that is the whole point, since
|
|
2879
2879
|
// the engine cannot know what a team installed. So nothing here validates a skill NAME. `yad doctor`
|
|
2880
|
-
// reports a binding on a step id the catalogue does not know, and changes nothing.
|
|
2881
|
-
//
|
|
2880
|
+
// reports a binding on a step id the catalogue does not know, and changes nothing. `yad skill list`
|
|
2881
|
+
// and `yad skill bind` ask `yad detect` (E50) whether a bound skill is installed on THIS machine, and
|
|
2882
|
+
// only ever warn: a teammate may have it, and the engine cannot see their home folder.
|
|
2882
2883
|
//
|
|
2883
2884
|
// SEVERAL SKILLS PER STEP IS A CHAIN, NEVER A PANEL (closed decision 7). They run in the order given,
|
|
2884
2885
|
// each one seeing what the one before it produced, and the LAST output is the artifact. A panel — run
|
|
@@ -2897,8 +2898,19 @@ const BUILD_STEP_ORDER = STEPS.filter((s) => s.phase === 'build').map((s) => s.i
|
|
|
2897
2898
|
// }
|
|
2898
2899
|
//
|
|
2899
2900
|
// A value may be one skill or a list of them; both are read back as a list, so nothing downstream has
|
|
2900
|
-
// to handle two shapes. The wrapper object exists so that
|
|
2901
|
-
// without the file changing shape
|
|
2901
|
+
// to handle two shapes. The wrapper object exists so that later rows can add keys beside `steps`
|
|
2902
|
+
// without the file changing shape — and E51 did:
|
|
2903
|
+
//
|
|
2904
|
+
// "profiles": {
|
|
2905
|
+
// "spike": { "steps": { "epic": "spike-epic" } }
|
|
2906
|
+
// }
|
|
2907
|
+
//
|
|
2908
|
+
// A binding under `profiles.<id>` applies only to an epic that RECORDS that route (`recordedRoute`,
|
|
2909
|
+
// below — never a route guessed from its chain). THE MOST SPECIFIC LINE WINS: the route's
|
|
2910
|
+
// own line, then the project-wide `steps`, then the catalogue. A team that binds `epic` for `spike`
|
|
2911
|
+
// gets it on spike epics only, and its project-wide lines still cover every other route. No shape
|
|
2912
|
+
// moves: an older yadflow keeps the unknown key (`yad skill bind` writes the document it read) and
|
|
2913
|
+
// simply runs the project-wide answer for every route.
|
|
2902
2914
|
|
|
2903
2915
|
// The catalogue's own answer for a step, Shape or Build. One lookup, because a step id belongs to
|
|
2904
2916
|
// exactly one of the two tables (`catalogueSkills` splits them on the same `phase === 'build'` rule),
|
|
@@ -2908,7 +2920,8 @@ const BUILD_STEP_ORDER = STEPS.filter((s) => s.phase === 'build').map((s) => s.i
|
|
|
2908
2920
|
// chain carrying a step called `constructor` would otherwise resolve its "skill" to a function.
|
|
2909
2921
|
const CATALOGUE_SKILL = { __proto__: null, ...STEP_SKILL, ...BUILD_STEP_SKILL };
|
|
2910
2922
|
|
|
2911
|
-
// Read a raw parsed `.sdlc/skills.json` into
|
|
2923
|
+
// Read a raw parsed `.sdlc/skills.json` into
|
|
2924
|
+
// `{ steps: { <id>: [skill, …] }, profiles: { <profile>: { steps: { <id>: [skill, …] } } } }`.
|
|
2912
2925
|
//
|
|
2913
2926
|
// Junk is DROPPED here rather than rejected, and that is deliberate: this runs inside `yad next`,
|
|
2914
2927
|
// which must keep working on a project whose config file someone mistyped. An empty string, an empty
|
|
@@ -2925,20 +2938,75 @@ const CATALOGUE_SKILL = { __proto__: null, ...STEP_SKILL, ...BUILD_STEP_SKILL };
|
|
|
2925
2938
|
// and the two surfaces would disagree about it: the rendered Build chain folds them (that is what
|
|
2926
2939
|
// `dedupeConsecutive` has always done for spec+tasks) while the cost note counted them, so a step
|
|
2927
2940
|
// bound to `["a", "a"]` would be billed for two runs and shown as one.
|
|
2928
|
-
|
|
2941
|
+
//
|
|
2942
|
+
// `profiles` gets the same treatment one level down (E51): a profile id is a key out of the same
|
|
2943
|
+
// hand-edited file, so `__proto__` there is added with `defineProperty` too, and a profile whose
|
|
2944
|
+
// `steps` binds nothing usable is left out — it would only route a lookup to an empty layer.
|
|
2945
|
+
const ownKey = (obj, key, value) => Object.defineProperty(obj, key, { value, enumerable: true, writable: true, configurable: true });
|
|
2946
|
+
|
|
2947
|
+
function normalizeSteps(steps) {
|
|
2929
2948
|
const out = {};
|
|
2930
|
-
const steps = isPlainObject(raw) ? raw.steps : null;
|
|
2931
2949
|
if (isPlainObject(steps)) {
|
|
2932
2950
|
for (const [id, value] of Object.entries(steps)) {
|
|
2933
2951
|
const list = dedupeConsecutive((Array.isArray(value) ? value : [value])
|
|
2934
2952
|
.filter((s) => typeof s === 'string' && s.trim())
|
|
2935
2953
|
.map((s) => s.trim()));
|
|
2936
|
-
if (list.length)
|
|
2954
|
+
if (list.length) ownKey(out, id, list);
|
|
2937
2955
|
}
|
|
2938
2956
|
}
|
|
2939
|
-
return
|
|
2957
|
+
return out;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
export function normalizeBindings(raw) {
|
|
2961
|
+
const profiles = {};
|
|
2962
|
+
const rawProfiles = isPlainObject(raw) ? raw.profiles : null;
|
|
2963
|
+
if (isPlainObject(rawProfiles)) {
|
|
2964
|
+
for (const [id, value] of Object.entries(rawProfiles)) {
|
|
2965
|
+
const steps = normalizeSteps(isPlainObject(value) ? value.steps : null);
|
|
2966
|
+
if (Object.keys(steps).length) ownKey(profiles, id, { steps });
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
return { steps: normalizeSteps(isPlainObject(raw) ? raw.steps : null), profiles };
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
// The bindings as ONE epic sees them: the project's, plus which route the epic is on (E51).
|
|
2973
|
+
//
|
|
2974
|
+
// A view, not a merge. `stepSkills` reads the route's layer first and the project's after it, so a
|
|
2975
|
+
// line in either layer keeps saying where it came from — `yad skill list` needs that for `source`,
|
|
2976
|
+
// and a merged object would make a route's line indistinguishable from a project-wide one. The same
|
|
2977
|
+
// object goes to every reader of one epic (`nextAction` and the line `yad next` prints beside it), so
|
|
2978
|
+
// the JSON and the prose cannot name two different skills for one step.
|
|
2979
|
+
//
|
|
2980
|
+
// `profile` is null for "no route": no epic yet (`yad next` on an empty Product, `yad setup`), or an
|
|
2981
|
+
// epic whose route nothing can tell. Then only the project-wide layer applies — never a guessed route.
|
|
2982
|
+
export function bindingsForProfile(bindings, profile) {
|
|
2983
|
+
const base = isPlainObject(bindings) ? bindings : { steps: {}, profiles: {} };
|
|
2984
|
+
const id = typeof profile === 'string' && profile ? profile : null;
|
|
2985
|
+
return { ...base, profile: id };
|
|
2940
2986
|
}
|
|
2941
2987
|
|
|
2988
|
+
// The route an epic RECORDS, as written — or null when it records none.
|
|
2989
|
+
//
|
|
2990
|
+
// NOT `epicProfileId`, which falls back to matching the chain. That guess is fine for what a step may
|
|
2991
|
+
// skip, where a wrong answer is a refusal a person sees at once. Here it would be a silent claim that
|
|
2992
|
+
// is believed and acted on: `yad next` names the skill and `yad-run` runs it. Every hand-written and
|
|
2993
|
+
// pre-shape-6 ledger is a shortened `classic`, and the shortest route that fits `[epic, epic-review]`
|
|
2994
|
+
// is `chore` — so a `chore`-only line would reach every older classic epic. The E40 rule for
|
|
2995
|
+
// `routeLacksStep`, for the same reason.
|
|
2996
|
+
//
|
|
2997
|
+
// A route this release does not KNOW is still used as written (rule 3): an epic from a newer release
|
|
2998
|
+
// that records it gets the lines the team bound for it, which is what `yad skill bind` promises when it
|
|
2999
|
+
// records one. The lookup is `Object.hasOwn`, so an unknown id is safe.
|
|
3000
|
+
export const recordedRoute = (state) =>
|
|
3001
|
+
(isPlainObject(state) && typeof state.profile === 'string' && state.profile ? state.profile : null);
|
|
3002
|
+
|
|
3003
|
+
// The view for an epic's own `state.json` — its recorded route. A view that already names a route is
|
|
3004
|
+
// kept: a caller that resolved it once hands the same object on.
|
|
3005
|
+
export const bindingsForEpic = (bindings, state) =>
|
|
3006
|
+
(isPlainObject(bindings) && Object.hasOwn(bindings, 'profile')
|
|
3007
|
+
? bindings
|
|
3008
|
+
: bindingsForProfile(bindings, recordedRoute(state)));
|
|
3009
|
+
|
|
2942
3010
|
// The project's bindings, or the empty set when the file is absent or unreadable.
|
|
2943
3011
|
export const loadSkillBindings = (root) =>
|
|
2944
3012
|
normalizeBindings(readJSON(path.join(root, PROJECT_FILES.skillsConfig), null));
|
|
@@ -2955,22 +3023,48 @@ export function stepSkills(stepId, bindings = null) {
|
|
|
2955
3023
|
// object `Object.prototype` answers for `constructor`, `toString` and friends — so a chain carrying
|
|
2956
3024
|
// a step called `constructor` resolved its "skill" to a function, and `yad next` threw trying to
|
|
2957
3025
|
// spread it. The catalogue map below is `__proto__: null` for the same reason.
|
|
2958
|
-
const
|
|
2959
|
-
|
|
2960
|
-
const bound = boundTo(stepId);
|
|
2961
|
-
if (Array.isArray(bound) && bound.length) return [...bound];
|
|
2962
|
-
// A step that took over from an OLD id inherits the old id's binding until it is given one of its
|
|
2963
|
-
// own (rule 3). A project that bound its own skill to `discovery` before E75 renamed the product
|
|
2964
|
-
// step to `foundation` would otherwise be switched back to the default without anyone deciding it.
|
|
2965
|
-
const legacy = Object.hasOwn(BINDING_SUCCEEDS, stepId) ? boundTo(BINDING_SUCCEEDS[stepId]) : null;
|
|
2966
|
-
if (Array.isArray(legacy) && legacy.length) return [...legacy];
|
|
3026
|
+
const found = boundSkills(stepId, bindings);
|
|
3027
|
+
if (found) return [...found.skills];
|
|
2967
3028
|
const fallback = CATALOGUE_SKILL[stepId];
|
|
2968
3029
|
return typeof fallback === 'string' ? [fallback] : [];
|
|
2969
3030
|
}
|
|
2970
3031
|
|
|
3032
|
+
// Which LAYER binds a step, and to what: `{ skills, source: 'profile' | 'project' }`, or null when
|
|
3033
|
+
// neither does (the step is on the catalogue's default). The most specific layer wins (E51): the
|
|
3034
|
+
// epic's route, then the project-wide `steps`.
|
|
3035
|
+
//
|
|
3036
|
+
// Inside each layer, a step that took over from an OLD id inherits the old id's binding until it is
|
|
3037
|
+
// given one of its own (rule 3). A project that bound its own skill to `discovery` before E75 renamed
|
|
3038
|
+
// the product step to `foundation` would otherwise be switched back to the default without anyone
|
|
3039
|
+
// deciding it. The layer order comes first: a route's `discovery` line beats a project-wide
|
|
3040
|
+
// `foundation` one, because it is the more specific CHOICE, whatever it is spelled.
|
|
3041
|
+
export function boundSkills(stepId, bindings = null) {
|
|
3042
|
+
const lookup = (steps) => {
|
|
3043
|
+
const boundTo = (id) => (isPlainObject(steps) && Object.hasOwn(steps, id) ? steps[id] : null);
|
|
3044
|
+
const own = boundTo(stepId);
|
|
3045
|
+
if (Array.isArray(own) && own.length) return own;
|
|
3046
|
+
const legacy = Object.hasOwn(BINDING_SUCCEEDS, stepId) ? boundTo(BINDING_SUCCEEDS[stepId]) : null;
|
|
3047
|
+
return Array.isArray(legacy) && legacy.length ? legacy : null;
|
|
3048
|
+
};
|
|
3049
|
+
const profiles = bindings?.profiles;
|
|
3050
|
+
const profile = bindings?.profile;
|
|
3051
|
+
const route = typeof profile === 'string' && isPlainObject(profiles) && Object.hasOwn(profiles, profile)
|
|
3052
|
+
? profiles[profile]?.steps : null;
|
|
3053
|
+
const fromRoute = lookup(route);
|
|
3054
|
+
if (fromRoute) return { skills: fromRoute, source: 'profile' };
|
|
3055
|
+
const fromProject = lookup(bindings?.steps);
|
|
3056
|
+
return fromProject ? { skills: fromProject, source: 'project' } : null;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
2971
3059
|
// new step id -> the old step id whose binding it inherits while it has none of its own.
|
|
2972
3060
|
const BINDING_SUCCEEDS = { __proto__: null, foundation: 'discovery' };
|
|
2973
3061
|
|
|
3062
|
+
// The step ids that read a line written under `id` — itself, and any step that took over from it. For
|
|
3063
|
+
// `yad doctor`: a `discovery` line on the `foundation` route is read for `foundation`, so it is not a
|
|
3064
|
+
// line no epic reaches.
|
|
3065
|
+
export const stepsReadingBinding = (id) =>
|
|
3066
|
+
[id, ...Object.keys(BINDING_SUCCEEDS).filter((k) => BINDING_SUCCEEDS[k] === id)];
|
|
3067
|
+
|
|
2974
3068
|
// The two keys every action object carries for its skill, from one resolved list.
|
|
2975
3069
|
//
|
|
2976
3070
|
// `skill` STAYS A STRING and is the first of the chain, because ~20 call sites and `yad next --json`
|
|
@@ -3318,8 +3412,11 @@ export function repairState(state, close = null) {
|
|
|
3318
3412
|
// reopened — a lane per step re-opened behind finished work (E41), each shaped like an action
|
|
3319
3413
|
// debt — the steps still owed as debt (`owedSteps`), the reminder that repeats until they are paid
|
|
3320
3414
|
export function nextAction(ledger, opts = {}) {
|
|
3321
|
-
const a = shapeNextAction(ledger, opts);
|
|
3322
3415
|
const state = ledger?.state;
|
|
3416
|
+
// E51: every skill this action names is the one for THIS epic's route. Resolved once, here, so the
|
|
3417
|
+
// Shape step, the Build lanes and the re-opened lanes all read the same view.
|
|
3418
|
+
opts = { ...opts, bindings: bindingsForEpic(opts.bindings ?? null, state) };
|
|
3419
|
+
const a = shapeNextAction(ledger, opts);
|
|
3323
3420
|
if (!state || !Array.isArray(state.steps) || isProductLevel(state) || backfillAnchorKind(state)) return a;
|
|
3324
3421
|
const lanes = reopenedLanes(ledger, { epicId: a.epicId, currentStep: state.currentStep, bindings: opts.bindings ?? null });
|
|
3325
3422
|
const debt = owedSteps(state).map((s) => ({ step: s.id, status: stepStatus(s) ?? s.status, record: s.record || null }));
|
package/cli/epic.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
DISCOVERY_EPIC, epicIds, epicLineage, epicRel, epicRoot, epicStories, featureStatus, FOUNDATION_EPIC, FOUNDATION_SECTIONS,
|
|
37
37
|
isGenesisType, isValidEpicId, lifecycleProfile, loadLedger, loadSkillBindings, PRODUCT_DONE, PRODUCT_EPICS,
|
|
38
38
|
planThreadedSeed, readFrontmatter, roadmapFeatures, seedableProfiles, seedFoundationState,
|
|
39
|
-
seedState, staleFoundationGuards, stepSkills, typeNoun, WORK_ITEM_TYPES, workItemType, writeJSON, writeState,
|
|
39
|
+
seedState, staleFoundationGuards, stepSkills, bindingsForEpic, typeNoun, WORK_ITEM_TYPES, workItemType, writeJSON, writeState,
|
|
40
40
|
} from './epic-state.mjs';
|
|
41
41
|
import { epicFiles, isVerifiedLedger, productConfigPath } from './manifest.mjs';
|
|
42
42
|
import { refreshIndexAfterWrite } from './product-index.mjs';
|
|
@@ -223,8 +223,9 @@ export async function runEpicNew(root, { slug, type = null, profile = null, stub
|
|
|
223
223
|
// Everything else asks the PROJECT first (E6): a team that bound its own skill to `epic` must be
|
|
224
224
|
// told to run that one, or this command would hand a brand-new epic straight to a skill their
|
|
225
225
|
// `yad next` will never name again. `yad-backfill` is the one exception, because waking a stub is
|
|
226
|
-
// the engine's own `promote` verb rather than a step on any chain.
|
|
227
|
-
|
|
226
|
+
// the engine's own `promote` verb rather than a step on any chain. The route is the one just seeded
|
|
227
|
+
// (E51), read off the state as `yad next` will read it, so the two name the same skill.
|
|
228
|
+
const skills = stub ? ['yad-backfill'] : stepSkills(first.id, bindingsForEpic(loadSkillBindings(root), state));
|
|
228
229
|
const skill = skills[0] || null;
|
|
229
230
|
// `nextSkills` appears only for a bound chain, the same rule `yad next --json` follows — a key that
|
|
230
231
|
// showed up on every seed would be one more always-null field for every reader to ignore.
|
|
@@ -314,7 +315,8 @@ function seedThreaded(root, { epic, dir, files, mdPath, fm, type, parent, profil
|
|
|
314
315
|
|
|
315
316
|
const { state } = plan;
|
|
316
317
|
const first = state.steps.find((s) => s.id === state.currentStep);
|
|
317
|
-
|
|
318
|
+
// The route the change takes from its parent, which `planThreadedSeed` records on the new state.
|
|
319
|
+
const skills = stepSkills(first.id, bindingsForEpic(loadSkillBindings(root), state));
|
|
318
320
|
const carried = state.steps.filter((s) => s.inherited);
|
|
319
321
|
if (json) {
|
|
320
322
|
return emitJSON({
|
|
@@ -389,7 +391,7 @@ export async function runFoundationNew(root, { today, json = false } = {}) {
|
|
|
389
391
|
for (const f of [files.approvals, files.comments]) if (!fs.existsSync(f)) writeJSON(f, []);
|
|
390
392
|
fs.mkdirSync(path.join(epicRoot(root, FOUNDATION_EPIC), 'reviews'), { recursive: true });
|
|
391
393
|
|
|
392
|
-
const skills = stepSkills(state.steps[0].id, loadSkillBindings(root));
|
|
394
|
+
const skills = stepSkills(state.steps[0].id, bindingsForEpic(loadSkillBindings(root), state));
|
|
393
395
|
const skill = skills[0] || null;
|
|
394
396
|
const required = FOUNDATION_SECTIONS.filter((s) => !s.optional).map((s) => s.file);
|
|
395
397
|
const optional = FOUNDATION_SECTIONS.filter((s) => s.optional).map((s) => s.file);
|
package/cli/manifest.mjs
CHANGED
|
@@ -539,6 +539,9 @@ export const PRODUCT_WIRING = {
|
|
|
539
539
|
{ src: 'skills/yad-checks/templates/checks/commit-message.sh', dest: 'checks/commit-message.sh', exec: true },
|
|
540
540
|
{ src: 'skills/yad-pr-template/templates/checks/pr-title.sh', dest: 'checks/pr-title.sh', exec: true },
|
|
541
541
|
{ src: 'skills/yad-pr-template/templates/checks/pr-template.sh', dest: 'checks/pr-template.sh', exec: true },
|
|
542
|
+
// E131: a member file is changed only by its own person, and each email it adds is proven on the PR.
|
|
543
|
+
// A Node script (`node checks/member-check.mjs`), so it needs no execute bit.
|
|
544
|
+
{ src: 'skills/yad-checks/templates/checks/member-check.mjs', dest: 'checks/member-check.mjs' },
|
|
542
545
|
],
|
|
543
546
|
github: [
|
|
544
547
|
{ src: 'skills/yad-product-bridge/templates/github/yad-gate-sync.yml', dest: '.github/workflows/yad-gate-sync.yml' },
|