mandrel 1.58.0 → 1.60.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/.agents/README.md +100 -98
- package/.agents/docs/SDLC.md +140 -141
- package/.agents/docs/configuration.md +16 -16
- package/.agents/docs/workflows.md +7 -8
- package/.agents/instructions.md +12 -11
- package/.agents/personas/architect.md +1 -1
- package/.agents/personas/product.md +1 -1
- package/.agents/personas/project-manager.md +14 -14
- package/.agents/personas/technical-writer.md +1 -1
- package/.agents/rules/changelog-style.md +5 -5
- package/.agents/rules/git-conventions.md +3 -3
- package/.agents/schemas/agentrc.schema.json +3 -3
- package/.agents/schemas/audit-rules.json +20 -0
- package/.agents/schemas/dispatch-manifest.json +4 -4
- package/.agents/schemas/epic-spec.schema.json +15 -45
- package/.agents/schemas/lifecycle/README.md +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/README.md +1 -1
- package/.agents/scripts/acceptance-eval.js +21 -4
- package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
- package/.agents/scripts/analyze-execution.js +2 -2
- package/.agents/scripts/assert-branch.js +1 -3
- package/.agents/scripts/audit-to-stories.js +1 -1
- package/.agents/scripts/bootstrap.js +1 -1
- package/.agents/scripts/check-arch-cycles.js +360 -0
- package/.agents/scripts/check-doc-links.js +2 -3
- package/.agents/scripts/coverage-capture.js +24 -3
- package/.agents/scripts/diagnose-friction.js +1 -1
- package/.agents/scripts/dispatcher.js +2 -2
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/epic-audit-prepare.js +3 -3
- package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
- package/.agents/scripts/epic-deliver-preflight.js +11 -9
- package/.agents/scripts/epic-deliver-prepare.js +13 -5
- package/.agents/scripts/epic-execute-record-wave.js +5 -5
- package/.agents/scripts/epic-plan-healthcheck.js +6 -10
- package/.agents/scripts/epic-plan-spec-validate.js +1 -1
- package/.agents/scripts/epic-reconcile.js +11 -29
- package/.agents/scripts/evidence-gate.js +2 -2
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/git-rebase-and-resolve.js +1 -1
- package/.agents/scripts/hierarchy-gate.js +40 -24
- package/.agents/scripts/lib/ITicketingProvider.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
- package/.agents/scripts/lib/baseline-snapshot.js +7 -7
- package/.agents/scripts/lib/baselines/kinds/coverage.js +33 -149
- package/.agents/scripts/lib/baselines/kinds/duplication.js +27 -116
- package/.agents/scripts/lib/baselines/kinds/kind-factory.js +192 -0
- package/.agents/scripts/lib/baselines/kinds/lighthouse.js +34 -133
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +31 -124
- package/.agents/scripts/lib/baselines/kinds/mutation.js +25 -111
- package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +59 -0
- package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +37 -0
- package/.agents/scripts/lib/baselines/writer.js +1 -1
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/close-validation/commands.js +188 -0
- package/.agents/scripts/lib/close-validation/gates.js +235 -0
- package/.agents/scripts/lib/close-validation/process.js +101 -0
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +1 -1
- package/.agents/scripts/lib/close-validation/runner.js +325 -0
- package/.agents/scripts/lib/close-validation/telemetry.js +70 -0
- package/.agents/scripts/lib/codebase-snapshot.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/quality.js +6 -6
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-resolver.js +2 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
- package/.agents/scripts/lib/config-settings-schema.js +3 -3
- package/.agents/scripts/lib/coverage-capture.js +147 -4
- package/.agents/scripts/lib/cpu-pool.js +14 -0
- package/.agents/scripts/lib/crap-utils.js +6 -11
- package/.agents/scripts/lib/duplicate-search.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +87 -0
- package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
- package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +10 -10
- package/.agents/scripts/lib/git-utils.js +24 -22
- package/.agents/scripts/lib/label-constants.js +3 -4
- package/.agents/scripts/lib/label-taxonomy.js +3 -8
- package/.agents/scripts/lib/maintainability-engine.js +1 -1
- package/.agents/scripts/lib/maintainability-utils.js +4 -187
- package/.agents/scripts/lib/observability/perf-report-readers.js +32 -23
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +81 -7
- package/.agents/scripts/lib/orchestration/code-review.js +95 -82
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
- package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
- package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +14 -37
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +22 -22
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +206 -58
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +27 -3
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +28 -8
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +13 -41
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +2 -3
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -8
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +103 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +22 -64
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +38 -76
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +10 -10
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +8 -20
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
- package/.agents/scripts/lib/orchestration/file-assumptions.js +6 -5
- package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +144 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +3 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +8 -8
- package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +3 -3
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +7 -7
- package/.agents/scripts/lib/orchestration/preflight-cache.js +36 -13
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
- package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
- package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +5 -60
- package/.agents/scripts/lib/orchestration/review-providers/native.js +7 -6
- package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +105 -0
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +7 -59
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +2 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -4
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +32 -35
- package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +1 -2
- package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +451 -503
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +8 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +47 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +2 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +358 -54
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +3 -2
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +32 -5
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +5 -18
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +3 -3
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +33 -16
- package/.agents/scripts/lib/orchestration/story-reachability.js +47 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
- package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +4 -35
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
- package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +44 -73
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +16 -7
- package/.agents/scripts/lib/orchestration/ticketing/state.js +53 -439
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +471 -0
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +3 -3
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +2 -8
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +1 -1
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
- package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
- package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
- package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
- package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
- package/.agents/scripts/lib/project-root.js +17 -0
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/spec/index.js +1 -1
- package/.agents/scripts/lib/spec/loader.js +2 -2
- package/.agents/scripts/lib/spec/state.js +7 -16
- package/.agents/scripts/lib/story-adjacency.js +76 -0
- package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
- package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
- package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
- package/.agents/scripts/lib/story-lifecycle.js +9 -9
- package/.agents/scripts/lib/story-plan.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
- package/.agents/scripts/lib/transpile.js +93 -0
- package/.agents/scripts/lib/wave-runner/tick.js +4 -153
- package/.agents/scripts/lib/workers/crap-worker.js +1 -1
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +20 -2
- package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +90 -0
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +26 -8
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +74 -0
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
- package/.agents/scripts/lifecycle-emit.js +1 -1
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/mappers.js +2 -2
- package/.agents/scripts/providers/github/tickets.js +114 -10
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/retro-run.js +2 -2
- package/.agents/scripts/run-lint.js +10 -1
- package/.agents/scripts/run-tests.js +24 -4
- package/.agents/scripts/single-story-init.js +1 -1
- package/.agents/scripts/stories-wave-tick.js +13 -10
- package/.agents/scripts/story-close.js +1 -1
- package/.agents/scripts/story-init.js +162 -26
- package/.agents/scripts/story-phase.js +5 -5
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/sync-branch-from-base.js +2 -2
- package/.agents/scripts/validate-docs-freshness.js +1 -1
- package/.agents/scripts/wave-tick.js +1 -1
- package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
- package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
- package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
- package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
- package/.agents/skills/core/scope-triage/SKILL.md +9 -10
- package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
- package/.agents/skills/skills.index.json +7 -7
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +1 -1
- package/.agents/templates/agent-protocol.md +2 -2
- package/.agents/workflows/agents-update.md +2 -2
- package/.agents/workflows/audit-architecture.md +2 -2
- package/.agents/workflows/audit-clean-code.md +2 -2
- package/.agents/workflows/audit-dependencies.md +1 -1
- package/.agents/workflows/audit-devops.md +1 -1
- package/.agents/workflows/audit-documentation.md +226 -0
- package/.agents/workflows/audit-lighthouse.md +1 -1
- package/.agents/workflows/audit-performance.md +2 -2
- package/.agents/workflows/audit-privacy.md +1 -1
- package/.agents/workflows/audit-quality.md +2 -2
- package/.agents/workflows/audit-security.md +2 -2
- package/.agents/workflows/audit-seo.md +1 -1
- package/.agents/workflows/audit-sre.md +1 -1
- package/.agents/workflows/audit-to-stories.md +10 -10
- package/.agents/workflows/audit-ux-ui.md +1 -1
- package/.agents/workflows/deliver.md +85 -0
- package/.agents/workflows/explain.md +3 -3
- package/.agents/workflows/git-merge-pr.md +1 -1
- package/.agents/workflows/git-pr-all.md +13 -10
- package/.agents/workflows/git-push.md +6 -3
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
- package/.agents/workflows/helpers/code-review.md +5 -5
- package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +59 -66
- package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
- package/.agents/workflows/helpers/diagnose.md +1 -1
- package/.agents/workflows/helpers/epic-audit.md +6 -6
- package/.agents/workflows/helpers/epic-deliver-story.md +28 -39
- package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
- package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
- package/.agents/workflows/helpers/epic-testing.md +3 -3
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
- package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
- package/.agents/workflows/helpers/signals.md +1 -1
- package/.agents/workflows/helpers/single-story-deliver.md +12 -11
- package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
- package/.agents/workflows/onboard.md +21 -20
- package/.agents/workflows/plan.md +89 -0
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/qa-run-harness.md +1 -1
- package/README.md +17 -20
- package/docs/CHANGELOG.md +1149 -0
- package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
- package/lib/cli/__tests__/update-reexec.test.js +513 -0
- package/lib/cli/init.js +338 -0
- package/lib/cli/update.js +413 -52
- package/package.json +3 -1
- package/.agents/scripts/lib/auto-refresh-baselines.js +0 -308
- package/.agents/scripts/lib/close-validation.js +0 -897
- package/.agents/scripts/lib/orchestration/cascade-grouping.js +0 -275
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +0 -69
- package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
- package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +0 -221
- package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +0 -123
- package/.agents/scripts/lib/task-utils.js +0 -26
- package/.agents/scripts/story-deliver-prepare.js +0 -267
|
@@ -11,26 +11,25 @@
|
|
|
11
11
|
* per-file percentages (which would over-weight small files).
|
|
12
12
|
*
|
|
13
13
|
* Lower duplication is better, so the gate's floor direction is `lte`
|
|
14
|
-
* (see `check-baselines/phases/floors.js#axisDirection`).
|
|
14
|
+
* (see `check-baselines/phases/floors.js#axisDirection`). New paths land
|
|
15
|
+
* in the `additions` bucket (Story #2012); removed paths count as
|
|
16
|
+
* improvements when they carried any duplication — the file is gone, so
|
|
17
|
+
* its prior debt is gone too.
|
|
15
18
|
*
|
|
16
19
|
* `kernelVersion()` returns a static in-repo semver — the duplication
|
|
17
20
|
* scorer is the in-repo scan + rollup contract (the jscpd output is
|
|
18
21
|
* normalised before it reaches this module), so there is no upstream
|
|
19
22
|
* library version to track the way CRAP/MI track `typhonjs-escomplex`.
|
|
20
|
-
* Bump `
|
|
23
|
+
* Bump the version passed to `makeBaselineKind` whenever the row shape or
|
|
24
|
+
* rollup math changes. Scaffold is generated by `makeBaselineKind`
|
|
25
|
+
* (Story #3983).
|
|
21
26
|
*/
|
|
22
27
|
|
|
23
|
-
import { componentMatches } from '../component-matcher.js';
|
|
24
28
|
import { canonicalise } from '../path-canon.js';
|
|
25
|
-
import {
|
|
29
|
+
import { makeBaselineKind } from './kind-factory.js';
|
|
26
30
|
|
|
27
31
|
export const name = 'duplication';
|
|
28
32
|
export const keyField = 'path';
|
|
29
|
-
const KERNEL_VERSION = '1.0.0';
|
|
30
|
-
|
|
31
|
-
export function kernelVersion() {
|
|
32
|
-
return KERNEL_VERSION;
|
|
33
|
-
}
|
|
34
33
|
|
|
35
34
|
export function projectRow(row) {
|
|
36
35
|
const duplicatedLines = Number(row.duplicatedLines ?? 0);
|
|
@@ -47,10 +46,6 @@ export function projectRow(row) {
|
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
export function sortRows(rows) {
|
|
51
|
-
return [...rows].sort((a, b) => a.path.localeCompare(b.path));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
49
|
/**
|
|
55
50
|
* Exact aggregate duplication ratio: total duplicated lines / total lines
|
|
56
51
|
* across the row set, expressed as a percentage. Averaging per-file
|
|
@@ -92,106 +87,22 @@ function roundTo2(value) {
|
|
|
92
87
|
return Number(value.toFixed(2));
|
|
93
88
|
}
|
|
94
89
|
|
|
95
|
-
export
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
* improvements when they carried any duplication; the file is gone, so its
|
|
115
|
-
* prior debt is gone too.
|
|
116
|
-
*
|
|
117
|
-
* No I/O. No process exit. No friction emission.
|
|
118
|
-
*/
|
|
119
|
-
export function compare(head, base) {
|
|
120
|
-
const headRows = Array.isArray(head?.rows) ? head.rows : [];
|
|
121
|
-
const baseRows = Array.isArray(base?.rows) ? base.rows : [];
|
|
122
|
-
const baseByKey = new Map();
|
|
123
|
-
for (const r of baseRows) baseByKey.set(r.path, r);
|
|
124
|
-
const seen = new Set();
|
|
125
|
-
const regressions = [];
|
|
126
|
-
const improvements = [];
|
|
127
|
-
const unchanged = [];
|
|
128
|
-
const additions = [];
|
|
129
|
-
for (const h of headRows) {
|
|
130
|
-
seen.add(h.path);
|
|
131
|
-
const b = baseByKey.get(h.path);
|
|
132
|
-
if (!b) {
|
|
133
|
-
additions.push({ key: h.path, head: h, base: null });
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
const delta = (h.percentage ?? 0) - (b.percentage ?? 0);
|
|
137
|
-
if (delta > 0) regressions.push({ key: h.path, head: h, base: b });
|
|
138
|
-
else if (delta < 0) improvements.push({ key: h.path, head: h, base: b });
|
|
139
|
-
else unchanged.push({ key: h.path, head: h, base: b });
|
|
140
|
-
}
|
|
141
|
-
for (const b of baseRows) {
|
|
142
|
-
if (seen.has(b.path)) continue;
|
|
143
|
-
if ((b.percentage ?? 0) > 0) {
|
|
144
|
-
improvements.push({ key: b.path, head: null, base: b });
|
|
145
|
-
} else {
|
|
146
|
-
unchanged.push({ key: b.path, head: null, base: b });
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return { regressions, improvements, unchanged, additions };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Pure stabilizer for s-stability-epsilon (Story #1964). Duplication rows
|
|
154
|
-
* match by `path`. The metric is the absolute `percentage` delta. Sub-
|
|
155
|
-
* epsilon deltas resolve to the prior bytes so env variance never rewrites
|
|
156
|
-
* the on-disk baseline; missing-prior rows fall through.
|
|
157
|
-
*
|
|
158
|
-
* @param {Array<{path: string, percentage: number}>} prior
|
|
159
|
-
* @param {Array<{path: string, percentage: number}>} regenerated
|
|
160
|
-
* @param {number} epsilon non-negative absolute tolerance on percentage
|
|
161
|
-
* @returns {Array<object>}
|
|
162
|
-
*/
|
|
163
|
-
export function applyEpsilon(prior, regenerated, epsilon) {
|
|
164
|
-
const priorRows = Array.isArray(prior) ? prior : [];
|
|
165
|
-
const regenRows = Array.isArray(regenerated) ? regenerated : [];
|
|
166
|
-
const eps = Number.isFinite(epsilon) && epsilon >= 0 ? epsilon : 0;
|
|
167
|
-
const priorByKey = new Map();
|
|
168
|
-
for (const r of priorRows) priorByKey.set(r.path, r);
|
|
169
|
-
return regenRows.map((row) => {
|
|
170
|
-
const p = priorByKey.get(row.path);
|
|
171
|
-
if (!p) return row;
|
|
172
|
-
return Math.abs((row.percentage ?? 0) - (p.percentage ?? 0)) <= eps
|
|
173
|
-
? p
|
|
174
|
-
: row;
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Pure scope-aware merge for s-diff-scoped-writes (Story #1974).
|
|
180
|
-
* Duplication rows match by `path`. In diff mode, rows whose `path` is
|
|
181
|
-
* OUTSIDE `scope.files` are preserved from `prior` verbatim; in-scope rows
|
|
182
|
-
* come from `regenerated`. In full mode (or no scope), regenerated wins
|
|
183
|
-
* everywhere.
|
|
184
|
-
*
|
|
185
|
-
* @param {Array<{path: string, percentage: number}>} prior
|
|
186
|
-
* @param {Array<{path: string, percentage: number}>} regenerated
|
|
187
|
-
* @param {{mode: 'full'|'diff', files: Set<string>}|null|undefined} scope
|
|
188
|
-
* @returns {Array<object>}
|
|
189
|
-
*/
|
|
190
|
-
export function mergeRows(prior, regenerated, scope) {
|
|
191
|
-
return mergeRowsByScope({
|
|
192
|
-
prior,
|
|
193
|
-
regenerated,
|
|
194
|
-
scope,
|
|
195
|
-
scopeKey: (row) => row.path,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
90
|
+
export const {
|
|
91
|
+
kernelVersion,
|
|
92
|
+
sortRows,
|
|
93
|
+
rollup,
|
|
94
|
+
compare,
|
|
95
|
+
applyEpsilon,
|
|
96
|
+
mergeRows,
|
|
97
|
+
} = makeBaselineKind({
|
|
98
|
+
keyField,
|
|
99
|
+
kernelVersion: '1.0.0',
|
|
100
|
+
axes: ['percentage'],
|
|
101
|
+
betterWhen: 'lower',
|
|
102
|
+
aggregate,
|
|
103
|
+
missingBasePolicy: 'addition',
|
|
104
|
+
removedRowPolicy: {
|
|
105
|
+
kind: 'improvement-when',
|
|
106
|
+
when: (b) => (b.percentage ?? 0) > 0,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kinds/kind-factory.js — shared scaffold factory for per-kind baseline
|
|
3
|
+
* modules (Story #3983).
|
|
4
|
+
*
|
|
5
|
+
* The five row-metric kinds (coverage, mutation, maintainability,
|
|
6
|
+
* lighthouse, duplication) used to hand-roll the same scaffold each:
|
|
7
|
+
* `kernelVersion`, `sortRows`, a component-aware `rollup`, a
|
|
8
|
+
* `compare(head, base)` that diffs rows by key into
|
|
9
|
+
* `{regressions, improvements, unchanged, additions}`, an
|
|
10
|
+
* `applyEpsilon` stabilizer (Story #1964), and a scope-aware
|
|
11
|
+
* `mergeRows` (Story #1974). Only the axis list, direction-of-better,
|
|
12
|
+
* aggregate math, and the missing/removed-row policies differ per kind.
|
|
13
|
+
*
|
|
14
|
+
* `makeBaselineKind` generates the scaffold once; each kind module stays
|
|
15
|
+
* a thin parameterization with a byte-identical exported surface. The
|
|
16
|
+
* Story #2012 class of fix ("new paths must land in `additions`, never
|
|
17
|
+
* the regression arm") lives here exactly once.
|
|
18
|
+
*
|
|
19
|
+
* All generated functions are pure: no I/O, no process exit, no
|
|
20
|
+
* friction emission.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { componentMatches } from '../component-matcher.js';
|
|
24
|
+
import { mergeRowsByScope } from '../scope.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build the shared scaffold for a per-kind baseline module.
|
|
28
|
+
*
|
|
29
|
+
* @param {{
|
|
30
|
+
* keyField: string,
|
|
31
|
+
* kernelVersion: string | (() => string),
|
|
32
|
+
* axes: string[],
|
|
33
|
+
* betterWhen: 'higher' | 'lower',
|
|
34
|
+
* aggregate: (rows: object[]) => Record<string, number>,
|
|
35
|
+
* missingBasePolicy?: 'addition' | 'perfect',
|
|
36
|
+
* removedRowPolicy?:
|
|
37
|
+
* | { kind: 'perfect-head' }
|
|
38
|
+
* | { kind: 'improvement-when', when: (row: object) => boolean },
|
|
39
|
+
* perfectRow?: (key: string) => object,
|
|
40
|
+
* }} opts
|
|
41
|
+
* - `keyField` — row identity property (`'path'` or `'route'`)
|
|
42
|
+
* - `kernelVersion` — static semver, or a thunk for kinds that pin
|
|
43
|
+
* to another kind's kernel (MI → CRAP)
|
|
44
|
+
* - `axes` — metric property names compared per row
|
|
45
|
+
* - `betterWhen` — `'higher'` (coverage, MI, …) or `'lower'`
|
|
46
|
+
* (duplication): decides which delta sign is a
|
|
47
|
+
* regression
|
|
48
|
+
* - `aggregate` — per-kind rollup math over a row set
|
|
49
|
+
* - `missingBasePolicy` — head row with no base row: `'addition'`
|
|
50
|
+
* (default; Story #2012 bucket) or `'perfect'`
|
|
51
|
+
* (classify against a perfect base — lighthouse,
|
|
52
|
+
* where a new route must meet the bar)
|
|
53
|
+
* - `removedRowPolicy` — base row with no head row: `'perfect-head'`
|
|
54
|
+
* classifies against a perfect head row;
|
|
55
|
+
* `'improvement-when'` pushes an improvement
|
|
56
|
+
* when `when(baseRow)` holds, else unchanged
|
|
57
|
+
* - `perfectRow` — builds the perfect row for the policies above
|
|
58
|
+
* @returns {{
|
|
59
|
+
* kernelVersion: () => string,
|
|
60
|
+
* sortRows: (rows: object[]) => object[],
|
|
61
|
+
* rollup: (rows: object[], components?: object[]) => Record<string, object>,
|
|
62
|
+
* compare: (head: object, base: object) => object,
|
|
63
|
+
* applyEpsilon: (prior: object[], regenerated: object[], epsilon: number) => object[],
|
|
64
|
+
* mergeRows: (prior: object[], regenerated: object[], scope: object) => object[],
|
|
65
|
+
* }}
|
|
66
|
+
*/
|
|
67
|
+
export function makeBaselineKind({
|
|
68
|
+
keyField,
|
|
69
|
+
kernelVersion,
|
|
70
|
+
axes,
|
|
71
|
+
betterWhen,
|
|
72
|
+
aggregate,
|
|
73
|
+
missingBasePolicy = 'addition',
|
|
74
|
+
removedRowPolicy = { kind: 'improvement-when', when: () => false },
|
|
75
|
+
perfectRow = null,
|
|
76
|
+
}) {
|
|
77
|
+
const keyOf = (row) => row[keyField];
|
|
78
|
+
const kernelVersionFn =
|
|
79
|
+
typeof kernelVersion === 'function' ? kernelVersion : () => kernelVersion;
|
|
80
|
+
|
|
81
|
+
function sortRows(rows) {
|
|
82
|
+
return [...rows].sort((a, b) => keyOf(a).localeCompare(keyOf(b)));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function rollup(rows, components = []) {
|
|
86
|
+
const out = { '*': aggregate(rows) };
|
|
87
|
+
for (const c of components ?? []) {
|
|
88
|
+
const matched = (rows ?? []).filter((r) => componentMatches(c, keyOf(r)));
|
|
89
|
+
out[c.name] = aggregate(matched);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function classify(regressions, improvements, unchanged, key, head, base) {
|
|
95
|
+
let down = false;
|
|
96
|
+
let up = false;
|
|
97
|
+
for (const axis of axes) {
|
|
98
|
+
const delta = (head[axis] ?? 0) - (base[axis] ?? 0);
|
|
99
|
+
const worse = betterWhen === 'higher' ? delta < 0 : delta > 0;
|
|
100
|
+
const better = betterWhen === 'higher' ? delta > 0 : delta < 0;
|
|
101
|
+
if (worse) down = true;
|
|
102
|
+
else if (better) up = true;
|
|
103
|
+
}
|
|
104
|
+
if (down) regressions.push({ key, head, base });
|
|
105
|
+
else if (up) improvements.push({ key, head, base });
|
|
106
|
+
else unchanged.push({ key, head, base });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function compare(head, base) {
|
|
110
|
+
const headRows = Array.isArray(head?.rows) ? head.rows : [];
|
|
111
|
+
const baseRows = Array.isArray(base?.rows) ? base.rows : [];
|
|
112
|
+
const baseByKey = new Map();
|
|
113
|
+
for (const r of baseRows) baseByKey.set(keyOf(r), r);
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
const regressions = [];
|
|
116
|
+
const improvements = [];
|
|
117
|
+
const unchanged = [];
|
|
118
|
+
const additions = [];
|
|
119
|
+
for (const h of headRows) {
|
|
120
|
+
const key = keyOf(h);
|
|
121
|
+
seen.add(key);
|
|
122
|
+
const b = baseByKey.get(key);
|
|
123
|
+
if (!b) {
|
|
124
|
+
if (missingBasePolicy === 'addition') {
|
|
125
|
+
additions.push({ key, head: h, base: null });
|
|
126
|
+
} else {
|
|
127
|
+
classify(
|
|
128
|
+
regressions,
|
|
129
|
+
improvements,
|
|
130
|
+
unchanged,
|
|
131
|
+
key,
|
|
132
|
+
h,
|
|
133
|
+
perfectRow(key),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
classify(regressions, improvements, unchanged, key, h, b);
|
|
139
|
+
}
|
|
140
|
+
for (const b of baseRows) {
|
|
141
|
+
const key = keyOf(b);
|
|
142
|
+
if (seen.has(key)) continue;
|
|
143
|
+
if (removedRowPolicy.kind === 'perfect-head') {
|
|
144
|
+
classify(regressions, improvements, unchanged, key, perfectRow(key), b);
|
|
145
|
+
} else if (removedRowPolicy.when(b)) {
|
|
146
|
+
improvements.push({ key, head: null, base: b });
|
|
147
|
+
} else {
|
|
148
|
+
unchanged.push({ key, head: null, base: b });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (missingBasePolicy === 'addition') {
|
|
152
|
+
return { regressions, improvements, unchanged, additions };
|
|
153
|
+
}
|
|
154
|
+
return { regressions, improvements, unchanged };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function applyEpsilon(prior, regenerated, epsilon) {
|
|
158
|
+
const priorRows = Array.isArray(prior) ? prior : [];
|
|
159
|
+
const regenRows = Array.isArray(regenerated) ? regenerated : [];
|
|
160
|
+
const eps = Number.isFinite(epsilon) && epsilon >= 0 ? epsilon : 0;
|
|
161
|
+
const priorByKey = new Map();
|
|
162
|
+
for (const r of priorRows) priorByKey.set(keyOf(r), r);
|
|
163
|
+
return regenRows.map((row) => {
|
|
164
|
+
const p = priorByKey.get(keyOf(row));
|
|
165
|
+
if (!p) return row;
|
|
166
|
+
let maxAxisDelta = 0;
|
|
167
|
+
for (const axis of axes) {
|
|
168
|
+
const d = Math.abs((row[axis] ?? 0) - (p[axis] ?? 0));
|
|
169
|
+
if (d > maxAxisDelta) maxAxisDelta = d;
|
|
170
|
+
}
|
|
171
|
+
return maxAxisDelta <= eps ? p : row;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function mergeRows(prior, regenerated, scope) {
|
|
176
|
+
return mergeRowsByScope({
|
|
177
|
+
prior,
|
|
178
|
+
regenerated,
|
|
179
|
+
scope,
|
|
180
|
+
scopeKey: keyOf,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
kernelVersion: kernelVersionFn,
|
|
186
|
+
sortRows,
|
|
187
|
+
rollup,
|
|
188
|
+
compare,
|
|
189
|
+
applyEpsilon,
|
|
190
|
+
mergeRows,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -3,19 +3,22 @@
|
|
|
3
3
|
* (Story #1891). Row shape: `{ route, performance, accessibility,
|
|
4
4
|
* bestPractices, seo }`. The key field is `route` (a path-like string that
|
|
5
5
|
* still passes the path-canon checks for absolute / `..` rejection).
|
|
6
|
+
*
|
|
7
|
+
* Higher score = better. New routes inherit a base of 100 for each axis
|
|
8
|
+
* (so lower scores register as regressions — a new route must meet the
|
|
9
|
+
* bar); dropped routes inherit a head of 100 (so a higher base registers
|
|
10
|
+
* as an improvement). Unlike the file-derived kinds, compare emits no
|
|
11
|
+
* `additions` bucket. Scaffold is generated by `makeBaselineKind`
|
|
12
|
+
* (Story #3983).
|
|
6
13
|
*/
|
|
7
14
|
|
|
8
|
-
import { componentMatches } from '../component-matcher.js';
|
|
9
15
|
import { canonicalise } from '../path-canon.js';
|
|
10
|
-
import {
|
|
16
|
+
import { makeBaselineKind } from './kind-factory.js';
|
|
11
17
|
|
|
12
18
|
export const name = 'lighthouse';
|
|
13
19
|
export const keyField = 'route';
|
|
14
|
-
const KERNEL_VERSION = '1.0.0';
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
return KERNEL_VERSION;
|
|
18
|
-
}
|
|
21
|
+
const LH_AXES = ['performance', 'accessibility', 'bestPractices', 'seo'];
|
|
19
22
|
|
|
20
23
|
function canonRoute(route) {
|
|
21
24
|
// Routes look like `/`, `/dashboard`, or `pricing` — strip the leading
|
|
@@ -36,75 +39,24 @@ export function projectRow(row) {
|
|
|
36
39
|
};
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
function meanOf(rows, axis) {
|
|
43
|
+
let sum = 0;
|
|
44
|
+
for (const r of rows) sum += r[axis] ?? 0;
|
|
45
|
+
return Number((sum / rows.length).toFixed(2));
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
function aggregate(rows) {
|
|
44
49
|
if (!rows || rows.length === 0) {
|
|
45
50
|
return { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 };
|
|
46
51
|
}
|
|
47
|
-
const sum = { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 };
|
|
48
|
-
for (const r of rows) {
|
|
49
|
-
sum.performance += r.performance ?? 0;
|
|
50
|
-
sum.accessibility += r.accessibility ?? 0;
|
|
51
|
-
sum.bestPractices += r.bestPractices ?? 0;
|
|
52
|
-
sum.seo += r.seo ?? 0;
|
|
53
|
-
}
|
|
54
52
|
return {
|
|
55
|
-
performance:
|
|
56
|
-
accessibility:
|
|
57
|
-
bestPractices:
|
|
58
|
-
seo:
|
|
53
|
+
performance: meanOf(rows, 'performance'),
|
|
54
|
+
accessibility: meanOf(rows, 'accessibility'),
|
|
55
|
+
bestPractices: meanOf(rows, 'bestPractices'),
|
|
56
|
+
seo: meanOf(rows, 'seo'),
|
|
59
57
|
};
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
export function rollup(rows, components = []) {
|
|
63
|
-
const out = { '*': aggregate(rows) };
|
|
64
|
-
for (const c of components ?? []) {
|
|
65
|
-
const matched = (rows ?? []).filter((r) => componentMatches(c, r.route));
|
|
66
|
-
out[c.name] = aggregate(matched);
|
|
67
|
-
}
|
|
68
|
-
return out;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Pure compare(head, base) for the lighthouse kind. Diffs rows by `route`.
|
|
73
|
-
*
|
|
74
|
-
* Higher score = better. A row regresses when any of performance,
|
|
75
|
-
* accessibility, bestPractices, or seo decreases vs the base. An
|
|
76
|
-
* improvement requires at least one score to increase and none to
|
|
77
|
-
* decrease. Otherwise the row is unchanged. New routes inherit a base of
|
|
78
|
-
* 100 for each axis (so lower scores register as regressions); dropped
|
|
79
|
-
* routes inherit a head of 100 (so a higher base registers as an
|
|
80
|
-
* improvement).
|
|
81
|
-
*
|
|
82
|
-
* No I/O. No process exit. No friction emission.
|
|
83
|
-
*/
|
|
84
|
-
const LH_AXES = ['performance', 'accessibility', 'bestPractices', 'seo'];
|
|
85
|
-
|
|
86
|
-
export function compare(head, base) {
|
|
87
|
-
const headRows = Array.isArray(head?.rows) ? head.rows : [];
|
|
88
|
-
const baseRows = Array.isArray(base?.rows) ? base.rows : [];
|
|
89
|
-
const baseByKey = new Map();
|
|
90
|
-
for (const r of baseRows) baseByKey.set(r.route, r);
|
|
91
|
-
const seen = new Set();
|
|
92
|
-
const regressions = [];
|
|
93
|
-
const improvements = [];
|
|
94
|
-
const unchanged = [];
|
|
95
|
-
for (const h of headRows) {
|
|
96
|
-
seen.add(h.route);
|
|
97
|
-
const b = baseByKey.get(h.route) ?? perfectLighthouseRow(h.route);
|
|
98
|
-
classify(regressions, improvements, unchanged, h.route, h, b);
|
|
99
|
-
}
|
|
100
|
-
for (const b of baseRows) {
|
|
101
|
-
if (seen.has(b.route)) continue;
|
|
102
|
-
const h = perfectLighthouseRow(b.route);
|
|
103
|
-
classify(regressions, improvements, unchanged, b.route, h, b);
|
|
104
|
-
}
|
|
105
|
-
return { regressions, improvements, unchanged };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
60
|
function perfectLighthouseRow(route) {
|
|
109
61
|
return {
|
|
110
62
|
route,
|
|
@@ -115,71 +67,20 @@ function perfectLighthouseRow(route) {
|
|
|
115
67
|
};
|
|
116
68
|
}
|
|
117
69
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* missing-prior rows fall through.
|
|
136
|
-
*
|
|
137
|
-
* @param {Array<{route: string, performance: number, accessibility: number, bestPractices: number, seo: number}>} prior
|
|
138
|
-
* @param {Array<{route: string, performance: number, accessibility: number, bestPractices: number, seo: number}>} regenerated
|
|
139
|
-
* @param {number} epsilon non-negative absolute tolerance per axis
|
|
140
|
-
* @returns {Array<object>}
|
|
141
|
-
*/
|
|
142
|
-
export function applyEpsilon(prior, regenerated, epsilon) {
|
|
143
|
-
const priorRows = Array.isArray(prior) ? prior : [];
|
|
144
|
-
const regenRows = Array.isArray(regenerated) ? regenerated : [];
|
|
145
|
-
const eps = Number.isFinite(epsilon) && epsilon >= 0 ? epsilon : 0;
|
|
146
|
-
const priorByKey = new Map();
|
|
147
|
-
for (const r of priorRows) priorByKey.set(r.route, r);
|
|
148
|
-
return regenRows.map((row) => {
|
|
149
|
-
const p = priorByKey.get(row.route);
|
|
150
|
-
if (!p) return row;
|
|
151
|
-
let maxAxisDelta = 0;
|
|
152
|
-
for (const axis of LH_AXES) {
|
|
153
|
-
const d = Math.abs((row[axis] ?? 0) - (p[axis] ?? 0));
|
|
154
|
-
if (d > maxAxisDelta) maxAxisDelta = d;
|
|
155
|
-
}
|
|
156
|
-
return maxAxisDelta <= eps ? p : row;
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Pure scope-aware merge for s-diff-scoped-writes (Story #1974). Lighthouse
|
|
162
|
-
* rows match by `route`. In diff mode, rows whose `route` is OUTSIDE
|
|
163
|
-
* `scope.files` are preserved from `prior` verbatim; in-scope rows come
|
|
164
|
-
* from `regenerated`. In full mode (or no scope), regenerated wins
|
|
165
|
-
* everywhere.
|
|
166
|
-
*
|
|
167
|
-
* Note: lighthouse routes are not file paths, so the scope filter only
|
|
168
|
-
* narrows naturally when callers seed `scope.files` with route strings.
|
|
169
|
-
* Auto-refresh callers using a Story file diff will see no in-scope rows
|
|
170
|
-
* and therefore preserve every prior row — which is the safe default for
|
|
171
|
-
* a baseline that is not file-derived.
|
|
172
|
-
*
|
|
173
|
-
* @param {Array<{route: string, performance: number, accessibility: number, bestPractices: number, seo: number}>} prior
|
|
174
|
-
* @param {Array<{route: string, performance: number, accessibility: number, bestPractices: number, seo: number}>} regenerated
|
|
175
|
-
* @param {{mode: 'full'|'diff', files: Set<string>}|null|undefined} scope
|
|
176
|
-
* @returns {Array<object>}
|
|
177
|
-
*/
|
|
178
|
-
export function mergeRows(prior, regenerated, scope) {
|
|
179
|
-
return mergeRowsByScope({
|
|
180
|
-
prior,
|
|
181
|
-
regenerated,
|
|
182
|
-
scope,
|
|
183
|
-
scopeKey: (row) => row.route,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
70
|
+
export const {
|
|
71
|
+
kernelVersion,
|
|
72
|
+
sortRows,
|
|
73
|
+
rollup,
|
|
74
|
+
compare,
|
|
75
|
+
applyEpsilon,
|
|
76
|
+
mergeRows,
|
|
77
|
+
} = makeBaselineKind({
|
|
78
|
+
keyField,
|
|
79
|
+
kernelVersion: '1.0.0',
|
|
80
|
+
axes: LH_AXES,
|
|
81
|
+
betterWhen: 'higher',
|
|
82
|
+
aggregate,
|
|
83
|
+
missingBasePolicy: 'perfect',
|
|
84
|
+
removedRowPolicy: { kind: 'perfect-head' },
|
|
85
|
+
perfectRow: perfectLighthouseRow,
|
|
86
|
+
});
|