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
package/cli/mode.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// `yad mode` / `yad mode solo --reason <text>` / `yad mode team [--reason <text>]` — who must approve (E10).
|
|
2
|
+
//
|
|
3
|
+
// Solo mode waives the approval requirement on every review gate: the merge and the resolved threads
|
|
4
|
+
// still decide. Until E10 only `yad setup --solo` / `--team <n>` set it, and nothing recorded who turned it
|
|
5
|
+
// on, when, or why. This command sets it and records that.
|
|
6
|
+
//
|
|
7
|
+
// TWO NAMES, THE OLD ONE DECIDES. The roadmap's word is `mode: solo | team`. It is written beside the
|
|
8
|
+
// `solo` flag every reader already knows — the gate, `yad next`, `yad doctor`, and CI in the user's repo
|
|
9
|
+
// running whatever release it pins — and `solo` stays the one READ for this major (the staged-rename
|
|
10
|
+
// pattern). `yad doctor` warns when a hand edit leaves the two disagreeing (`mode:disagree`).
|
|
11
|
+
//
|
|
12
|
+
// NOTHING LOOKS BACK. A gate that already passed keeps its closing record. An open review follows the new
|
|
13
|
+
// mode from its next sync, and this command names each one, so the change is never a surprise.
|
|
14
|
+
//
|
|
15
|
+
// A REASON FOR SOLO. Turning solo on stops every open gate from counting approvals, CI's included on a
|
|
16
|
+
// verified Product, so it needs `--reason`. Turning it off makes the gates stricter, and the reason is
|
|
17
|
+
// optional — the same split as `yad kill` / `yad unkill`.
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
|
|
20
|
+
import { c, fail, hand, info, ok, readJSONStrict, warn, writeProductConfig, emitJSON } from './lib.mjs';
|
|
21
|
+
import { isVerifiedLedger, productConfigPath, PROJECT_FILES } from './manifest.mjs';
|
|
22
|
+
import { epicIds, epicRoot, loadLedger, stepStatus } from './epic-state.mjs';
|
|
23
|
+
import { isSolo } from './gate.mjs';
|
|
24
|
+
import { printTeamHint, soloTeamHint } from './people.mjs';
|
|
25
|
+
import { recordActor } from './skip.mjs';
|
|
26
|
+
|
|
27
|
+
export const MODES = ['solo', 'team'];
|
|
28
|
+
|
|
29
|
+
const isObj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
30
|
+
|
|
31
|
+
// The mode the gates act on today. Read from `solo` (and the older `review_gate.solo`), never from `mode`.
|
|
32
|
+
export const modeOf = (hub) => (isSolo(hub) ? 'solo' : 'team');
|
|
33
|
+
|
|
34
|
+
// PURE. The fields a write of mode `to` puts on the Product config, given what is there now. Shared by this
|
|
35
|
+
// command and `yad setup`, so the two writers can never spell the switch differently (staged-rename trap 1).
|
|
36
|
+
// solo / mode always both, in step.
|
|
37
|
+
// review_gate `review_gate.solo: true` also switches solo on, so switching to team turns it off too;
|
|
38
|
+
// otherwise the old flag would keep the gates waived under a file that says `team`.
|
|
39
|
+
// mode_set only when the mode the gates act on actually changes: who, when, why, and from what.
|
|
40
|
+
export function modeFields(hub, to, { by = null, date = null, reason = null } = {}) {
|
|
41
|
+
const cur = isObj(hub) ? hub : {};
|
|
42
|
+
const from = modeOf(cur);
|
|
43
|
+
const fields = { solo: to === 'solo', mode: to };
|
|
44
|
+
if (to === 'team' && isObj(cur.review_gate) && cur.review_gate.solo === true) {
|
|
45
|
+
fields.review_gate = { ...cur.review_gate, solo: false };
|
|
46
|
+
}
|
|
47
|
+
if (from !== to) fields.mode_set = { from, to, by: by || null, date: date || null, reason: reason || null };
|
|
48
|
+
return fields;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// PURE. What `yad mode <to>` would do.
|
|
52
|
+
// changed: false the file already says it both ways — nothing to write.
|
|
53
|
+
// flipped: true the gates act differently from the next sync; `mode_set` records it.
|
|
54
|
+
// flipped: false, changed the gates act the same, and the new name is added beside the old one.
|
|
55
|
+
export function planMode(hub, { to, reason = null, by = null, date = null } = {}) {
|
|
56
|
+
if (!MODES.includes(to)) {
|
|
57
|
+
return { ok: false, message: `unknown mode: ${to ?? '(none)'}`, hint: 'usage: yad mode [solo --reason "<why>" | team [--reason "<why>"]]' };
|
|
58
|
+
}
|
|
59
|
+
const cur = isObj(hub) ? hub : {};
|
|
60
|
+
const from = modeOf(cur);
|
|
61
|
+
const why = typeof reason === 'string' ? reason.trim() : '';
|
|
62
|
+
if (from !== to && to === 'solo' && !why) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
message: 'turning solo mode on needs a reason',
|
|
66
|
+
hint: 'yad mode solo --reason "<why>" — every open review gate stops counting approvals, CI\'s included',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const fields = modeFields(cur, to, { by, date, reason: why || null });
|
|
70
|
+
const next = { ...cur, ...fields };
|
|
71
|
+
const changed = Object.keys(fields).some((k) => JSON.stringify(cur[k]) !== JSON.stringify(fields[k]));
|
|
72
|
+
return { ok: true, from, to, flipped: from !== to, changed, hub: next };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Every review gate that is open right now, across the feature epics and the Foundation. These are the ones
|
|
76
|
+
// whose rule changes with the mode; a passed gate keeps its record and a gate not yet opened reads the mode
|
|
77
|
+
// when it opens. An epic whose ledger cannot be read is left out here — `yad doctor` names it.
|
|
78
|
+
export function openReviews(root) {
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const epic of epicIds(root)) {
|
|
81
|
+
let state;
|
|
82
|
+
try { state = loadLedger(epicRoot(root, epic)).state; } catch { continue; }
|
|
83
|
+
for (const s of Array.isArray(state?.steps) ? state.steps : []) {
|
|
84
|
+
if (s?.type === 'review+approve' && stepStatus(s) === 'in_review') out.push({ epic, step: s.id });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const makeBail = (json) => (message, hint) => {
|
|
91
|
+
if (json) emitJSON({ ok: false, error: message, hint: hint || null });
|
|
92
|
+
else { fail(message); if (hint) hand(hint); }
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const MEANING = {
|
|
97
|
+
solo: 'review gates stop counting approvals — the merge and the resolved threads decide, and each gate that passes records `waived: "solo"`',
|
|
98
|
+
team: 'review gates count approvals again',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const setLine = (set) => {
|
|
102
|
+
if (!isObj(set)) return '';
|
|
103
|
+
const who = [set.by ? `by ${set.by}` : '', set.date ? `on ${set.date}` : ''].filter(Boolean).join(' ');
|
|
104
|
+
return `set to ${set.to}${set.from ? ` from ${set.from}` : ''}${who ? ` ${who}` : ''}${set.reason ? `: ${set.reason}` : ''}`;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// `yad mode` reads; `yad mode solo|team` sets.
|
|
108
|
+
//
|
|
109
|
+
// With no argument in solo mode it also counts people once (E74) and suggests `yad mode team` when the
|
|
110
|
+
// count shows more than one person may work here — see `teamHint`. Team mode reads no count.
|
|
111
|
+
// `headCount` is a count the caller already read; the CLI passes none, a test passes one.
|
|
112
|
+
export async function runMode(root, { to = null, reason = null, json = false, today = null, headCount = null } = {}) {
|
|
113
|
+
const bail = makeBail(json);
|
|
114
|
+
const file = productConfigPath(root);
|
|
115
|
+
const rel = path.relative(root, file).split(path.sep).join('/');
|
|
116
|
+
let hub;
|
|
117
|
+
try {
|
|
118
|
+
hub = readJSONStrict(file, null);
|
|
119
|
+
} catch {
|
|
120
|
+
return bail(`${rel} does not parse — nothing is written over it`, 'fix the JSON or restore it from git, then run this again');
|
|
121
|
+
}
|
|
122
|
+
if (hub !== null && !isObj(hub)) return bail(`${rel} has the wrong shape — nothing is written over it`, 'expected a JSON object; fix it or re-run `yad setup`');
|
|
123
|
+
|
|
124
|
+
if (to === null) {
|
|
125
|
+
const mode = modeOf(hub);
|
|
126
|
+
const name = isObj(hub) && hub.mode !== undefined ? hub.mode : null;
|
|
127
|
+
const set = isObj(hub) && isObj(hub.mode_set) ? hub.mode_set : null;
|
|
128
|
+
const hint = soloTeamHint(root, hub, { solo: mode === 'solo', headCount, today });
|
|
129
|
+
if (json) return emitJSON({ ok: true, mode, name, agrees: name === null || name === mode, set, suggest: hint });
|
|
130
|
+
ok(`mode: ${mode} — ${MEANING[mode]}`);
|
|
131
|
+
if (!hub) info('no Product config yet — team mode is the default; `yad setup` records it');
|
|
132
|
+
if (set) info(setLine(set));
|
|
133
|
+
if (name !== null && name !== mode) warn(`the file also says mode: ${JSON.stringify(name)}, but the old \`solo\` flag is the one read — run \`yad mode ${mode}\` to make them agree`);
|
|
134
|
+
printTeamHint(hint, { unknown: true });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The word is checked first, so a typo on a fresh directory is named as a typo, not as a missing setup.
|
|
139
|
+
if (!MODES.includes(to)) return bail(`unknown mode: ${to}`, 'usage: yad mode [solo --reason "<why>" | team [--reason "<why>"]]');
|
|
140
|
+
if (!hub) return bail(`no Product config at ${rel}`, 'run `yad setup` first — it records the mode with everything else');
|
|
141
|
+
const plan = planMode(hub, { to, reason, by: recordActor(root), date: today });
|
|
142
|
+
if (!plan.ok) return bail(plan.message, plan.hint);
|
|
143
|
+
if (plan.changed) writeProductConfig(root, plan.hub);
|
|
144
|
+
// On a verified Product CI writes the ledger only at a merge, so an open review is never `in_review` in
|
|
145
|
+
// state.json — the platform's open PRs are the list. Say so, rather than print nothing and read as "none".
|
|
146
|
+
const verified = isVerifiedLedger(hub);
|
|
147
|
+
const open = plan.flipped && !verified ? openReviews(root) : [];
|
|
148
|
+
if (json) {
|
|
149
|
+
return emitJSON({ ok: true, mode: to, changed: plan.changed, flipped: plan.flipped, set: plan.flipped ? plan.hub.mode_set : null, openReviews: open, openReviewsKnown: !verified });
|
|
150
|
+
}
|
|
151
|
+
if (!plan.changed) return ok(`already ${to} — nothing changed`);
|
|
152
|
+
if (!plan.flipped) {
|
|
153
|
+
// Reached two ways: `mode` was absent (a Product set up before E10), or a hand edit left it saying the
|
|
154
|
+
// other mode — which is where `mode:disagree` sends people. Say which one happened.
|
|
155
|
+
const what = hub.mode === undefined ? 'the new name `mode` was added beside `solo`' : '`mode` now matches `solo`';
|
|
156
|
+
ok(`already ${to} — ${what}; the gates act exactly as before`);
|
|
157
|
+
} else {
|
|
158
|
+
ok(`mode: ${to} — ${MEANING[to]}`);
|
|
159
|
+
if (plan.hub.mode_set.reason) info(`reason: ${plan.hub.mode_set.reason}`);
|
|
160
|
+
if (verified) {
|
|
161
|
+
info(`this Product's ledger is verified, so its open reviews live on the platform, not in state.json — every open review PR/MR follows the ${to} rule from its next CI run`);
|
|
162
|
+
} else if (open.length) {
|
|
163
|
+
info(`${open.length} open review(s) follow the ${to} rule from their next sync: ${open.map((r) => `${r.epic} ${r.step}`).join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
info(c.dim('a gate that already passed keeps its record'));
|
|
166
|
+
if (to === 'solo') info(c.dim('`yad doctor` warns if branch protection still requires an approval you cannot give yourself'));
|
|
167
|
+
}
|
|
168
|
+
const back = to === 'solo' ? 'yad mode team' : 'yad mode solo --reason "<why>"';
|
|
169
|
+
hand(`commit ${PROJECT_FILES.productConfig} and ${PROJECT_FILES.hubConfig} so every machine and CI run sees it (reverse with \`${back}\`)`);
|
|
170
|
+
}
|