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
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
* Build the wave DAG from the Epic's open child Stories.
|
|
3
3
|
*
|
|
4
4
|
* `getSubTickets` returns the **direct** children of a parent ticket via
|
|
5
|
-
* native sub-issues + checklist links + body reverse-lookup
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* **grandchildren** of the Epic. We therefore walk one level deeper from
|
|
9
|
-
* each `type::feature` direct child to collect the real Story set, and
|
|
10
|
-
* union with any direct-child Stories (some Epics still carry Stories
|
|
11
|
-
* directly while migrating).
|
|
5
|
+
* native sub-issues + checklist links + body reverse-lookup. The
|
|
6
|
+
* canonical 2-tier hierarchy is Epic → Story, so the Epic's direct
|
|
7
|
+
* `type::story` children are the complete Story set.
|
|
12
8
|
*
|
|
13
9
|
* We additionally filter out closed Stories — `getSubTickets`'s reverse-
|
|
14
10
|
* reference search can surface closed-as-obsolete tickets whose body
|
|
@@ -19,14 +15,14 @@
|
|
|
19
15
|
* Throws if no open Stories are found.
|
|
20
16
|
*/
|
|
21
17
|
|
|
22
|
-
import { parseBlockedBy } from '../../../dependency-parser.js';
|
|
23
18
|
import { computeWaves } from '../../../Graph.js';
|
|
24
19
|
import { TYPE_LABELS } from '../../../label-constants.js';
|
|
20
|
+
import { buildStoryAdjacency } from '../../../story-adjacency.js';
|
|
25
21
|
import { WaveScheduler } from '../wave-scheduler.js';
|
|
26
22
|
|
|
27
23
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* Collect the Epic's direct `type::story` children and return the open
|
|
25
|
+
* ones, deduped by id.
|
|
30
26
|
*
|
|
31
27
|
* Exported so `snapshot.js#discoverStoryIds` and `epic-deliver-preflight`
|
|
32
28
|
* can share the same enumeration contract — the snapshot.end payload,
|
|
@@ -34,20 +30,9 @@ import { WaveScheduler } from '../wave-scheduler.js';
|
|
|
34
30
|
*/
|
|
35
31
|
export async function discoverOpenStories({ epicId, provider }) {
|
|
36
32
|
const descendants = (await provider.getSubTickets(epicId)) ?? [];
|
|
37
|
-
const features = descendants.filter((t) =>
|
|
38
|
-
(t.labels ?? []).includes(TYPE_LABELS.FEATURE),
|
|
39
|
-
);
|
|
40
|
-
const grandchildren = (
|
|
41
|
-
await Promise.all(
|
|
42
|
-
features.map(async (f) => {
|
|
43
|
-
const id = f.id ?? f.number;
|
|
44
|
-
return id == null ? [] : ((await provider.getSubTickets(id)) ?? []);
|
|
45
|
-
}),
|
|
46
|
-
)
|
|
47
|
-
).flat();
|
|
48
33
|
const seen = new Set();
|
|
49
34
|
const stories = [];
|
|
50
|
-
for (const t of
|
|
35
|
+
for (const t of descendants) {
|
|
51
36
|
const labels = t.labels ?? [];
|
|
52
37
|
if (!labels.includes(TYPE_LABELS.STORY)) continue;
|
|
53
38
|
const rawState = t.state ?? 'open';
|
|
@@ -116,30 +101,17 @@ function normalizeWavesForEmit(waves) {
|
|
|
116
101
|
* Convert an ordered list of story tickets into the adjacency/taskMap shape
|
|
117
102
|
* that `Graph.computeWaves()` expects.
|
|
118
103
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* object (present in fixture / test payloads; optional in live GitHub
|
|
125
|
-
* payloads).
|
|
126
|
-
* Only edges to other stories in this Epic are retained — foreign IDs are
|
|
127
|
-
* dropped so the DAG stays closed over the scheduled set.
|
|
104
|
+
* The adjacency comes from the shared story-level builder
|
|
105
|
+
* (`lib/story-adjacency.js#buildStoryAdjacency`), which owns the
|
|
106
|
+
* dependency-source ordering contract (body `blocked by` references via
|
|
107
|
+
* `parseBlockedBy`, then explicit `dependencies[]`) and drops foreign
|
|
108
|
+
* edges so the DAG stays closed over the scheduled set.
|
|
128
109
|
*/
|
|
129
110
|
function buildStoryDag(stories) {
|
|
130
|
-
const adjacency =
|
|
111
|
+
const adjacency = buildStoryAdjacency(stories);
|
|
131
112
|
const taskMap = new Map();
|
|
132
|
-
const storyIds = new Set(stories.map((s) => Number(s.id ?? s.number)));
|
|
133
113
|
for (const s of stories) {
|
|
134
114
|
const id = Number(s.id ?? s.number);
|
|
135
|
-
const fromBody = parseBlockedBy(s.body ?? '');
|
|
136
|
-
const fromField = Array.isArray(s.dependencies)
|
|
137
|
-
? s.dependencies.map(Number)
|
|
138
|
-
: [];
|
|
139
|
-
const merged = [...new Set([...fromBody, ...fromField])]
|
|
140
|
-
.map(Number)
|
|
141
|
-
.filter((dep) => dep !== id && storyIds.has(dep));
|
|
142
|
-
adjacency.set(id, merged);
|
|
143
115
|
taskMap.set(id, { ...s, id });
|
|
144
116
|
}
|
|
145
117
|
return { adjacency, taskMap };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Epic snapshot phase — fetch Epic ticket and enforce the acceptance-spec
|
|
3
3
|
* start gate.
|
|
4
4
|
*
|
|
5
|
-
* Auto-close is now the default for `/
|
|
5
|
+
* Auto-close is now the default for `/deliver` (the human PR-merge is
|
|
6
6
|
* the gate); no per-Epic label snapshot is required.
|
|
7
7
|
*
|
|
8
8
|
* Acceptance-spec start gate (relaxed): an Epic may be delivered when
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* `context::acceptance-spec` ticket is linked to the Epic. The ticket's
|
|
12
12
|
* GitHub state (open / closed) is **not** checked — presence is
|
|
13
13
|
* sufficient, matching the PRD and Tech Spec contract. The reviewer's
|
|
14
|
-
* OK during /
|
|
14
|
+
* OK during /plan Phase 7 is the approval signal, not a manual
|
|
15
15
|
* ticket-close action. This still refuses to launch Epics that skipped
|
|
16
|
-
* the /
|
|
16
|
+
* the /plan Phase 7 acceptance-spec authoring step (or didn't
|
|
17
17
|
* waive), surfacing the gap at delivery time rather than letting Story
|
|
18
18
|
* dispatch race ahead without a spec at all.
|
|
19
19
|
*/
|
|
@@ -66,8 +66,8 @@ export async function runSnapshotPhase(ctx, collaborators, state) {
|
|
|
66
66
|
/**
|
|
67
67
|
* Enumerate the Story IDs owned by an Epic. Delegates to
|
|
68
68
|
* `discoverOpenStories` so the snapshot.end payload and the wave DAG
|
|
69
|
-
* input set never disagree — both
|
|
70
|
-
* exclude closed reverse-referenced tickets.
|
|
69
|
+
* input set never disagree — both enumerate the Epic's direct Story
|
|
70
|
+
* children and exclude closed reverse-referenced tickets.
|
|
71
71
|
*
|
|
72
72
|
* Returns a sorted array of positive integers (sort order makes the
|
|
73
73
|
* ledger record deterministic across runs and platform iteration
|
|
@@ -82,7 +82,7 @@ async function discoverStoryIds({ epicId, provider }) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Refuse to launch /
|
|
85
|
+
* Refuse to launch /deliver when the acceptance-spec precondition has
|
|
86
86
|
* not been satisfied. Throws a clear `Error` (per
|
|
87
87
|
* orchestration-error-handling rule) so the `runAsCli` boundary maps it to
|
|
88
88
|
* `process.exit(1)` with the operator-visible message intact.
|
|
@@ -104,7 +104,7 @@ function assertAcceptanceSpecGate({ epic, epicId }) {
|
|
|
104
104
|
if (!acceptanceSpecId) {
|
|
105
105
|
throw new Error(
|
|
106
106
|
`[epic-deliver] Epic #${epicId} cannot launch: no context::acceptance-spec is linked and the acceptance::n-a waiver label is absent. ` +
|
|
107
|
-
'Run /
|
|
107
|
+
'Run /plan Phase 7 to author an acceptance-spec, or apply the acceptance::n-a label to the Epic to opt out.',
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -24,7 +24,7 @@ import { EPIC_RUN_PROGRESS_TYPE, STATE_EMOJI } from './signals.js';
|
|
|
24
24
|
* (`…`) when the string was longer. Returns the empty string for any
|
|
25
25
|
* falsy input so table cells never render `undefined`/`null`.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
function truncate(s, n) {
|
|
28
28
|
if (!s) return '';
|
|
29
29
|
return s.length > n ? `${s.slice(0, n - 1)}…` : s;
|
|
30
30
|
}
|
|
@@ -240,7 +240,7 @@ export async function renderProgressBody({
|
|
|
240
240
|
/**
|
|
241
241
|
* Render and upsert the rolled-up `epic-run-progress` comment on the Epic.
|
|
242
242
|
*
|
|
243
|
-
* Called by `/
|
|
243
|
+
* Called by `/deliver` Step 2b (`epic-execute-record-wave.js`) after
|
|
244
244
|
* each wave completes. The caller folds `state.waves[]` from the
|
|
245
245
|
* `epic-run-state` checkpoint into the per-wave rows and persists the
|
|
246
246
|
* unified rollup as a fenced-JSON payload on the Epic ticket via
|
|
@@ -272,7 +272,6 @@ export async function renderProgressBody({
|
|
|
272
272
|
* wave: number,
|
|
273
273
|
* concurrencyCap?: number,
|
|
274
274
|
* stories?: Array<{ id: number, title?: string, state?: string,
|
|
275
|
-
* tasksDone?: number, tasksTotal?: number,
|
|
276
275
|
* blockerCommentId?: string }>,
|
|
277
276
|
* }>,
|
|
278
277
|
* currentWave: number,
|
|
@@ -29,7 +29,7 @@ export const PHASE_TIMINGS_TYPE = 'phase-timings';
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Structured-comment kind for the per-Story run-progress snapshot that
|
|
32
|
-
* `/
|
|
32
|
+
* `/deliver` upserts on every Task transition. Read by
|
|
33
33
|
* ProgressReporter so the Epic-level table reflects sub-agent state in
|
|
34
34
|
* near-real time instead of label-derived classifications.
|
|
35
35
|
*/
|
|
@@ -91,7 +91,7 @@ export function phaseToState(phase) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Parse a `story-run-progress` structured comment posted by `/
|
|
94
|
+
* Parse a `story-run-progress` structured comment posted by `/deliver`.
|
|
95
95
|
* Returns `null` for any malformed body — the caller falls back to the
|
|
96
96
|
* ticket-label state derivation in that case.
|
|
97
97
|
*
|
|
@@ -100,7 +100,6 @@ export function phaseToState(phase) {
|
|
|
100
100
|
* storyId: number,
|
|
101
101
|
* branch?: string,
|
|
102
102
|
* phase: 'init'|'implementing'|'closing'|'blocked'|'done',
|
|
103
|
-
* tasks?: [{ id, title?, state, commitSha? }],
|
|
104
103
|
* title?: string,
|
|
105
104
|
* updatedAt?: string,
|
|
106
105
|
* }
|
|
@@ -109,16 +108,11 @@ export function parseStoryRunProgressComment(comment) {
|
|
|
109
108
|
const payload = parseFencedJsonComment(comment);
|
|
110
109
|
if (!payload || typeof payload !== 'object') return null;
|
|
111
110
|
const phase = typeof payload.phase === 'string' ? payload.phase : undefined;
|
|
112
|
-
const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];
|
|
113
|
-
const tasksTotal = tasks.length;
|
|
114
|
-
const tasksDone = tasks.filter((t) => t && t.state === 'done').length;
|
|
115
111
|
return {
|
|
116
112
|
storyId: Number(payload.storyId),
|
|
117
113
|
title: typeof payload.title === 'string' ? payload.title : '',
|
|
118
114
|
phase,
|
|
119
115
|
state: phaseToState(phase),
|
|
120
|
-
tasksDone,
|
|
121
|
-
tasksTotal,
|
|
122
116
|
};
|
|
123
117
|
}
|
|
124
118
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* progress-reporter/transport.js — outbound I/O for the
|
|
3
|
-
* `/
|
|
3
|
+
* `/deliver` progress narrative.
|
|
4
4
|
*
|
|
5
5
|
* Extracted from the parent `progress-reporter.js` so the
|
|
6
6
|
* GitHub-comment posting surface (in `composition.js`) and the
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* The webhook events emitted from this module are:
|
|
18
18
|
*
|
|
19
|
-
* - `epic-started` — fired once at /
|
|
19
|
+
* - `epic-started` — fired once at /deliver kickoff
|
|
20
20
|
* - `epic-progress` — fired at wave boundaries / blocker transitions
|
|
21
21
|
* - `epic-blocked` — wave aggregated to blocked/failed outside halt path
|
|
22
22
|
* - `epic-unblocked` — operator flipped back to executing
|
|
@@ -118,7 +118,7 @@ export async function emitEpicProgress({
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
* Fire a curated `epic-started` webhook event at /
|
|
121
|
+
* Fire a curated `epic-started` webhook event at /deliver kickoff.
|
|
122
122
|
* The Slack consumer anchors the rest of the epic narrative to this fire.
|
|
123
123
|
* Failures are swallowed.
|
|
124
124
|
*/
|
|
@@ -157,7 +157,7 @@ export async function emitEpicStarted({
|
|
|
157
157
|
/**
|
|
158
158
|
* Fire a curated `epic-blocked` webhook event when a wave aggregates to
|
|
159
159
|
* `blocked` or `failed` outside the `BlockerHandler.halt` code path (the
|
|
160
|
-
* /
|
|
160
|
+
* /deliver host-LLM loop has no handler instance — it calls this
|
|
161
161
|
* helper directly from `epic-execute-record-wave.js`). The payload shape
|
|
162
162
|
* matches the inline emit in `BlockerHandler.halt` so downstream consumers
|
|
163
163
|
* see one canonical envelope regardless of which entry point fired.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { resolveComponents } from '../../../baselines/components.js';
|
|
3
|
+
import { componentOrder } from './_bullet-format.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared component-regression walk for the progress-signal drift detectors
|
|
7
|
+
* (Story #3984). `crap-drift.js` and `maintainability-drift.js` previously
|
|
8
|
+
* duplicated this baseline-rollup → per-component compare → bullet-list
|
|
9
|
+
* shape verbatim; the only divergence was the breach comparator (CRAP is
|
|
10
|
+
* "lower is better", maintainability is "higher is better") and the bullet
|
|
11
|
+
* text. Both detectors now parameterize this walk.
|
|
12
|
+
*
|
|
13
|
+
* Pure — the caller loads the baseline via `lib/baselines/reader.js#load(...)`
|
|
14
|
+
* and passes the resulting `{ rollup }` plus the gate config block.
|
|
15
|
+
*
|
|
16
|
+
* Only components whose rollup breaches the configured floor surface — a
|
|
17
|
+
* breach in a component-scoped floor does NOT report against `*` unless
|
|
18
|
+
* `*` itself breaches. This keeps the rollout narrowly targeted: when an
|
|
19
|
+
* operator wires up a per-component floor for `api`, regressing `api`
|
|
20
|
+
* names `api` — not `*`.
|
|
21
|
+
*
|
|
22
|
+
* @param {{
|
|
23
|
+
* rollup?: Record<string, Record<string, number>>,
|
|
24
|
+
* gateConfig?: { floors?: Record<string, Record<string, number>> } & object,
|
|
25
|
+
* }} params
|
|
26
|
+
* @param {{
|
|
27
|
+
* isBreach: (value: number, floor: number) => boolean,
|
|
28
|
+
* formatBullet: (name: string, axis: string, value: number, floor: number) => string,
|
|
29
|
+
* }} spec
|
|
30
|
+
* @returns {string[]}
|
|
31
|
+
*/
|
|
32
|
+
export function walkComponentRegressions(params = {}, spec) {
|
|
33
|
+
const rollup = params.rollup ?? {};
|
|
34
|
+
const gateConfig = params.gateConfig ?? {};
|
|
35
|
+
const floors = gateConfig.floors ?? {};
|
|
36
|
+
const components = resolveComponents(gateConfig);
|
|
37
|
+
const names = new Set([
|
|
38
|
+
...Object.keys(components),
|
|
39
|
+
...Object.keys(floors),
|
|
40
|
+
...Object.keys(rollup),
|
|
41
|
+
]);
|
|
42
|
+
const bullets = [];
|
|
43
|
+
for (const name of [...names].sort(componentOrder)) {
|
|
44
|
+
const aggregate = rollup[name];
|
|
45
|
+
if (!aggregate || typeof aggregate !== 'object') continue;
|
|
46
|
+
const floor = floors[name] ?? floors['*'];
|
|
47
|
+
if (!floor || typeof floor !== 'object') continue;
|
|
48
|
+
for (const axis of Object.keys(floor).sort()) {
|
|
49
|
+
const target = floor[axis];
|
|
50
|
+
const value = aggregate[axis];
|
|
51
|
+
if (typeof target !== 'number' || !Number.isFinite(target)) continue;
|
|
52
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) continue;
|
|
53
|
+
if (!spec.isBreach(value, target)) continue;
|
|
54
|
+
bullets.push(spec.formatBullet(name, axis, value, target));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return bullets;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Shared wave-start snapshot persistence for the drift detectors. Both
|
|
62
|
+
* detectors persist a `{ capturedAt, ...metadata, scores }` JSON document
|
|
63
|
+
* under `<cwd>/<baselineDir>/<filename>` so a resumed epic run can re-read
|
|
64
|
+
* the wave-start anchor rather than lose it, and both treat persistence as
|
|
65
|
+
* best-effort (the in-memory baseline still works when a write fails).
|
|
66
|
+
*
|
|
67
|
+
* @param {{
|
|
68
|
+
* fs: { readFileSync?: Function, writeFileSync?: Function, mkdirSync?: Function },
|
|
69
|
+
* baselinePath: string,
|
|
70
|
+
* metadata?: Record<string, unknown>,
|
|
71
|
+
* }} opts
|
|
72
|
+
*/
|
|
73
|
+
export function createSnapshotStore({ fs, baselinePath, metadata = {} }) {
|
|
74
|
+
return {
|
|
75
|
+
persist(scores) {
|
|
76
|
+
if (!fs.writeFileSync) return;
|
|
77
|
+
try {
|
|
78
|
+
fs.mkdirSync?.(path.dirname(baselinePath), { recursive: true });
|
|
79
|
+
fs.writeFileSync(
|
|
80
|
+
baselinePath,
|
|
81
|
+
JSON.stringify(
|
|
82
|
+
{ capturedAt: new Date().toISOString(), ...metadata, scores },
|
|
83
|
+
null,
|
|
84
|
+
2,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
} catch {
|
|
88
|
+
// persistence is best-effort; the in-memory baseline still works
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
load() {
|
|
93
|
+
if (!fs.readFileSync) return null;
|
|
94
|
+
try {
|
|
95
|
+
const raw = fs.readFileSync(baselinePath, 'utf-8');
|
|
96
|
+
const parsed = JSON.parse(raw);
|
|
97
|
+
return parsed?.scores ?? null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import nodeFs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { resolveComponents } from '../../../baselines/components.js';
|
|
4
3
|
import { loadCoverage as defaultLoadCoverage } from '../../../coverage-utils.js';
|
|
5
4
|
import { calculateCrapForSource } from '../../../crap-engine.js';
|
|
6
|
-
import {
|
|
5
|
+
import { formatNumber } from './_bullet-format.js';
|
|
6
|
+
import {
|
|
7
|
+
createSnapshotStore,
|
|
8
|
+
walkComponentRegressions,
|
|
9
|
+
} from './component-drift.js';
|
|
7
10
|
|
|
8
11
|
const DEFAULT_THRESHOLD = 5.0;
|
|
9
12
|
const DEFAULT_CEILING = 30;
|
|
@@ -43,11 +46,10 @@ export function coverageKeyMatches(key, suffix) {
|
|
|
43
46
|
*
|
|
44
47
|
* 🧨 crap: <component> <axis> <value> > floor <floor>
|
|
45
48
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `*` itself breaches.
|
|
49
|
-
*
|
|
50
|
-
* names `api` — not `*`.
|
|
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).
|
|
51
53
|
*
|
|
52
54
|
* @param {{
|
|
53
55
|
* rollup?: Record<string, Record<string, number>>,
|
|
@@ -56,34 +58,11 @@ export function coverageKeyMatches(key, suffix) {
|
|
|
56
58
|
* @returns {string[]}
|
|
57
59
|
*/
|
|
58
60
|
export function detectComponentRegressions(params = {}) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
...Object.keys(components),
|
|
65
|
-
...Object.keys(floors),
|
|
66
|
-
...Object.keys(rollup),
|
|
67
|
-
]);
|
|
68
|
-
const bullets = [];
|
|
69
|
-
// CRAP is a "lower is better" gate — every axis under crap is ≤ floor.
|
|
70
|
-
for (const name of [...names].sort(componentOrder)) {
|
|
71
|
-
const aggregate = rollup[name];
|
|
72
|
-
if (!aggregate || typeof aggregate !== 'object') continue;
|
|
73
|
-
const floor = floors[name] ?? floors['*'];
|
|
74
|
-
if (!floor || typeof floor !== 'object') continue;
|
|
75
|
-
for (const axis of Object.keys(floor).sort()) {
|
|
76
|
-
const cap = floor[axis];
|
|
77
|
-
const value = aggregate[axis];
|
|
78
|
-
if (typeof cap !== 'number' || !Number.isFinite(cap)) continue;
|
|
79
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) continue;
|
|
80
|
-
if (value <= cap) continue;
|
|
81
|
-
bullets.push(
|
|
82
|
-
`🧨 crap: ${name} ${axis} ${formatNumber(value)} > floor ${formatNumber(cap)}`,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return bullets;
|
|
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
|
+
});
|
|
87
66
|
}
|
|
88
67
|
|
|
89
68
|
/**
|
|
@@ -141,6 +120,11 @@ export function createCrapDriftDetector(opts = {}) {
|
|
|
141
120
|
const baselineDir = opts.baselineDir ?? '.agents/state';
|
|
142
121
|
const baselinePath = path.join(cwd, baselineDir, BASELINE_FILENAME);
|
|
143
122
|
const logger = opts.logger ?? null;
|
|
123
|
+
const store = createSnapshotStore({
|
|
124
|
+
fs,
|
|
125
|
+
baselinePath,
|
|
126
|
+
metadata: { ceiling, threshold },
|
|
127
|
+
});
|
|
144
128
|
|
|
145
129
|
let baseline = null;
|
|
146
130
|
|
|
@@ -220,39 +204,13 @@ export function createCrapDriftDetector(opts = {}) {
|
|
|
220
204
|
}
|
|
221
205
|
}
|
|
222
206
|
baseline = snapshot;
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
fs.mkdirSync?.(path.dirname(baselinePath), { recursive: true });
|
|
226
|
-
fs.writeFileSync(
|
|
227
|
-
baselinePath,
|
|
228
|
-
JSON.stringify(
|
|
229
|
-
{
|
|
230
|
-
capturedAt: new Date().toISOString(),
|
|
231
|
-
ceiling,
|
|
232
|
-
threshold,
|
|
233
|
-
scores: snapshot,
|
|
234
|
-
},
|
|
235
|
-
null,
|
|
236
|
-
2,
|
|
237
|
-
),
|
|
238
|
-
);
|
|
239
|
-
} catch {
|
|
240
|
-
// persistence is best-effort; the in-memory baseline still works
|
|
241
|
-
}
|
|
242
|
-
}
|
|
207
|
+
store.persist(snapshot);
|
|
243
208
|
return snapshot;
|
|
244
209
|
},
|
|
245
210
|
|
|
246
211
|
loadBaseline() {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const raw = fs.readFileSync(baselinePath, 'utf-8');
|
|
250
|
-
const parsed = JSON.parse(raw);
|
|
251
|
-
baseline = parsed?.scores ?? null;
|
|
252
|
-
return baseline;
|
|
253
|
-
} catch {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
212
|
+
baseline = store.load();
|
|
213
|
+
return baseline;
|
|
256
214
|
},
|
|
257
215
|
|
|
258
216
|
async detect() {
|
package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import nodeFs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import { resolveComponents } from '../../../baselines/components.js';
|
|
5
4
|
import { calculateForSource } from '../../../maintainability-engine.js';
|
|
6
|
-
import {
|
|
5
|
+
import { formatNumber } from './_bullet-format.js';
|
|
6
|
+
import {
|
|
7
|
+
createSnapshotStore,
|
|
8
|
+
walkComponentRegressions,
|
|
9
|
+
} from './component-drift.js';
|
|
7
10
|
|
|
8
11
|
const DEFAULT_THRESHOLD = 2.0;
|
|
9
12
|
// Distinct from the canonical ratchet baseline at `baselines/maintainability.json`
|
|
@@ -12,6 +15,35 @@ const DEFAULT_THRESHOLD = 2.0;
|
|
|
12
15
|
// grep for the canonical baseline no longer hits the snapshot.
|
|
13
16
|
const BASELINE_FILENAME = 'wave-mi-snapshot.json';
|
|
14
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Detect per-component maintainability regressions from a baseline rollup
|
|
20
|
+
* against the gate's configured floors. Pure — the caller loads the
|
|
21
|
+
* baseline via `lib/baselines/reader.js#load('maintainability')` and passes
|
|
22
|
+
* the resulting `{ rollup }` plus the gate config block.
|
|
23
|
+
*
|
|
24
|
+
* Bullet shape (Task #1919, Epic #1786):
|
|
25
|
+
*
|
|
26
|
+
* 📉 maintainability: <component> <axis> <value> < floor <floor>
|
|
27
|
+
*
|
|
28
|
+
* Maintainability is "higher is better" — every axis breach reports when
|
|
29
|
+
* `value < floor`. Component-scoped breaches do NOT trigger a `*` bullet
|
|
30
|
+
* unless `*` itself breaches. The walk itself is the shared
|
|
31
|
+
* `component-drift.js` helper (Story #3984).
|
|
32
|
+
*
|
|
33
|
+
* @param {{
|
|
34
|
+
* rollup?: Record<string, Record<string, number>>,
|
|
35
|
+
* gateConfig?: { floors?: Record<string, Record<string, number>> } & object,
|
|
36
|
+
* }} params
|
|
37
|
+
* @returns {string[]}
|
|
38
|
+
*/
|
|
39
|
+
export function detectComponentRegressions(params = {}) {
|
|
40
|
+
return walkComponentRegressions(params, {
|
|
41
|
+
isBreach: (value, target) => value < target,
|
|
42
|
+
formatBullet: (name, axis, value, target) =>
|
|
43
|
+
`📉 maintainability: ${name} ${axis} ${formatNumber(value)} < floor ${formatNumber(target)}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
15
47
|
/**
|
|
16
48
|
* Detects per-file maintainability drop versus a wave-start baseline.
|
|
17
49
|
*
|
|
@@ -40,56 +72,6 @@ const BASELINE_FILENAME = 'wave-mi-snapshot.json';
|
|
|
40
72
|
* baselineDir?: string, // directory (under cwd) to persist snapshot
|
|
41
73
|
* }} [opts]
|
|
42
74
|
*/
|
|
43
|
-
/**
|
|
44
|
-
* Detect per-component maintainability regressions from a baseline rollup
|
|
45
|
-
* against the gate's configured floors. Pure — the caller loads the
|
|
46
|
-
* baseline via `lib/baselines/reader.js#load('maintainability')` and passes
|
|
47
|
-
* the resulting `{ rollup }` plus the gate config block.
|
|
48
|
-
*
|
|
49
|
-
* Bullet shape (Task #1919, Epic #1786):
|
|
50
|
-
*
|
|
51
|
-
* 📉 maintainability: <component> <axis> <value> < floor <floor>
|
|
52
|
-
*
|
|
53
|
-
* Maintainability is "higher is better" — every axis breach reports when
|
|
54
|
-
* `value < floor`. Component-scoped breaches do NOT trigger a `*` bullet
|
|
55
|
-
* unless `*` itself breaches.
|
|
56
|
-
*
|
|
57
|
-
* @param {{
|
|
58
|
-
* rollup?: Record<string, Record<string, number>>,
|
|
59
|
-
* gateConfig?: { floors?: Record<string, Record<string, number>> } & object,
|
|
60
|
-
* }} params
|
|
61
|
-
* @returns {string[]}
|
|
62
|
-
*/
|
|
63
|
-
export function detectComponentRegressions(params = {}) {
|
|
64
|
-
const rollup = params.rollup ?? {};
|
|
65
|
-
const gateConfig = params.gateConfig ?? {};
|
|
66
|
-
const floors = gateConfig.floors ?? {};
|
|
67
|
-
const components = resolveComponents(gateConfig);
|
|
68
|
-
const names = new Set([
|
|
69
|
-
...Object.keys(components),
|
|
70
|
-
...Object.keys(floors),
|
|
71
|
-
...Object.keys(rollup),
|
|
72
|
-
]);
|
|
73
|
-
const bullets = [];
|
|
74
|
-
for (const name of [...names].sort(componentOrder)) {
|
|
75
|
-
const aggregate = rollup[name];
|
|
76
|
-
if (!aggregate || typeof aggregate !== 'object') continue;
|
|
77
|
-
const floor = floors[name] ?? floors['*'];
|
|
78
|
-
if (!floor || typeof floor !== 'object') continue;
|
|
79
|
-
for (const axis of Object.keys(floor).sort()) {
|
|
80
|
-
const target = floor[axis];
|
|
81
|
-
const value = aggregate[axis];
|
|
82
|
-
if (typeof target !== 'number' || !Number.isFinite(target)) continue;
|
|
83
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) continue;
|
|
84
|
-
if (value >= target) continue;
|
|
85
|
-
bullets.push(
|
|
86
|
-
`📉 maintainability: ${name} ${axis} ${formatNumber(value)} < floor ${formatNumber(target)}`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return bullets;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
75
|
export function createMaintainabilityDriftDetector(opts = {}) {
|
|
94
76
|
const fs = opts.fs ?? nodeFs;
|
|
95
77
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -100,6 +82,7 @@ export function createMaintainabilityDriftDetector(opts = {}) {
|
|
|
100
82
|
: DEFAULT_THRESHOLD;
|
|
101
83
|
const baselineDir = opts.baselineDir ?? '.agents/state';
|
|
102
84
|
const baselinePath = path.join(cwd, baselineDir, BASELINE_FILENAME);
|
|
85
|
+
const store = createSnapshotStore({ fs, baselinePath });
|
|
103
86
|
|
|
104
87
|
let baseline = null;
|
|
105
88
|
|
|
@@ -126,34 +109,13 @@ export function createMaintainabilityDriftDetector(opts = {}) {
|
|
|
126
109
|
if (s != null) snapshot[f] = s;
|
|
127
110
|
}
|
|
128
111
|
baseline = snapshot;
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
fs.mkdirSync?.(path.dirname(baselinePath), { recursive: true });
|
|
132
|
-
fs.writeFileSync(
|
|
133
|
-
baselinePath,
|
|
134
|
-
JSON.stringify(
|
|
135
|
-
{ capturedAt: new Date().toISOString(), scores: snapshot },
|
|
136
|
-
null,
|
|
137
|
-
2,
|
|
138
|
-
),
|
|
139
|
-
);
|
|
140
|
-
} catch {
|
|
141
|
-
// persistence is best-effort; the in-memory baseline still works
|
|
142
|
-
}
|
|
143
|
-
}
|
|
112
|
+
store.persist(snapshot);
|
|
144
113
|
return snapshot;
|
|
145
114
|
},
|
|
146
115
|
|
|
147
116
|
loadBaseline() {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const raw = fs.readFileSync(baselinePath, 'utf-8');
|
|
151
|
-
const parsed = JSON.parse(raw);
|
|
152
|
-
baseline = parsed?.scores ?? null;
|
|
153
|
-
return baseline;
|
|
154
|
-
} catch {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
117
|
+
baseline = store.load();
|
|
118
|
+
return baseline;
|
|
157
119
|
},
|
|
158
120
|
|
|
159
121
|
async detect() {
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* After Story #908, in-session Agent-tool fan-out replaces the subprocess
|
|
6
6
|
* spawn pipeline. The launcher's primary responsibility is `planWave(stories)`:
|
|
7
7
|
* given a wave's Story tickets it returns a stable list of
|
|
8
|
-
* `{ storyId, worktree }` entries. The `/
|
|
8
|
+
* `{ storyId, worktree }` entries. The `/deliver` skill consumes that
|
|
9
9
|
* list (one wave at a time) to format one assistant turn containing N
|
|
10
10
|
* parallel `Agent` tool calls (subagent_type `general-purpose`), each of
|
|
11
|
-
* which drives `/
|
|
11
|
+
* which drives `/deliver <storyId>` for one Story.
|
|
12
12
|
*
|
|
13
13
|
* `launchWave(stories)` is a convenience for callers that already hold a
|
|
14
14
|
* concrete dispatch adapter (tests, future programmatic harnesses). It calls
|
|
@@ -46,7 +46,7 @@ export class StoryLauncher {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Produce the dispatch plan for a wave. Pure: no side effects, no IO. The
|
|
49
|
-
* caller (the `/
|
|
49
|
+
* caller (the `/deliver` skill's wave loop, or `launchWave` below)
|
|
50
50
|
* decides what to do with the plan.
|
|
51
51
|
*
|
|
52
52
|
* @param {Array<number|{id?:number,storyId?:number,number?:number}>} stories
|
|
@@ -78,7 +78,7 @@ export class StoryLauncher {
|
|
|
78
78
|
async launchWave(stories, signal) {
|
|
79
79
|
if (typeof this.dispatch !== 'function') {
|
|
80
80
|
throw new TypeError(
|
|
81
|
-
'StoryLauncher.launchWave requires a dispatch adapter (in-session Agent-tool fan-out is the responsibility of the /
|
|
81
|
+
'StoryLauncher.launchWave requires a dispatch adapter (in-session Agent-tool fan-out is the responsibility of the /deliver skill).',
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
const plan = this.planWave(stories);
|