yadflow 4.2.0 → 4.3.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 +15 -0
- package/README.md +1 -1
- package/bin/commands.mjs +1 -1
- package/bin/yad.mjs +9 -4
- package/cli/find-product.mjs +24 -0
- package/cli/reconcile.mjs +19 -3
- package/cli/setup.mjs +12 -3
- package/cli/toolbox.mjs +46 -15
- package/cli/workspace.mjs +9 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# [4.3.0](https://github.com/abdelrahmannasr/yadflow/compare/v4.2.0...v4.3.0) (2026-10-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **toolbox:** E85 review 1 — hint order, the folder check looks in, team-wide remove advice ([c7f6592](https://github.com/abdelrahmannasr/yadflow/commit/c7f65922cdad49e9816c4cbe81190283392c1df6))
|
|
7
|
+
* **toolbox:** E85 review 2 — look for tools at the top of the repo, read --dir's Product ([82fb5ed](https://github.com/abdelrahmannasr/yadflow/commit/82fb5ed6a1afcf364a9844d3faf2676bd4a84274))
|
|
8
|
+
* **toolbox:** E85 review 3 — a worktree is its own checkout; look at the deepest top ([4926799](https://github.com/abdelrahmannasr/yadflow/commit/4926799f6db758c7abb59029bd8e71923550c227))
|
|
9
|
+
* **toolbox:** E85 review 4 — find the checkout top under both spellings of a linked folder ([9728384](https://github.com/abdelrahmannasr/yadflow/commit/9728384c140178d55608d2ab2b67037a2f80140d))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* **toolbox:** offer the missing tools in setup, check, update and join (E85) ([53e06c6](https://github.com/abdelrahmannasr/yadflow/commit/53e06c6da8bf585ced68435b052d01ba4ca5e6fa))
|
|
15
|
+
|
|
1
16
|
# [4.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v4.1.0...v4.2.0) (2026-10-01)
|
|
2
17
|
|
|
3
18
|
|
package/README.md
CHANGED
|
@@ -257,7 +257,7 @@ The record is worth trusting because people approve what goes into it.
|
|
|
257
257
|
- linked changes and `yad thread`
|
|
258
258
|
- `yad history`, `yad next` and `yad usage`
|
|
259
259
|
- `yad detect`, which lists the skills, agents, MCP servers and plugins installed for each AI agent
|
|
260
|
-
- `yad toolbox list`, which shows the external tools yadflow can use, which ones you have, and what happens without each; `yad toolbox add` / `remove` record the tools your team uses, and `yad toolbox check` lists the ones not ready on your machine
|
|
260
|
+
- `yad toolbox list`, which shows the external tools yadflow can use, which ones you have, and what happens without each; `yad toolbox add` / `remove` record the tools your team uses, and `yad toolbox check` lists the ones not ready on your machine (`yad setup`, `check`, `update` and `join` print the same list, and never install anything)
|
|
261
261
|
- draft saving, claims and owners
|
|
262
262
|
- the CI checks
|
|
263
263
|
- the Review Companion
|
package/bin/commands.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
export { runSetup } from '../cli/setup.mjs';
|
|
14
14
|
export { runNew, runInit, runJoin } from '../cli/workspace.mjs';
|
|
15
|
-
export { findProduct, hasProduct, workspaceProduct, registryEntryFor, WORKSPACE_FILE } from '../cli/find-product.mjs';
|
|
15
|
+
export { findProduct, hasProduct, workspaceProduct, registryEntryFor, toolsFolder, WORKSPACE_FILE } from '../cli/find-product.mjs';
|
|
16
16
|
export { reconcile } from '../cli/reconcile.mjs';
|
|
17
17
|
export { gateOpen, gateSync, gateComments, gateStatus, gateCi, gateReview, gateTrailer, gateWalkthrough, gateRepair } from '../cli/gate.mjs';
|
|
18
18
|
export { gateApprove, gateComment, gateAdvance } from '../cli/gate-local.mjs';
|
package/bin/yad.mjs
CHANGED
|
@@ -24,7 +24,8 @@ ${c.bold('Start a workspace')} ${c.dim('(the folder holding product/ and the cod
|
|
|
24
24
|
Never commits, pushes, or changes a shared file
|
|
25
25
|
|
|
26
26
|
${c.bold('Setup & maintenance')}
|
|
27
|
-
yad setup Guided first-run setup (profile interview, install, connect & wire repos
|
|
27
|
+
yad setup Guided first-run setup (profile interview, install, connect & wire repos,
|
|
28
|
+
then the tools this project uses that are missing — offered, never installed)
|
|
28
29
|
profile flags: --solo | --team <n>, --greenfield | --brownfield,
|
|
29
30
|
--monorepo | --separate, --tools (configure design/testing/learning now)
|
|
30
31
|
yad check Report what is missing / drifted / modified / stale / legacy (read-only)
|
|
@@ -539,8 +540,12 @@ async function main() {
|
|
|
539
540
|
// registers (through `.yad-workspace.json`) — and says where, on stderr. A code-repo command keeps acting
|
|
540
541
|
// on the repo it runs in, and is handed the Product to read (its registry: name, platform, branch).
|
|
541
542
|
const dirGiven = process.argv.slice(2).some((a) => a === '--dir' || a.startsWith('--dir='));
|
|
543
|
+
// E85: where `check` and `update` look for tools, and whose choices they read — worked out from the
|
|
544
|
+
// folder the person ran yad in (or named), before a Product command moves `o.dir` to the Product.
|
|
545
|
+
let toolboxAt = null;
|
|
542
546
|
if (PRODUCT_CMDS.has(cmd) || REPO_CMDS.has(cmd)) {
|
|
543
547
|
const found = commands.findProduct(dirGiven ? o.dir : shellCwd());
|
|
548
|
+
toolboxAt = { from: commands.toolsFolder(found, dirGiven ? o.dir : shellCwd()), product: found?.root ?? null };
|
|
544
549
|
const shown = (p) => path.relative(process.cwd(), p) || '.';
|
|
545
550
|
if (found?.problem) warn(`${found.problem} — using ${o.dir}`);
|
|
546
551
|
else if (found?.elsewhere) {
|
|
@@ -625,11 +630,11 @@ async function main() {
|
|
|
625
630
|
}
|
|
626
631
|
case 'check':
|
|
627
632
|
if (noProduct()) break;
|
|
628
|
-
result = await commands.reconcile(o.dir, { fix: o.fix, scope: o.scope, force: o.force, push: o.push, allowBranch: o.allowBranch, overwriteLocal: o.overwriteLocal, today });
|
|
633
|
+
result = await commands.reconcile(o.dir, { fix: o.fix, scope: o.scope, force: o.force, push: o.push, allowBranch: o.allowBranch, overwriteLocal: o.overwriteLocal, today, toolbox: toolboxAt });
|
|
629
634
|
break;
|
|
630
635
|
case 'update':
|
|
631
636
|
if (noProduct()) break;
|
|
632
|
-
result = await commands.reconcile(o.dir, { fix: true, scope: 'changed', force: o.force, push: o.push, allowBranch: o.allowBranch, overwriteLocal: o.overwriteLocal, today });
|
|
637
|
+
result = await commands.reconcile(o.dir, { fix: true, scope: 'changed', force: o.force, push: o.push, allowBranch: o.allowBranch, overwriteLocal: o.overwriteLocal, today, toolbox: toolboxAt });
|
|
633
638
|
break;
|
|
634
639
|
case 'doctor':
|
|
635
640
|
result = await commands.runDoctor(o.dir, { json: o.json });
|
|
@@ -655,7 +660,7 @@ async function main() {
|
|
|
655
660
|
else if (found?.elsewhere) warn(`this folder is not in a repo the Product registers — the Product is ${rel(found.elsewhere)}: cd there, or pass --dir ${rel(found.elsewhere)}`);
|
|
656
661
|
const productRoot = found?.root ?? null;
|
|
657
662
|
const run = action === 'list' ? commands.runToolboxList : commands.runToolboxCheck;
|
|
658
|
-
result = run(o.dir || process.cwd(), { json: o.json, productRoot });
|
|
663
|
+
result = run(commands.toolsFolder(found, o.dir || process.cwd()), { json: o.json, productRoot });
|
|
659
664
|
break;
|
|
660
665
|
}
|
|
661
666
|
if (extra) { refuse(`yad toolbox ${action} takes one tool id (got also: ${extra})`, usage); break; }
|
package/cli/find-product.mjs
CHANGED
|
@@ -181,6 +181,30 @@ export function gitTopOf(dir) {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// Where `yad toolbox` and the toolbox section of `check` / `update` look for tools (E85), given
|
|
185
|
+
// `findProduct(from)`'s answer. Finding reads `<folder>/.claude/…` and never walks up, so a subfolder
|
|
186
|
+
// must be lifted to the top of what the person is working in. That is the DEEPEST of these that holds
|
|
187
|
+
// `from`: the git checkout it is in (a worktree inside `api/` is its own checkout, on its own branch — never
|
|
188
|
+
// swapped for api's, as in E80 review 3), the registered repo holding it (`apps/web` in a monorepo is not
|
|
189
|
+
// a checkout of its own), and the Product found from it. A checkout ABOVE the Product (a Product that is
|
|
190
|
+
// not a git repo, inside one that is not ours) is never deeper than the Product, so it never wins. Reads
|
|
191
|
+
// only whether `.git` exists; runs nothing.
|
|
192
|
+
export function toolsFolder(found, from) {
|
|
193
|
+
const at = real(from);
|
|
194
|
+
if (!at) return from;
|
|
195
|
+
// Both spellings: the shell's (through a link) and the one on disk, so `check` (given the shell's
|
|
196
|
+
// folder) and `yad toolbox check` (given the process's) look in the same place (E85 review 4).
|
|
197
|
+
const candidates = [gitTopOf(from), gitTopOf(at)];
|
|
198
|
+
if (found?.root && found.repo && typeof found.repo.path === 'string') candidates.push(path.resolve(found.root, found.repo.path));
|
|
199
|
+
if (found?.root && (found.via === 'here' || found.via === 'above')) candidates.push(found.root);
|
|
200
|
+
let best = null;
|
|
201
|
+
for (const c of candidates) {
|
|
202
|
+
const cAt = c && real(c);
|
|
203
|
+
if (cAt && within(at, cAt) && (!best || cAt.length > best.length)) best = cAt;
|
|
204
|
+
}
|
|
205
|
+
return best ?? from;
|
|
206
|
+
}
|
|
207
|
+
|
|
184
208
|
// The registry entry for the code repo a code-repo command runs in (E80), with the folder to work in.
|
|
185
209
|
// It is the CHECKOUT that must be the registered repo — not merely a folder inside it: a worktree or a
|
|
186
210
|
// repo nested in `backend/` is another checkout, on another branch, and must never be swapped for
|
package/cli/reconcile.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { gitHead, packRepo } from './setup.mjs';
|
|
20
20
|
import { groupByRoot, commitUpdates, repoLabel } from './update-commit.mjs';
|
|
21
21
|
import { hasSiblingRepo, workspaceFileState, writeWorkspaceFile, WORKSPACE_FILE } from './find-product.mjs';
|
|
22
|
+
import { offerToolbox } from './toolbox.mjs';
|
|
22
23
|
|
|
23
24
|
const MARK = { missing: c.red('missing'), new: c.cyan('new'), outdated: c.yellow('outdated'), modified: c.cyan('modified'), stale: c.yellow('stale'), legacy: c.yellow('legacy'), removed: c.yellow('removed'), ok: c.green('ok') };
|
|
24
25
|
|
|
@@ -30,7 +31,7 @@ const repoShown = (repo) => `product (the code repo at ${forTerminal(repo.path)}
|
|
|
30
31
|
const shown = (a) => a.shownScope ?? a.scope;
|
|
31
32
|
const itemsOf = (actions) => actions.map((a) => ({ scope: a.scope, item: a.item, status: a.status, product: !a.fromRepo }));
|
|
32
33
|
|
|
33
|
-
export async function reconcile(root, { fix = false, scope = 'all', force = false, push = false, allowBranch = false, overwriteLocal = false } = {}) {
|
|
34
|
+
export async function reconcile(root, { fix = false, scope = 'all', force = false, push = false, allowBranch = false, overwriteLocal = false, toolbox: toolboxAt = null } = {}) {
|
|
34
35
|
log(c.bold(`\nSDLC reconcile ${c.dim('v' + VERSION)}`));
|
|
35
36
|
log(c.dim(`target: ${root}\n`));
|
|
36
37
|
|
|
@@ -239,7 +240,9 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
|
|
|
239
240
|
if (workspaceFile === 'missing') info(`no ${WORKSPACE_FILE} beside the Product — \`yad check --fix\` writes it, so yad finds the Product from its code repos`);
|
|
240
241
|
if (push) warn('--push has no effect without --fix (there is nothing applied to commit).');
|
|
241
242
|
if (fixable.length || gaps.length) hand('run `yad check --fix` to reconcile (or `yad setup` for missing one-time setup).');
|
|
242
|
-
|
|
243
|
+
// After the --fix hint, never above it: --fix installs no tool, and must not read as if it did.
|
|
244
|
+
const toolbox = toolboxSection(root, toolboxAt);
|
|
245
|
+
return { fix: false, counts, gaps, items: itemsOf(actions), applied: 0, modified: modified.length, commits: [], workspaceFile, toolbox };
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
// --- apply --- (collect the applied actions so --push can stage each repo's exact allowlist) ---
|
|
@@ -314,5 +317,18 @@ export async function reconcile(root, { fix = false, scope = 'all', force = fals
|
|
|
314
317
|
},
|
|
315
318
|
});
|
|
316
319
|
}
|
|
317
|
-
|
|
320
|
+
const toolbox = toolboxSection(root, toolboxAt);
|
|
321
|
+
return { fix: true, counts, gaps, items: itemsOf(actions), applied, modified: modified.length, commits, workspaceFile, toolbox };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// E85: `yad check` and `yad update` end with the toolbox check — the tools this Product uses that are not
|
|
325
|
+
// here. Report only: it never adds to what `--fix` applies, never changes the exit code, and installs
|
|
326
|
+
// nothing. Both verbs, because both run here and a release can add a core tool. `at` comes from the
|
|
327
|
+
// command line: `from`, the top of the repo the person ran yad in, where tools are looked for (as
|
|
328
|
+
// `yad toolbox check` does — a code repo's own skills count); `product`, the Product whose choices are
|
|
329
|
+
// read, which is not `root` under `--dir <a code repo>`.
|
|
330
|
+
function toolboxSection(root, at) {
|
|
331
|
+
log('');
|
|
332
|
+
log(c.bold('Toolbox'));
|
|
333
|
+
return offerToolbox(at?.from || root, at?.product || root);
|
|
318
334
|
}
|
package/cli/setup.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { modeFields, modeOf } from './mode.mjs';
|
|
17
17
|
import { recordActor } from './skip.mjs';
|
|
18
18
|
import { loadSkillBindings, stepSkills } from './epic-state.mjs';
|
|
19
|
+
import { offerToolbox } from './toolbox.mjs';
|
|
19
20
|
|
|
20
21
|
// Parse a comma/space separated list into a clean, deduped array of trimmed tokens.
|
|
21
22
|
export function parseList(s) {
|
|
@@ -322,8 +323,9 @@ export async function runSetup(root, opts = {}) {
|
|
|
322
323
|
|
|
323
324
|
// 0. Profile interview — branch the wizard to the user's situation (solo/team, code, repo layout).
|
|
324
325
|
const { solo, team_size, codebase, repo_layout, configureTools } = await resolveProfile(root, opts);
|
|
325
|
-
// Steps: interview, preflight, install, Product, tools (1 if deferred else 3), repos, wire, coderabbit,
|
|
326
|
-
|
|
326
|
+
// Steps: interview, preflight, install, Product, tools (1 if deferred else 3), repos, wire, coderabbit,
|
|
327
|
+
// toolbox, done.
|
|
328
|
+
const total = 9 + (configureTools ? 3 : 1);
|
|
327
329
|
let _n = 0;
|
|
328
330
|
const S = (title) => step(++_n, total, title);
|
|
329
331
|
|
|
@@ -672,6 +674,13 @@ export async function runSetup(root, opts = {}) {
|
|
|
672
674
|
}
|
|
673
675
|
}
|
|
674
676
|
|
|
677
|
+
// E85: the external tools this project uses that are not here, and how to get each. AFTER the tools
|
|
678
|
+
// step, so a connector chosen there counts as in use. It offers and never installs: nothing is run and
|
|
679
|
+
// no file is written, and a missing tool never fails setup (each has a fallback).
|
|
680
|
+
S('Toolbox (external tools this project uses)');
|
|
681
|
+
guide(['Each tool is optional and has a fallback. yad installs nothing: it prints how to get each one, and you choose.']);
|
|
682
|
+
const toolbox = offerToolbox(root, root);
|
|
683
|
+
|
|
675
684
|
// Summary + version stamp
|
|
676
685
|
S('Done');
|
|
677
686
|
writeJSON(path.join(root, PROJECT_FILES.version), { version: VERSION, ideTargets, updatedAt: opts.today ?? null });
|
|
@@ -713,7 +722,7 @@ export async function runSetup(root, opts = {}) {
|
|
|
713
722
|
// step has written, so a scripted setup passes every flag (or SDLC_NONINTERACTIVE for the defaults).
|
|
714
723
|
return {
|
|
715
724
|
profile: { solo, team_size, codebase, repo_layout }, ideTargets,
|
|
716
|
-
repos: registry.repos.map((r) => r.name ?? null), next: epicSkill,
|
|
725
|
+
repos: registry.repos.map((r) => r.name ?? null), next: epicSkill, toolbox,
|
|
717
726
|
};
|
|
718
727
|
}
|
|
719
728
|
|
package/cli/toolbox.mjs
CHANGED
|
@@ -604,30 +604,61 @@ export function toolboxCheck(root, productRoot, opts = {}) {
|
|
|
604
604
|
return { used, findings, problems };
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
-
//
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (!findings.length) {
|
|
613
|
-
|
|
614
|
-
return undefined;
|
|
615
|
-
}
|
|
616
|
-
log(c.bold(`${findings.length} of the ${used.length} tool(s) this project uses ${findings.length === 1 ? 'is' : 'are'} not ready here. None is required.`));
|
|
607
|
+
// The lines a check prints, from `toolboxCheck`'s answer, without the closing hint. One printer for
|
|
608
|
+
// `yad toolbox check` and for the toolbox step of setup, update, check and join (E85), so they cannot
|
|
609
|
+
// say it differently.
|
|
610
|
+
export function toolboxCheckLines({ used, findings }) {
|
|
611
|
+
if (!used.length) return [` ${c.dim('•')} this project uses no tool from the toolbox — \`yad toolbox list\` shows them all`];
|
|
612
|
+
if (!findings.length) return [` ${c.green('✓')} all ${used.length} tool(s) this project uses are here: ${used.map((r) => clean(r.name)).join(', ')}`];
|
|
613
|
+
const lines = [c.bold(`${findings.length} of the ${used.length} tool(s) this project uses ${findings.length === 1 ? 'is' : 'are'} not ready here. None is required.`)];
|
|
617
614
|
for (const f of findings) {
|
|
618
615
|
const r = used.find((u) => u.id === f.id);
|
|
619
616
|
const what = f.problem === 'missing' ? 'not found'
|
|
620
617
|
: f.problem === 'disabled' ? 'installed, but its plugin is turned off in your Claude Code settings'
|
|
621
618
|
: `version ${clean(f.version)} is outside the known-good range ${f.versions} — still used`;
|
|
622
|
-
|
|
623
|
-
if (f.problem === 'disabled')
|
|
624
|
-
else for (const line of howToGet(r))
|
|
625
|
-
if (f.problem !== 'out-of-range')
|
|
619
|
+
lines.push(` ${f.problem === 'out-of-range' ? c.yellow('!') : c.dim('–')} ${c.bold(clean(f.name))}: ${what}`);
|
|
620
|
+
if (f.problem === 'disabled') lines.push(` ${c.dim('turn it on with /plugin in Claude Code')}`);
|
|
621
|
+
else for (const line of howToGet(r)) lines.push(` ${c.dim(line)}`);
|
|
622
|
+
if (f.problem !== 'out-of-range') lines.push(` ${c.dim(`without it: ${clean(f.fallback)}`)}`);
|
|
626
623
|
}
|
|
627
|
-
|
|
624
|
+
return lines;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// NEVER FAILS (decided with the row): no tool is required, so a missing one is news, not an error. Exit 0.
|
|
628
|
+
export function runToolboxCheck(root, { json = false, productRoot = null, ...opts } = {}) {
|
|
629
|
+
const res = toolboxCheck(root, productRoot, opts);
|
|
630
|
+
const { used, findings, problems } = res;
|
|
631
|
+
if (json) return emitJSON({ ok: true, ...(productRoot ? where(productRoot) : { product: null, file: null }), used: used.map((r) => r.id), findings, problems });
|
|
632
|
+
for (const p of problems) warn(p);
|
|
633
|
+
for (const line of toolboxCheckLines(res)) log(line);
|
|
634
|
+
if (findings.length) info(productRoot ? 'stop using a tool, for the whole team, with `yad toolbox remove <id>`' : 'no Product here, so this checks the core tools only');
|
|
628
635
|
return undefined;
|
|
629
636
|
}
|
|
630
637
|
|
|
638
|
+
// The toolbox step of `yad setup`, `yad check`, `yad update` and `yad join` (E85). It OFFERS: it prints
|
|
639
|
+
// what the project uses that is not here and how to get it. It installs nothing, runs no program and
|
|
640
|
+
// writes no file (decided with the row, as `yad toolbox add` is) — and it never fails the command it is
|
|
641
|
+
// part of. Returns the part of the --json answer it adds: `{ used, findings, problems }`.
|
|
642
|
+
// `canRemove: false` leaves out the `remove` advice — for `yad join`, whose reader just cloned the team's
|
|
643
|
+
// choices: one person's machine lacking a tool is no reason to change what the whole team uses.
|
|
644
|
+
export function offerToolbox(root, productRoot, { canRemove = true, ...opts } = {}) {
|
|
645
|
+
let res;
|
|
646
|
+
// A step inside another command: if finding tools throws (a home folder that cannot be read), the
|
|
647
|
+
// command it is part of still finishes, and says why the step is missing.
|
|
648
|
+
try { res = toolboxCheck(root, productRoot, opts); } catch (e) {
|
|
649
|
+
warn(`toolbox: could not check the tools here (${clean(e?.message ?? String(e))}) — run \`yad toolbox check\` to see why`);
|
|
650
|
+
return { used: [], findings: [], problems: [], error: 'could not check' };
|
|
651
|
+
}
|
|
652
|
+
for (const p of res.problems) warn(p);
|
|
653
|
+
for (const line of toolboxCheckLines(res)) log(line);
|
|
654
|
+
if (res.findings.length) {
|
|
655
|
+
info(canRemove
|
|
656
|
+
? 'nothing is installed for you: follow the steps you choose, or, for the whole team, stop using a tool with `yad toolbox remove <id>`'
|
|
657
|
+
: 'nothing is installed for you: follow the steps you choose');
|
|
658
|
+
}
|
|
659
|
+
return { used: res.used.map((r) => r.id), findings: res.findings, problems: res.problems };
|
|
660
|
+
}
|
|
661
|
+
|
|
631
662
|
// `--detect skill:x,plugin:p` → { skills: ['x'], plugins: ['p'] }, or { error }.
|
|
632
663
|
export function parseDetect(text) {
|
|
633
664
|
const out = {};
|
package/cli/workspace.mjs
CHANGED
|
@@ -32,6 +32,7 @@ import { productConfigPath, PROJECT_FILES } from './manifest.mjs';
|
|
|
32
32
|
import { runSetup, insideWorkspace, throughGitDir, selectIdeTargets } from './setup.mjs';
|
|
33
33
|
import { moduleActions, gitHookActions } from './plan.mjs';
|
|
34
34
|
import { writeWorkspaceFile, WORKSPACE_FILE } from './find-product.mjs';
|
|
35
|
+
import { offerToolbox } from './toolbox.mjs';
|
|
35
36
|
|
|
36
37
|
// E80: the file that lets yad find the Product from inside the repos it registers. Written by all three.
|
|
37
38
|
function noteWorkspaceFile(product) {
|
|
@@ -483,13 +484,20 @@ export async function runJoin(cwd, url, folder, opts = {}) {
|
|
|
483
484
|
const hook = gitHookActions(product);
|
|
484
485
|
for (const a of hook) a.apply();
|
|
485
486
|
if (hook.length) ok('installed the git pre-commit hook in this clone');
|
|
487
|
+
// E85: the tools the team chose (the Product's toolbox.json, just cloned) that this machine lacks. A
|
|
488
|
+
// new teammate is the person most likely to miss one. Offered, never installed.
|
|
489
|
+
log('');
|
|
490
|
+
log(c.bold('Toolbox'));
|
|
491
|
+
const toolbox = offerToolbox(product, product, { canRemove: false });
|
|
486
492
|
|
|
493
|
+
// Under a heading of its own, so these lines do not read as part of the toolbox list above.
|
|
487
494
|
log('');
|
|
495
|
+
log(c.bold('Next'));
|
|
488
496
|
if (repos.failed.length) hand(`${repos.failed.length} repo(s) not cloned — fix what is named above and re-run \`yad join ${shown(url)}\`; it keeps what is already there`);
|
|
489
497
|
hand(`start with: cd ${fromShell(product)} && yad next`);
|
|
490
498
|
return {
|
|
491
499
|
workspace, product,
|
|
492
500
|
repos, skills: { installed: skills.installed, stale: skills.stale.length, shared: skills.shared.length },
|
|
493
|
-
hook: hook.length ? 'installed' : 'unchanged',
|
|
501
|
+
hook: hook.length ? 'installed' : 'unchanged', toolbox,
|
|
494
502
|
};
|
|
495
503
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yadflow",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "Shared project memory and workflows for teams building with AI: a versioned record of plans, decisions, contracts and reviews across repos, human-approved review gates, and a zero-dependency `yad` CLI plus 38 skills for your AI assistant.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "AbdelRahman Nasr",
|