yadflow 4.0.0 → 4.1.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/bin/commands.mjs CHANGED
@@ -35,6 +35,7 @@ export { runRepo } from '../cli/repo.mjs';
35
35
  export { runRiskMap } from '../cli/riskmap-command.mjs';
36
36
  export { runCodeowners } from '../cli/codeowners-command.mjs';
37
37
  export { runDocs } from '../cli/docs.mjs';
38
+ export { runDetect } from '../cli/detect.mjs';
38
39
  export { runDoctor } from '../cli/doctor.mjs';
39
40
  export { runMigrate, warnIfProjectAhead } from '../cli/migrate.mjs';
40
41
  export { runNext } from '../cli/next.mjs';
package/bin/yad.mjs CHANGED
@@ -116,6 +116,10 @@ ${c.bold('Where am I / what next')}
116
116
  Bind a step to a skill of your own. Several skills run in
117
117
  the order given, one after another — each costs tokens
118
118
  yad skill unbind <step> Drop the binding; the step goes back to the engine's default
119
+ yad detect [--json] [--dir <folder>] Which skills, agents, MCP servers and plugins are installed
120
+ for the agents that work here — in this folder and in your
121
+ home folder, and which agents read each place. Read-only;
122
+ MCP servers are named, never their commands or settings
119
123
  yad skip <epic> <step> --reason <text> Mark an optional step N/A for this epic. Which steps
120
124
  those are comes from the epic's lifecycle route — on classic
121
125
  and analysis-first, ui-design: a backend/API/data epic with no
