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
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/orchestration/cascade-grouping.js — Cascade dispatch helpers.
|
|
3
|
-
*
|
|
4
|
-
* Pure helpers used by `cascadeCompletion` (see `./ticketing.js`) to
|
|
5
|
-
* partition a list of parent tickets into disjoint shared-ancestor groups
|
|
6
|
-
* and to buffer per-parent log output so parallel dispatch produces a
|
|
7
|
-
* byte-identical log stream to the serial baseline.
|
|
8
|
-
*
|
|
9
|
-
* Pulled out of `ticketing.js` so the cascade orchestrator stays under the
|
|
10
|
-
* project's per-file maintainability ceiling. No state is held at module
|
|
11
|
-
* scope — every helper takes its dependencies as arguments.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Walks `parent: #N` references upward from the given ticket id until no new
|
|
16
|
-
* ancestors are discovered. Returns the set of every ticket id reachable
|
|
17
|
-
* along the chain, including the starting id. Cycle-safe by construction —
|
|
18
|
-
* the visited set acts as the seen guard, so a cyclic `parent: #N` graph
|
|
19
|
-
* terminates in finite steps without revisiting nodes.
|
|
20
|
-
*
|
|
21
|
-
* Pure of side effects beyond the provider reads it issues. Provider
|
|
22
|
-
* failures on a single hop fall back to "no further ancestors discovered"
|
|
23
|
-
* for that branch (the chain truncates rather than throwing); this matches
|
|
24
|
-
* `cascadeCompletion`'s tolerant posture toward transient reads.
|
|
25
|
-
*
|
|
26
|
-
* @param {import('../ITicketingProvider.js').ITicketingProvider} provider
|
|
27
|
-
* @param {number} startId
|
|
28
|
-
* @param {Map<number, Set<number>>} [cache] - Optional per-call cache of
|
|
29
|
-
* already-walked chains keyed by intermediate id. Reused across parents
|
|
30
|
-
* in {@link groupByAncestor} to amortise repeat walks.
|
|
31
|
-
* @returns {Promise<Set<number>>} ancestor set including `startId`.
|
|
32
|
-
*/
|
|
33
|
-
export async function walkAncestorChain(provider, startId, cache) {
|
|
34
|
-
// Inner DFS with memoisation. `inProgress` guards cycles so a cyclic
|
|
35
|
-
// `parent: #N` graph terminates without recursion depth issues. Each
|
|
36
|
-
// visited node gets its own cache entry holding the set of ids reachable
|
|
37
|
-
// from it (inclusive), so a sibling walk that re-enters this subgraph
|
|
38
|
-
// can splice the cached set wholesale instead of re-reading the provider.
|
|
39
|
-
async function visit(id, inProgress) {
|
|
40
|
-
if (cache?.has(id)) return cache.get(id);
|
|
41
|
-
if (inProgress.has(id)) {
|
|
42
|
-
// Cycle: return a singleton so the caller still includes `id` in its
|
|
43
|
-
// ancestor set without recursing further through this loop. Do NOT
|
|
44
|
-
// memoise — the partial result is incomplete for `id`'s true chain.
|
|
45
|
-
return new Set([id]);
|
|
46
|
-
}
|
|
47
|
-
inProgress.add(id);
|
|
48
|
-
|
|
49
|
-
const set = new Set([id]);
|
|
50
|
-
let ticket = null;
|
|
51
|
-
try {
|
|
52
|
-
ticket = await provider.getTicket(id);
|
|
53
|
-
} catch {
|
|
54
|
-
// Provider read failure: truncate the chain branch. Memoise as the
|
|
55
|
-
// singleton so subsequent walks don't retry an already-failed read.
|
|
56
|
-
inProgress.delete(id);
|
|
57
|
-
cache?.set(id, set);
|
|
58
|
-
return set;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (ticket?.body) {
|
|
62
|
-
const matches = [...ticket.body.matchAll(/parent:\s*#(\d+)/gi)];
|
|
63
|
-
for (const m of matches) {
|
|
64
|
-
const next = Number.parseInt(m[1], 10);
|
|
65
|
-
if (!Number.isFinite(next)) continue;
|
|
66
|
-
const subset = await visit(next, inProgress);
|
|
67
|
-
for (const v of subset) set.add(v);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
inProgress.delete(id);
|
|
72
|
-
cache?.set(id, set);
|
|
73
|
-
return set;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return visit(startId, new Set());
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Partitions a list of parent ids into disjoint groups whose members share
|
|
81
|
-
* at least one ancestor (transitively, via `parent: #N` references walked
|
|
82
|
-
* to fixpoint).
|
|
83
|
-
*
|
|
84
|
-
* Two parents end up in the same group if and only if their ancestor sets
|
|
85
|
-
* overlap on at least one ticket id. Parents with no shared ancestors end
|
|
86
|
-
* up in singleton groups. The union of the returned groups equals the
|
|
87
|
-
* input set; the order of `parents[]` is preserved within each group, and
|
|
88
|
-
* groups are returned in the order their first member appears in the
|
|
89
|
-
* input.
|
|
90
|
-
*
|
|
91
|
-
* Pure of side effects beyond the provider reads needed to walk chains.
|
|
92
|
-
* Walked ancestor sets are cached per call so a parent that contributes
|
|
93
|
-
* to multiple groups is not re-walked. Cycle-safe — see
|
|
94
|
-
* {@link walkAncestorChain}.
|
|
95
|
-
*
|
|
96
|
-
* Used by `cascadeCompletion` to dispatch disjoint groups in parallel
|
|
97
|
-
* while keeping shared-ancestor groups strictly sequential (concurrent
|
|
98
|
-
* transitions on the same ancestor would race the "all children done?"
|
|
99
|
-
* check).
|
|
100
|
-
*
|
|
101
|
-
* @param {Array<number>} parents
|
|
102
|
-
* @param {import('../ITicketingProvider.js').ITicketingProvider} provider
|
|
103
|
-
* @returns {Promise<Array<Array<number>>>} disjoint groups of parent ids.
|
|
104
|
-
*/
|
|
105
|
-
export async function groupByAncestor(parents, provider) {
|
|
106
|
-
if (!Array.isArray(parents) || parents.length === 0) return [];
|
|
107
|
-
|
|
108
|
-
// Walk each parent's ancestor chain once, sharing a cache so a parent
|
|
109
|
-
// that re-enters an already-walked subgraph reuses the cached set.
|
|
110
|
-
const cache = new Map();
|
|
111
|
-
const ancestorsByParent = new Map();
|
|
112
|
-
for (const parentId of parents) {
|
|
113
|
-
const chain = await walkAncestorChain(provider, parentId, cache);
|
|
114
|
-
ancestorsByParent.set(parentId, chain);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return unionFindByAncestor(parents, ancestorsByParent);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Union-Find over `parents`, joined whenever any two parents' ancestor
|
|
122
|
-
* chains overlap on at least one id. Returns the parents bucketed by
|
|
123
|
-
* representative, in input order both for the groups themselves and for
|
|
124
|
-
* members within each group.
|
|
125
|
-
*
|
|
126
|
-
* Pulled out of {@link groupByAncestor} to keep that function's CRAP
|
|
127
|
-
* under the v6 ceiling.
|
|
128
|
-
*
|
|
129
|
-
* @param {Array<number>} parents
|
|
130
|
-
* @param {Map<number, Set<number>>} ancestorsByParent
|
|
131
|
-
* @returns {Array<Array<number>>}
|
|
132
|
-
*/
|
|
133
|
-
function unionFindByAncestor(parents, ancestorsByParent) {
|
|
134
|
-
const parentIndex = new Map();
|
|
135
|
-
parents.forEach((p, i) => {
|
|
136
|
-
parentIndex.set(p, i);
|
|
137
|
-
});
|
|
138
|
-
const uf = parents.map((_, i) => i);
|
|
139
|
-
const find = (i) => {
|
|
140
|
-
while (uf[i] !== i) {
|
|
141
|
-
uf[i] = uf[uf[i]];
|
|
142
|
-
i = uf[i];
|
|
143
|
-
}
|
|
144
|
-
return i;
|
|
145
|
-
};
|
|
146
|
-
const union = (a, b) => {
|
|
147
|
-
const ra = find(a);
|
|
148
|
-
const rb = find(b);
|
|
149
|
-
if (ra !== rb) uf[ra] = rb;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
// For each ancestor id, collect parents whose chain hits it; union them.
|
|
153
|
-
const ancestorToParents = new Map();
|
|
154
|
-
for (const [parentId, chain] of ancestorsByParent) {
|
|
155
|
-
for (const ancestorId of chain) {
|
|
156
|
-
if (!ancestorToParents.has(ancestorId)) {
|
|
157
|
-
ancestorToParents.set(ancestorId, []);
|
|
158
|
-
}
|
|
159
|
-
ancestorToParents.get(ancestorId).push(parentId);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
for (const sharing of ancestorToParents.values()) {
|
|
163
|
-
if (sharing.length < 2) continue;
|
|
164
|
-
const first = parentIndex.get(sharing[0]);
|
|
165
|
-
for (let i = 1; i < sharing.length; i++) {
|
|
166
|
-
union(first, parentIndex.get(sharing[i]));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Bucket parents by representative, preserving first-seen order for
|
|
171
|
-
// both groups and within-group ordering.
|
|
172
|
-
const repToGroup = new Map();
|
|
173
|
-
const groupOrder = [];
|
|
174
|
-
for (const parentId of parents) {
|
|
175
|
-
const rep = find(parentIndex.get(parentId));
|
|
176
|
-
if (!repToGroup.has(rep)) {
|
|
177
|
-
repToGroup.set(rep, []);
|
|
178
|
-
groupOrder.push(rep);
|
|
179
|
-
}
|
|
180
|
-
repToGroup.get(rep).push(parentId);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return groupOrder.map((rep) => repToGroup.get(rep));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Dispatches cascade work across disjoint shared-ancestor groups in
|
|
188
|
-
* parallel while running each within-group parent sequentially in input
|
|
189
|
-
* order. Per-parent output is captured into a buffered logger so the
|
|
190
|
-
* visible log stream is byte-identical to a serial baseline; the buffer
|
|
191
|
-
* is flushed to `flushLogger` in the original `parsedParents` order
|
|
192
|
-
* after every group resolves.
|
|
193
|
-
*
|
|
194
|
-
* The actual per-parent work is supplied by `processParent` so this
|
|
195
|
-
* helper stays free of cascade-specific dependencies — its only job is
|
|
196
|
-
* the parallel-dispatch + ordered-flush scaffolding.
|
|
197
|
-
*
|
|
198
|
-
* @template R
|
|
199
|
-
* @param {Object} args
|
|
200
|
-
* @param {Array<number>} args.parsedParents - Parent ids in their
|
|
201
|
-
* original input order. Drives both the group-membership lookup and
|
|
202
|
-
* the post-dispatch log flush order.
|
|
203
|
-
* @param {Array<Array<number>>} args.groups - Disjoint groups returned
|
|
204
|
-
* by {@link groupByAncestor}.
|
|
205
|
-
* @param {(parentId: number, bufferedLogger: object) => Promise<R>} args.processParent
|
|
206
|
-
* Per-parent worker. Receives the parent id and a buffered logger.
|
|
207
|
-
* Its resolved value is collected into `args.parsedParents`-ordered
|
|
208
|
-
* results.
|
|
209
|
-
* @param {{ debug: Function, info: Function, warn: Function, error: Function }} args.flushLogger
|
|
210
|
-
* Real logger that receives the buffered output after dispatch.
|
|
211
|
-
* @returns {Promise<Array<R>>} per-parent results in `parsedParents`
|
|
212
|
-
* order.
|
|
213
|
-
*/
|
|
214
|
-
export async function dispatchCascadeGroups({
|
|
215
|
-
parsedParents,
|
|
216
|
-
groups,
|
|
217
|
-
processParent,
|
|
218
|
-
flushLogger,
|
|
219
|
-
}) {
|
|
220
|
-
const parentLoggers = new Map();
|
|
221
|
-
const parentResults = new Map();
|
|
222
|
-
for (const parentId of parsedParents) {
|
|
223
|
-
parentLoggers.set(parentId, createBufferedLogger());
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
await Promise.all(
|
|
227
|
-
groups.map(async (group) => {
|
|
228
|
-
for (const parentId of group) {
|
|
229
|
-
const logger = parentLoggers.get(parentId);
|
|
230
|
-
const result = await processParent(parentId, logger);
|
|
231
|
-
parentResults.set(parentId, result);
|
|
232
|
-
}
|
|
233
|
-
}),
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
const results = [];
|
|
237
|
-
for (const parentId of parsedParents) {
|
|
238
|
-
const lg = parentLoggers.get(parentId);
|
|
239
|
-
if (lg) {
|
|
240
|
-
for (const entry of lg.buffer) {
|
|
241
|
-
flushLogger[entry.level](entry.message);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
const result = parentResults.get(parentId);
|
|
245
|
-
if (result !== undefined) results.push(result);
|
|
246
|
-
}
|
|
247
|
-
return results;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Buffered logger shaped like the public `Logger` surface. Stores every
|
|
252
|
-
* emitted line in `buffer[]` instead of writing to the console. Callers
|
|
253
|
-
* flush the buffer to a real logger after the buffered region completes
|
|
254
|
-
* so the visible log output is byte-identical to a serial run.
|
|
255
|
-
*
|
|
256
|
-
* @returns {{ buffer: Array<{ level: 'debug'|'info'|'warn'|'error', message: string }>, debug: Function, info: Function, warn: Function, error: Function }}
|
|
257
|
-
*/
|
|
258
|
-
export function createBufferedLogger() {
|
|
259
|
-
const buffer = [];
|
|
260
|
-
return {
|
|
261
|
-
buffer,
|
|
262
|
-
debug(message) {
|
|
263
|
-
buffer.push({ level: 'debug', message });
|
|
264
|
-
},
|
|
265
|
-
info(message) {
|
|
266
|
-
buffer.push({ level: 'info', message });
|
|
267
|
-
},
|
|
268
|
-
warn(message) {
|
|
269
|
-
buffer.push({ level: 'warn', message });
|
|
270
|
-
},
|
|
271
|
-
error(message) {
|
|
272
|
-
buffer.push({ level: 'error', message });
|
|
273
|
-
},
|
|
274
|
-
};
|
|
275
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* progress-reporter.js — facade module for the /epic-deliver progress
|
|
3
|
-
* narrative. Story #1847 split the original 1158-LOC monolith into three
|
|
4
|
-
* sibling sub-modules under `progress-reporter/`:
|
|
5
|
-
*
|
|
6
|
-
* - `composition.js` — structured-comment body builders and the pure
|
|
7
|
-
* rendering helpers (the legacy ProgressReporter class used these).
|
|
8
|
-
* - `transport.js` — the curated webhook emit surface (epic-started,
|
|
9
|
-
* epic-progress, epic-blocked, epic-unblocked).
|
|
10
|
-
* - `signals.js` — pure parse/aggregate over `story-run-progress` and
|
|
11
|
-
* `phase-timings` structured comments + the shared state lookup
|
|
12
|
-
* tables (PHASE_TO_STATE, PHASE_ORDER, STATE_EMOJI).
|
|
13
|
-
*
|
|
14
|
-
* Epic #2646 Story C (Task #2699) — the tick-based polling
|
|
15
|
-
* `ProgressReporter` class that used to live here was deleted in favour
|
|
16
|
-
* of the bus-driven `lifecycle/listeners/progress-reporter.js` which
|
|
17
|
-
* already consumes `story.dispatch.end` + `wave.end` to compose the
|
|
18
|
-
* `epic-run-progress` body. The webhook helpers and parse/aggregate
|
|
19
|
-
* exports remain at this path so existing importers
|
|
20
|
-
* (`epic-execute-record-wave.js`, `wave-record-notifications.js`,
|
|
21
|
-
* `crap-remediation-1641.test.js`) keep resolving — only the periodic
|
|
22
|
-
* emission shell went away.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
deriveState as deriveStateFromComposition,
|
|
27
|
-
renderProgressBody as renderProgressBodyFromComposition,
|
|
28
|
-
truncate as truncateFromComposition,
|
|
29
|
-
upsertEpicRunProgress as upsertEpicRunProgressFromComposition,
|
|
30
|
-
} from './progress-reporter/composition.js';
|
|
31
|
-
import {
|
|
32
|
-
aggregatePhaseTimings as aggregatePhaseTimingsFromSignals,
|
|
33
|
-
EPIC_RUN_PROGRESS_TYPE as EPIC_RUN_PROGRESS_TYPE_FROM_SIGNALS,
|
|
34
|
-
PHASE_TIMINGS_TYPE as PHASE_TIMINGS_TYPE_FROM_SIGNALS,
|
|
35
|
-
parsePhaseTimingsComment as parsePhaseTimingsCommentFromSignals,
|
|
36
|
-
parseStoryRunProgressComment as parseStoryRunProgressCommentFromSignals,
|
|
37
|
-
phaseToState as phaseToStateFromSignals,
|
|
38
|
-
renderPhaseTimingsSection as renderPhaseTimingsSectionFromSignals,
|
|
39
|
-
STORY_RUN_PROGRESS_TYPE as STORY_RUN_PROGRESS_TYPE_FROM_SIGNALS,
|
|
40
|
-
} from './progress-reporter/signals.js';
|
|
41
|
-
import {
|
|
42
|
-
EPIC_PROGRESS_EVENT as EPIC_PROGRESS_EVENT_FROM_TRANSPORT,
|
|
43
|
-
emitEpicBlocked as emitEpicBlockedFromTransport,
|
|
44
|
-
emitEpicProgress as emitEpicProgressFromTransport,
|
|
45
|
-
emitEpicStarted as emitEpicStartedFromTransport,
|
|
46
|
-
emitEpicUnblocked as emitEpicUnblockedFromTransport,
|
|
47
|
-
} from './progress-reporter/transport.js';
|
|
48
|
-
|
|
49
|
-
// Re-exports — sub-module surfaces are aliased back to the parent path so
|
|
50
|
-
// existing imports (epic-execute-record-wave.js,
|
|
51
|
-
// wave-record-notifications.js) keep resolving.
|
|
52
|
-
export const EPIC_RUN_PROGRESS_TYPE = EPIC_RUN_PROGRESS_TYPE_FROM_SIGNALS;
|
|
53
|
-
export const PHASE_TIMINGS_TYPE = PHASE_TIMINGS_TYPE_FROM_SIGNALS;
|
|
54
|
-
export const STORY_RUN_PROGRESS_TYPE = STORY_RUN_PROGRESS_TYPE_FROM_SIGNALS;
|
|
55
|
-
export const EPIC_PROGRESS_EVENT = EPIC_PROGRESS_EVENT_FROM_TRANSPORT;
|
|
56
|
-
export const emitEpicProgress = emitEpicProgressFromTransport;
|
|
57
|
-
export const emitEpicStarted = emitEpicStartedFromTransport;
|
|
58
|
-
export const emitEpicBlocked = emitEpicBlockedFromTransport;
|
|
59
|
-
export const emitEpicUnblocked = emitEpicUnblockedFromTransport;
|
|
60
|
-
export const parseStoryRunProgressComment =
|
|
61
|
-
parseStoryRunProgressCommentFromSignals;
|
|
62
|
-
export const parsePhaseTimingsComment = parsePhaseTimingsCommentFromSignals;
|
|
63
|
-
export const aggregatePhaseTimings = aggregatePhaseTimingsFromSignals;
|
|
64
|
-
export const renderPhaseTimingsSection = renderPhaseTimingsSectionFromSignals;
|
|
65
|
-
export const phaseToState = phaseToStateFromSignals;
|
|
66
|
-
export const upsertEpicRunProgress = upsertEpicRunProgressFromComposition;
|
|
67
|
-
export const deriveState = deriveStateFromComposition;
|
|
68
|
-
export const renderProgressBody = renderProgressBodyFromComposition;
|
|
69
|
-
export const truncate = truncateFromComposition;
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/orchestration/reconciler.js — Ticket Hierarchy Reconciliation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Logger } from '../Logger.js';
|
|
6
|
-
import { AGENT_LABELS, TYPE_LABELS } from '../label-constants.js';
|
|
7
|
-
import { concurrentMap } from '../util/concurrent-map.js';
|
|
8
|
-
import { STATE_LABELS } from './ticketing.js';
|
|
9
|
-
|
|
10
|
-
const AGENT_DONE_LABEL = STATE_LABELS.DONE;
|
|
11
|
-
|
|
12
|
-
// Inlined parent-id parser. The reconciler only needs the direct parent
|
|
13
|
-
// reference scraped from a ticket body's `parent: #N` trailer; pulling the
|
|
14
|
-
// helper inline keeps reconciler.js self-contained for the 3-tier
|
|
15
|
-
// hierarchy walk (Stories → Features).
|
|
16
|
-
const PARENT_ID_PATTERN = /^parent:\s*#(\d+)/m;
|
|
17
|
-
|
|
18
|
-
function parseParentIdFromBody(body) {
|
|
19
|
-
const match = (body ?? '').match(PARENT_ID_PATTERN);
|
|
20
|
-
return match ? Number.parseInt(match[1], 10) : null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Cap=4 — bounded parallelism for ticket-update fan-outs. Reconciliation
|
|
24
|
-
// iterates an Epic-sized set of independent GitHub mutations; cap matches
|
|
25
|
-
// the established pattern across the orchestration layer (wave-gate,
|
|
26
|
-
// progress-reporter, sub-issue link reconcile) and stays well under the
|
|
27
|
-
// secondary rate-limit ceiling observed for issue PATCHes.
|
|
28
|
-
const RECONCILE_CONCURRENCY = 4;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Reconcile the ticket hierarchy bottom-up (Stories → Features).
|
|
32
|
-
*
|
|
33
|
-
* Walks every Story and Feature under the Epic; if all children of a
|
|
34
|
-
* container are done, closes the container and applies `agent::done`.
|
|
35
|
-
*
|
|
36
|
-
* Epic auto-closure is intentionally excluded — Epics close only through
|
|
37
|
-
* the formal `/epic-deliver` workflow.
|
|
38
|
-
*
|
|
39
|
-
* Per-ticket provider failures are logged and swallowed so a single bad
|
|
40
|
-
* ticket cannot halt reconciliation across the rest of the graph.
|
|
41
|
-
*
|
|
42
|
-
* @param {import('../ITicketingProvider.js').ITicketingProvider} provider Ticketing provider.
|
|
43
|
-
* @param {number} _epicId Epic id (reserved — currently unused; kept for call-site stability).
|
|
44
|
-
* @param {object} _epic Epic ticket record (reserved — currently unused).
|
|
45
|
-
* @param {object[]} allTickets Every ticket under the Epic.
|
|
46
|
-
* @param {boolean} dryRun When true, mutate nothing.
|
|
47
|
-
* @returns {Promise<void>}
|
|
48
|
-
*/
|
|
49
|
-
export async function reconcileHierarchy(
|
|
50
|
-
provider,
|
|
51
|
-
_epicId,
|
|
52
|
-
_epic,
|
|
53
|
-
allTickets,
|
|
54
|
-
dryRun,
|
|
55
|
-
) {
|
|
56
|
-
const ticketMap = new Map(allTickets.map((t) => [t.id, t]));
|
|
57
|
-
|
|
58
|
-
const childrenOf = new Map();
|
|
59
|
-
for (const ticket of allTickets) {
|
|
60
|
-
const parentId = parseParentIdFromBody(ticket.body);
|
|
61
|
-
if (parentId != null) {
|
|
62
|
-
if (!childrenOf.has(parentId)) childrenOf.set(parentId, []);
|
|
63
|
-
childrenOf.get(parentId).push(ticket.id);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function isDone(ticketId) {
|
|
68
|
-
const t = ticketMap.get(ticketId);
|
|
69
|
-
if (!t) return false;
|
|
70
|
-
return (
|
|
71
|
-
t.state === 'closed' ||
|
|
72
|
-
(t.labelSet ?? new Set(t.labels)).has(AGENT_DONE_LABEL)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function shouldClose(id) {
|
|
77
|
-
const ticket = ticketMap.get(id);
|
|
78
|
-
if (!ticket || ticket.state === 'closed') return null;
|
|
79
|
-
const children = childrenOf.get(id) ?? [];
|
|
80
|
-
if (children.length === 0) return null;
|
|
81
|
-
if (!children.every((cid) => isDone(cid))) return null;
|
|
82
|
-
return ticket;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function applyClose(id, typeName, ticket) {
|
|
86
|
-
try {
|
|
87
|
-
await provider.updateTicket(id, {
|
|
88
|
-
labels: {
|
|
89
|
-
add: [AGENT_DONE_LABEL],
|
|
90
|
-
remove: [AGENT_LABELS.READY, AGENT_LABELS.EXECUTING],
|
|
91
|
-
},
|
|
92
|
-
state: 'closed',
|
|
93
|
-
state_reason: 'completed',
|
|
94
|
-
});
|
|
95
|
-
ticket.state = 'closed';
|
|
96
|
-
Logger.info(`✅ ${typeName} #${id} closed and marked agent::done.`);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
Logger.warn(`Failed to close ${typeName} #${id}: ${err.message}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async function maybeClose(id, typeName) {
|
|
103
|
-
const ticket = shouldClose(id);
|
|
104
|
-
if (!ticket) return;
|
|
105
|
-
Logger.info(
|
|
106
|
-
`All children of ${typeName} #${id} "${ticket.title}" are done. Closing...`,
|
|
107
|
-
);
|
|
108
|
-
if (dryRun) {
|
|
109
|
-
Logger.info(
|
|
110
|
-
`[DRY-RUN] Would close ${typeName} #${id} and set agent::done.`,
|
|
111
|
-
);
|
|
112
|
-
ticket.state = 'closed';
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
await applyClose(id, typeName, ticket);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const storyIds = allTickets
|
|
119
|
-
.filter((t) => (t.labelSet ?? new Set(t.labels)).has(TYPE_LABELS.STORY))
|
|
120
|
-
.map((t) => t.id);
|
|
121
|
-
const featureIds = allTickets
|
|
122
|
-
.filter((t) => (t.labelSet ?? new Set(t.labels)).has(TYPE_LABELS.FEATURE))
|
|
123
|
-
.map((t) => t.id);
|
|
124
|
-
|
|
125
|
-
// cap=4 — Stories are container leaves of this reconcile (their children
|
|
126
|
-
// are Tasks already settled by reconcileClosedTasks); independent close
|
|
127
|
-
// mutations can fan out without ordering. Features stay sequential because
|
|
128
|
-
// a Feature may parent another Feature, and bottom-up close depends on
|
|
129
|
-
// child-Feature state already being mutated in the same pass.
|
|
130
|
-
await concurrentMap(storyIds, (id) => maybeClose(id, 'Story'), {
|
|
131
|
-
concurrency: RECONCILE_CONCURRENCY,
|
|
132
|
-
});
|
|
133
|
-
for (const id of featureIds) await maybeClose(id, 'Feature');
|
|
134
|
-
|
|
135
|
-
// EXCLUSION: Epic auto-closure removed.
|
|
136
|
-
// The Epic ticket now stays open until the formal /epic-deliver workflow is executed.
|
|
137
|
-
}
|