@barefootjs/jsx 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.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +150 -1
- package/dist/profiler.d.ts +115 -0
- package/dist/profiler.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/profiler.test.ts +149 -0
- package/src/index.ts +9 -0
- package/src/profiler.ts +328 -0
package/dist/index.d.ts
CHANGED
|
@@ -183,8 +183,8 @@ export { isLowerableObjectRestDestructure } from './loop-destructure.ts';
|
|
|
183
183
|
export { buildComponentGraph, buildComponentAnalysis, buildGraphFromIR, buildEventSummary, buildLoopSummary, buildWhyUpdate, traceUpdatePath, formatComponentGraph, formatUpdatePath, formatEventSummary, formatLoopSummary, formatWhyUpdate, describeFallback, formatFallbackExplanations, buildComponentSummary, formatComponentSummary, formatSignalTrace, generateStaticTrace, graphToJSON, resolveSetters, buildLocalFunctionSetterMap, makeIdCallRegex, } from './debug.ts';
|
|
184
184
|
export type { ComponentGraph, ComponentAnalysis, SignalNode, MemoNode, EffectNode, DomBinding, UpdatePath, SignalTrace, EventBinding, SetterRef, FnSetterResolution, EventSummary, LoopInfo, LoopChildBinding, LoopSummary, WhyUpdateResult, WhyUpdateDep, WhyUpdateSource, FallbackExplanation, ComponentSummary } from './debug.ts';
|
|
185
185
|
export type { WrapReason } from './ir-to-client-js/reactivity.ts';
|
|
186
|
-
export { PROFILE_SCHEMA_VERSION, buildStaticBudget, formatStaticBudget, diffStaticBudget, formatBudgetDiff, buildProfileReport, formatProfileReport, buildIdIndex, joinProfilerEvents, parseProfilerId, analyzeHotSubscribers, formatHotSubscribers, findUninstrumentedEffects, analyzeWastedReReruns, formatWastedReReruns, analyzeBatchAdvisor, formatBatchAdvisor, } from './profiler.ts';
|
|
187
|
-
export type { StaticBudget, StaticBudgetOptions, FanOutEntry, BudgetHandler, BudgetDiff, FanOutChange, ProfileReport, ProfileReportInput, ProfileCoverage, DiagnosticsSummary, EffectCandidate, IdIndex, ResolvedNode, JoinResult, JoinedEvent, UnattributedId, HotSubscribersResult, HotSubscriber, HotSubscribersOptions, WastedReRunsResult, WastedSubscriber, WastedReRunsOptions, BatchAdvisorResult, BatchCandidate, BatchSafety, } from './profiler.ts';
|
|
186
|
+
export { PROFILE_SCHEMA_VERSION, buildStaticBudget, formatStaticBudget, diffStaticBudget, formatBudgetDiff, buildProfileReport, formatProfileReport, buildIdIndex, joinProfilerEvents, parseProfilerId, analyzeHotSubscribers, formatHotSubscribers, findUninstrumentedEffects, analyzeWastedReReruns, formatWastedReReruns, analyzeBatchAdvisor, formatBatchAdvisor, evaluateProfileGates, } from './profiler.ts';
|
|
187
|
+
export type { StaticBudget, StaticBudgetOptions, FanOutEntry, BudgetHandler, BudgetDiff, FanOutChange, ProfileReport, ProfileReportInput, ProfileCoverage, DiagnosticsSummary, EffectCandidate, IdIndex, ResolvedNode, JoinResult, JoinedEvent, UnattributedId, HotSubscribersResult, HotSubscriber, HotSubscribersOptions, WastedReRunsResult, WastedSubscriber, WastedReRunsOptions, BatchAdvisorResult, BatchCandidate, BatchSafety, ProfileSeverity, ProfileStatus, AgentFinding, ScenarioGuidance, GateName, GateConfig, GateCheck, GateResult, } from './profiler.ts';
|
|
188
188
|
export { buildReactiveProfile, buildProfileFromGraph, diffProfiles, formatSingleProfile, formatProfileTable, formatProfileDiff, profileToJSON, } from './debug-profile.ts';
|
|
189
189
|
export type { ComponentProfile, ComponentProfileMetrics, ProfileFinding, ProfileDiff, ProfileDiffEntry, } from './debug-profile.ts';
|
|
190
190
|
export { BOOLEAN_ATTRS, isBooleanAttr } from './html-constants.ts';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -20725,6 +20725,71 @@ function turnToEventBinding(graph, events) {
|
|
|
20725
20725
|
}
|
|
20726
20726
|
return out;
|
|
20727
20727
|
}
|
|
20728
|
+
function nextCommandsForSubscriber(fallbackComponent, subscriber) {
|
|
20729
|
+
const cmds = [];
|
|
20730
|
+
const parsed = parseProfilerId(subscriber);
|
|
20731
|
+
const component = parsed?.component ?? fallbackComponent;
|
|
20732
|
+
if (parsed) {
|
|
20733
|
+
if (parsed.kind === "memo" || parsed.kind === "signal") {
|
|
20734
|
+
cmds.push(`bf debug trace ${component} ${parsed.rest} --json`);
|
|
20735
|
+
} else if (parsed.kind === "binding") {
|
|
20736
|
+
cmds.push(`bf debug why-update ${component} ${parsed.rest} --json`);
|
|
20737
|
+
}
|
|
20738
|
+
}
|
|
20739
|
+
cmds.push(`bf debug graph ${component} --json`);
|
|
20740
|
+
return cmds;
|
|
20741
|
+
}
|
|
20742
|
+
function buildAgentFindings(component, hotSubscribers, wastedReReruns, batchAdvisor, unattributed) {
|
|
20743
|
+
const findings = [];
|
|
20744
|
+
for (const s of hotSubscribers.subscribers) {
|
|
20745
|
+
if (!s.hot)
|
|
20746
|
+
continue;
|
|
20747
|
+
findings.push({
|
|
20748
|
+
kind: "hot-subscriber",
|
|
20749
|
+
severity: "warning",
|
|
20750
|
+
actionable: s.loc !== undefined,
|
|
20751
|
+
subscriber: s.subscriber,
|
|
20752
|
+
loc: s.loc,
|
|
20753
|
+
message: `${s.name ?? s.subscriber} ran ${s.runsPerTurn.toFixed(1)}×/turn — re-run pressure (split or batch).`,
|
|
20754
|
+
nextCommands: nextCommandsForSubscriber(component, s.subscriber)
|
|
20755
|
+
});
|
|
20756
|
+
}
|
|
20757
|
+
for (const s of wastedReReruns.subscribers) {
|
|
20758
|
+
if (!s.wasted)
|
|
20759
|
+
continue;
|
|
20760
|
+
findings.push({
|
|
20761
|
+
kind: "wasted-re-run",
|
|
20762
|
+
severity: "warning",
|
|
20763
|
+
actionable: s.loc !== undefined,
|
|
20764
|
+
subscriber: s.subscriber,
|
|
20765
|
+
loc: s.loc,
|
|
20766
|
+
message: `${s.name ?? s.subscriber} produced identical output in ${Math.round(s.wastedRatio * 100)}% of runs — finer split.`,
|
|
20767
|
+
nextCommands: nextCommandsForSubscriber(component, s.subscriber)
|
|
20768
|
+
});
|
|
20769
|
+
}
|
|
20770
|
+
for (const c of batchAdvisor.candidates) {
|
|
20771
|
+
findings.push({
|
|
20772
|
+
kind: "batch-candidate",
|
|
20773
|
+
severity: c.safety === "safe" ? "warning" : "info",
|
|
20774
|
+
actionable: c.safety === "safe" && c.loc !== undefined,
|
|
20775
|
+
subscriber: c.turn,
|
|
20776
|
+
loc: c.loc,
|
|
20777
|
+
message: `${c.handler ?? c.turn} re-ran shared effects ${c.savings}× extra across ${c.writes} writes — batch() candidate (${c.safety}).`,
|
|
20778
|
+
nextCommands: nextCommandsForSubscriber(component, c.turn)
|
|
20779
|
+
});
|
|
20780
|
+
}
|
|
20781
|
+
for (const u of unattributed) {
|
|
20782
|
+
findings.push({
|
|
20783
|
+
kind: "coverage-gap",
|
|
20784
|
+
severity: "warning",
|
|
20785
|
+
actionable: true,
|
|
20786
|
+
subscriber: u.id,
|
|
20787
|
+
message: `Unresolved subscriber id "${u.id}" — could not map to source (scope caveat).`,
|
|
20788
|
+
nextCommands: nextCommandsForSubscriber(component, u.id)
|
|
20789
|
+
});
|
|
20790
|
+
}
|
|
20791
|
+
return findings;
|
|
20792
|
+
}
|
|
20728
20793
|
function buildProfileReport(input) {
|
|
20729
20794
|
const { source, filePath, componentName, scenario, events } = input;
|
|
20730
20795
|
const primary = buildComponentAnalysis(source, filePath, componentName).graph;
|
|
@@ -20801,6 +20866,27 @@ function buildProfileReport(input) {
|
|
|
20801
20866
|
handlerIds.add(e.turn);
|
|
20802
20867
|
}
|
|
20803
20868
|
}
|
|
20869
|
+
const findings = buildAgentFindings(primary.componentName, hotSubscribers, wastedReReruns, batchAdvisor, unattributed);
|
|
20870
|
+
const status = findings.some((f) => f.severity === "warning" || f.severity === "error") ? "warning" : "ok";
|
|
20871
|
+
const ratio = handlersTotal > 0 ? Math.min(1, handlerIds.size / handlersTotal) : 1;
|
|
20872
|
+
let guidance;
|
|
20873
|
+
if (turnSeqs.size === 0) {
|
|
20874
|
+
guidance = handlersTotal === 0 ? {
|
|
20875
|
+
reason: "no-handlers",
|
|
20876
|
+
message: "No event handlers — use the static budget instead of a dynamic run.",
|
|
20877
|
+
nextCommands: [`bf debug profile ${primary.componentName} --json`]
|
|
20878
|
+
} : {
|
|
20879
|
+
reason: "no-interactions",
|
|
20880
|
+
message: "Handlers exist but none fired — they likely live in composed children. Drive the component with a story/scenario file.",
|
|
20881
|
+
nextCommands: [`bf debug profile ${primary.componentName} --scenario <story.tsx> --json`]
|
|
20882
|
+
};
|
|
20883
|
+
} else if (ratio < 1) {
|
|
20884
|
+
guidance = {
|
|
20885
|
+
reason: "partial-coverage",
|
|
20886
|
+
message: `Only ${handlerIds.size}/${handlersTotal} handlers exercised — a story/scenario file can cover the rest.`,
|
|
20887
|
+
nextCommands: [`bf debug profile ${primary.componentName} --scenario <story.tsx> --json`]
|
|
20888
|
+
};
|
|
20889
|
+
}
|
|
20804
20890
|
return {
|
|
20805
20891
|
kind: "profile",
|
|
20806
20892
|
schemaVersion: PROFILE_SCHEMA_VERSION,
|
|
@@ -20815,9 +20901,13 @@ function buildProfileReport(input) {
|
|
|
20815
20901
|
coverage: {
|
|
20816
20902
|
handlersFired: handlerIds.size,
|
|
20817
20903
|
handlersTotal,
|
|
20904
|
+
ratio,
|
|
20818
20905
|
unattributed,
|
|
20819
20906
|
diagnostics: { count: diagnostics.length, sample: diagnostics.slice(0, 3).map((d) => d.id) }
|
|
20820
|
-
}
|
|
20907
|
+
},
|
|
20908
|
+
status,
|
|
20909
|
+
findings,
|
|
20910
|
+
...guidance ? { guidance } : {}
|
|
20821
20911
|
};
|
|
20822
20912
|
}
|
|
20823
20913
|
function formatProfileReport(r) {
|
|
@@ -20842,9 +20932,67 @@ function formatProfileReport(r) {
|
|
|
20842
20932
|
if (c.diagnostics.count > 0) {
|
|
20843
20933
|
lines.push(` · ${c.diagnostics.count} anonymous runtime id(s) (non-actionable bookkeeping)`);
|
|
20844
20934
|
}
|
|
20935
|
+
lines.push("");
|
|
20936
|
+
lines.push(`status: ${r.status} (${r.findings.length} finding(s))`);
|
|
20937
|
+
if (r.guidance) {
|
|
20938
|
+
lines.push(` guidance: ${r.guidance.message}`);
|
|
20939
|
+
lines.push(` next: ${r.guidance.nextCommands[0]}`);
|
|
20940
|
+
} else if (r.findings.length > 0) {
|
|
20941
|
+
lines.push(` next: ${r.findings[0].nextCommands[0]}`);
|
|
20942
|
+
}
|
|
20845
20943
|
return lines.join(`
|
|
20846
20944
|
`);
|
|
20847
20945
|
}
|
|
20946
|
+
function evaluateProfileGates(report, config) {
|
|
20947
|
+
const failOn = new Set(config.failOn ?? []);
|
|
20948
|
+
const checks = [];
|
|
20949
|
+
if (failOn.has("coverage") || config.minCoverage !== undefined) {
|
|
20950
|
+
const threshold = config.minCoverage ?? 1;
|
|
20951
|
+
const observed = report.coverage.ratio;
|
|
20952
|
+
checks.push({
|
|
20953
|
+
gate: "coverage",
|
|
20954
|
+
passed: observed >= threshold,
|
|
20955
|
+
observed,
|
|
20956
|
+
threshold,
|
|
20957
|
+
message: `coverage ${(observed * 100).toFixed(0)}% ${observed >= threshold ? "≥" : "<"} required ${(threshold * 100).toFixed(0)}%`
|
|
20958
|
+
});
|
|
20959
|
+
}
|
|
20960
|
+
if (failOn.has("unresolved") || config.maxUnresolved !== undefined) {
|
|
20961
|
+
const threshold = config.maxUnresolved ?? 0;
|
|
20962
|
+
const observed = report.coverage.unattributed.length;
|
|
20963
|
+
checks.push({
|
|
20964
|
+
gate: "unresolved",
|
|
20965
|
+
passed: observed <= threshold,
|
|
20966
|
+
observed,
|
|
20967
|
+
threshold,
|
|
20968
|
+
message: `${observed} unresolved id(s) ${observed <= threshold ? "≤" : ">"} allowed ${threshold}`
|
|
20969
|
+
});
|
|
20970
|
+
}
|
|
20971
|
+
if (failOn.has("hot") || config.maxRunsPerTurn !== undefined) {
|
|
20972
|
+
const observed = report.hotSubscribers.subscribers.reduce((m, s) => Math.max(m, s.runsPerTurn), 0);
|
|
20973
|
+
if (config.maxRunsPerTurn !== undefined) {
|
|
20974
|
+
const threshold = config.maxRunsPerTurn;
|
|
20975
|
+
checks.push({
|
|
20976
|
+
gate: "hot",
|
|
20977
|
+
passed: observed <= threshold,
|
|
20978
|
+
observed,
|
|
20979
|
+
threshold,
|
|
20980
|
+
message: `max ${observed.toFixed(1)} runs/turn ${observed <= threshold ? "≤" : ">"} budget ${threshold}`
|
|
20981
|
+
});
|
|
20982
|
+
} else {
|
|
20983
|
+
const anyHot = report.hotSubscribers.subscribers.some((s) => s.hot);
|
|
20984
|
+
checks.push({
|
|
20985
|
+
gate: "hot",
|
|
20986
|
+
passed: !anyHot,
|
|
20987
|
+
observed,
|
|
20988
|
+
threshold: null,
|
|
20989
|
+
message: anyHot ? `hot subscriber(s) present (max ${observed.toFixed(1)} runs/turn)` : "no hot subscribers"
|
|
20990
|
+
});
|
|
20991
|
+
}
|
|
20992
|
+
}
|
|
20993
|
+
const failed = checks.filter((c) => !c.passed).map((c) => c.gate);
|
|
20994
|
+
return { passed: failed.length === 0, failed, checks };
|
|
20995
|
+
}
|
|
20848
20996
|
// src/debug-profile.ts
|
|
20849
20997
|
var THRESHOLDS = {
|
|
20850
20998
|
highFanOut: 3,
|
|
@@ -21572,6 +21720,7 @@ export {
|
|
|
21572
21720
|
extractFunctionParams,
|
|
21573
21721
|
extractArrowBodyExpression,
|
|
21574
21722
|
exprToString,
|
|
21723
|
+
evaluateProfileGates,
|
|
21575
21724
|
evalStringArrayJoin,
|
|
21576
21725
|
enableCompilerInstrumentation,
|
|
21577
21726
|
emitParsedExpr,
|
package/dist/profiler.d.ts
CHANGED
|
@@ -476,6 +476,13 @@ export interface ProfileCoverage {
|
|
|
476
476
|
handlersFired: number;
|
|
477
477
|
/** Handlers the IR knows about (`buildEventSummary`). */
|
|
478
478
|
handlersTotal: number;
|
|
479
|
+
/**
|
|
480
|
+
* `handlersFired / handlersTotal` in `[0,1]` — the fraction of known handlers
|
|
481
|
+
* this run exercised, so an agent can gate on it (`--min-coverage`) without
|
|
482
|
+
* dividing the two counts itself (#1841). `1` when the component has no
|
|
483
|
+
* handlers (nothing to cover ⇒ trivially complete, not a gap).
|
|
484
|
+
*/
|
|
485
|
+
ratio: number;
|
|
479
486
|
/** SR4 ids the IR could not resolve — the honest, actionable gap. */
|
|
480
487
|
unattributed: UnattributedId[];
|
|
481
488
|
/**
|
|
@@ -501,6 +508,76 @@ export interface ProfileReport {
|
|
|
501
508
|
wastedReReruns: WastedReRunsResult;
|
|
502
509
|
batchAdvisor: BatchAdvisorResult;
|
|
503
510
|
coverage: ProfileCoverage;
|
|
511
|
+
/**
|
|
512
|
+
* Normalized run status an agent can branch on without parsing prose (#1841):
|
|
513
|
+
* `ok` when nothing is flagged, `warning` when any finding is severity
|
|
514
|
+
* `warning` or higher — regardless of its `actionable` flag, since an
|
|
515
|
+
* unresolved-but-real cost (e.g. a hot subscriber with no source loc) is still
|
|
516
|
+
* worth surfacing. A failed *gate* escalates this to `error` — but gates are
|
|
517
|
+
* policy applied by the CLI from flags, so the builder only ever sets
|
|
518
|
+
* `ok`/`warning` here.
|
|
519
|
+
*/
|
|
520
|
+
status: ProfileStatus;
|
|
521
|
+
/**
|
|
522
|
+
* Flattened, normalized findings (#1841): the hot/wasted/batch/coverage tables
|
|
523
|
+
* re-expressed with a single `severity` scale, an explicit `actionable` flag,
|
|
524
|
+
* and `nextCommands` — the exact follow-up `bf debug …` invocations to run.
|
|
525
|
+
* The structured tables above stay the source of truth; this is the agent view.
|
|
526
|
+
*/
|
|
527
|
+
findings: AgentFinding[];
|
|
528
|
+
/**
|
|
529
|
+
* Coverage guidance for an under-exercised run (#1841): present only when the
|
|
530
|
+
* scenario fired no or only some handlers, which usually means a story file is
|
|
531
|
+
* needed (handlers live in composed children).
|
|
532
|
+
*/
|
|
533
|
+
guidance?: ScenarioGuidance;
|
|
534
|
+
}
|
|
535
|
+
/** Normalized finding severity an agent can branch on without parsing prose. */
|
|
536
|
+
export type ProfileSeverity = 'info' | 'warning' | 'error';
|
|
537
|
+
/** Top-level run status: `ok` (clean), `warning` (findings), `error` (gate failed). */
|
|
538
|
+
export type ProfileStatus = 'ok' | 'warning' | 'error';
|
|
539
|
+
/**
|
|
540
|
+
* One normalized, machine-actionable finding flattened from the per-analysis
|
|
541
|
+
* tables (hot subscribers / wasted re-runs / batch advisor / coverage gaps).
|
|
542
|
+
* Carries the agent contract fields the issue asks for: a normalized `severity`,
|
|
543
|
+
* an explicit `actionable` flag (`false` ⇒ no safe direct fix — e.g. an
|
|
544
|
+
* unverified advisory), and `nextCommands` — valid, ready-to-run `bf debug …`
|
|
545
|
+
* follow-ups.
|
|
546
|
+
*/
|
|
547
|
+
export interface AgentFinding {
|
|
548
|
+
kind: 'hot-subscriber' | 'wasted-re-run' | 'batch-candidate' | 'coverage-gap';
|
|
549
|
+
severity: ProfileSeverity;
|
|
550
|
+
/**
|
|
551
|
+
* Whether this finding has a concrete fix worth acting on directly. `false`
|
|
552
|
+
* marks a finding an agent should *not* apply blindly — an unverified `batch()`
|
|
553
|
+
* advisory (the wrap could change behavior) or a finding whose source location
|
|
554
|
+
* the id index couldn't resolve. A coverage gap is `actionable: true`: the next
|
|
555
|
+
* step (widen the scenario, inspect the graph) is clear even without a `loc`.
|
|
556
|
+
*/
|
|
557
|
+
actionable: boolean;
|
|
558
|
+
/** The compiler subscriber/turn id this finding is about, when it has one. */
|
|
559
|
+
subscriber?: string;
|
|
560
|
+
/** Source location, when the id resolved to an IR node. */
|
|
561
|
+
loc?: {
|
|
562
|
+
file: string;
|
|
563
|
+
line: number;
|
|
564
|
+
};
|
|
565
|
+
/** One-line human summary — the same sentence the text report would print. */
|
|
566
|
+
message: string;
|
|
567
|
+
/** Follow-up commands to run next — valid, ready-to-run `bf debug …` lines. */
|
|
568
|
+
nextCommands: string[];
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Coverage guidance for an under-exercised run (#1841). `--scenario auto` can
|
|
572
|
+
* only fire handlers the IR exposes on *this* component; for a compound/context
|
|
573
|
+
* component whose handlers live in composed children it fires `0/N`, and the
|
|
574
|
+
* honest next step is a story/scenario file rather than trusting a thin run.
|
|
575
|
+
*/
|
|
576
|
+
export interface ScenarioGuidance {
|
|
577
|
+
/** Why coverage was incomplete — drives the suggested next step. */
|
|
578
|
+
reason: 'no-handlers' | 'no-interactions' | 'partial-coverage';
|
|
579
|
+
message: string;
|
|
580
|
+
nextCommands: string[];
|
|
504
581
|
}
|
|
505
582
|
export interface ProfileReportInput {
|
|
506
583
|
source: string;
|
|
@@ -536,4 +613,42 @@ export interface ProfileReportInput {
|
|
|
536
613
|
*/
|
|
537
614
|
export declare function buildProfileReport(input: ProfileReportInput): ProfileReport;
|
|
538
615
|
export declare function formatProfileReport(r: ProfileReport): string;
|
|
616
|
+
/** The gate names `--fail-on` accepts. `regression` applies to `--diff` mode. */
|
|
617
|
+
export type GateName = 'unresolved' | 'hot' | 'coverage' | 'regression';
|
|
618
|
+
/**
|
|
619
|
+
* Gate thresholds an agent/CI supplies via flags. A gate is *active* when it is
|
|
620
|
+
* named in `failOn` or its numeric threshold is set — so `--max-unresolved 0`
|
|
621
|
+
* enforces the unresolved gate without also passing `--fail-on unresolved`.
|
|
622
|
+
*/
|
|
623
|
+
export interface GateConfig {
|
|
624
|
+
failOn?: readonly GateName[];
|
|
625
|
+
/** `--min-coverage`: minimum `coverage.ratio` in `[0,1]`. */
|
|
626
|
+
minCoverage?: number;
|
|
627
|
+
/** `--max-runs-per-turn`: budget for the hottest subscriber's runs/turn. */
|
|
628
|
+
maxRunsPerTurn?: number;
|
|
629
|
+
/** `--max-unresolved`: maximum allowed unresolved (actionable) coverage gaps. */
|
|
630
|
+
maxUnresolved?: number;
|
|
631
|
+
}
|
|
632
|
+
export interface GateCheck {
|
|
633
|
+
gate: GateName;
|
|
634
|
+
passed: boolean;
|
|
635
|
+
/** The measured value the gate compared (ratio, count, or runs/turn). */
|
|
636
|
+
observed: number;
|
|
637
|
+
/** The threshold it was compared against; `null` for a boolean gate. */
|
|
638
|
+
threshold: number | null;
|
|
639
|
+
message: string;
|
|
640
|
+
}
|
|
641
|
+
export interface GateResult {
|
|
642
|
+
passed: boolean;
|
|
643
|
+
/** Names of the gates that failed — the `gates.failed` an agent branches on. */
|
|
644
|
+
failed: GateName[];
|
|
645
|
+
checks: GateCheck[];
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Evaluate the dynamic-run gates (#1841) against a measured report. Pure: maps a
|
|
649
|
+
* `ProfileReport` + thresholds to a pass/fail decision the CLI turns into an
|
|
650
|
+
* exit code. The `regression` gate is *not* evaluated here — it belongs to
|
|
651
|
+
* `--diff` mode, which the CLI gates directly off the `BudgetDiff`.
|
|
652
|
+
*/
|
|
653
|
+
export declare function evaluateProfileGates(report: ProfileReport, config: GateConfig): GateResult;
|
|
539
654
|
//# sourceMappingURL=profiler.d.ts.map
|
package/dist/profiler.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../src/profiler.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../src/profiler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jsx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"directory": "packages/jsx"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@barefootjs/shared": "0.
|
|
56
|
+
"@barefootjs/shared": "0.16.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@barefootjs/client": ">=0.2.0",
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
buildIdIndex,
|
|
21
21
|
joinProfilerEvents,
|
|
22
22
|
findUninstrumentedEffects,
|
|
23
|
+
evaluateProfileGates,
|
|
23
24
|
} from '../profiler'
|
|
24
25
|
import { buildComponentAnalysis } from '../debug'
|
|
25
26
|
import type { ProfilerEvent } from '@barefootjs/shared'
|
|
@@ -533,3 +534,151 @@ describe('findUninstrumentedEffects (#1849 B6)', () => {
|
|
|
533
534
|
expect(e1.candidates).toEqual([{ file: 'C.tsx', line: 8 }])
|
|
534
535
|
})
|
|
535
536
|
})
|
|
537
|
+
|
|
538
|
+
describe('agent contract: status, findings, guidance (#1841)', () => {
|
|
539
|
+
const src = `
|
|
540
|
+
'use client'
|
|
541
|
+
import { createSignal, createMemo } from '@barefootjs/client'
|
|
542
|
+
export function Calc() {
|
|
543
|
+
const [count, setCount] = createSignal(0)
|
|
544
|
+
const a = createMemo(() => count() * 2)
|
|
545
|
+
return <button onClick={() => setCount(count() + 1)}>{a()}</button>
|
|
546
|
+
}
|
|
547
|
+
`
|
|
548
|
+
let n = 0
|
|
549
|
+
const ev = (type: ProfilerEvent['type'], f: Partial<ProfilerEvent> = {}): ProfilerEvent =>
|
|
550
|
+
({ type, seq: n++, turn: null, ...f })
|
|
551
|
+
|
|
552
|
+
// A memo that re-runs 3× in a single turn → runsPerTurn 3 → flagged `hot`.
|
|
553
|
+
function hotRunEvents(): ProfilerEvent[] {
|
|
554
|
+
n = 0
|
|
555
|
+
const turn = 'Calc#handler:s0:click'
|
|
556
|
+
const events: ProfilerEvent[] = [ev('turnBegin', { handlerId: turn })]
|
|
557
|
+
for (let i = 0; i < 3; i++) {
|
|
558
|
+
events.push(ev('effectEnter', { subscriber: 'Calc#memo:a', turn }))
|
|
559
|
+
events.push(ev('effectExit', { subscriber: 'Calc#memo:a', dur: 1, turn }))
|
|
560
|
+
}
|
|
561
|
+
events.push(ev('turnEnd', {}))
|
|
562
|
+
return events
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
test('a clean run is status ok with no findings', () => {
|
|
566
|
+
n = 0
|
|
567
|
+
// One memo run in a turn → runsPerTurn 1 → not hot, nothing flagged.
|
|
568
|
+
const turn = 'Calc#handler:s0:click'
|
|
569
|
+
const events: ProfilerEvent[] = [
|
|
570
|
+
ev('turnBegin', { handlerId: turn }),
|
|
571
|
+
ev('effectEnter', { subscriber: 'Calc#memo:a', turn }),
|
|
572
|
+
ev('effectExit', { subscriber: 'Calc#memo:a', dur: 1, turn }),
|
|
573
|
+
ev('turnEnd', {}),
|
|
574
|
+
]
|
|
575
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events })
|
|
576
|
+
expect(r.status).toBe('ok')
|
|
577
|
+
expect(r.findings).toHaveLength(0)
|
|
578
|
+
expect(r.coverage.ratio).toBe(1)
|
|
579
|
+
expect(r.guidance).toBeUndefined()
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
test('a hot subscriber becomes a warning finding with valid nextCommands', () => {
|
|
583
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events: hotRunEvents() })
|
|
584
|
+
expect(r.status).toBe('warning')
|
|
585
|
+
const hot = r.findings.find(f => f.kind === 'hot-subscriber')!
|
|
586
|
+
expect(hot.severity).toBe('warning')
|
|
587
|
+
expect(hot.actionable).toBe(true)
|
|
588
|
+
expect(hot.subscriber).toBe('Calc#memo:a')
|
|
589
|
+
// A memo id maps to a name `bf debug trace` accepts; graph is the fallback.
|
|
590
|
+
expect(hot.nextCommands).toContain('bf debug trace Calc a --json')
|
|
591
|
+
expect(hot.nextCommands).toContain('bf debug graph Calc --json')
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
test('an unresolved id is an actionable coverage-gap finding', () => {
|
|
595
|
+
n = 0
|
|
596
|
+
// A profiler-shaped id with no matching IR node → SR4 coverage gap.
|
|
597
|
+
const events: ProfilerEvent[] = [ev('effectEnter', { subscriber: 'Calc#memo:ghost' })]
|
|
598
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events })
|
|
599
|
+
const gap = r.findings.find(f => f.kind === 'coverage-gap')!
|
|
600
|
+
expect(gap.actionable).toBe(true)
|
|
601
|
+
expect(gap.subscriber).toBe('Calc#memo:ghost')
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
test('nextCommands target the component parsed from the id, not the root', () => {
|
|
605
|
+
n = 0
|
|
606
|
+
// A scenario-file run resolves subscribers from composed children: the id's
|
|
607
|
+
// component (`Child`) differs from the profiled root (`Calc`). Follow-up
|
|
608
|
+
// commands must target `Child`, or they point an agent at the wrong file.
|
|
609
|
+
const events: ProfilerEvent[] = [ev('effectEnter', { subscriber: 'Child#memo:ghost' })]
|
|
610
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events })
|
|
611
|
+
const gap = r.findings.find(f => f.kind === 'coverage-gap')!
|
|
612
|
+
expect(gap.nextCommands).toContain('bf debug trace Child ghost --json')
|
|
613
|
+
expect(gap.nextCommands).toContain('bf debug graph Child --json')
|
|
614
|
+
expect(gap.nextCommands.every(c => !c.includes('graph Calc'))).toBe(true)
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
test('coverage.ratio is clamped to 1 when the stream over-counts handlers', () => {
|
|
618
|
+
n = 0
|
|
619
|
+
// Calc exposes a single handler (handlersTotal 1), but a malformed stream
|
|
620
|
+
// reports two distinct turn ids — without clamping the ratio would be 2.0
|
|
621
|
+
// and silently pass a `--min-coverage` gate it shouldn't.
|
|
622
|
+
const events: ProfilerEvent[] = [
|
|
623
|
+
ev('effectEnter', { subscriber: 'Calc#memo:a', turn: 'Calc#handler:s0:click' }),
|
|
624
|
+
ev('effectEnter', { subscriber: 'Calc#memo:a', turn: 'Calc#handler:s9:click' }),
|
|
625
|
+
]
|
|
626
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events })
|
|
627
|
+
expect(r.coverage.ratio).toBe(1)
|
|
628
|
+
expect(r.coverage.ratio).toBeLessThanOrEqual(1)
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
test('a zero-turn run emits guidance pointing at a story file', () => {
|
|
632
|
+
n = 0
|
|
633
|
+
// Handlers exist (the onClick) but none fired → no-interactions guidance.
|
|
634
|
+
const events: ProfilerEvent[] = [ev('effectEnter', { subscriber: 'Calc#memo:a' })]
|
|
635
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events })
|
|
636
|
+
expect(r.turns).toBe(0)
|
|
637
|
+
expect(r.guidance?.reason).toBe('no-interactions')
|
|
638
|
+
expect(r.guidance?.nextCommands[0]).toContain('--scenario <story.tsx>')
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
describe('evaluateProfileGates', () => {
|
|
642
|
+
test('hot gate fails when a subscriber exceeds the runs/turn budget', () => {
|
|
643
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events: hotRunEvents() })
|
|
644
|
+
const fail = evaluateProfileGates(r, { maxRunsPerTurn: 2 })
|
|
645
|
+
expect(fail.passed).toBe(false)
|
|
646
|
+
expect(fail.failed).toContain('hot')
|
|
647
|
+
const pass = evaluateProfileGates(r, { maxRunsPerTurn: 5 })
|
|
648
|
+
expect(pass.passed).toBe(true)
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
test('bare --fail-on hot trips on any flagged hot subscriber', () => {
|
|
652
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events: hotRunEvents() })
|
|
653
|
+
const g = evaluateProfileGates(r, { failOn: ['hot'] })
|
|
654
|
+
expect(g.passed).toBe(false)
|
|
655
|
+
expect(g.checks[0].threshold).toBeNull()
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
test('coverage gate compares ratio against --min-coverage', () => {
|
|
659
|
+
n = 0
|
|
660
|
+
// Handlers exist but only mount runs, no turn → ratio 0.
|
|
661
|
+
const events: ProfilerEvent[] = [ev('effectEnter', { subscriber: 'Calc#memo:a' })]
|
|
662
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events })
|
|
663
|
+
expect(r.coverage.ratio).toBe(0)
|
|
664
|
+
const g = evaluateProfileGates(r, { minCoverage: 0.8 })
|
|
665
|
+
expect(g.passed).toBe(false)
|
|
666
|
+
expect(g.failed).toContain('coverage')
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
test('unresolved gate counts actionable gaps against --max-unresolved', () => {
|
|
670
|
+
n = 0
|
|
671
|
+
const events: ProfilerEvent[] = [ev('effectEnter', { subscriber: 'Calc#memo:ghost' })]
|
|
672
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events })
|
|
673
|
+
expect(evaluateProfileGates(r, { maxUnresolved: 0 }).passed).toBe(false)
|
|
674
|
+
expect(evaluateProfileGates(r, { maxUnresolved: 5 }).passed).toBe(true)
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
test('no configured gate yields an empty, passing result', () => {
|
|
678
|
+
const r = buildProfileReport({ source: src, filePath: 'Calc.tsx', scenario: 'auto', events: hotRunEvents() })
|
|
679
|
+
const g = evaluateProfileGates(r, {})
|
|
680
|
+
expect(g.passed).toBe(true)
|
|
681
|
+
expect(g.checks).toHaveLength(0)
|
|
682
|
+
})
|
|
683
|
+
})
|
|
684
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -308,6 +308,7 @@ export {
|
|
|
308
308
|
formatWastedReReruns,
|
|
309
309
|
analyzeBatchAdvisor,
|
|
310
310
|
formatBatchAdvisor,
|
|
311
|
+
evaluateProfileGates,
|
|
311
312
|
} from './profiler.ts'
|
|
312
313
|
export type {
|
|
313
314
|
StaticBudget,
|
|
@@ -335,6 +336,14 @@ export type {
|
|
|
335
336
|
BatchAdvisorResult,
|
|
336
337
|
BatchCandidate,
|
|
337
338
|
BatchSafety,
|
|
339
|
+
ProfileSeverity,
|
|
340
|
+
ProfileStatus,
|
|
341
|
+
AgentFinding,
|
|
342
|
+
ScenarioGuidance,
|
|
343
|
+
GateName,
|
|
344
|
+
GateConfig,
|
|
345
|
+
GateCheck,
|
|
346
|
+
GateResult,
|
|
338
347
|
} from './profiler.ts'
|
|
339
348
|
|
|
340
349
|
// Reactive profile — findings layer (#1690 dogfood: Bug A/C/D fixes, batch-candidate dedup,
|
package/src/profiler.ts
CHANGED
|
@@ -1360,6 +1360,13 @@ export interface ProfileCoverage {
|
|
|
1360
1360
|
handlersFired: number
|
|
1361
1361
|
/** Handlers the IR knows about (`buildEventSummary`). */
|
|
1362
1362
|
handlersTotal: number
|
|
1363
|
+
/**
|
|
1364
|
+
* `handlersFired / handlersTotal` in `[0,1]` — the fraction of known handlers
|
|
1365
|
+
* this run exercised, so an agent can gate on it (`--min-coverage`) without
|
|
1366
|
+
* dividing the two counts itself (#1841). `1` when the component has no
|
|
1367
|
+
* handlers (nothing to cover ⇒ trivially complete, not a gap).
|
|
1368
|
+
*/
|
|
1369
|
+
ratio: number
|
|
1363
1370
|
/** SR4 ids the IR could not resolve — the honest, actionable gap. */
|
|
1364
1371
|
unattributed: UnattributedId[]
|
|
1365
1372
|
/**
|
|
@@ -1386,6 +1393,79 @@ export interface ProfileReport {
|
|
|
1386
1393
|
wastedReReruns: WastedReRunsResult
|
|
1387
1394
|
batchAdvisor: BatchAdvisorResult
|
|
1388
1395
|
coverage: ProfileCoverage
|
|
1396
|
+
/**
|
|
1397
|
+
* Normalized run status an agent can branch on without parsing prose (#1841):
|
|
1398
|
+
* `ok` when nothing is flagged, `warning` when any finding is severity
|
|
1399
|
+
* `warning` or higher — regardless of its `actionable` flag, since an
|
|
1400
|
+
* unresolved-but-real cost (e.g. a hot subscriber with no source loc) is still
|
|
1401
|
+
* worth surfacing. A failed *gate* escalates this to `error` — but gates are
|
|
1402
|
+
* policy applied by the CLI from flags, so the builder only ever sets
|
|
1403
|
+
* `ok`/`warning` here.
|
|
1404
|
+
*/
|
|
1405
|
+
status: ProfileStatus
|
|
1406
|
+
/**
|
|
1407
|
+
* Flattened, normalized findings (#1841): the hot/wasted/batch/coverage tables
|
|
1408
|
+
* re-expressed with a single `severity` scale, an explicit `actionable` flag,
|
|
1409
|
+
* and `nextCommands` — the exact follow-up `bf debug …` invocations to run.
|
|
1410
|
+
* The structured tables above stay the source of truth; this is the agent view.
|
|
1411
|
+
*/
|
|
1412
|
+
findings: AgentFinding[]
|
|
1413
|
+
/**
|
|
1414
|
+
* Coverage guidance for an under-exercised run (#1841): present only when the
|
|
1415
|
+
* scenario fired no or only some handlers, which usually means a story file is
|
|
1416
|
+
* needed (handlers live in composed children).
|
|
1417
|
+
*/
|
|
1418
|
+
guidance?: ScenarioGuidance
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// -- Agent contract (#1841): normalized severity, actionable findings, guidance
|
|
1422
|
+
|
|
1423
|
+
/** Normalized finding severity an agent can branch on without parsing prose. */
|
|
1424
|
+
export type ProfileSeverity = 'info' | 'warning' | 'error'
|
|
1425
|
+
|
|
1426
|
+
/** Top-level run status: `ok` (clean), `warning` (findings), `error` (gate failed). */
|
|
1427
|
+
export type ProfileStatus = 'ok' | 'warning' | 'error'
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* One normalized, machine-actionable finding flattened from the per-analysis
|
|
1431
|
+
* tables (hot subscribers / wasted re-runs / batch advisor / coverage gaps).
|
|
1432
|
+
* Carries the agent contract fields the issue asks for: a normalized `severity`,
|
|
1433
|
+
* an explicit `actionable` flag (`false` ⇒ no safe direct fix — e.g. an
|
|
1434
|
+
* unverified advisory), and `nextCommands` — valid, ready-to-run `bf debug …`
|
|
1435
|
+
* follow-ups.
|
|
1436
|
+
*/
|
|
1437
|
+
export interface AgentFinding {
|
|
1438
|
+
kind: 'hot-subscriber' | 'wasted-re-run' | 'batch-candidate' | 'coverage-gap'
|
|
1439
|
+
severity: ProfileSeverity
|
|
1440
|
+
/**
|
|
1441
|
+
* Whether this finding has a concrete fix worth acting on directly. `false`
|
|
1442
|
+
* marks a finding an agent should *not* apply blindly — an unverified `batch()`
|
|
1443
|
+
* advisory (the wrap could change behavior) or a finding whose source location
|
|
1444
|
+
* the id index couldn't resolve. A coverage gap is `actionable: true`: the next
|
|
1445
|
+
* step (widen the scenario, inspect the graph) is clear even without a `loc`.
|
|
1446
|
+
*/
|
|
1447
|
+
actionable: boolean
|
|
1448
|
+
/** The compiler subscriber/turn id this finding is about, when it has one. */
|
|
1449
|
+
subscriber?: string
|
|
1450
|
+
/** Source location, when the id resolved to an IR node. */
|
|
1451
|
+
loc?: { file: string; line: number }
|
|
1452
|
+
/** One-line human summary — the same sentence the text report would print. */
|
|
1453
|
+
message: string
|
|
1454
|
+
/** Follow-up commands to run next — valid, ready-to-run `bf debug …` lines. */
|
|
1455
|
+
nextCommands: string[]
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Coverage guidance for an under-exercised run (#1841). `--scenario auto` can
|
|
1460
|
+
* only fire handlers the IR exposes on *this* component; for a compound/context
|
|
1461
|
+
* component whose handlers live in composed children it fires `0/N`, and the
|
|
1462
|
+
* honest next step is a story/scenario file rather than trusting a thin run.
|
|
1463
|
+
*/
|
|
1464
|
+
export interface ScenarioGuidance {
|
|
1465
|
+
/** Why coverage was incomplete — drives the suggested next step. */
|
|
1466
|
+
reason: 'no-handlers' | 'no-interactions' | 'partial-coverage'
|
|
1467
|
+
message: string
|
|
1468
|
+
nextCommands: string[]
|
|
1389
1469
|
}
|
|
1390
1470
|
|
|
1391
1471
|
export interface ProfileReportInput {
|
|
@@ -1412,6 +1492,104 @@ export interface ProfileReportInput {
|
|
|
1412
1492
|
wastedRatio?: number
|
|
1413
1493
|
}
|
|
1414
1494
|
|
|
1495
|
+
/**
|
|
1496
|
+
* The follow-up `bf debug …` commands an agent should run to investigate a
|
|
1497
|
+
* subscriber finding (#1841). Every command is valid and ready to run: a
|
|
1498
|
+
* memo/signal id resolves to a name `bf debug trace` accepts; a DOM-binding id
|
|
1499
|
+
* resolves to a slotId `bf debug why-update` accepts; `bf debug graph` is always
|
|
1500
|
+
* applicable, so it is the universal fallback.
|
|
1501
|
+
*
|
|
1502
|
+
* Commands target the component parsed *from the id* (`<Component>#…`), not the
|
|
1503
|
+
* primary component — a scenario-file run resolves subscribers from composed
|
|
1504
|
+
* children (`extraSources`), so a child's finding must point at the child. The
|
|
1505
|
+
* passed `fallbackComponent` is used only for ids that don't parse (e.g. an
|
|
1506
|
+
* anonymous `e1`).
|
|
1507
|
+
*/
|
|
1508
|
+
function nextCommandsForSubscriber(fallbackComponent: string, subscriber: string): string[] {
|
|
1509
|
+
const cmds: string[] = []
|
|
1510
|
+
const parsed = parseProfilerId(subscriber)
|
|
1511
|
+
const component = parsed?.component ?? fallbackComponent
|
|
1512
|
+
if (parsed) {
|
|
1513
|
+
if (parsed.kind === 'memo' || parsed.kind === 'signal') {
|
|
1514
|
+
cmds.push(`bf debug trace ${component} ${parsed.rest} --json`)
|
|
1515
|
+
} else if (parsed.kind === 'binding') {
|
|
1516
|
+
cmds.push(`bf debug why-update ${component} ${parsed.rest} --json`)
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
cmds.push(`bf debug graph ${component} --json`)
|
|
1520
|
+
return cmds
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* Flatten the per-analysis tables into the normalized agent findings (#1841).
|
|
1525
|
+
* Only *flagged* rows become findings — a hot subscriber that isn't `hot`, or a
|
|
1526
|
+
* subscriber below the wasted threshold, is data in the tables but not something
|
|
1527
|
+
* the agent must act on. Coverage gaps (unresolved ids) are actionable findings;
|
|
1528
|
+
* the non-actionable bookkeeping ids stay in `coverage.diagnostics`.
|
|
1529
|
+
*/
|
|
1530
|
+
function buildAgentFindings(
|
|
1531
|
+
component: string,
|
|
1532
|
+
hotSubscribers: HotSubscribersResult,
|
|
1533
|
+
wastedReReruns: WastedReRunsResult,
|
|
1534
|
+
batchAdvisor: BatchAdvisorResult,
|
|
1535
|
+
unattributed: readonly UnattributedId[],
|
|
1536
|
+
): AgentFinding[] {
|
|
1537
|
+
const findings: AgentFinding[] = []
|
|
1538
|
+
for (const s of hotSubscribers.subscribers) {
|
|
1539
|
+
if (!s.hot) continue
|
|
1540
|
+
findings.push({
|
|
1541
|
+
kind: 'hot-subscriber',
|
|
1542
|
+
severity: 'warning',
|
|
1543
|
+
actionable: s.loc !== undefined,
|
|
1544
|
+
subscriber: s.subscriber,
|
|
1545
|
+
loc: s.loc,
|
|
1546
|
+
message: `${s.name ?? s.subscriber} ran ${s.runsPerTurn.toFixed(1)}×/turn — re-run pressure (split or batch).`,
|
|
1547
|
+
nextCommands: nextCommandsForSubscriber(component, s.subscriber),
|
|
1548
|
+
})
|
|
1549
|
+
}
|
|
1550
|
+
for (const s of wastedReReruns.subscribers) {
|
|
1551
|
+
if (!s.wasted) continue
|
|
1552
|
+
findings.push({
|
|
1553
|
+
kind: 'wasted-re-run',
|
|
1554
|
+
severity: 'warning',
|
|
1555
|
+
actionable: s.loc !== undefined,
|
|
1556
|
+
subscriber: s.subscriber,
|
|
1557
|
+
loc: s.loc,
|
|
1558
|
+
message: `${s.name ?? s.subscriber} produced identical output in ${Math.round(s.wastedRatio * 100)}% of runs — finer split.`,
|
|
1559
|
+
nextCommands: nextCommandsForSubscriber(component, s.subscriber),
|
|
1560
|
+
})
|
|
1561
|
+
}
|
|
1562
|
+
for (const c of batchAdvisor.candidates) {
|
|
1563
|
+
findings.push({
|
|
1564
|
+
kind: 'batch-candidate',
|
|
1565
|
+
// Only a *proven-safe* batch is advised as actionable warning; an
|
|
1566
|
+
// unverified one is surfaced as info so an agent doesn't apply a wrap that
|
|
1567
|
+
// could change behavior (mirrors the batch advisor's own safety gate).
|
|
1568
|
+
severity: c.safety === 'safe' ? 'warning' : 'info',
|
|
1569
|
+
actionable: c.safety === 'safe' && c.loc !== undefined,
|
|
1570
|
+
subscriber: c.turn,
|
|
1571
|
+
loc: c.loc,
|
|
1572
|
+
message: `${c.handler ?? c.turn} re-ran shared effects ${c.savings}× extra across ${c.writes} writes — batch() candidate (${c.safety}).`,
|
|
1573
|
+
// The turn id is `<Component>#handler:…` — for a scenario-file run it can be
|
|
1574
|
+
// a composed child, so route through the helper to target the right one.
|
|
1575
|
+
nextCommands: nextCommandsForSubscriber(component, c.turn),
|
|
1576
|
+
})
|
|
1577
|
+
}
|
|
1578
|
+
for (const u of unattributed) {
|
|
1579
|
+
findings.push({
|
|
1580
|
+
kind: 'coverage-gap',
|
|
1581
|
+
severity: 'warning',
|
|
1582
|
+
actionable: true,
|
|
1583
|
+
subscriber: u.id,
|
|
1584
|
+
message: `Unresolved subscriber id "${u.id}" — could not map to source (scope caveat).`,
|
|
1585
|
+
// `u.id` is shaped `<Component>#…` and may name a composed child — route
|
|
1586
|
+
// through the helper so the command targets that component, not the root.
|
|
1587
|
+
nextCommands: nextCommandsForSubscriber(component, u.id),
|
|
1588
|
+
})
|
|
1589
|
+
}
|
|
1590
|
+
return findings
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1415
1593
|
/**
|
|
1416
1594
|
* Assemble a dynamic profile (SR1–SR4 + analyses, SR7) from a recorded event
|
|
1417
1595
|
* stream. Pure: the DOM run that *produces* `events` lives in the driver (the
|
|
@@ -1524,6 +1702,41 @@ export function buildProfileReport(input: ProfileReportInput): ProfileReport {
|
|
|
1524
1702
|
}
|
|
1525
1703
|
}
|
|
1526
1704
|
|
|
1705
|
+
const findings = buildAgentFindings(primary.componentName, hotSubscribers, wastedReReruns, batchAdvisor, unattributed)
|
|
1706
|
+
// Status is measurement-only here: any warning/error finding ⇒ `warning`. A
|
|
1707
|
+
// failed gate escalates to `error`, but gates are CLI policy (flags), so the
|
|
1708
|
+
// builder never sets `error` itself.
|
|
1709
|
+
const status: ProfileStatus = findings.some(f => f.severity === 'warning' || f.severity === 'error') ? 'warning' : 'ok'
|
|
1710
|
+
|
|
1711
|
+
// Coverage ratio: 1 when there is nothing to cover (no handlers), else the
|
|
1712
|
+
// exercised fraction. Clamped to `[0,1]` — a malformed stream (more distinct
|
|
1713
|
+
// turn ids than `buildEventSummary` knows about, e.g. missing `extraSources`)
|
|
1714
|
+
// must not push the ratio past 1 and break a gate's assumptions. Drives
|
|
1715
|
+
// `--min-coverage` and the guidance below.
|
|
1716
|
+
const ratio = handlersTotal > 0 ? Math.min(1, handlerIds.size / handlersTotal) : 1
|
|
1717
|
+
let guidance: ScenarioGuidance | undefined
|
|
1718
|
+
if (turnSeqs.size === 0) {
|
|
1719
|
+
guidance =
|
|
1720
|
+
handlersTotal === 0
|
|
1721
|
+
? {
|
|
1722
|
+
reason: 'no-handlers',
|
|
1723
|
+
message: 'No event handlers — use the static budget instead of a dynamic run.',
|
|
1724
|
+
nextCommands: [`bf debug profile ${primary.componentName} --json`],
|
|
1725
|
+
}
|
|
1726
|
+
: {
|
|
1727
|
+
reason: 'no-interactions',
|
|
1728
|
+
message:
|
|
1729
|
+
'Handlers exist but none fired — they likely live in composed children. Drive the component with a story/scenario file.',
|
|
1730
|
+
nextCommands: [`bf debug profile ${primary.componentName} --scenario <story.tsx> --json`],
|
|
1731
|
+
}
|
|
1732
|
+
} else if (ratio < 1) {
|
|
1733
|
+
guidance = {
|
|
1734
|
+
reason: 'partial-coverage',
|
|
1735
|
+
message: `Only ${handlerIds.size}/${handlersTotal} handlers exercised — a story/scenario file can cover the rest.`,
|
|
1736
|
+
nextCommands: [`bf debug profile ${primary.componentName} --scenario <story.tsx> --json`],
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1527
1740
|
return {
|
|
1528
1741
|
kind: 'profile',
|
|
1529
1742
|
schemaVersion: PROFILE_SCHEMA_VERSION,
|
|
@@ -1538,12 +1751,16 @@ export function buildProfileReport(input: ProfileReportInput): ProfileReport {
|
|
|
1538
1751
|
coverage: {
|
|
1539
1752
|
handlersFired: handlerIds.size,
|
|
1540
1753
|
handlersTotal,
|
|
1754
|
+
ratio,
|
|
1541
1755
|
unattributed,
|
|
1542
1756
|
// Roll the (potentially hundreds of) bookkeeping ids up to a count + a
|
|
1543
1757
|
// small sample so JSON consumers aren't flooded (#1849 B7). `diagnostics`
|
|
1544
1758
|
// is already sorted hottest-first by `joinProfilerEvents`.
|
|
1545
1759
|
diagnostics: { count: diagnostics.length, sample: diagnostics.slice(0, 3).map(d => d.id) },
|
|
1546
1760
|
},
|
|
1761
|
+
status,
|
|
1762
|
+
findings,
|
|
1763
|
+
...(guidance ? { guidance } : {}),
|
|
1547
1764
|
}
|
|
1548
1765
|
}
|
|
1549
1766
|
|
|
@@ -1579,5 +1796,116 @@ export function formatProfileReport(r: ProfileReport): string {
|
|
|
1579
1796
|
if (c.diagnostics.count > 0) {
|
|
1580
1797
|
lines.push(` · ${c.diagnostics.count} anonymous runtime id(s) (non-actionable bookkeeping)`)
|
|
1581
1798
|
}
|
|
1799
|
+
// Agent contract footer (#1841): a normalized status and the single most
|
|
1800
|
+
// useful next command, so a human reading the text output sees the same
|
|
1801
|
+
// signal `--json` consumers branch on.
|
|
1802
|
+
lines.push('')
|
|
1803
|
+
lines.push(`status: ${r.status} (${r.findings.length} finding(s))`)
|
|
1804
|
+
if (r.guidance) {
|
|
1805
|
+
lines.push(` guidance: ${r.guidance.message}`)
|
|
1806
|
+
lines.push(` next: ${r.guidance.nextCommands[0]}`)
|
|
1807
|
+
} else if (r.findings.length > 0) {
|
|
1808
|
+
lines.push(` next: ${r.findings[0].nextCommands[0]}`)
|
|
1809
|
+
}
|
|
1582
1810
|
return lines.join('\n')
|
|
1583
1811
|
}
|
|
1812
|
+
|
|
1813
|
+
// -- Gates (#1841): turn a measured report into a pass/fail CI decision --------
|
|
1814
|
+
|
|
1815
|
+
/** The gate names `--fail-on` accepts. `regression` applies to `--diff` mode. */
|
|
1816
|
+
export type GateName = 'unresolved' | 'hot' | 'coverage' | 'regression'
|
|
1817
|
+
|
|
1818
|
+
/**
|
|
1819
|
+
* Gate thresholds an agent/CI supplies via flags. A gate is *active* when it is
|
|
1820
|
+
* named in `failOn` or its numeric threshold is set — so `--max-unresolved 0`
|
|
1821
|
+
* enforces the unresolved gate without also passing `--fail-on unresolved`.
|
|
1822
|
+
*/
|
|
1823
|
+
export interface GateConfig {
|
|
1824
|
+
failOn?: readonly GateName[]
|
|
1825
|
+
/** `--min-coverage`: minimum `coverage.ratio` in `[0,1]`. */
|
|
1826
|
+
minCoverage?: number
|
|
1827
|
+
/** `--max-runs-per-turn`: budget for the hottest subscriber's runs/turn. */
|
|
1828
|
+
maxRunsPerTurn?: number
|
|
1829
|
+
/** `--max-unresolved`: maximum allowed unresolved (actionable) coverage gaps. */
|
|
1830
|
+
maxUnresolved?: number
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
export interface GateCheck {
|
|
1834
|
+
gate: GateName
|
|
1835
|
+
passed: boolean
|
|
1836
|
+
/** The measured value the gate compared (ratio, count, or runs/turn). */
|
|
1837
|
+
observed: number
|
|
1838
|
+
/** The threshold it was compared against; `null` for a boolean gate. */
|
|
1839
|
+
threshold: number | null
|
|
1840
|
+
message: string
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
export interface GateResult {
|
|
1844
|
+
passed: boolean
|
|
1845
|
+
/** Names of the gates that failed — the `gates.failed` an agent branches on. */
|
|
1846
|
+
failed: GateName[]
|
|
1847
|
+
checks: GateCheck[]
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/**
|
|
1851
|
+
* Evaluate the dynamic-run gates (#1841) against a measured report. Pure: maps a
|
|
1852
|
+
* `ProfileReport` + thresholds to a pass/fail decision the CLI turns into an
|
|
1853
|
+
* exit code. The `regression` gate is *not* evaluated here — it belongs to
|
|
1854
|
+
* `--diff` mode, which the CLI gates directly off the `BudgetDiff`.
|
|
1855
|
+
*/
|
|
1856
|
+
export function evaluateProfileGates(report: ProfileReport, config: GateConfig): GateResult {
|
|
1857
|
+
const failOn = new Set(config.failOn ?? [])
|
|
1858
|
+
const checks: GateCheck[] = []
|
|
1859
|
+
|
|
1860
|
+
if (failOn.has('coverage') || config.minCoverage !== undefined) {
|
|
1861
|
+
const threshold = config.minCoverage ?? 1
|
|
1862
|
+
const observed = report.coverage.ratio
|
|
1863
|
+
checks.push({
|
|
1864
|
+
gate: 'coverage',
|
|
1865
|
+
passed: observed >= threshold,
|
|
1866
|
+
observed,
|
|
1867
|
+
threshold,
|
|
1868
|
+
message: `coverage ${(observed * 100).toFixed(0)}% ${observed >= threshold ? '≥' : '<'} required ${(threshold * 100).toFixed(0)}%`,
|
|
1869
|
+
})
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
if (failOn.has('unresolved') || config.maxUnresolved !== undefined) {
|
|
1873
|
+
const threshold = config.maxUnresolved ?? 0
|
|
1874
|
+
const observed = report.coverage.unattributed.length
|
|
1875
|
+
checks.push({
|
|
1876
|
+
gate: 'unresolved',
|
|
1877
|
+
passed: observed <= threshold,
|
|
1878
|
+
observed,
|
|
1879
|
+
threshold,
|
|
1880
|
+
message: `${observed} unresolved id(s) ${observed <= threshold ? '≤' : '>'} allowed ${threshold}`,
|
|
1881
|
+
})
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
if (failOn.has('hot') || config.maxRunsPerTurn !== undefined) {
|
|
1885
|
+
const observed = report.hotSubscribers.subscribers.reduce((m, s) => Math.max(m, s.runsPerTurn), 0)
|
|
1886
|
+
if (config.maxRunsPerTurn !== undefined) {
|
|
1887
|
+
// Numeric budget: the hottest subscriber must not exceed it.
|
|
1888
|
+
const threshold = config.maxRunsPerTurn
|
|
1889
|
+
checks.push({
|
|
1890
|
+
gate: 'hot',
|
|
1891
|
+
passed: observed <= threshold,
|
|
1892
|
+
observed,
|
|
1893
|
+
threshold,
|
|
1894
|
+
message: `max ${observed.toFixed(1)} runs/turn ${observed <= threshold ? '≤' : '>'} budget ${threshold}`,
|
|
1895
|
+
})
|
|
1896
|
+
} else {
|
|
1897
|
+
// Bare `--fail-on hot`: any subscriber the analysis flagged `hot` fails it.
|
|
1898
|
+
const anyHot = report.hotSubscribers.subscribers.some(s => s.hot)
|
|
1899
|
+
checks.push({
|
|
1900
|
+
gate: 'hot',
|
|
1901
|
+
passed: !anyHot,
|
|
1902
|
+
observed,
|
|
1903
|
+
threshold: null,
|
|
1904
|
+
message: anyHot ? `hot subscriber(s) present (max ${observed.toFixed(1)} runs/turn)` : 'no hot subscribers',
|
|
1905
|
+
})
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
const failed = checks.filter(c => !c.passed).map(c => c.gate)
|
|
1910
|
+
return { passed: failed.length === 0, failed, checks }
|
|
1911
|
+
}
|