@@ -553,12 +557,12 @@ async function main() {
553
557
  // A project written by a newer yadflow is warned about before any command reads it (docs/migrations/
554
558
  // shape-8.md). Not on `hook` — its stderr is the channel a block reason reaches a model on — and not
555
559
  // where the command reports the same thing itself (doctor, migrate) or runs before a project exists.
556
- if (!['hook', 'doctor', 'migrate', 'setup', 'report', 'new', 'init', 'join'].includes(cmd)) commands.warnIfProjectAhead(o.dir || process.cwd());
560
+ if (!['hook', 'doctor', 'migrate', 'setup', 'report', 'detect', 'new', 'init', 'join'].includes(cmd)) commands.warnIfProjectAhead(o.dir || process.cwd());
557
561
  // E122: the Product's settings (or an epic's PR ledger) under two names that say different things.
558
562
  // Every command refuses rather than pick one — whichever it picked, somebody's edit would be silently
559
563
  // ignored. Not `doctor` (it reports the drift), `migrate` (it ends it) or `report` (it files a bug
560
564
  // about a broken flow, and reads nothing it could get wrong). `hook` returned above: it guards.
561
- if (!['doctor', 'migrate', 'report'].includes(cmd)) {
565
+ if (!['doctor', 'migrate', 'report', 'detect'].includes(cmd)) {
562
566
  // Both the folder the command runs on and, for a code-repo command, the Product it reads.
563
567
  for (const root of new Set([o.dir || process.cwd(), o.product].filter(Boolean))) {
564
568
  const drift = commands.productDriftError(root);
@@ -619,6 +623,13 @@ async function main() {
619
623
  case 'migrate':
620
624
  result = await commands.runMigrate(o.dir, { apply: o.apply, json: o.json, keep: o.keep ?? null });
621
625
  break;
626
+ case 'detect': {
627
+ // E50. Reads the folder and the home folder; writes nothing.
628
+ const [, extra] = o._;
629
+ if (extra) { refuse(`yad detect takes no words (got: ${extra})`, 'usage: yad detect [--json] [--dir <folder>]'); break; }
630
+ result = commands.runDetect(o.dir || process.cwd(), { json: o.json });
631
+ break;
632
+ }
622
633
  case 'report':
623
634
  result = await commands.runReport(o.dir, { message: o.message });
624
635
  break;
@@ -948,7 +959,7 @@ main()
948
959
  })
949
960
  .catch(async (err) => {
950
961
  // Under --json every failure is still one object on stdout (E1) — a refusal from the parser, a
951
- // thrown YadError, a bug. The `code` is the `YAD-` code README "Troubleshooting" is keyed on.
962
+ // thrown YadError, a bug. The `code` is the `YAD-` code the docs/CLI.md "Error codes" table is keyed on.
952
963
  // If the command had already answered, stdout holds its object, and the failure goes to stderr.
953
964
  if (inJSON() && !jsonEmitted()) {
954
965
  if (err?.parsedCmd !== undefined && runningCmd === null) beginJSON(commandName(err.parsedWords ?? []));
@@ -959,7 +970,7 @@ main()
959
970
  const code = err?.code && /^YAD-/.test(err.code) ? ` [${err.code}]` : '';
960
971
  log(c.red(`\nyad failed${code}: ${err?.message || err}`));
961
972
  if (err?.hint) log(c.yellow(` → ${err.hint}`));
962
- if (code) log(c.dim(' (see README "Troubleshooting" for this code, or run `yad doctor`)'));
973
+ if (code) log(c.dim(' (see the error codes in https://github.com/abdelrahmannasr/yadflow/blob/main/docs/CLI.md#error-codes, or run `yad doctor`)'));
963
974
  process.exitCode = 1;
964
975
  // Offer to report the failure — but only interactively (never in CI), and opt-out via
965
976
  // YAD_NO_REPORT. `report` failing on its own would land here, so never re-offer for it.
@@ -990,7 +1001,8 @@ main()
990
1001
  // block reason travels on — an update banner there would land in front of a model.
991
1002
  // Resolved the way main() resolves it, NOT from argv[2]: that is the first raw argument, so
992
1003
  // `yad --dir <path> hook ledger-guard` puts `--dir` there and the banner slips through.
993
- if (parseArgs(process.argv.slice(2))._[0] !== 'hook') {
1004
+ // Nor `detect` (E50): it promises no network and no file written, and the check does both.
1005
+ if (!['hook', 'detect'].includes(parseArgs(process.argv.slice(2))._[0])) {
994
1006
  const { maybeNotifyUpdate } = await import('./commands.mjs');
995
1007
  await maybeNotifyUpdate();
996
1008
  }
package/cli/capture.mjs CHANGED
@@ -319,7 +319,7 @@ export async function runCapture(root, { hook = false, noPush = false, now = Dat
319
319
  // secret captured by mistake, say) is not continued from a stale copy (E43 review 3). This prune clears
320
320
  // every stale copy on the hand path; a LOCAL branch is still pushed back, and the hook, which never
321
321
  // fetches, still reads a copy another fetch left. Removing one for good means `git branch -D` and
322
- // `git branch -dr origin/…` on every machine too (README). The same
322
+ // `git branch -dr origin/…` on every machine too (docs/CLI.md, Background capture). The same
323
323
  // low-speed limit as the push, so a stalled HTTPS connection gives up instead of holding the capture.
324
324
  spawnSync('git', ['-c', 'http.lowSpeedLimit=1000', '-c', 'http.lowSpeedTime=20', 'fetch', '--quiet', '--prune', '--no-tags', 'origin', `+refs/heads/${WIP_PREFIX}/${name}/*:refs/remotes/origin/${WIP_PREFIX}/${name}/*`],
325
325
  { cwd: root, stdio: 'ignore', timeout: 30_000, env: { ...env, ...pushEnv(env) } });
package/cli/claims.mjs CHANGED
@@ -22,7 +22,7 @@
22
22
  // may lose the race in silence. Nothing reads those copies while the local branch exists, and a capture by
23
23
  // hand fetches (with prune) before it relies on them.
24
24
  //
25
- // LIMITS, said in the README: capture off, or offline, is invisible; the push runs at most every 5 minutes;
25
+ // LIMITS, said in docs/CLI.md (claims): capture off, or offline, is invisible; the push runs at most every 5 minutes;
26
26
  // two people with one git name share branches, so they never see each other; clocks are the committer's.
27
27
  import { spawnSync } from 'node:child_process';
28
28
  import fs from 'node:fs';