mandrel 1.94.0 → 2.0.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 +59 -73
- package/.agents/agents/story-worker.md +12 -13
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +104 -138
- package/.agents/docs/execution-reference.md +17 -20
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +70 -81
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +6 -7
- package/.agents/rules/git-conventions.md +16 -22
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +54 -214
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/risk-verdict.schema.json +0 -13
- package/.agents/scripts/analyze-execution.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/check-lifecycle-lint.js +6 -9
- package/.agents/scripts/check-prepush-recovery.js +1 -1
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
- package/.agents/scripts/lib/baselines/kernel.js +206 -18
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
- package/.agents/scripts/lib/checks/index.js +2 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +16 -7
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +45 -29
- package/.agents/scripts/lib/config/explain.js +55 -36
- package/.agents/scripts/lib/config/limits.js +17 -58
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +1 -1
- package/.agents/scripts/lib/config/runners.js +17 -50
- package/.agents/scripts/lib/config/temp-paths.js +19 -14
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +2 -7
- package/.agents/scripts/lib/config-settings-schema-delivery.js +24 -148
- package/.agents/scripts/lib/config-settings-schema-quality.js +8 -14
- package/.agents/scripts/lib/config-settings-schema.js +52 -38
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +2 -2
- package/.agents/scripts/lib/duplicate-search.js +211 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +5 -5
- package/.agents/scripts/lib/framework-version.js +2 -3
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
- package/.agents/scripts/lib/git-utils.js +32 -6
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/label-constants.js +10 -23
- package/.agents/scripts/lib/label-taxonomy.js +9 -43
- package/.agents/scripts/lib/observability/active-story-env.js +1 -1
- package/.agents/scripts/lib/observability/hook-heartbeat.js +20 -52
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +8 -5
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +83 -20
- package/.agents/scripts/lib/orchestration/code-review.js +20 -268
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
- package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
- package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +13 -13
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
- package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
- package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
- package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
- package/.agents/scripts/lib/orchestration/ticket-validator.js +2 -7
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
- package/.agents/scripts/lib/planning-corpus.js +16 -11
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/story-adjacency.js +11 -14
- package/.agents/scripts/lib/story-body/story-body.js +124 -70
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
- package/.agents/scripts/lib/validation-evidence.js +1 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
- package/.agents/scripts/lint-issue-body.js +71 -40
- package/.agents/scripts/mandrel-update-preflight.js +1 -1
- package/.agents/scripts/notify.js +4 -3
- package/.agents/scripts/plan-context.js +64 -74
- package/.agents/scripts/plan-persist.js +121 -280
- package/.agents/scripts/plan-run-epilogue.js +97 -0
- package/.agents/scripts/providers/github/issues.js +17 -33
- package/.agents/scripts/providers/github/mappers.js +0 -12
- package/.agents/scripts/providers/github/tickets.js +2 -5
- package/.agents/scripts/resolve-plan-run.js +117 -0
- package/.agents/scripts/signals-view.js +24 -19
- package/.agents/scripts/single-story-close.js +11 -14
- package/.agents/scripts/single-story-confirm-merge.js +39 -23
- package/.agents/scripts/single-story-init.js +29 -20
- package/.agents/scripts/stories-wave-tick.js +6 -6
- package/.agents/scripts/story-plan.js +26 -47
- package/.agents/scripts/update-ticket-state.js +6 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +3 -43
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +3 -3
- package/.agents/workflows/audit-clean-code.md +3 -3
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-lighthouse.md +3 -3
- package/.agents/workflows/audit-navigability.md +3 -2
- package/.agents/workflows/audit-performance.md +3 -3
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +3 -3
- package/.agents/workflows/audit-security.md +3 -3
- package/.agents/workflows/audit-seo.md +3 -3
- package/.agents/workflows/audit-sre.md +3 -3
- package/.agents/workflows/audit-to-stories.md +20 -20
- package/.agents/workflows/audit-ux-ui.md +3 -3
- package/.agents/workflows/deliver.md +122 -174
- package/.agents/workflows/git-cleanup.md +3 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +16 -29
- package/.agents/workflows/helpers/code-review.md +70 -193
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
- package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
- package/.agents/workflows/mandrel-update.md +2 -1
- package/.agents/workflows/plan.md +112 -145
- package/.agents/workflows/qa-assist.md +24 -30
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -2
- package/README.md +9 -8
- package/docs/CHANGELOG.md +30 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +2 -2
- package/.agents/agents/retro.md +0 -42
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer.md +0 -33
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/security-engineer.md +0 -111
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
- package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
- package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/bookkeeping-reconcile.js +0 -117
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -852
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
- package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
- package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/slice-phase.js +0 -361
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -464
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
- package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
- package/.agents/workflows/helpers/deliver-epic.md +0 -998
- package/.agents/workflows/helpers/deliver-stories.md +0 -450
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -353
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* progress-reporter/transport.js — outbound I/O for the
|
|
3
|
-
* `/deliver` progress narrative.
|
|
4
|
-
*
|
|
5
|
-
* Extracted from the parent `progress-reporter.js` so the
|
|
6
|
-
* GitHub-comment posting surface (in `composition.js`) and the
|
|
7
|
-
* progress-signal aggregation (in `signals.js`) can be reasoned about
|
|
8
|
-
* independently of the webhook fan-out.
|
|
9
|
-
*
|
|
10
|
-
* Each `emit*` helper dispatches a curated event-shaped payload through
|
|
11
|
-
* the caller-supplied `notify` function. The `notify` contract is owned
|
|
12
|
-
* by `lib/notifications/*` and ultimately reaches the configured webhook
|
|
13
|
-
* via gh-exec (the retry/backoff loop lives there, not here — this
|
|
14
|
-
* module's role is the boundary). Failures from `notify` are swallowed
|
|
15
|
-
* by design: a flaky webhook URL must never crash the runner mid-wave.
|
|
16
|
-
*
|
|
17
|
-
* The webhook events emitted from this module are:
|
|
18
|
-
*
|
|
19
|
-
* - `epic-started` — fired once at /deliver kickoff
|
|
20
|
-
* - `epic-progress` — fired at wave boundaries / blocker transitions
|
|
21
|
-
* - `epic-blocked` — wave aggregated to blocked/failed outside halt path
|
|
22
|
-
* - `epic-unblocked` — operator flipped back to executing
|
|
23
|
-
*
|
|
24
|
-
* Each fire passes `skipComment: true` so the operator-facing GitHub
|
|
25
|
-
* comments stay owned by `composition.js` (`upsertEpicRunProgress` and
|
|
26
|
-
* `ProgressReporter.fire()`) — there is exactly one comment writer and
|
|
27
|
-
* exactly one webhook writer per event.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Webhook event name for the curated epic-progress rollup. Distinct from
|
|
32
|
-
* the `epic-run-progress` structured-comment kind in `signals.js` — the
|
|
33
|
-
* comment is the operator-facing per-poll snapshot on the Epic ticket,
|
|
34
|
-
* the webhook event is the coarse-grained rollup that fires at wave
|
|
35
|
-
* boundaries and after blocker transitions.
|
|
36
|
-
*/
|
|
37
|
-
export const EPIC_PROGRESS_EVENT = 'epic-progress';
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Fire a curated `epic-progress` webhook event. Event-driven only — called
|
|
41
|
-
* per recorder beat and after blocker raise/clear transitions. Carries the
|
|
42
|
-
* rollup payload `{ pct, done, total, phase, openBlockers }`, which Slack
|
|
43
|
-
* consumers and downstream subscribers use to track epic progress without
|
|
44
|
-
* subscribing to per-story chatter.
|
|
45
|
-
*
|
|
46
|
-
* The dispatch passes `skipComment: true` — the operator-facing GitHub
|
|
47
|
-
* comment is owned by `ProgressReporter.fire()` and `upsertEpicRunProgress`,
|
|
48
|
-
* not by this webhook fire.
|
|
49
|
-
*
|
|
50
|
-
* Failures are swallowed by design: the runner must keep moving even if
|
|
51
|
-
* the webhook URL is misconfigured or the network is flaky.
|
|
52
|
-
*
|
|
53
|
-
* Story #4155 — the Epic `/deliver` runtime cut over to the continuous
|
|
54
|
-
* ready-set scheduler, which has **no wave index**. The wave segment was
|
|
55
|
-
* dropped from the message and the rollup payload entirely (rather than
|
|
56
|
-
* rendered as `Wave undefined/undefined`); the sole live caller
|
|
57
|
-
* (`wave-record-notifications.js`) never supplied wave coordinates.
|
|
58
|
-
*
|
|
59
|
-
* @param {{
|
|
60
|
-
* notify: Function|null,
|
|
61
|
-
* epicId: number,
|
|
62
|
-
* done: number,
|
|
63
|
-
* total: number,
|
|
64
|
-
* phase?: string,
|
|
65
|
-
* openBlockers?: Array<{ reason: string, storyId?: number }>,
|
|
66
|
-
* logger?: { warn?: Function },
|
|
67
|
-
* }} args
|
|
68
|
-
* @returns {Promise<{ payload: object } | null>}
|
|
69
|
-
*/
|
|
70
|
-
export async function emitEpicProgress({
|
|
71
|
-
notify,
|
|
72
|
-
epicId,
|
|
73
|
-
done,
|
|
74
|
-
total,
|
|
75
|
-
phase,
|
|
76
|
-
openBlockers = [],
|
|
77
|
-
logger,
|
|
78
|
-
}) {
|
|
79
|
-
if (typeof notify !== 'function') return null;
|
|
80
|
-
const epicIdNum = Number(epicId);
|
|
81
|
-
if (!Number.isInteger(epicIdNum) || epicIdNum <= 0) return null;
|
|
82
|
-
const totalN = Math.max(0, Number(total) || 0);
|
|
83
|
-
const doneN = Math.max(0, Math.min(totalN, Number(done) || 0));
|
|
84
|
-
const pct = totalN === 0 ? 0 : Math.round((doneN / totalN) * 100);
|
|
85
|
-
const blockerCount = Array.isArray(openBlockers) ? openBlockers.length : 0;
|
|
86
|
-
const blockerSuffix =
|
|
87
|
-
blockerCount > 0
|
|
88
|
-
? ` · 🚧 ${blockerCount} blocker${blockerCount === 1 ? '' : 's'}`
|
|
89
|
-
: '';
|
|
90
|
-
const message = `Epic #${epicIdNum} progress · ${doneN}/${totalN} stories done (${pct}%)${blockerSuffix}`;
|
|
91
|
-
|
|
92
|
-
const payload = {
|
|
93
|
-
severity: blockerCount > 0 ? 'high' : 'medium',
|
|
94
|
-
message,
|
|
95
|
-
event: EPIC_PROGRESS_EVENT,
|
|
96
|
-
level: 'epic',
|
|
97
|
-
epicId: epicIdNum,
|
|
98
|
-
};
|
|
99
|
-
if (phase) payload.phase = phase;
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
await notify(epicIdNum, payload, { skipComment: true });
|
|
103
|
-
} catch (err) {
|
|
104
|
-
logger?.warn?.(
|
|
105
|
-
`[emitEpicProgress] notify dispatch failed (swallowed): ${err?.message ?? err}`,
|
|
106
|
-
);
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
payload: {
|
|
111
|
-
pct,
|
|
112
|
-
done: doneN,
|
|
113
|
-
total: totalN,
|
|
114
|
-
phase,
|
|
115
|
-
openBlockers: openBlockers ?? [],
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Fire a curated `epic-started` webhook event at /deliver kickoff.
|
|
122
|
-
* The Slack consumer anchors the rest of the epic narrative to this fire.
|
|
123
|
-
* Failures are swallowed.
|
|
124
|
-
*
|
|
125
|
-
* Story #4155 — the ready-set runtime has no wave count, so the wave
|
|
126
|
-
* segment was dropped from the message (rather than rendered as
|
|
127
|
-
* `undefined wave(s)`); the sole live caller never supplied one.
|
|
128
|
-
*/
|
|
129
|
-
export async function emitEpicStarted({
|
|
130
|
-
notify,
|
|
131
|
-
epicId,
|
|
132
|
-
totalStories,
|
|
133
|
-
title,
|
|
134
|
-
logger,
|
|
135
|
-
}) {
|
|
136
|
-
if (typeof notify !== 'function') return null;
|
|
137
|
-
const epicIdNum = Number(epicId);
|
|
138
|
-
if (!Number.isInteger(epicIdNum) || epicIdNum <= 0) return null;
|
|
139
|
-
const message = `Epic #${epicIdNum} started · ${totalStories} stor${totalStories === 1 ? 'y' : 'ies'}${title ? ` — ${title}` : ''}`;
|
|
140
|
-
try {
|
|
141
|
-
await notify(
|
|
142
|
-
epicIdNum,
|
|
143
|
-
{
|
|
144
|
-
severity: 'medium',
|
|
145
|
-
message,
|
|
146
|
-
event: 'epic-started',
|
|
147
|
-
level: 'epic',
|
|
148
|
-
epicId: epicIdNum,
|
|
149
|
-
},
|
|
150
|
-
{ skipComment: true },
|
|
151
|
-
);
|
|
152
|
-
} catch (err) {
|
|
153
|
-
logger?.warn?.(
|
|
154
|
-
`[emitEpicStarted] notify dispatch failed (swallowed): ${err?.message ?? err}`,
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Fire a curated `epic-blocked` webhook event when a wave aggregates to
|
|
162
|
-
* `blocked` or `failed` outside the `BlockerHandler.halt` code path (the
|
|
163
|
-
* /deliver host-LLM loop has no handler instance — it calls this
|
|
164
|
-
* helper directly from `epic-execute-record-wave.js`). The payload shape
|
|
165
|
-
* matches the inline emit in `BlockerHandler.halt` so downstream consumers
|
|
166
|
-
* see one canonical envelope regardless of which entry point fired.
|
|
167
|
-
* Failures are swallowed.
|
|
168
|
-
*/
|
|
169
|
-
export async function emitEpicBlocked({
|
|
170
|
-
notify,
|
|
171
|
-
epicId,
|
|
172
|
-
reason,
|
|
173
|
-
storyId,
|
|
174
|
-
logger,
|
|
175
|
-
}) {
|
|
176
|
-
if (typeof notify !== 'function') return null;
|
|
177
|
-
const epicIdNum = Number(epicId);
|
|
178
|
-
if (!Number.isInteger(epicIdNum) || epicIdNum <= 0) return null;
|
|
179
|
-
const storyPart = storyId ? ` (story #${storyId})` : '';
|
|
180
|
-
const message = `🚨 Action Required: Epic #${epicIdNum}${storyPart} blocked: ${reason}`;
|
|
181
|
-
try {
|
|
182
|
-
await notify(
|
|
183
|
-
epicIdNum,
|
|
184
|
-
{
|
|
185
|
-
severity: 'high',
|
|
186
|
-
message,
|
|
187
|
-
event: 'epic-blocked',
|
|
188
|
-
level: 'epic',
|
|
189
|
-
epicId: epicIdNum,
|
|
190
|
-
},
|
|
191
|
-
{ skipComment: true },
|
|
192
|
-
);
|
|
193
|
-
} catch (err) {
|
|
194
|
-
logger?.warn?.(
|
|
195
|
-
`[emitEpicBlocked] notify dispatch failed (swallowed): ${err?.message ?? err}`,
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Fire a curated `epic-unblocked` webhook event after the operator flips
|
|
203
|
-
* the Epic label back to `agent::executing`. Paired with `epic-blocked` so
|
|
204
|
-
* downstream consumers can track open-blocker lifecycle. Failures are
|
|
205
|
-
* swallowed.
|
|
206
|
-
*/
|
|
207
|
-
export async function emitEpicUnblocked({
|
|
208
|
-
notify,
|
|
209
|
-
epicId,
|
|
210
|
-
resolvedBlocker,
|
|
211
|
-
logger,
|
|
212
|
-
}) {
|
|
213
|
-
if (typeof notify !== 'function') return null;
|
|
214
|
-
const epicIdNum = Number(epicId);
|
|
215
|
-
if (!Number.isInteger(epicIdNum) || epicIdNum <= 0) return null;
|
|
216
|
-
const reasonPart = resolvedBlocker?.reason
|
|
217
|
-
? ` (${resolvedBlocker.reason})`
|
|
218
|
-
: '';
|
|
219
|
-
const message = `Epic #${epicIdNum} unblocked${reasonPart} · resuming.`;
|
|
220
|
-
try {
|
|
221
|
-
await notify(
|
|
222
|
-
epicIdNum,
|
|
223
|
-
{
|
|
224
|
-
severity: 'medium',
|
|
225
|
-
message,
|
|
226
|
-
event: 'epic-unblocked',
|
|
227
|
-
level: 'epic',
|
|
228
|
-
epicId: epicIdNum,
|
|
229
|
-
},
|
|
230
|
-
{ skipComment: true },
|
|
231
|
-
);
|
|
232
|
-
} catch (err) {
|
|
233
|
-
logger?.warn?.(
|
|
234
|
-
`[emitEpicUnblocked] notify dispatch failed (swallowed): ${err?.message ?? err}`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
return null;
|
|
238
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared bullet-formatting helpers for progress-signal detectors.
|
|
3
|
-
*
|
|
4
|
-
* Both crap-drift and maintainability-drift produce component-level
|
|
5
|
-
* regression bullets and need the same two pure utilities — hoisted here
|
|
6
|
-
* so they are maintained in one place (Story #3631, refs #3631).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Comparator that sorts the wildcard component `'*'` before all named
|
|
11
|
-
* components, with named components in locale order.
|
|
12
|
-
*
|
|
13
|
-
* @param {string} a
|
|
14
|
-
* @param {string} b
|
|
15
|
-
* @returns {number}
|
|
16
|
-
*/
|
|
17
|
-
export function componentOrder(a, b) {
|
|
18
|
-
if (a === '*') return -1;
|
|
19
|
-
if (b === '*') return 1;
|
|
20
|
-
return a.localeCompare(b);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Formats a numeric gate value for display: integers render without a
|
|
25
|
-
* decimal point; non-integers are fixed to two decimal places.
|
|
26
|
-
*
|
|
27
|
-
* @param {number} value
|
|
28
|
-
* @returns {string}
|
|
29
|
-
*/
|
|
30
|
-
export function formatNumber(value) {
|
|
31
|
-
return Number.isInteger(value) ? String(value) : value.toFixed(2);
|
|
32
|
-
}
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import nodeFs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { resolveComponents } from '../../../baselines/components.js';
|
|
4
|
-
import { componentOrder } from './_bullet-format.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Shared component-regression walk for the progress-signal drift detectors
|
|
8
|
-
* (Story #3984). `crap-drift.js` and `maintainability-drift.js` previously
|
|
9
|
-
* duplicated this baseline-rollup → per-component compare → bullet-list
|
|
10
|
-
* shape verbatim; the only divergence was the breach comparator (CRAP is
|
|
11
|
-
* "lower is better", maintainability is "higher is better") and the bullet
|
|
12
|
-
* text. Both detectors now parameterize this walk.
|
|
13
|
-
*
|
|
14
|
-
* Pure — the caller loads the baseline via `lib/baselines/reader.js#load(...)`
|
|
15
|
-
* and passes the resulting `{ rollup }` plus the gate config block.
|
|
16
|
-
*
|
|
17
|
-
* Only components whose rollup breaches the configured floor surface — a
|
|
18
|
-
* breach in a component-scoped floor does NOT report against `*` unless
|
|
19
|
-
* `*` itself breaches. This keeps the rollout narrowly targeted: when an
|
|
20
|
-
* operator wires up a per-component floor for `api`, regressing `api`
|
|
21
|
-
* names `api` — not `*`.
|
|
22
|
-
*
|
|
23
|
-
* @param {{
|
|
24
|
-
* rollup?: Record<string, Record<string, number>>,
|
|
25
|
-
* gateConfig?: { floors?: Record<string, Record<string, number>> } & object,
|
|
26
|
-
* }} params
|
|
27
|
-
* @param {{
|
|
28
|
-
* isBreach: (value: number, floor: number) => boolean,
|
|
29
|
-
* formatBullet: (name: string, axis: string, value: number, floor: number) => string,
|
|
30
|
-
* }} spec
|
|
31
|
-
* @returns {string[]}
|
|
32
|
-
*/
|
|
33
|
-
export function walkComponentRegressions(params = {}, spec) {
|
|
34
|
-
const rollup = params.rollup ?? {};
|
|
35
|
-
const gateConfig = params.gateConfig ?? {};
|
|
36
|
-
const floors = gateConfig.floors ?? {};
|
|
37
|
-
const components = resolveComponents(gateConfig);
|
|
38
|
-
const names = new Set([
|
|
39
|
-
...Object.keys(components),
|
|
40
|
-
...Object.keys(floors),
|
|
41
|
-
...Object.keys(rollup),
|
|
42
|
-
]);
|
|
43
|
-
const bullets = [];
|
|
44
|
-
for (const name of [...names].sort(componentOrder)) {
|
|
45
|
-
const aggregate = rollup[name];
|
|
46
|
-
if (!aggregate || typeof aggregate !== 'object') continue;
|
|
47
|
-
const floor = floors[name] ?? floors['*'];
|
|
48
|
-
if (!floor || typeof floor !== 'object') continue;
|
|
49
|
-
for (const axis of Object.keys(floor).sort()) {
|
|
50
|
-
const target = floor[axis];
|
|
51
|
-
const value = aggregate[axis];
|
|
52
|
-
if (typeof target !== 'number' || !Number.isFinite(target)) continue;
|
|
53
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) continue;
|
|
54
|
-
if (!spec.isBreach(value, target)) continue;
|
|
55
|
-
bullets.push(spec.formatBullet(name, axis, value, target));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return bullets;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Shared wave-start snapshot persistence for the drift detectors. Both
|
|
63
|
-
* detectors persist a `{ capturedAt, ...metadata, scores }` JSON document
|
|
64
|
-
* under `<cwd>/<baselineDir>/<filename>` so a resumed epic run can re-read
|
|
65
|
-
* the wave-start anchor rather than lose it, and both treat persistence as
|
|
66
|
-
* best-effort (the in-memory baseline still works when a write fails).
|
|
67
|
-
*
|
|
68
|
-
* @param {{
|
|
69
|
-
* fs: { readFileSync?: Function, writeFileSync?: Function, mkdirSync?: Function },
|
|
70
|
-
* baselinePath: string,
|
|
71
|
-
* metadata?: Record<string, unknown>,
|
|
72
|
-
* }} opts
|
|
73
|
-
*/
|
|
74
|
-
function createSnapshotStore({ fs, baselinePath, metadata = {} }) {
|
|
75
|
-
return {
|
|
76
|
-
persist(scores) {
|
|
77
|
-
if (!fs.writeFileSync) return;
|
|
78
|
-
try {
|
|
79
|
-
fs.mkdirSync?.(path.dirname(baselinePath), { recursive: true });
|
|
80
|
-
fs.writeFileSync(
|
|
81
|
-
baselinePath,
|
|
82
|
-
JSON.stringify(
|
|
83
|
-
{ capturedAt: new Date().toISOString(), ...metadata, scores },
|
|
84
|
-
null,
|
|
85
|
-
2,
|
|
86
|
-
),
|
|
87
|
-
);
|
|
88
|
-
} catch {
|
|
89
|
-
// persistence is best-effort; the in-memory baseline still works
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
load() {
|
|
94
|
-
if (!fs.readFileSync) return null;
|
|
95
|
-
try {
|
|
96
|
-
const raw = fs.readFileSync(baselinePath, 'utf-8');
|
|
97
|
-
const parsed = JSON.parse(raw);
|
|
98
|
-
return parsed?.scores ?? null;
|
|
99
|
-
} catch {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Shared per-file/per-method drift-detector skeleton for the progress-signal
|
|
108
|
-
* detectors (Story #4076). `crap-drift.js` and `maintainability-drift.js`
|
|
109
|
-
* previously duplicated the same `{ baselinePath, captureBaseline,
|
|
110
|
-
* loadBaseline, detect }` shape — identical snapshot-store wiring, identical
|
|
111
|
-
* "distinct from canonical baseline" framing, and identical
|
|
112
|
-
* swallow-and-warn resilience. The only divergence is the per-axis scoring
|
|
113
|
-
* (`scoreFile`), the snapshot value extracted from a score (`captureScore`),
|
|
114
|
-
* and the per-detect comparison that emits bullets (`detect`). Both detectors
|
|
115
|
-
* now supply those deltas to this factory, mirroring the "shared walker +
|
|
116
|
-
* per-axis delta" pattern already established by `walkComponentRegressions`.
|
|
117
|
-
*
|
|
118
|
-
* The factory owns:
|
|
119
|
-
*
|
|
120
|
-
* - `fs` / `cwd` / `files` resolution from `opts`.
|
|
121
|
-
* - The `<cwd>/<baselineDir>/<baselineFilename>` baseline path and the
|
|
122
|
-
* `createSnapshotStore` wiring (with caller-supplied `metadata`).
|
|
123
|
-
* - The in-memory `baseline` cache and the `captureBaseline` /
|
|
124
|
-
* `loadBaseline` lifecycle.
|
|
125
|
-
*
|
|
126
|
-
* The caller owns:
|
|
127
|
-
*
|
|
128
|
-
* - `scoreFile(relPath, ctx)` — returns the per-file score (any shape) or
|
|
129
|
-
* `null` when the file can't be scored. `ctx` is whatever
|
|
130
|
-
* `beforeScore()` returned for this pass (e.g. a coverage map), or
|
|
131
|
-
* `undefined` when `beforeScore` is omitted.
|
|
132
|
-
* - `captureScore(score)` — maps a `scoreFile` result to the value
|
|
133
|
-
* persisted in the snapshot (e.g. extract `crap` from each method row).
|
|
134
|
-
* - `detect({ baseline, scoreFile })` — async; returns the bullet list.
|
|
135
|
-
* Receives the loaded baseline and a `scoreFile(relPath)` bound to this
|
|
136
|
-
* pass's score context so the per-axis compare stays free of plumbing.
|
|
137
|
-
* - `beforeScore()` (optional) — runs once per `captureBaseline` / `detect`
|
|
138
|
-
* pass and returns the score context threaded into `scoreFile`.
|
|
139
|
-
*
|
|
140
|
-
* @param {{
|
|
141
|
-
* cwd?: string,
|
|
142
|
-
* files?: string[],
|
|
143
|
-
* fs?: { readFileSync?: Function, writeFileSync?: Function, mkdirSync?: Function, existsSync?: Function },
|
|
144
|
-
* baselineDir?: string,
|
|
145
|
-
* baselineFilename: string,
|
|
146
|
-
* metadata?: Record<string, unknown>,
|
|
147
|
-
* beforeScore?: () => unknown,
|
|
148
|
-
* scoreFile: (relPath: string, ctx: unknown) => unknown,
|
|
149
|
-
* captureScore: (score: unknown) => unknown,
|
|
150
|
-
* detect: (args: { baseline: Record<string, unknown>, scoreFile: (relPath: string) => unknown }) => Promise<string[]>,
|
|
151
|
-
* }} opts
|
|
152
|
-
*/
|
|
153
|
-
export function createDriftDetector(opts = {}) {
|
|
154
|
-
const fs = opts.fs ?? nodeFs;
|
|
155
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
156
|
-
const files = Array.isArray(opts.files) ? [...opts.files] : [];
|
|
157
|
-
const baselineDir = opts.baselineDir ?? '.agents/state';
|
|
158
|
-
const baselinePath = path.join(cwd, baselineDir, opts.baselineFilename);
|
|
159
|
-
const beforeScore = opts.beforeScore ?? (() => undefined);
|
|
160
|
-
const scoreFile = opts.scoreFile;
|
|
161
|
-
const captureScore = opts.captureScore;
|
|
162
|
-
const runDetect = opts.detect;
|
|
163
|
-
const store = createSnapshotStore({
|
|
164
|
-
fs,
|
|
165
|
-
baselinePath,
|
|
166
|
-
metadata: opts.metadata ?? {},
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
let baseline = null;
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
get baselinePath() {
|
|
173
|
-
return baselinePath;
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
captureBaseline() {
|
|
177
|
-
const ctx = beforeScore();
|
|
178
|
-
const snapshot = {};
|
|
179
|
-
for (const f of files) {
|
|
180
|
-
const score = scoreFile(f, ctx);
|
|
181
|
-
if (score == null) continue;
|
|
182
|
-
snapshot[f] = captureScore(score);
|
|
183
|
-
}
|
|
184
|
-
baseline = snapshot;
|
|
185
|
-
store.persist(snapshot);
|
|
186
|
-
return snapshot;
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
loadBaseline() {
|
|
190
|
-
baseline = store.load();
|
|
191
|
-
return baseline;
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
async detect() {
|
|
195
|
-
if (!baseline) return [];
|
|
196
|
-
const ctx = beforeScore();
|
|
197
|
-
return runDetect({
|
|
198
|
-
baseline,
|
|
199
|
-
scoreFile: (relPath) => scoreFile(relPath, ctx),
|
|
200
|
-
});
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
}
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import nodeFs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { loadCoverage as defaultLoadCoverage } from '../../../coverage-utils.js';
|
|
4
|
-
import { calculateCrapForSource } from '../../../crap-engine.js';
|
|
5
|
-
import { formatNumber } from './_bullet-format.js';
|
|
6
|
-
import {
|
|
7
|
-
createDriftDetector,
|
|
8
|
-
walkComponentRegressions,
|
|
9
|
-
} from './component-drift.js';
|
|
10
|
-
|
|
11
|
-
const DEFAULT_THRESHOLD = 5.0;
|
|
12
|
-
const DEFAULT_CEILING = 30;
|
|
13
|
-
// Distinct from the canonical ratchet baseline at `baselines/crap.json`
|
|
14
|
-
// (Epic #730 Story 5.5) — this is a per-wave drift SNAPSHOT, not the
|
|
15
|
-
// committed score baseline. Filename intentionally differs so a repo-wide
|
|
16
|
-
// grep for the canonical baseline no longer hits the snapshot.
|
|
17
|
-
const BASELINE_FILENAME = 'wave-crap-snapshot.json';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Normalise a repo-relative path for coverage-key lookup. Pure; exported
|
|
21
|
-
* for unit tests so the underlying branches don't have to be exercised
|
|
22
|
-
* through the closure-bound `findCoverageEntry`.
|
|
23
|
-
*/
|
|
24
|
-
export function normaliseCoveragePath(relPath) {
|
|
25
|
-
return String(relPath)
|
|
26
|
-
.replace(/\\/g, '/')
|
|
27
|
-
.replace(/^\.\/+/, '');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* True when a coverage map key matches a (normalised) relative path,
|
|
32
|
-
* either by full equality or by suffix-with-slash. Pure.
|
|
33
|
-
*/
|
|
34
|
-
export function coverageKeyMatches(key, suffix) {
|
|
35
|
-
const norm = String(key).replace(/\\/g, '/');
|
|
36
|
-
return norm === suffix || norm.endsWith(`/${suffix}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Detect per-component CRAP regressions from a baseline rollup against the
|
|
41
|
-
* gate's configured floors. Pure — the caller loads the baseline via
|
|
42
|
-
* `lib/baselines/reader.js#load('crap')` and passes the resulting
|
|
43
|
-
* `{ rollup }` plus the gate config block.
|
|
44
|
-
*
|
|
45
|
-
* Bullet shape (Task #1919, Epic #1786):
|
|
46
|
-
*
|
|
47
|
-
* 🧨 crap: <component> <axis> <value> > floor <floor>
|
|
48
|
-
*
|
|
49
|
-
* CRAP is a "lower is better" gate — every axis breach reports when
|
|
50
|
-
* `value > floor`. Component-scoped breaches do NOT trigger a `*` bullet
|
|
51
|
-
* unless `*` itself breaches. The walk itself is the shared
|
|
52
|
-
* `component-drift.js` helper (Story #3984).
|
|
53
|
-
*
|
|
54
|
-
* @param {{
|
|
55
|
-
* rollup?: Record<string, Record<string, number>>,
|
|
56
|
-
* gateConfig?: { floors?: Record<string, Record<string, number>> } & object,
|
|
57
|
-
* }} params
|
|
58
|
-
* @returns {string[]}
|
|
59
|
-
*/
|
|
60
|
-
export function detectComponentRegressions(params = {}) {
|
|
61
|
-
return walkComponentRegressions(params, {
|
|
62
|
-
isBreach: (value, cap) => value > cap,
|
|
63
|
-
formatBullet: (name, axis, value, cap) =>
|
|
64
|
-
`🧨 crap: ${name} ${axis} ${formatNumber(value)} > floor ${formatNumber(cap)}`,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Detects per-method CRAP drift versus a wave-start baseline.
|
|
70
|
-
*
|
|
71
|
-
* Mirrors the `maintainability-drift.js` contract but at per-method
|
|
72
|
-
* granularity: baseline snapshots are `{ "<file>::<method>@<startLine>": crap }`
|
|
73
|
-
* rather than a single score per file. A method surfaces as a Notable bullet
|
|
74
|
-
* when either condition holds:
|
|
75
|
-
*
|
|
76
|
-
* - **Crossed the ceiling** — current CRAP ≥ `ceiling` and baseline was
|
|
77
|
-
* either absent or below `ceiling`. Captures newly introduced hotspots
|
|
78
|
-
* and methods that tipped over the budget.
|
|
79
|
-
* - **Rose by ≥ threshold** — baseline present and `current - baseline ≥
|
|
80
|
-
* threshold`. Captures silent regressions that stay under the ceiling
|
|
81
|
-
* but are trending badly.
|
|
82
|
-
*
|
|
83
|
-
* Bullet shape:
|
|
84
|
-
*
|
|
85
|
-
* 🧨 CRAP drift: <file>::<method> <score> (ceiling <N>)
|
|
86
|
-
*
|
|
87
|
-
* Persistence: baseline is written to `<cwd>/<baselineDir>/wave-crap-snapshot.json`
|
|
88
|
-
* so a resumed epic run can reuse the wave-start anchor rather than lose it.
|
|
89
|
-
*
|
|
90
|
-
* Resilience: read / scoring errors for an individual file are swallowed (and
|
|
91
|
-
* optionally logged) so a single unscorable file cannot take the progress
|
|
92
|
-
* reporter down.
|
|
93
|
-
*
|
|
94
|
-
* @param {{
|
|
95
|
-
* cwd?: string,
|
|
96
|
-
* files?: string[],
|
|
97
|
-
* fs?: { readFileSync: Function, writeFileSync?: Function, mkdirSync?: Function, existsSync?: Function },
|
|
98
|
-
* calculate?: (source: string, coverageForFile: object|null) => Array<{ method: string, startLine: number, crap: number|null }>,
|
|
99
|
-
* loadCoverage?: (coveragePath: string) => object|null,
|
|
100
|
-
* coveragePath?: string | null,
|
|
101
|
-
* threshold?: number,
|
|
102
|
-
* ceiling?: number,
|
|
103
|
-
* baselineDir?: string,
|
|
104
|
-
* logger?: { warn?: Function },
|
|
105
|
-
* }} [opts]
|
|
106
|
-
*/
|
|
107
|
-
export function createCrapDriftDetector(opts = {}) {
|
|
108
|
-
const fs = opts.fs ?? nodeFs;
|
|
109
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
110
|
-
const files = Array.isArray(opts.files) ? [...opts.files] : [];
|
|
111
|
-
const calculate = opts.calculate ?? calculateCrapForSource;
|
|
112
|
-
const loadCoverage = opts.loadCoverage ?? defaultLoadCoverage;
|
|
113
|
-
const coveragePath = opts.coveragePath ?? null;
|
|
114
|
-
const threshold = Number.isFinite(opts.threshold)
|
|
115
|
-
? opts.threshold
|
|
116
|
-
: DEFAULT_THRESHOLD;
|
|
117
|
-
const ceiling = Number.isFinite(opts.ceiling)
|
|
118
|
-
? opts.ceiling
|
|
119
|
-
: DEFAULT_CEILING;
|
|
120
|
-
const logger = opts.logger ?? null;
|
|
121
|
-
|
|
122
|
-
function methodKey(method, startLine) {
|
|
123
|
-
return `${method}@${startLine}`;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Hoisted helpers — exported via `_internal` for unit coverage on each
|
|
127
|
-
// branch. Keeping them outside the closure keeps the orchestration
|
|
128
|
-
// method's complexity low.
|
|
129
|
-
// (see normaliseCoveragePath / coverageKeyMatches at module top.)
|
|
130
|
-
function findCoverageEntry(coverageMap, relPath) {
|
|
131
|
-
if (!coverageMap || typeof coverageMap !== 'object') return null;
|
|
132
|
-
const suffix = normaliseCoveragePath(relPath);
|
|
133
|
-
if (!suffix) return null;
|
|
134
|
-
for (const key of Object.keys(coverageMap)) {
|
|
135
|
-
if (coverageKeyMatches(key, suffix)) {
|
|
136
|
-
return coverageMap[key] ?? null;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function scoreFile(relPath, coverageMap) {
|
|
143
|
-
try {
|
|
144
|
-
const abs = path.join(cwd, relPath);
|
|
145
|
-
const src = fs.readFileSync(abs, 'utf-8');
|
|
146
|
-
const entry = findCoverageEntry(coverageMap, relPath);
|
|
147
|
-
const rows = calculate(src, entry) ?? [];
|
|
148
|
-
const methods = {};
|
|
149
|
-
for (const row of rows) {
|
|
150
|
-
if (!row || typeof row.method !== 'string') continue;
|
|
151
|
-
if (typeof row.startLine !== 'number') continue;
|
|
152
|
-
if (typeof row.crap !== 'number' || !Number.isFinite(row.crap))
|
|
153
|
-
continue;
|
|
154
|
-
methods[methodKey(row.method, row.startLine)] = {
|
|
155
|
-
method: row.method,
|
|
156
|
-
startLine: row.startLine,
|
|
157
|
-
crap: row.crap,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
return methods;
|
|
161
|
-
} catch (err) {
|
|
162
|
-
logger?.warn?.(
|
|
163
|
-
`[crap-drift] scoring failed for ${relPath}: ${err?.message ?? err}`,
|
|
164
|
-
);
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function readCoverageMap() {
|
|
170
|
-
if (!coveragePath) return null;
|
|
171
|
-
try {
|
|
172
|
-
return loadCoverage(coveragePath);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
logger?.warn?.(
|
|
175
|
-
`[crap-drift] coverage load failed (${coveragePath}): ${err?.message ?? err}`,
|
|
176
|
-
);
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return createDriftDetector({
|
|
182
|
-
fs,
|
|
183
|
-
cwd,
|
|
184
|
-
files: opts.files,
|
|
185
|
-
baselineDir: opts.baselineDir,
|
|
186
|
-
baselineFilename: BASELINE_FILENAME,
|
|
187
|
-
metadata: { ceiling, threshold },
|
|
188
|
-
beforeScore: readCoverageMap,
|
|
189
|
-
scoreFile,
|
|
190
|
-
captureScore: (methods) => {
|
|
191
|
-
const persisted = {};
|
|
192
|
-
for (const [key, row] of Object.entries(methods)) {
|
|
193
|
-
persisted[key] = row.crap;
|
|
194
|
-
}
|
|
195
|
-
return persisted;
|
|
196
|
-
},
|
|
197
|
-
async detect({ baseline, scoreFile: score }) {
|
|
198
|
-
const bullets = [];
|
|
199
|
-
// Union of files watched and files present in baseline, so methods that
|
|
200
|
-
// appeared since the snapshot (new files) are still checked, and methods
|
|
201
|
-
// that disappeared from baseline-only files don't get us stuck iterating
|
|
202
|
-
// anything spurious.
|
|
203
|
-
const fileSet = new Set([...files, ...Object.keys(baseline)]);
|
|
204
|
-
for (const relPath of fileSet) {
|
|
205
|
-
const currentMethods = score(relPath);
|
|
206
|
-
if (!currentMethods) continue;
|
|
207
|
-
const fileBaseline = baseline[relPath] ?? {};
|
|
208
|
-
for (const [key, row] of Object.entries(currentMethods)) {
|
|
209
|
-
const prev = fileBaseline[key];
|
|
210
|
-
const crossed =
|
|
211
|
-
row.crap >= ceiling &&
|
|
212
|
-
(prev === undefined || prev === null || prev < ceiling);
|
|
213
|
-
const rose =
|
|
214
|
-
typeof prev === 'number' &&
|
|
215
|
-
Number.isFinite(prev) &&
|
|
216
|
-
row.crap - prev >= threshold;
|
|
217
|
-
if (crossed || rose) {
|
|
218
|
-
bullets.push(
|
|
219
|
-
`🧨 CRAP drift: ${relPath}::${row.method} ${row.crap.toFixed(2)} (ceiling ${ceiling})`,
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
return bullets;
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
}
|