@gr8ful/spf 0.8.1 → 0.8.2
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/dist/cli/commands/doctor.js +44 -11
- package/package.json +1 -1
|
@@ -53,6 +53,33 @@ async function withProbeStatus(label, run) {
|
|
|
53
53
|
* a plain `✓` so a real negative finding (e.g. "unreachable", a `/v1`
|
|
54
54
|
* double-path warning) can't be mistaken for a pass at a glance.
|
|
55
55
|
*/
|
|
56
|
+
/**
|
|
57
|
+
* Cross-check ONE chain's own `requiredAgents`/`requiredSuites` (derived
|
|
58
|
+
* from what it actually does — `resolveRequiredAgents`/`resolveRequiredSuites`,
|
|
59
|
+
* same as `spf list`/a real dispatch would resolve) against what `cfg`
|
|
60
|
+
* actually has. Shared by the repo-chain loop below (every `.spf/chains/
|
|
61
|
+
* *.yaml` chain) and `watch.chain`/`watch.refine.chain` — the two chains
|
|
62
|
+
* `spf watch` will ACTUALLY dispatch, built-in or not. Extracted specifically
|
|
63
|
+
* because the repo-chain loop used to be the ONLY place this ran, and it
|
|
64
|
+
* skips every built-in chain by construction (`.filter((c) => c.source !==
|
|
65
|
+
* undefined)`) — so a `watch.chain` naming a built-in like `plan-build-test`
|
|
66
|
+
* never went through it at all: `spf doctor` could report clean while `spf
|
|
67
|
+
* watch`'s very first claimed issue failed at runtime on a missing
|
|
68
|
+
* `quality.suites` entry doctor never actually looked for.
|
|
69
|
+
*/
|
|
70
|
+
function checkChainRequirements(report, label, chain, cfg) {
|
|
71
|
+
const owners = resolveRequiredAgents(chain, {});
|
|
72
|
+
const missingOwners = owners.filter((o) => !cfg.agents.some((a) => a.name === o));
|
|
73
|
+
check(report, `${label} owners`, missingOwners.length === 0, missingOwners.length === 0 ? owners.join(", ") || "(none)" : `unknown agent(s): ${missingOwners.join(", ")} — not in cfg.agents`);
|
|
74
|
+
const suiteNames = resolveRequiredSuites(chain, {});
|
|
75
|
+
const missingSuites = suiteNames.filter((s) => !(s in cfg.quality.suites));
|
|
76
|
+
const missingChecks = missingSuites.length === 0 ? suiteNames.flatMap((s) => cfg.quality.suites[s].filter((n) => !cfg.quality.checks.some((c) => c.name === n))) : [];
|
|
77
|
+
check(report, `${label} suites`, missingSuites.length === 0 && missingChecks.length === 0, missingSuites.length > 0
|
|
78
|
+
? `unknown suite(s): ${missingSuites.join(", ")} — not in cfg.quality.suites`
|
|
79
|
+
: missingChecks.length > 0
|
|
80
|
+
? `suite(s) name unknown check(s): ${missingChecks.join(", ")}`
|
|
81
|
+
: suiteNames.join(", ") || "(none)");
|
|
82
|
+
}
|
|
56
83
|
function check(report, name, ok, detail, severity) {
|
|
57
84
|
report.checks.push({ name, ok, detail, severity });
|
|
58
85
|
if (!ok)
|
|
@@ -468,17 +495,7 @@ export async function doctorCommand(argv) {
|
|
|
468
495
|
check(report, `repo chain ${problem.file}`, false, problem.message);
|
|
469
496
|
}
|
|
470
497
|
for (const chain of allChains().filter((c) => c.source !== undefined)) {
|
|
471
|
-
|
|
472
|
-
const missingOwners = owners.filter((o) => !cfg.agents.some((a) => a.name === o));
|
|
473
|
-
check(report, `repo chain "${chain.name}" owners`, missingOwners.length === 0, missingOwners.length === 0 ? owners.join(", ") || "(none)" : `unknown agent(s): ${missingOwners.join(", ")} — not in cfg.agents`);
|
|
474
|
-
const suiteNames = resolveRequiredSuites(chain, {});
|
|
475
|
-
const missingSuites = suiteNames.filter((s) => !(s in cfg.quality.suites));
|
|
476
|
-
const missingChecks = missingSuites.length === 0 ? suiteNames.flatMap((s) => cfg.quality.suites[s].filter((n) => !cfg.quality.checks.some((c) => c.name === n))) : [];
|
|
477
|
-
check(report, `repo chain "${chain.name}" suites`, missingSuites.length === 0 && missingChecks.length === 0, missingSuites.length > 0
|
|
478
|
-
? `unknown suite(s): ${missingSuites.join(", ")} — not in cfg.quality.suites`
|
|
479
|
-
: missingChecks.length > 0
|
|
480
|
-
? `suite(s) name unknown check(s): ${missingChecks.join(", ")}`
|
|
481
|
-
: suiteNames.join(", ") || "(none)");
|
|
498
|
+
checkChainRequirements(report, `repo chain "${chain.name}"`, chain, cfg);
|
|
482
499
|
// Informational: the derived sequence itself, same string `spf list`
|
|
483
500
|
// shows — printed here so the chain's author can eyeball what they
|
|
484
501
|
// actually wrote without a second command.
|
|
@@ -537,6 +554,19 @@ export async function doctorCommand(argv) {
|
|
|
537
554
|
// same way it would for `spf watch` itself, no special-casing needed.
|
|
538
555
|
const watchChain = findChain(cfg.watch.chain);
|
|
539
556
|
check(report, "watch.chain", Boolean(watchChain), watchChain ? `${cfg.watch.chain}${watchChain.source ? ` (repo: ${repoChainLabel(watchChain.source)})` : ""}` : `"${cfg.watch.chain}" is not a registered chain`);
|
|
557
|
+
// The "repo chain X suites/owners" loop above only covers chains with a
|
|
558
|
+
// `.source` (loaded from `.spf/chains/*.yaml`) — a BUILT-IN chain named
|
|
559
|
+
// by `watch.chain` (the common case: `plan-build-test`, etc.) never went
|
|
560
|
+
// through that check, and the "roster + suites validate" call earlier
|
|
561
|
+
// only validates suites already present in `cfg.quality.suites`, not
|
|
562
|
+
// whether `watch.chain` actually NEEDS one that's missing entirely. That
|
|
563
|
+
// gap is exactly how `spf doctor` can report clean while `spf watch`'s
|
|
564
|
+
// very first claimed issue fails at runtime with "quality.suites.\"test\"
|
|
565
|
+
// is not configured" — this closes it by checking the chain `spf watch`
|
|
566
|
+
// will ACTUALLY dispatch, the same way the repo-chain loop already does.
|
|
567
|
+
if (watchChain) {
|
|
568
|
+
checkChainRequirements(report, `watch.chain "${cfg.watch.chain}"`, watchChain, cfg);
|
|
569
|
+
}
|
|
540
570
|
// Informational only, never a failure: a chain that skips review and/or
|
|
541
571
|
// never commits is a legitimate choice (e.g. `scout`, `plan`) — this is
|
|
542
572
|
// here so an unattended `spf watch` posture is a visible fact, not a
|
|
@@ -558,6 +588,9 @@ export async function doctorCommand(argv) {
|
|
|
558
588
|
if (cfg.watch.refine.enabled) {
|
|
559
589
|
const refineChain = findChain(cfg.watch.refine.chain);
|
|
560
590
|
check(report, "watch.refine.chain", Boolean(refineChain), refineChain ? `${cfg.watch.refine.chain}${refineChain.source ? ` (repo: ${repoChainLabel(refineChain.source)})` : ""}` : `"${cfg.watch.refine.chain}" is not a registered chain`);
|
|
591
|
+
if (refineChain) {
|
|
592
|
+
checkChainRequirements(report, `watch.refine.chain "${cfg.watch.refine.chain}"`, refineChain, cfg);
|
|
593
|
+
}
|
|
561
594
|
check(report, "watch.refine issue authoring", cfg.watch.issue_provider === "github" || cfg.watch.issue_provider === "jira", cfg.watch.issue_provider === "github"
|
|
562
595
|
? "github supports issue authoring (createIssue/sub-issues)"
|
|
563
596
|
: cfg.watch.issue_provider === "jira"
|