@barefootjs/cli 0.15.1 → 0.16.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/dist/index.js +237 -6
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -20959,6 +20959,76 @@ function turnToEventBinding(graph, events) {
|
|
|
20959
20959
|
}
|
|
20960
20960
|
return out;
|
|
20961
20961
|
}
|
|
20962
|
+
function nextCommandsForSubscriber(fallbackComponent, subscriber) {
|
|
20963
|
+
const cmds = [];
|
|
20964
|
+
const parsed = parseProfilerId(subscriber);
|
|
20965
|
+
const component = parsed?.component ?? fallbackComponent;
|
|
20966
|
+
if (parsed) {
|
|
20967
|
+
if (parsed.kind === "memo" || parsed.kind === "signal") {
|
|
20968
|
+
cmds.push(`bf debug trace ${component} ${parsed.rest} --json`);
|
|
20969
|
+
} else if (parsed.kind === "binding") {
|
|
20970
|
+
cmds.push(`bf debug why-update ${component} ${parsed.rest} --json`);
|
|
20971
|
+
}
|
|
20972
|
+
}
|
|
20973
|
+
cmds.push(`bf debug graph ${component} --json`);
|
|
20974
|
+
return cmds;
|
|
20975
|
+
}
|
|
20976
|
+
function buildAgentFindings(component, hotSubscribers, wastedReReruns, batchAdvisor, unattributed) {
|
|
20977
|
+
const findings = [];
|
|
20978
|
+
for (const s of hotSubscribers.subscribers) {
|
|
20979
|
+
if (!s.hot) continue;
|
|
20980
|
+
findings.push({
|
|
20981
|
+
kind: "hot-subscriber",
|
|
20982
|
+
severity: "warning",
|
|
20983
|
+
actionable: s.loc !== void 0,
|
|
20984
|
+
subscriber: s.subscriber,
|
|
20985
|
+
loc: s.loc,
|
|
20986
|
+
message: `${s.name ?? s.subscriber} ran ${s.runsPerTurn.toFixed(1)}\xD7/turn \u2014 re-run pressure (split or batch).`,
|
|
20987
|
+
nextCommands: nextCommandsForSubscriber(component, s.subscriber)
|
|
20988
|
+
});
|
|
20989
|
+
}
|
|
20990
|
+
for (const s of wastedReReruns.subscribers) {
|
|
20991
|
+
if (!s.wasted) continue;
|
|
20992
|
+
findings.push({
|
|
20993
|
+
kind: "wasted-re-run",
|
|
20994
|
+
severity: "warning",
|
|
20995
|
+
actionable: s.loc !== void 0,
|
|
20996
|
+
subscriber: s.subscriber,
|
|
20997
|
+
loc: s.loc,
|
|
20998
|
+
message: `${s.name ?? s.subscriber} produced identical output in ${Math.round(s.wastedRatio * 100)}% of runs \u2014 finer split.`,
|
|
20999
|
+
nextCommands: nextCommandsForSubscriber(component, s.subscriber)
|
|
21000
|
+
});
|
|
21001
|
+
}
|
|
21002
|
+
for (const c of batchAdvisor.candidates) {
|
|
21003
|
+
findings.push({
|
|
21004
|
+
kind: "batch-candidate",
|
|
21005
|
+
// Only a *proven-safe* batch is advised as actionable warning; an
|
|
21006
|
+
// unverified one is surfaced as info so an agent doesn't apply a wrap that
|
|
21007
|
+
// could change behavior (mirrors the batch advisor's own safety gate).
|
|
21008
|
+
severity: c.safety === "safe" ? "warning" : "info",
|
|
21009
|
+
actionable: c.safety === "safe" && c.loc !== void 0,
|
|
21010
|
+
subscriber: c.turn,
|
|
21011
|
+
loc: c.loc,
|
|
21012
|
+
message: `${c.handler ?? c.turn} re-ran shared effects ${c.savings}\xD7 extra across ${c.writes} writes \u2014 batch() candidate (${c.safety}).`,
|
|
21013
|
+
// The turn id is `<Component>#handler:…` — for a scenario-file run it can be
|
|
21014
|
+
// a composed child, so route through the helper to target the right one.
|
|
21015
|
+
nextCommands: nextCommandsForSubscriber(component, c.turn)
|
|
21016
|
+
});
|
|
21017
|
+
}
|
|
21018
|
+
for (const u of unattributed) {
|
|
21019
|
+
findings.push({
|
|
21020
|
+
kind: "coverage-gap",
|
|
21021
|
+
severity: "warning",
|
|
21022
|
+
actionable: true,
|
|
21023
|
+
subscriber: u.id,
|
|
21024
|
+
message: `Unresolved subscriber id "${u.id}" \u2014 could not map to source (scope caveat).`,
|
|
21025
|
+
// `u.id` is shaped `<Component>#…` and may name a composed child — route
|
|
21026
|
+
// through the helper so the command targets that component, not the root.
|
|
21027
|
+
nextCommands: nextCommandsForSubscriber(component, u.id)
|
|
21028
|
+
});
|
|
21029
|
+
}
|
|
21030
|
+
return findings;
|
|
21031
|
+
}
|
|
20962
21032
|
function buildProfileReport(input) {
|
|
20963
21033
|
const { source, filePath, componentName, scenario, events } = input;
|
|
20964
21034
|
const primary = buildComponentAnalysis(source, filePath, componentName).graph;
|
|
@@ -21037,6 +21107,27 @@ function buildProfileReport(input) {
|
|
|
21037
21107
|
handlerIds.add(e.turn);
|
|
21038
21108
|
}
|
|
21039
21109
|
}
|
|
21110
|
+
const findings = buildAgentFindings(primary.componentName, hotSubscribers, wastedReReruns, batchAdvisor, unattributed);
|
|
21111
|
+
const status = findings.some((f) => f.severity === "warning" || f.severity === "error") ? "warning" : "ok";
|
|
21112
|
+
const ratio = handlersTotal > 0 ? Math.min(1, handlerIds.size / handlersTotal) : 1;
|
|
21113
|
+
let guidance;
|
|
21114
|
+
if (turnSeqs.size === 0) {
|
|
21115
|
+
guidance = handlersTotal === 0 ? {
|
|
21116
|
+
reason: "no-handlers",
|
|
21117
|
+
message: "No event handlers \u2014 use the static budget instead of a dynamic run.",
|
|
21118
|
+
nextCommands: [`bf debug profile ${primary.componentName} --json`]
|
|
21119
|
+
} : {
|
|
21120
|
+
reason: "no-interactions",
|
|
21121
|
+
message: "Handlers exist but none fired \u2014 they likely live in composed children. Drive the component with a story/scenario file.",
|
|
21122
|
+
nextCommands: [`bf debug profile ${primary.componentName} --scenario <story.tsx> --json`]
|
|
21123
|
+
};
|
|
21124
|
+
} else if (ratio < 1) {
|
|
21125
|
+
guidance = {
|
|
21126
|
+
reason: "partial-coverage",
|
|
21127
|
+
message: `Only ${handlerIds.size}/${handlersTotal} handlers exercised \u2014 a story/scenario file can cover the rest.`,
|
|
21128
|
+
nextCommands: [`bf debug profile ${primary.componentName} --scenario <story.tsx> --json`]
|
|
21129
|
+
};
|
|
21130
|
+
}
|
|
21040
21131
|
return {
|
|
21041
21132
|
kind: "profile",
|
|
21042
21133
|
schemaVersion: PROFILE_SCHEMA_VERSION,
|
|
@@ -21051,12 +21142,16 @@ function buildProfileReport(input) {
|
|
|
21051
21142
|
coverage: {
|
|
21052
21143
|
handlersFired: handlerIds.size,
|
|
21053
21144
|
handlersTotal,
|
|
21145
|
+
ratio,
|
|
21054
21146
|
unattributed,
|
|
21055
21147
|
// Roll the (potentially hundreds of) bookkeeping ids up to a count + a
|
|
21056
21148
|
// small sample so JSON consumers aren't flooded (#1849 B7). `diagnostics`
|
|
21057
21149
|
// is already sorted hottest-first by `joinProfilerEvents`.
|
|
21058
21150
|
diagnostics: { count: diagnostics.length, sample: diagnostics.slice(0, 3).map((d) => d.id) }
|
|
21059
|
-
}
|
|
21151
|
+
},
|
|
21152
|
+
status,
|
|
21153
|
+
findings,
|
|
21154
|
+
...guidance ? { guidance } : {}
|
|
21060
21155
|
};
|
|
21061
21156
|
}
|
|
21062
21157
|
function formatProfileReport(r2) {
|
|
@@ -21083,8 +21178,66 @@ function formatProfileReport(r2) {
|
|
|
21083
21178
|
if (c.diagnostics.count > 0) {
|
|
21084
21179
|
lines.push(` \xB7 ${c.diagnostics.count} anonymous runtime id(s) (non-actionable bookkeeping)`);
|
|
21085
21180
|
}
|
|
21181
|
+
lines.push("");
|
|
21182
|
+
lines.push(`status: ${r2.status} (${r2.findings.length} finding(s))`);
|
|
21183
|
+
if (r2.guidance) {
|
|
21184
|
+
lines.push(` guidance: ${r2.guidance.message}`);
|
|
21185
|
+
lines.push(` next: ${r2.guidance.nextCommands[0]}`);
|
|
21186
|
+
} else if (r2.findings.length > 0) {
|
|
21187
|
+
lines.push(` next: ${r2.findings[0].nextCommands[0]}`);
|
|
21188
|
+
}
|
|
21086
21189
|
return lines.join("\n");
|
|
21087
21190
|
}
|
|
21191
|
+
function evaluateProfileGates(report, config) {
|
|
21192
|
+
const failOn = new Set(config.failOn ?? []);
|
|
21193
|
+
const checks = [];
|
|
21194
|
+
if (failOn.has("coverage") || config.minCoverage !== void 0) {
|
|
21195
|
+
const threshold = config.minCoverage ?? 1;
|
|
21196
|
+
const observed = report.coverage.ratio;
|
|
21197
|
+
checks.push({
|
|
21198
|
+
gate: "coverage",
|
|
21199
|
+
passed: observed >= threshold,
|
|
21200
|
+
observed,
|
|
21201
|
+
threshold,
|
|
21202
|
+
message: `coverage ${(observed * 100).toFixed(0)}% ${observed >= threshold ? "\u2265" : "<"} required ${(threshold * 100).toFixed(0)}%`
|
|
21203
|
+
});
|
|
21204
|
+
}
|
|
21205
|
+
if (failOn.has("unresolved") || config.maxUnresolved !== void 0) {
|
|
21206
|
+
const threshold = config.maxUnresolved ?? 0;
|
|
21207
|
+
const observed = report.coverage.unattributed.length;
|
|
21208
|
+
checks.push({
|
|
21209
|
+
gate: "unresolved",
|
|
21210
|
+
passed: observed <= threshold,
|
|
21211
|
+
observed,
|
|
21212
|
+
threshold,
|
|
21213
|
+
message: `${observed} unresolved id(s) ${observed <= threshold ? "\u2264" : ">"} allowed ${threshold}`
|
|
21214
|
+
});
|
|
21215
|
+
}
|
|
21216
|
+
if (failOn.has("hot") || config.maxRunsPerTurn !== void 0) {
|
|
21217
|
+
const observed = report.hotSubscribers.subscribers.reduce((m, s) => Math.max(m, s.runsPerTurn), 0);
|
|
21218
|
+
if (config.maxRunsPerTurn !== void 0) {
|
|
21219
|
+
const threshold = config.maxRunsPerTurn;
|
|
21220
|
+
checks.push({
|
|
21221
|
+
gate: "hot",
|
|
21222
|
+
passed: observed <= threshold,
|
|
21223
|
+
observed,
|
|
21224
|
+
threshold,
|
|
21225
|
+
message: `max ${observed.toFixed(1)} runs/turn ${observed <= threshold ? "\u2264" : ">"} budget ${threshold}`
|
|
21226
|
+
});
|
|
21227
|
+
} else {
|
|
21228
|
+
const anyHot = report.hotSubscribers.subscribers.some((s) => s.hot);
|
|
21229
|
+
checks.push({
|
|
21230
|
+
gate: "hot",
|
|
21231
|
+
passed: !anyHot,
|
|
21232
|
+
observed,
|
|
21233
|
+
threshold: null,
|
|
21234
|
+
message: anyHot ? `hot subscriber(s) present (max ${observed.toFixed(1)} runs/turn)` : "no hot subscribers"
|
|
21235
|
+
});
|
|
21236
|
+
}
|
|
21237
|
+
}
|
|
21238
|
+
const failed = checks.filter((c) => !c.passed).map((c) => c.gate);
|
|
21239
|
+
return { passed: failed.length === 0, failed, checks };
|
|
21240
|
+
}
|
|
21088
21241
|
var PROFILE_SCHEMA_VERSION, DEFAULT_FANOUT_THRESHOLD, DEFAULT_HOT_RUNS_PER_TURN, BAR_EIGHTHS, DEFAULT_WASTED_RATIO;
|
|
21089
21242
|
var init_profiler = __esm({
|
|
21090
21243
|
"../jsx/src/profiler.ts"() {
|
|
@@ -21786,6 +21939,7 @@ __export(src_exports, {
|
|
|
21786
21939
|
emitParsedExpr: () => emitParsedExpr,
|
|
21787
21940
|
enableCompilerInstrumentation: () => enableCompilerInstrumentation,
|
|
21788
21941
|
evalStringArrayJoin: () => evalStringArrayJoin,
|
|
21942
|
+
evaluateProfileGates: () => evaluateProfileGates,
|
|
21789
21943
|
exprToString: () => exprToString,
|
|
21790
21944
|
extractArrowBodyExpression: () => extractArrowBodyExpression,
|
|
21791
21945
|
extractFunctionParams: () => extractFunctionParams,
|
|
@@ -104393,6 +104547,18 @@ __export(debug_profile_exports, {
|
|
|
104393
104547
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
104394
104548
|
import { readFileSync as readFileSync19 } from "node:fs";
|
|
104395
104549
|
import path24 from "node:path";
|
|
104550
|
+
function parseFailOn(raw) {
|
|
104551
|
+
const parts = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
104552
|
+
if (parts.length === 0) fail("--fail-on requires at least one gate name.");
|
|
104553
|
+
const out = [];
|
|
104554
|
+
for (const p of parts) {
|
|
104555
|
+
if (!GATE_NAMES.includes(p)) {
|
|
104556
|
+
fail(`--fail-on: unknown gate "${p}" (expected ${GATE_NAMES.join(", ")}).`);
|
|
104557
|
+
}
|
|
104558
|
+
if (!out.includes(p)) out.push(p);
|
|
104559
|
+
}
|
|
104560
|
+
return out;
|
|
104561
|
+
}
|
|
104396
104562
|
function parseFlags2(args2) {
|
|
104397
104563
|
const flags = { positional: [] };
|
|
104398
104564
|
const value2 = (args3, i, name2) => {
|
|
@@ -104419,6 +104585,10 @@ function parseFlags2(args2) {
|
|
|
104419
104585
|
else if (a === "--top") flags.topN = num(args2, ++i, "--top", { integer: true, min: 1 });
|
|
104420
104586
|
else if (a === "--hot-ms") flags.minMs = num(args2, ++i, "--hot-ms", { min: 0 });
|
|
104421
104587
|
else if (a === "--wasted-pct") flags.wastedPct = num(args2, ++i, "--wasted-pct", { min: 0, max: 100 });
|
|
104588
|
+
else if (a === "--fail-on") flags.failOn = parseFailOn(value2(args2, ++i, "--fail-on"));
|
|
104589
|
+
else if (a === "--min-coverage") flags.minCoverage = num(args2, ++i, "--min-coverage", { min: 0, max: 1 });
|
|
104590
|
+
else if (a === "--max-runs-per-turn") flags.maxRunsPerTurn = num(args2, ++i, "--max-runs-per-turn", { min: 0 });
|
|
104591
|
+
else if (a === "--max-unresolved") flags.maxUnresolved = num(args2, ++i, "--max-unresolved", { integer: true, min: 0 });
|
|
104422
104592
|
else if (a.startsWith("-")) fail(`Unknown flag "${a}".`);
|
|
104423
104593
|
else flags.positional.push(a);
|
|
104424
104594
|
}
|
|
@@ -104429,6 +104599,11 @@ function fail(message) {
|
|
|
104429
104599
|
console.error(USAGE);
|
|
104430
104600
|
process.exit(1);
|
|
104431
104601
|
}
|
|
104602
|
+
function formatGates(g) {
|
|
104603
|
+
const lines = [`gates: ${g.passed ? "PASS" : "FAIL"}`];
|
|
104604
|
+
for (const c of g.checks) lines.push(` ${c.passed ? "\u2713" : "\u2717"} ${c.gate}: ${c.message}`);
|
|
104605
|
+
return lines.join("\n");
|
|
104606
|
+
}
|
|
104432
104607
|
async function run22(args2, ctx2) {
|
|
104433
104608
|
if (args2.includes("--help") || args2.includes("-h")) {
|
|
104434
104609
|
console.log(HELP2);
|
|
@@ -104438,13 +104613,23 @@ async function run22(args2, ctx2) {
|
|
|
104438
104613
|
if (flags.scenario && flags.diff) {
|
|
104439
104614
|
fail("--scenario and --diff cannot be combined: --scenario measures a run, --diff compares two compiles. Pick one.");
|
|
104440
104615
|
}
|
|
104616
|
+
const wantsRegression = (flags.failOn ?? []).includes("regression");
|
|
104617
|
+
const dynamicFailOn = (flags.failOn ?? []).filter((g) => g !== "regression");
|
|
104618
|
+
const wantsDynamicGate = dynamicFailOn.length > 0 || flags.minCoverage !== void 0 || flags.maxRunsPerTurn !== void 0 || flags.maxUnresolved !== void 0;
|
|
104619
|
+
if (wantsDynamicGate && !flags.scenario) {
|
|
104620
|
+
fail("Coverage / unresolved / hot gates need a measured run \u2014 add --scenario auto|<file>.");
|
|
104621
|
+
}
|
|
104622
|
+
if (wantsRegression && !flags.diff) {
|
|
104623
|
+
fail("--fail-on regression needs a compile comparison \u2014 add --diff <ref>.");
|
|
104624
|
+
}
|
|
104441
104625
|
const {
|
|
104442
104626
|
buildStaticBudget: buildStaticBudget2,
|
|
104443
104627
|
formatStaticBudget: formatStaticBudget2,
|
|
104444
104628
|
diffStaticBudget: diffStaticBudget2,
|
|
104445
104629
|
formatBudgetDiff: formatBudgetDiff2,
|
|
104446
104630
|
buildProfileReport: buildProfileReport2,
|
|
104447
|
-
formatProfileReport: formatProfileReport2
|
|
104631
|
+
formatProfileReport: formatProfileReport2,
|
|
104632
|
+
evaluateProfileGates: evaluateProfileGates2
|
|
104448
104633
|
} = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
104449
104634
|
const componentName = flags.positional[0];
|
|
104450
104635
|
if (!componentName) {
|
|
@@ -104488,12 +104673,22 @@ async function run22(args2, ctx2) {
|
|
|
104488
104673
|
// `--wasted-pct` is a percentage on the CLI; the analysis takes a [0,1] fraction.
|
|
104489
104674
|
wastedRatio: flags.wastedPct !== void 0 ? flags.wastedPct / 100 : void 0
|
|
104490
104675
|
});
|
|
104676
|
+
const gateConfig = {
|
|
104677
|
+
failOn: dynamicFailOn,
|
|
104678
|
+
minCoverage: flags.minCoverage,
|
|
104679
|
+
maxRunsPerTurn: flags.maxRunsPerTurn,
|
|
104680
|
+
maxUnresolved: flags.maxUnresolved
|
|
104681
|
+
};
|
|
104682
|
+
const gates = wantsDynamicGate ? evaluateProfileGates2(report, gateConfig) : void 0;
|
|
104683
|
+
if (gates && !gates.passed) report.status = "error";
|
|
104491
104684
|
if (ctx2.jsonFlag) {
|
|
104492
|
-
console.log(JSON.stringify(report, null, 2));
|
|
104685
|
+
console.log(JSON.stringify(gates ? { ...report, gates } : report, null, 2));
|
|
104493
104686
|
} else {
|
|
104494
104687
|
console.log(formatProfileReport2(report));
|
|
104495
104688
|
if (fired === 0) console.log(" note: no interactive elements were found to fire.");
|
|
104689
|
+
if (gates) console.log("\n" + formatGates(gates));
|
|
104496
104690
|
}
|
|
104691
|
+
if (gates && !gates.passed) process.exit(1);
|
|
104497
104692
|
} catch (err) {
|
|
104498
104693
|
console.error(`Error: ${err.message}`);
|
|
104499
104694
|
process.exit(1);
|
|
@@ -104515,10 +104710,24 @@ async function run22(args2, ctx2) {
|
|
|
104515
104710
|
fanOutThreshold: flags.fanOutThreshold
|
|
104516
104711
|
});
|
|
104517
104712
|
const diff = diffStaticBudget2(base, head);
|
|
104713
|
+
const gates = wantsRegression ? {
|
|
104714
|
+
passed: !diff.regressed,
|
|
104715
|
+
failed: diff.regressed ? ["regression"] : [],
|
|
104716
|
+
checks: [
|
|
104717
|
+
{
|
|
104718
|
+
gate: "regression",
|
|
104719
|
+
passed: !diff.regressed,
|
|
104720
|
+
observed: diff.regressed ? 1 : 0,
|
|
104721
|
+
threshold: 0,
|
|
104722
|
+
message: diff.regressed ? "a reactivity metric regressed" : "no regression"
|
|
104723
|
+
}
|
|
104724
|
+
]
|
|
104725
|
+
} : void 0;
|
|
104518
104726
|
if (ctx2.jsonFlag) {
|
|
104519
|
-
console.log(JSON.stringify(diff, null, 2));
|
|
104727
|
+
console.log(JSON.stringify(gates ? { ...diff, gates } : diff, null, 2));
|
|
104520
104728
|
} else {
|
|
104521
104729
|
console.log(formatBudgetDiff2(diff));
|
|
104730
|
+
if (gates) console.log("\n" + formatGates(gates));
|
|
104522
104731
|
}
|
|
104523
104732
|
if (diff.regressed) process.exit(1);
|
|
104524
104733
|
return;
|
|
@@ -104552,12 +104761,12 @@ function readFileAtRef(filePath, ref) {
|
|
|
104552
104761
|
return gitError(err);
|
|
104553
104762
|
}
|
|
104554
104763
|
}
|
|
104555
|
-
var USAGE, HELP2;
|
|
104764
|
+
var USAGE, HELP2, GATE_NAMES;
|
|
104556
104765
|
var init_debug_profile2 = __esm({
|
|
104557
104766
|
"src/commands/debug-profile.ts"() {
|
|
104558
104767
|
"use strict";
|
|
104559
104768
|
init_resolve_source();
|
|
104560
|
-
USAGE = "Usage: bf debug profile <component> [--diff <ref>] [--scenario auto|<file>] [--fanout <n>] [--top <n>] [--hot-ms <n>] [--wasted-pct <n>] [--json]";
|
|
104769
|
+
USAGE = "Usage: bf debug profile <component> [--diff <ref>] [--scenario auto|<file>] [--fanout <n>] [--top <n>] [--hot-ms <n>] [--wasted-pct <n>] [--fail-on <gates>] [--min-coverage <r>] [--max-runs-per-turn <n>] [--max-unresolved <n>] [--json]";
|
|
104561
104770
|
HELP2 = `bf debug profile \u2014 reactive performance profiler
|
|
104562
104771
|
|
|
104563
104772
|
Find and fix wasted reactive work: re-runs that produce nothing, fan-out that
|
|
@@ -104621,6 +104830,25 @@ FLAGS
|
|
|
104621
104830
|
is additive-only. Stable schema with deterministic
|
|
104622
104831
|
tie-breaking; structural findings reproduce run-to-run
|
|
104623
104832
|
(wall-clock-timed ranks can shift near rounding boundaries).
|
|
104833
|
+
A dynamic run also carries \`status\`, normalized \`findings\`
|
|
104834
|
+
(severity + actionable + nextCommands), and \u2014 when handlers
|
|
104835
|
+
were under-exercised \u2014 \`guidance\` pointing at a story file.
|
|
104836
|
+
|
|
104837
|
+
AGENT GATES (CI pass/fail with intent)
|
|
104838
|
+
By default a run never fails CI. Opt into a gate and the command exits non-zero
|
|
104839
|
+
when it trips, emitting a \`gates\` block ({passed, failed, checks}) in --json and
|
|
104840
|
+
escalating \`status\` to "error". Gates compose with --json so an agent decides
|
|
104841
|
+
fail-vs-continue without parsing prose.
|
|
104842
|
+
|
|
104843
|
+
--fail-on <gates> Comma-separated: unresolved,hot,coverage (need
|
|
104844
|
+
--scenario) and regression (needs --diff). Each uses its
|
|
104845
|
+
default threshold unless a numeric flag below overrides.
|
|
104846
|
+
--min-coverage <r> Fail when coverage ratio < r (0\u20131). Implies the
|
|
104847
|
+
coverage gate. Needs --scenario.
|
|
104848
|
+
--max-runs-per-turn <n> Fail when the hottest subscriber exceeds n runs/turn.
|
|
104849
|
+
Implies the hot gate. Needs --scenario.
|
|
104850
|
+
--max-unresolved <n> Fail when more than n unresolved (actionable) coverage
|
|
104851
|
+
gaps remain. Implies the unresolved gate. Needs --scenario.
|
|
104624
104852
|
|
|
104625
104853
|
EXAMPLES
|
|
104626
104854
|
bf debug profile calendar # static budget, no run
|
|
@@ -104628,6 +104856,8 @@ EXAMPLES
|
|
|
104628
104856
|
bf debug profile calendar --scenario auto --top 5 --hot-ms 1
|
|
104629
104857
|
bf debug profile checkout --scenario ./stories/checkout.tsx --json
|
|
104630
104858
|
bf debug profile checkout --diff origin/main # regression gate (CI)
|
|
104859
|
+
bf debug profile calendar --scenario auto --min-coverage 0.8 --fail-on hot --json
|
|
104860
|
+
bf debug profile checkout --diff origin/main --fail-on regression --json
|
|
104631
104861
|
|
|
104632
104862
|
NOTES
|
|
104633
104863
|
\u2022 Instrumentation is dev-only and is stripped from production builds.
|
|
@@ -104635,6 +104865,7 @@ NOTES
|
|
|
104635
104865
|
static budget and --diff need no build.
|
|
104636
104866
|
\u2022 Composes with \`bf debug graph/trace/why-update\`: those say *where to look*,
|
|
104637
104867
|
profile says *what it cost and what to change*, citing the same source lines.`;
|
|
104868
|
+
GATE_NAMES = ["unresolved", "hot", "coverage", "regression"];
|
|
104638
104869
|
}
|
|
104639
104870
|
});
|
|
104640
104871
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "CLI for agent-driven UI component discovery and scaffolding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"esbuild": "^0.25.0",
|
|
31
31
|
"typescript": "^5.0.0",
|
|
32
|
-
"@barefootjs/client": "0.
|
|
33
|
-
"@barefootjs/shared": "0.
|
|
32
|
+
"@barefootjs/client": "0.16.0",
|
|
33
|
+
"@barefootjs/shared": "0.16.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@barefootjs/jsx": "0.
|
|
36
|
+
"@barefootjs/jsx": "0.16.0",
|
|
37
37
|
"@types/node": "^22.0.0",
|
|
38
38
|
"@happy-dom/global-registrator": "^20.0.11",
|
|
39
39
|
"happy-dom": "^20.0.11"
|