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/CHANGELOG.md +23 -0
- package/README.md +237 -334
- package/bin/commands.mjs +1 -0
- package/bin/yad.mjs +17 -5
- package/cli/capture.mjs +1 -1
- package/cli/claims.mjs +1 -1
- package/cli/detect.mjs +642 -0
- package/cli/errors.mjs +2 -2
- package/package.json +6 -5
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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';
|