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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* epic-plan-healthcheck.js — Post-Plan Readiness Check
|
|
7
7
|
*
|
|
8
|
-
* Runs at the end of /
|
|
8
|
+
* Runs at the end of /plan (Phase 10) to validate the backlog and
|
|
9
9
|
* optionally prime the execution environment before handing off to
|
|
10
10
|
* /epic-deliver.
|
|
11
11
|
*
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* node epic-plan-healthcheck.js --epic <EPIC_ID> \
|
|
28
28
|
* [--paranoid] [--prime-install] [--dry-run]
|
|
29
29
|
*
|
|
30
|
-
* @see .agents/workflows/epic
|
|
30
|
+
* @see .agents/workflows/helpers/plan-epic.md Phase 10
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { spawnSync } from 'node:child_process';
|
|
@@ -131,7 +131,7 @@ function checkGitRemote(baseBranch, cwd) {
|
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Detect whether a Story body carries an inline `## Acceptance` section with
|
|
134
|
-
* at least one checklist item. Epic #3078 — under
|
|
134
|
+
* at least one checklist item. Epic #3078 — under 2-tier hierarchy, Stories
|
|
135
135
|
* carry acceptance inline, so the hierarchy check uses this signal as the
|
|
136
136
|
* mark of a complete, executable Story.
|
|
137
137
|
*
|
|
@@ -152,7 +152,7 @@ function hasInlineAcceptance(body) {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
|
-
* Validate Epic ticket hierarchy.
|
|
155
|
+
* Validate Epic ticket hierarchy. 2-tier is the only supported hierarchy
|
|
156
156
|
* after Task #3154 deleted `planning.hierarchy`: every Story must carry an
|
|
157
157
|
* inline `## Acceptance` checklist; there is no Task layer to graph.
|
|
158
158
|
*
|
|
@@ -181,13 +181,9 @@ async function checkTickets(provider, epicId) {
|
|
|
181
181
|
return { ok: false, detail: `Epic #${epicId} has no child tickets.` };
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
const features = tickets.filter((t) =>
|
|
185
|
-
t.labels.includes(TYPE_LABELS.FEATURE),
|
|
186
|
-
);
|
|
187
184
|
const stories = tickets.filter((t) => t.labels.includes(TYPE_LABELS.STORY));
|
|
188
185
|
|
|
189
186
|
const errors = [];
|
|
190
|
-
if (features.length === 0) errors.push('no type::feature tickets');
|
|
191
187
|
if (stories.length === 0) errors.push('no type::story tickets');
|
|
192
188
|
|
|
193
189
|
const missingAcceptance = stories.filter(
|
|
@@ -214,7 +210,7 @@ async function checkTickets(provider, epicId) {
|
|
|
214
210
|
|
|
215
211
|
return {
|
|
216
212
|
ok: true,
|
|
217
|
-
detail: `${
|
|
213
|
+
detail: `${stories.length} stories (2-tier, inline acceptance) — hierarchy valid${advisory}.`,
|
|
218
214
|
};
|
|
219
215
|
}
|
|
220
216
|
|
|
@@ -313,7 +309,7 @@ export async function runPlanHealthcheck(opts = {}) {
|
|
|
313
309
|
await timed('git-remote', async () => checkGitRemote(baseBranch, cwd)),
|
|
314
310
|
);
|
|
315
311
|
|
|
316
|
-
// Paranoid lane: ticket-hierarchy revalidation (
|
|
312
|
+
// Paranoid lane: ticket-hierarchy revalidation (2-tier only).
|
|
317
313
|
if (paranoid) {
|
|
318
314
|
const provider = opts.injectedProvider || createProvider(config);
|
|
319
315
|
progress('CHECK', 'Validating ticket hierarchy...');
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* epic-plan-spec-validate.js — Phase 7.5 Tech Spec post-authoring gate CLI.
|
|
5
5
|
*
|
|
6
|
-
* `/
|
|
6
|
+
* `/plan` Phase 7 authors the Tech Spec; Phase 8.3 (Holistic
|
|
7
7
|
* Consolidation) reconciles the draft ticket array against the Tech Spec's
|
|
8
8
|
* `## Delivery Slicing` section, which the decompose-author skill uses as the
|
|
9
9
|
* capability-boundary anchor. When that section is absent the consolidation
|
|
@@ -195,11 +195,11 @@ export async function fetchGhState(provider, epicId) {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
* Walk the spec
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
198
|
+
* Walk the spec and yield one `{ slug, entity, title, parentSlug,
|
|
199
|
+
* dependsOn }` record per logical entity (epic → story), mirroring the
|
|
200
|
+
* diff engine's `flattenSpec`. Local to the CLI so the reseed pass
|
|
201
|
+
* (below) can map spec slugs onto live GH issues without importing the
|
|
202
|
+
* diff engine's private walker. Pure.
|
|
203
203
|
*
|
|
204
204
|
* @param {object} spec
|
|
205
205
|
* @returns {Array<{slug: string, entity: string, title: string, parentSlug: string|null, dependsOn: string[]}>}
|
|
@@ -216,32 +216,14 @@ export function flattenSpecForReseed(spec) {
|
|
|
216
216
|
dependsOn: [],
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
|
-
for (const
|
|
219
|
+
for (const story of spec.stories ?? []) {
|
|
220
220
|
out.push({
|
|
221
|
-
slug:
|
|
222
|
-
entity: '
|
|
223
|
-
title: String(
|
|
221
|
+
slug: story.slug,
|
|
222
|
+
entity: 'story',
|
|
223
|
+
title: String(story.title ?? ''),
|
|
224
224
|
parentSlug: 'epic',
|
|
225
|
-
dependsOn: [],
|
|
225
|
+
dependsOn: story.dependsOn ?? [],
|
|
226
226
|
});
|
|
227
|
-
for (const story of feature.stories ?? []) {
|
|
228
|
-
out.push({
|
|
229
|
-
slug: story.slug,
|
|
230
|
-
entity: 'story',
|
|
231
|
-
title: String(story.title ?? ''),
|
|
232
|
-
parentSlug: feature.slug,
|
|
233
|
-
dependsOn: story.dependsOn ?? [],
|
|
234
|
-
});
|
|
235
|
-
for (const task of story.tasks ?? []) {
|
|
236
|
-
out.push({
|
|
237
|
-
slug: task.slug,
|
|
238
|
-
entity: 'task',
|
|
239
|
-
title: String(task.title ?? ''),
|
|
240
|
-
parentSlug: story.slug,
|
|
241
|
-
dependsOn: [],
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
227
|
}
|
|
246
228
|
return out;
|
|
247
229
|
}
|
|
@@ -255,7 +237,7 @@ export function flattenSpecForReseed(spec) {
|
|
|
255
237
|
* file is absent (a fresh checkout, a reaped temp dir, the exact
|
|
256
238
|
* situation `--resume` exists to recover from), `loadState` returns an
|
|
257
239
|
* empty mapping, the diff engine sees every spec slug as unmapped, and
|
|
258
|
-
* `apply` recreates the entire
|
|
240
|
+
* `apply` recreates the entire Story set on top of the existing
|
|
259
241
|
* one — duplicating every child. This is precisely the failure `--resume`
|
|
260
242
|
* is meant to prevent.
|
|
261
243
|
*
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* evidence-gate.js — evidence-aware wrapper around a single shell gate.
|
|
5
5
|
*
|
|
6
|
-
* Tech Spec #819 §"Evidence record (Story 7)" — `/
|
|
6
|
+
* Tech Spec #819 §"Evidence record (Story 7)" — `/deliver` Phase 3
|
|
7
7
|
* (close-validation) runs `npm run lint` and `npm test` against the Epic
|
|
8
8
|
* branch before opening the PR.
|
|
9
9
|
* If the same gate has already passed against the current `git rev-parse
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
import { spawnSync } from 'node:child_process';
|
|
48
48
|
import { parseArgs } from 'node:util';
|
|
49
49
|
import { runAsCli } from './lib/cli-utils.js';
|
|
50
|
-
import { PROJECT_ROOT } from './lib/config-resolver.js';
|
|
51
50
|
import { gitSpawn } from './lib/git-utils.js';
|
|
52
51
|
import { Logger } from './lib/Logger.js';
|
|
52
|
+
import { PROJECT_ROOT } from './lib/project-root.js';
|
|
53
53
|
import {
|
|
54
54
|
hashCommandConfig,
|
|
55
55
|
recordPass,
|
|
@@ -91,7 +91,7 @@ export function renderWorkflowsDoc(catalog) {
|
|
|
91
91
|
'Every command file lives at `.agents/workflows/<name>.md` and is projected',
|
|
92
92
|
'into a flat `.claude/commands/` tree by `npm run sync:commands` (the',
|
|
93
93
|
'UserPromptSubmit hook keeps it current) so it shows up as a bare `/<name>`',
|
|
94
|
-
'slash command (e.g. `/
|
|
94
|
+
'slash command (e.g. `/deliver`). The projection writes only',
|
|
95
95
|
'`.claude/commands/<name>.md` — there is no plugin manifest and no',
|
|
96
96
|
'marketplace listing. The commands load in every Claude Code environment.',
|
|
97
97
|
'',
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
|
|
41
41
|
import { parseArgs } from 'node:util';
|
|
42
42
|
import { runAsCli } from './lib/cli-utils.js';
|
|
43
|
-
import { PROJECT_ROOT } from './lib/config-resolver.js';
|
|
44
43
|
import { gitSpawn } from './lib/git-utils.js';
|
|
45
44
|
import { Logger } from './lib/Logger.js';
|
|
45
|
+
import { PROJECT_ROOT } from './lib/project-root.js';
|
|
46
46
|
|
|
47
47
|
function currentBranch(cwd) {
|
|
48
48
|
const res = gitSpawn(cwd, 'rev-parse', '--abbrev-ref', 'HEAD');
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* .agents/scripts/hierarchy-gate.js — Hierarchy Completeness Gate
|
|
5
5
|
*
|
|
6
|
-
* Walks the Epic's full sub-issue graph (
|
|
6
|
+
* Walks the Epic's full sub-issue graph (Stories) and verifies
|
|
7
7
|
* every descendant is closed. Where the wave gate asks "did the sprint
|
|
8
8
|
* complete what it committed to?" (manifest view), this gate asks "is
|
|
9
9
|
* anything still open under this Epic?" (live GitHub graph view).
|
|
@@ -16,14 +16,13 @@
|
|
|
16
16
|
* top-level Stories outside the Epic's sub-issue graph.
|
|
17
17
|
*
|
|
18
18
|
* Per ticket type the rule is:
|
|
19
|
-
* - Features — must be closed.
|
|
20
19
|
* - Stories — must be closed.
|
|
21
20
|
* - Auxiliary (context::prd, context::tech-spec) — ignored.
|
|
22
21
|
* These are closed by the operator after the Epic PR merges, so
|
|
23
22
|
* requiring them closed here would block every Epic.
|
|
24
23
|
*
|
|
25
|
-
* **
|
|
26
|
-
*
|
|
24
|
+
* **2-tier hierarchy (Story #4041).** Mandrel ships only Epic / Story
|
|
25
|
+
* tickets. `getSubTickets(<storyId>)` returns `[]`; the walk
|
|
27
26
|
* terminates at the Story. Acceptance criteria live inline on the
|
|
28
27
|
* Story body.
|
|
29
28
|
*
|
|
@@ -42,11 +41,17 @@ import { resolveConfig } from './lib/config-resolver.js';
|
|
|
42
41
|
import { Logger } from './lib/Logger.js';
|
|
43
42
|
import { CONTEXT_LABELS, TYPE_LABELS } from './lib/label-constants.js';
|
|
44
43
|
import { createProvider } from './lib/provider-factory.js';
|
|
44
|
+
import { concurrentMap } from './lib/util/concurrent-map.js';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Bounded fan-out for per-level `getSubTickets` calls. Matches the
|
|
48
|
+
* wave-record-io.js precedent (Story #3024).
|
|
49
|
+
*/
|
|
50
|
+
const SUB_TICKET_FETCH_CONCURRENCY = 4;
|
|
45
51
|
|
|
46
52
|
function classify(ticket) {
|
|
47
53
|
const labels = ticket.labels ?? [];
|
|
48
54
|
if (labels.includes(TYPE_LABELS.STORY)) return 'story';
|
|
49
|
-
if (labels.includes(TYPE_LABELS.FEATURE)) return 'feature';
|
|
50
55
|
if (
|
|
51
56
|
labels.includes(CONTEXT_LABELS.PRD) ||
|
|
52
57
|
labels.includes(CONTEXT_LABELS.TECH_SPEC)
|
|
@@ -70,22 +75,35 @@ function ticketIsComplete(ticket) {
|
|
|
70
75
|
*/
|
|
71
76
|
async function collectDescendants(provider, epicId) {
|
|
72
77
|
const visited = new Set([epicId]);
|
|
73
|
-
const queue = [epicId];
|
|
74
78
|
const out = [];
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
// Level-order BFS: each round fetches the whole frontier's children with a
|
|
80
|
+
// bounded-parallel map instead of one awaited round-trip per node. Stories
|
|
81
|
+
// are leaves (no sub-issues by contract), so they are never expanded
|
|
82
|
+
// — that skip alone removes the largest class of wasted GraphQL calls.
|
|
83
|
+
let frontier = [epicId];
|
|
84
|
+
while (frontier.length > 0) {
|
|
85
|
+
const levels = await concurrentMap(
|
|
86
|
+
frontier,
|
|
87
|
+
async (parentId) => {
|
|
88
|
+
try {
|
|
89
|
+
return await provider.getSubTickets(parentId);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
throw new Error(`getSubTickets(#${parentId}) failed: ${err.message}`);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{ concurrency: SUB_TICKET_FETCH_CONCURRENCY },
|
|
95
|
+
);
|
|
96
|
+
const next = [];
|
|
97
|
+
for (const children of levels) {
|
|
98
|
+
for (const child of children) {
|
|
99
|
+
if (visited.has(child.id)) continue;
|
|
100
|
+
visited.add(child.id);
|
|
101
|
+
out.push(child);
|
|
102
|
+
const labels = child.labels ?? [];
|
|
103
|
+
if (!labels.includes(TYPE_LABELS.STORY)) next.push(child.id);
|
|
104
|
+
}
|
|
88
105
|
}
|
|
106
|
+
frontier = next;
|
|
89
107
|
}
|
|
90
108
|
return out;
|
|
91
109
|
}
|
|
@@ -106,7 +124,7 @@ export async function runHierarchyGate({ epicId, injectedProvider } = {}) {
|
|
|
106
124
|
process.exit(2);
|
|
107
125
|
}
|
|
108
126
|
|
|
109
|
-
const failures = {
|
|
127
|
+
const failures = { story: [], other: [] };
|
|
110
128
|
let auxiliaryDeferred = 0;
|
|
111
129
|
for (const ticket of descendants) {
|
|
112
130
|
const kind = classify(ticket);
|
|
@@ -124,15 +142,13 @@ export async function runHierarchyGate({ epicId, injectedProvider } = {}) {
|
|
|
124
142
|
}
|
|
125
143
|
}
|
|
126
144
|
|
|
127
|
-
const totalOpen =
|
|
128
|
-
failures.feature.length + failures.story.length + failures.other.length;
|
|
145
|
+
const totalOpen = failures.story.length + failures.other.length;
|
|
129
146
|
|
|
130
147
|
if (totalOpen > 0) {
|
|
131
148
|
Logger.error(
|
|
132
149
|
`[hierarchy-gate] ❌ Hierarchy-completeness gate FAILED for Epic #${epicId}: ${totalOpen} descendant(s) incomplete.`,
|
|
133
150
|
);
|
|
134
151
|
const sections = [
|
|
135
|
-
['feature', 'Features'],
|
|
136
152
|
['story', 'Stories'],
|
|
137
153
|
['other', 'Untyped descendants'],
|
|
138
154
|
];
|
|
@@ -143,7 +159,7 @@ export async function runHierarchyGate({ epicId, injectedProvider } = {}) {
|
|
|
143
159
|
Logger.error(` - #${item.id} (${item.reason}) — ${item.title}`);
|
|
144
160
|
}
|
|
145
161
|
}
|
|
146
|
-
Logger.error('\nClose the open descendants and re-run `/
|
|
162
|
+
Logger.error('\nClose the open descendants and re-run `/deliver`.');
|
|
147
163
|
process.exit(1);
|
|
148
164
|
}
|
|
149
165
|
|
|
@@ -154,7 +154,7 @@ export class ITicketingProvider {
|
|
|
154
154
|
/**
|
|
155
155
|
* Create a child ticket within an Epic's structural hierarchy.
|
|
156
156
|
*
|
|
157
|
-
* @param {number} parentId - GitHub Issue number of the immediate structural parent (e.g. Epic
|
|
157
|
+
* @param {number} parentId - GitHub Issue number of the immediate structural parent (e.g. Epic or Story).
|
|
158
158
|
* @param {{
|
|
159
159
|
* epicId: number,
|
|
160
160
|
* title: string,
|
|
@@ -60,7 +60,7 @@ export function matchesAnyFilePattern(patterns, files) {
|
|
|
60
60
|
* callers MUST pass the requested Epic's own branch ref (e.g.
|
|
61
61
|
* `refs/heads/epic/<id>`) so the change set is pinned to that Epic's branch
|
|
62
62
|
* rather than whatever HEAD the shared checkout happens to sit on. Under two
|
|
63
|
-
* concurrent `/
|
|
63
|
+
* concurrent `/deliver` runs sharing one checkout, diffing against
|
|
64
64
|
* `HEAD` silently resolves the *other* Epic's change set (Story #3362). When
|
|
65
65
|
* `headRef` cannot be resolved in the repo, the selector returns a
|
|
66
66
|
* `degraded: true` envelope (or hard-fails in gate-mode) instead of diffing
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/audit-to-stories/seed-epic-from-findings.js
|
|
3
3
|
*
|
|
4
|
-
* Build the `--idea`-shaped seed markdown that `/
|
|
4
|
+
* Build the `--idea`-shaped seed markdown that `/plan` Phase 1
|
|
5
5
|
* consumes when the operator picks the Single-Epic grouping mode.
|
|
6
6
|
*
|
|
7
7
|
* The seed renders the canonical one-pager sections so the idea-refinement
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Recommended Direction (rollup of recommendations by dimension)
|
|
11
11
|
* - Key Assumptions (carries the source-report links forward)
|
|
12
12
|
* - MVP Scope (the proposed Stories, one bullet per group)
|
|
13
|
-
* - Key Files (explicit file paths so /
|
|
13
|
+
* - Key Files (explicit file paths so /plan Phase 7 decompose
|
|
14
14
|
* has concrete anchors)
|
|
15
15
|
* - Not Doing (out-of-scope items by convention)
|
|
16
16
|
*
|
|
@@ -28,8 +28,8 @@ import { calculateAll, scanDirectory } from './maintainability-utils.js';
|
|
|
28
28
|
* baseline-snapshot.js — per-Epic baseline lifecycle helpers.
|
|
29
29
|
*
|
|
30
30
|
* Story #1396 (Epic #1386). The Epic-snapshot scheme freezes the maintainability
|
|
31
|
-
* and crap baselines at /
|
|
32
|
-
* at /
|
|
31
|
+
* and crap baselines at /plan time and reconciles them back to `main`
|
|
32
|
+
* at /deliver time. Two helpers, both pure-ish (deterministic given the
|
|
33
33
|
* working tree + injected I/O):
|
|
34
34
|
*
|
|
35
35
|
* - forkMainToEpic({ epicId, cwd }) — copies the tracked main baselines
|
|
@@ -37,20 +37,20 @@ import { calculateAll, scanDirectory } from './maintainability-utils.js';
|
|
|
37
37
|
* source content produces the same destination bytes (no fs churn). When
|
|
38
38
|
* the source baseline is missing, emits a warn through the injected
|
|
39
39
|
* logger and returns `{ written: false, reason: 'source-missing' }` for
|
|
40
|
-
* that file — callers (e.g. /
|
|
40
|
+
* that file — callers (e.g. /plan Phase 7) treat the absence as
|
|
41
41
|
* non-fatal and stay in `--full-scope` mode.
|
|
42
42
|
*
|
|
43
43
|
* - regenerateMainFromTree({ cwd }) — re-scores maintainability + crap
|
|
44
44
|
* against the current working tree and writes the result to the tracked
|
|
45
45
|
* main baseline paths. Returns `{ didChange, paths }` where `didChange`
|
|
46
46
|
* is true iff any baseline file's content differs from what's already on
|
|
47
|
-
* disk. Callers in /
|
|
47
|
+
* disk. Callers in /deliver use `didChange === false` to skip the
|
|
48
48
|
* `baseline-refresh: epic-<id>` commit.
|
|
49
49
|
*
|
|
50
50
|
* Lifecycle note (Story #1467): per-epic ratchet snapshots are ephemeral
|
|
51
51
|
* scratch state under the `temp/epic-<id>/baselines/` namespace, NOT committed
|
|
52
52
|
* artifacts. They inherit the existing per-epic temp-tree cleanup contract —
|
|
53
|
-
* `/
|
|
53
|
+
* `/deliver` reaps the parent `temp/epic-<id>/` directory on merge, so
|
|
54
54
|
* no manual prune is required. Earlier versions of this module wrote under
|
|
55
55
|
* `baselines/epic/<id>/`, which committed them to git and accumulated obsolete
|
|
56
56
|
* snapshots forever.
|
|
@@ -103,7 +103,7 @@ export function epicSnapshotPathFor({ epicId, kind, cwd = process.cwd() }) {
|
|
|
103
103
|
* - Source baseline missing → returned per-file `{ written: false,
|
|
104
104
|
* reason: 'source-missing' }`. Logger warn fires once per missing file.
|
|
105
105
|
* Caller stays in `--full-scope` mode.
|
|
106
|
-
* - Source unreadable / not parseable → throws. Re-running /
|
|
106
|
+
* - Source unreadable / not parseable → throws. Re-running /plan
|
|
107
107
|
* with `--force` after fixing the source recovers.
|
|
108
108
|
*
|
|
109
109
|
* @param {{
|
|
@@ -428,7 +428,7 @@ export function commitSnapshotsToEpicBranch({
|
|
|
428
428
|
* via `delivery.quality.gates.crap.coveragePath`. When coverage is missing and
|
|
429
429
|
* `requireCoverage` is true, the crap regeneration is skipped (didChange stays
|
|
430
430
|
* false for that file) and a warn is emitted — the operator is expected to run
|
|
431
|
-
* `npm run test:coverage` before /
|
|
431
|
+
* `npm run test:coverage` before /deliver if a refresh is anticipated.
|
|
432
432
|
*
|
|
433
433
|
* @param {{
|
|
434
434
|
* cwd?: string,
|
|
@@ -7,19 +7,22 @@
|
|
|
7
7
|
* `*` key and each component. Per-row denominators land later when the
|
|
8
8
|
* components resolver and per-component weighting (Story #1902, #1919)
|
|
9
9
|
* arrive; the rollup signature is stable so callers don't churn.
|
|
10
|
+
*
|
|
11
|
+
* Higher percentages are better. New paths land in the `additions` bucket
|
|
12
|
+
* (Story #2012 — partial coverage on a new file must never flip to a
|
|
13
|
+
* regression); removed paths inherit a perfect head row so any lower base
|
|
14
|
+
* registers as an improvement. Scaffold (sortRows / rollup / compare /
|
|
15
|
+
* applyEpsilon / mergeRows) is generated by `makeBaselineKind`
|
|
16
|
+
* (Story #3983).
|
|
10
17
|
*/
|
|
11
18
|
|
|
12
|
-
import { componentMatches } from '../component-matcher.js';
|
|
13
19
|
import { canonicalise } from '../path-canon.js';
|
|
14
|
-
import {
|
|
20
|
+
import { makeBaselineKind } from './kind-factory.js';
|
|
15
21
|
|
|
16
22
|
export const name = 'coverage';
|
|
17
23
|
export const keyField = 'path';
|
|
18
|
-
const KERNEL_VERSION = '1.0.0';
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
return KERNEL_VERSION;
|
|
22
|
-
}
|
|
25
|
+
const COV_AXES = ['lines', 'branches', 'functions'];
|
|
23
26
|
|
|
24
27
|
export function projectRow(row) {
|
|
25
28
|
return {
|
|
@@ -35,160 +38,41 @@ function roundPct(v) {
|
|
|
35
38
|
return Number(v.toFixed(2));
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
function meanOf(rows, axis) {
|
|
42
|
+
let sum = 0;
|
|
43
|
+
for (const r of rows) sum += r[axis] ?? 0;
|
|
44
|
+
return Number((sum / rows.length).toFixed(2));
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
function aggregate(rows) {
|
|
43
48
|
if (!rows || rows.length === 0) {
|
|
44
49
|
return { lines: 0, branches: 0, functions: 0 };
|
|
45
50
|
}
|
|
46
|
-
let l = 0;
|
|
47
|
-
let b = 0;
|
|
48
|
-
let f = 0;
|
|
49
|
-
for (const row of rows) {
|
|
50
|
-
l += row.lines ?? 0;
|
|
51
|
-
b += row.branches ?? 0;
|
|
52
|
-
f += row.functions ?? 0;
|
|
53
|
-
}
|
|
54
51
|
return {
|
|
55
|
-
lines:
|
|
56
|
-
branches:
|
|
57
|
-
functions:
|
|
52
|
+
lines: meanOf(rows, 'lines'),
|
|
53
|
+
branches: meanOf(rows, 'branches'),
|
|
54
|
+
functions: meanOf(rows, 'functions'),
|
|
58
55
|
};
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
export function rollup(rows, components = []) {
|
|
62
|
-
const out = { '*': aggregate(rows) };
|
|
63
|
-
for (const c of components ?? []) {
|
|
64
|
-
const matched = (rows ?? []).filter((r) => componentMatches(c, r.path));
|
|
65
|
-
out[c.name] = aggregate(matched);
|
|
66
|
-
}
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Pure compare(head, base) for the coverage kind. Diffs rows by `path`.
|
|
72
|
-
* Higher percentages are better — a row regresses if any axis (lines,
|
|
73
|
-
* branches, functions) drops vs base; improves if any axis rises with no
|
|
74
|
-
* axis dropping; unchanged otherwise. New paths (head has a row that
|
|
75
|
-
* base lacks) land in the `additions` bucket; absolute-floor enforcement
|
|
76
|
-
* is the unified `check-baselines` gate's job and runs independently.
|
|
77
|
-
* Removed paths inherit a head of 100% so any lower base registers as
|
|
78
|
-
* an improvement.
|
|
79
|
-
*
|
|
80
|
-
* Story #2012 — sibling fix to maintainability.compare. The prior
|
|
81
|
-
* behaviour treated new paths as base=100% on every axis, so any partial
|
|
82
|
-
* coverage on a new file flipped to a regression.
|
|
83
|
-
*
|
|
84
|
-
* No I/O. No process exit. No friction emission.
|
|
85
|
-
*/
|
|
86
|
-
const COV_AXES = ['lines', 'branches', 'functions'];
|
|
87
|
-
|
|
88
|
-
export function compare(head, base) {
|
|
89
|
-
const headRows = Array.isArray(head?.rows) ? head.rows : [];
|
|
90
|
-
const baseRows = Array.isArray(base?.rows) ? base.rows : [];
|
|
91
|
-
const baseByKey = new Map();
|
|
92
|
-
for (const r of baseRows) baseByKey.set(r.path, r);
|
|
93
|
-
const seen = new Set();
|
|
94
|
-
const regressions = [];
|
|
95
|
-
const improvements = [];
|
|
96
|
-
const unchanged = [];
|
|
97
|
-
const additions = [];
|
|
98
|
-
for (const h of headRows) {
|
|
99
|
-
seen.add(h.path);
|
|
100
|
-
const b = baseByKey.get(h.path);
|
|
101
|
-
if (!b) {
|
|
102
|
-
additions.push({ key: h.path, head: h, base: null });
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
classifyCoverage(regressions, improvements, unchanged, h.path, h, b);
|
|
106
|
-
}
|
|
107
|
-
for (const b of baseRows) {
|
|
108
|
-
if (seen.has(b.path)) continue;
|
|
109
|
-
const h = perfectCoverageRow(b.path);
|
|
110
|
-
classifyCoverage(regressions, improvements, unchanged, b.path, h, b);
|
|
111
|
-
}
|
|
112
|
-
return { regressions, improvements, unchanged, additions };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
58
|
function perfectCoverageRow(path) {
|
|
116
59
|
return { path, lines: 100, branches: 100, functions: 100 };
|
|
117
60
|
}
|
|
118
61
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
else unchanged.push({ key, head, base });
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Pure stabilizer for s-stability-epsilon (Story #1964). Folds sub-epsilon
|
|
141
|
-
* row deltas back to the prior bytes so env variance (e.g. ±0.05% jitter
|
|
142
|
-
* on a coverage axis) does not rewrite the on-disk baseline.
|
|
143
|
-
*
|
|
144
|
-
* For coverage, the comparison metric is the maximum |delta| across the
|
|
145
|
-
* three axes (lines, branches, functions). When the prior row exists and
|
|
146
|
-
* every axis delta is within `epsilon`, the prior row is returned
|
|
147
|
-
* verbatim; otherwise the regenerated row wins. Missing-prior rows always
|
|
148
|
-
* fall through to the regenerated row.
|
|
149
|
-
*
|
|
150
|
-
* No I/O. No mutation of inputs.
|
|
151
|
-
*
|
|
152
|
-
* @param {Array<{path: string, lines: number, branches: number, functions: number}>} prior
|
|
153
|
-
* @param {Array<{path: string, lines: number, branches: number, functions: number}>} regenerated
|
|
154
|
-
* @param {number} epsilon non-negative absolute tolerance (percentage points)
|
|
155
|
-
* @returns {Array<object>}
|
|
156
|
-
*/
|
|
157
|
-
export 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(r.path, r);
|
|
163
|
-
return regenRows.map((row) => {
|
|
164
|
-
const p = priorByKey.get(row.path);
|
|
165
|
-
if (!p) return row;
|
|
166
|
-
let maxAxisDelta = 0;
|
|
167
|
-
for (const axis of COV_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
|
-
/**
|
|
176
|
-
* Pure scope-aware merge for s-diff-scoped-writes (Story #1974). Coverage
|
|
177
|
-
* rows match by `path`. In diff mode, rows whose `path` is OUTSIDE
|
|
178
|
-
* `scope.files` are preserved from `prior` verbatim; in-scope rows come
|
|
179
|
-
* from `regenerated`. In full mode (or no scope), regenerated wins
|
|
180
|
-
* everywhere. Pure; downstream `sortRows` re-sorts before write.
|
|
181
|
-
*
|
|
182
|
-
* @param {Array<{path: string, lines: number, branches: number, functions: number}>} prior
|
|
183
|
-
* @param {Array<{path: string, lines: number, branches: number, functions: number}>} regenerated
|
|
184
|
-
* @param {{mode: 'full'|'diff', files: Set<string>}|null|undefined} scope
|
|
185
|
-
* @returns {Array<object>}
|
|
186
|
-
*/
|
|
187
|
-
export function mergeRows(prior, regenerated, scope) {
|
|
188
|
-
return mergeRowsByScope({
|
|
189
|
-
prior,
|
|
190
|
-
regenerated,
|
|
191
|
-
scope,
|
|
192
|
-
scopeKey: (row) => row.path,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
62
|
+
export const {
|
|
63
|
+
kernelVersion,
|
|
64
|
+
sortRows,
|
|
65
|
+
rollup,
|
|
66
|
+
compare,
|
|
67
|
+
applyEpsilon,
|
|
68
|
+
mergeRows,
|
|
69
|
+
} = makeBaselineKind({
|
|
70
|
+
keyField,
|
|
71
|
+
kernelVersion: '1.0.0',
|
|
72
|
+
axes: COV_AXES,
|
|
73
|
+
betterWhen: 'higher',
|
|
74
|
+
aggregate,
|
|
75
|
+
missingBasePolicy: 'addition',
|
|
76
|
+
removedRowPolicy: { kind: 'perfect-head' },
|
|
77
|
+
perfectRow: perfectCoverageRow,
|
|
78
|
+
});
|