mandrel 1.94.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +59 -73
- package/.agents/agents/story-worker.md +12 -13
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +104 -138
- package/.agents/docs/execution-reference.md +17 -20
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +70 -81
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +6 -7
- package/.agents/rules/git-conventions.md +16 -22
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +54 -214
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/risk-verdict.schema.json +0 -13
- package/.agents/scripts/analyze-execution.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/check-lifecycle-lint.js +6 -9
- package/.agents/scripts/check-prepush-recovery.js +1 -1
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
- package/.agents/scripts/lib/baselines/kernel.js +206 -18
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
- package/.agents/scripts/lib/checks/index.js +2 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +16 -7
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +45 -29
- package/.agents/scripts/lib/config/explain.js +55 -36
- package/.agents/scripts/lib/config/limits.js +17 -58
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +1 -1
- package/.agents/scripts/lib/config/runners.js +17 -50
- package/.agents/scripts/lib/config/temp-paths.js +19 -14
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +2 -7
- package/.agents/scripts/lib/config-settings-schema-delivery.js +24 -148
- package/.agents/scripts/lib/config-settings-schema-quality.js +8 -14
- package/.agents/scripts/lib/config-settings-schema.js +52 -38
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +2 -2
- package/.agents/scripts/lib/duplicate-search.js +211 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +5 -5
- package/.agents/scripts/lib/framework-version.js +2 -3
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
- package/.agents/scripts/lib/git-utils.js +32 -6
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/label-constants.js +10 -23
- package/.agents/scripts/lib/label-taxonomy.js +9 -43
- package/.agents/scripts/lib/observability/active-story-env.js +1 -1
- package/.agents/scripts/lib/observability/hook-heartbeat.js +20 -52
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +8 -5
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +83 -20
- package/.agents/scripts/lib/orchestration/code-review.js +20 -268
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
- package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
- package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +13 -13
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
- package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
- package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
- package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
- package/.agents/scripts/lib/orchestration/ticket-validator.js +2 -7
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
- package/.agents/scripts/lib/planning-corpus.js +16 -11
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/story-adjacency.js +11 -14
- package/.agents/scripts/lib/story-body/story-body.js +124 -70
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
- package/.agents/scripts/lib/validation-evidence.js +1 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
- package/.agents/scripts/lint-issue-body.js +71 -40
- package/.agents/scripts/mandrel-update-preflight.js +1 -1
- package/.agents/scripts/notify.js +4 -3
- package/.agents/scripts/plan-context.js +64 -74
- package/.agents/scripts/plan-persist.js +121 -280
- package/.agents/scripts/plan-run-epilogue.js +97 -0
- package/.agents/scripts/providers/github/issues.js +17 -33
- package/.agents/scripts/providers/github/mappers.js +0 -12
- package/.agents/scripts/providers/github/tickets.js +2 -5
- package/.agents/scripts/resolve-plan-run.js +117 -0
- package/.agents/scripts/signals-view.js +24 -19
- package/.agents/scripts/single-story-close.js +11 -14
- package/.agents/scripts/single-story-confirm-merge.js +39 -23
- package/.agents/scripts/single-story-init.js +29 -20
- package/.agents/scripts/stories-wave-tick.js +6 -6
- package/.agents/scripts/story-plan.js +26 -47
- package/.agents/scripts/update-ticket-state.js +6 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +3 -43
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +3 -3
- package/.agents/workflows/audit-clean-code.md +3 -3
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-lighthouse.md +3 -3
- package/.agents/workflows/audit-navigability.md +3 -2
- package/.agents/workflows/audit-performance.md +3 -3
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +3 -3
- package/.agents/workflows/audit-security.md +3 -3
- package/.agents/workflows/audit-seo.md +3 -3
- package/.agents/workflows/audit-sre.md +3 -3
- package/.agents/workflows/audit-to-stories.md +20 -20
- package/.agents/workflows/audit-ux-ui.md +3 -3
- package/.agents/workflows/deliver.md +122 -174
- package/.agents/workflows/git-cleanup.md +3 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +16 -29
- package/.agents/workflows/helpers/code-review.md +70 -193
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
- package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
- package/.agents/workflows/mandrel-update.md +2 -1
- package/.agents/workflows/plan.md +112 -145
- package/.agents/workflows/qa-assist.md +24 -30
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -2
- package/README.md +9 -8
- package/docs/CHANGELOG.md +30 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +2 -2
- package/.agents/agents/retro.md +0 -42
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer.md +0 -33
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/security-engineer.md +0 -111
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
- package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
- package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/bookkeeping-reconcile.js +0 -117
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -852
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
- package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
- package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/slice-phase.js +0 -361
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -464
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
- package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
- package/.agents/workflows/helpers/deliver-epic.md +0 -998
- package/.agents/workflows/helpers/deliver-stories.md +0 -450
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -353
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
|
@@ -1,57 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Story sizing model —
|
|
3
|
-
* declaration that lifts the hard file-width rejection. Co-located with
|
|
4
|
-
* `ticket-validator.js`, but kept as its own module so the validator's
|
|
5
|
-
* primary file stays under the maintainability ceiling. The validator
|
|
6
|
-
* imports `computeSizingFindings` and `renderHardFindingError` and stitches
|
|
7
|
-
* the result onto its return value as `findings` / `errors`.
|
|
2
|
+
* Story sizing model — v2 model-capacity split advisory.
|
|
8
3
|
*
|
|
9
|
-
* `DEFAULT_TASK_SIZING`
|
|
4
|
+
* Per-Story file/AC ceilings (`DEFAULT_TASK_SIZING.softFiles` /
|
|
5
|
+
* `hardFiles` / `softAcceptanceCount`) are gone. A Story is sized by
|
|
6
|
+
* **cohesion first**; the numeric backstop only asks whether the authored
|
|
7
|
+
* Story ticket itself is pathologically verbose. See `docs/roadmap.md`
|
|
8
|
+
* § v2.0.0 design decision 2.
|
|
9
|
+
*
|
|
10
|
+
* Co-located with `ticket-validator.js`, but kept as its own module so the
|
|
11
|
+
* validator's primary file stays under the maintainability ceiling. The
|
|
12
|
+
* validator imports `computeSizingFindings` and `renderHardFindingError` and
|
|
13
|
+
* stitches the result onto its return value as `findings` / `errors`.
|
|
14
|
+
*
|
|
15
|
+
* `DEFAULT_MODEL_CAPACITY` is the **single source of truth** for the capacity
|
|
10
16
|
* thresholds. The decomposer prompt template
|
|
11
17
|
* (`.agents/scripts/lib/templates/decomposer-prompts.js`) and the authoring
|
|
12
18
|
* SKILL (`.agents/skills/core/epic-plan-decompose-author/SKILL.md`) reference
|
|
13
19
|
* these numbers rather than restating divergent ones — the prompt generates
|
|
14
20
|
* its threshold sentence from this constant so the two surfaces cannot drift.
|
|
15
|
-
* Keep it in lockstep with `.agents/schemas/agentrc.schema.json`
|
|
16
|
-
* (`$defs.taskSizing`) and the JS mirror in
|
|
17
|
-
* `.agents/scripts/lib/config-settings-schema.js`.
|
|
18
21
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
* `
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `wide` declaration carrying a one-line human-readable reason. Declaring
|
|
28
|
-
* `wide` with a reason lifts the `hardFiles` rejection; no Story is
|
|
29
|
-
* rejected for width when `wide` is declared.
|
|
30
|
-
* - Cohesion is the primary heuristic (one Story = one coherent change with
|
|
31
|
-
* one reason to exist); the numeric ceilings are a backstop. See the
|
|
32
|
-
* decomposer prompt and authoring SKILL.
|
|
22
|
+
* Capacity model:
|
|
23
|
+
* - Plan-time **session mass** = authored tokens only (`estimateTokens` of
|
|
24
|
+
* the Story's goal/reason/acceptance/verify/slicing/spec/change paths).
|
|
25
|
+
* - Soft / hard ceilings are **absolute** authored-token counts (no
|
|
26
|
+
* `maxTokenBudget` denominator — that envelope was retired).
|
|
27
|
+
* - The optional `wide` declaration lifts the hard session-mass rejection.
|
|
28
|
+
* - Cohesion remains the primary heuristic; capacity is a pathological-
|
|
29
|
+
* verbosity backstop only (not an operator-tunable agentrc knob).
|
|
33
30
|
*/
|
|
34
31
|
|
|
35
32
|
import { parse as parseStoryBody } from '../story-body/story-body.js';
|
|
33
|
+
import { estimateTokens } from './context-envelope.js';
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
36
|
* Normalize a Story's `body` to the structured object the sizing layers
|
|
39
37
|
* score, mirroring `validateAcFreshness` / `collectStoryAssumptionEntries`
|
|
40
38
|
* (Story #3302) and `resolveStructuredBody` in `task-body-validator.js`.
|
|
41
39
|
*
|
|
42
|
-
* The decomposer emits `body` as the canonical serialized **string**
|
|
43
|
-
* (`decomposer-prompts.js`), but the sizing layers historically read
|
|
44
|
-
* `story.body` only when it was already an object — so on the production
|
|
45
|
-
* string shape `changes` / `wide` fell through to empty and the `hardFiles`
|
|
46
|
-
* / unanchored-constant backstops emitted nothing. A defensive parse here
|
|
47
|
-
* restores parity:
|
|
48
|
-
* - **string body** → parsed via `parseStoryBody`; an unparseable string
|
|
49
|
-
* yields `null` (the gate degrades to "no structured signal", never
|
|
50
|
-
* throws mid-validation).
|
|
51
|
-
* - **object body** → returned verbatim (a caller may pass the
|
|
52
|
-
* pre-serialize shape directly; `parse` round-trips it).
|
|
53
|
-
* - **null / other** → `null`.
|
|
54
|
-
*
|
|
55
40
|
* @param {object} story
|
|
56
41
|
* @returns {object|null}
|
|
57
42
|
*/
|
|
@@ -71,13 +56,8 @@ function resolveStoryBody(story) {
|
|
|
71
56
|
|
|
72
57
|
/**
|
|
73
58
|
* Resolve the acceptance-criteria array for a Story, preferring the
|
|
74
|
-
* authoritative top-level `story.acceptance`
|
|
75
|
-
*
|
|
76
|
-
* structured body's `acceptance`. Reading the top-level array makes the
|
|
77
|
-
* acceptance ceiling correct regardless of body shape — a string body whose
|
|
78
|
-
* structured `acceptance` is only reachable after a parse, or an object body
|
|
79
|
-
* (Story #4271). Falls back to `resolveStoryBody(story).acceptance` only when
|
|
80
|
-
* the top-level array is absent.
|
|
59
|
+
* authoritative top-level `story.acceptance` over the structured body's
|
|
60
|
+
* `acceptance`.
|
|
81
61
|
*
|
|
82
62
|
* @param {object} story
|
|
83
63
|
* @returns {unknown[]}
|
|
@@ -89,17 +69,20 @@ function resolveAcceptance(story) {
|
|
|
89
69
|
}
|
|
90
70
|
|
|
91
71
|
/**
|
|
92
|
-
* Resolve the
|
|
93
|
-
*
|
|
94
|
-
* inside the structured body (the shape `story-body.js` parses out). Prefer the
|
|
95
|
-
* top-level array, falling back to the resolved body's `depends_on`. Returns
|
|
96
|
-
* only non-empty string slugs — mirroring the `story-body.js` normalization so
|
|
97
|
-
* a blank / non-string edge never counts as a real dependency.
|
|
72
|
+
* Resolve the verify-commands array for a Story, preferring the top-level
|
|
73
|
+
* `story.verify` over the structured body's `verify`.
|
|
98
74
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
75
|
+
* @param {object} story
|
|
76
|
+
* @returns {unknown[]}
|
|
77
|
+
*/
|
|
78
|
+
function resolveVerify(story) {
|
|
79
|
+
if (Array.isArray(story?.verify)) return story.verify;
|
|
80
|
+
const body = resolveStoryBody(story);
|
|
81
|
+
return Array.isArray(body?.verify) ? body.verify : [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the `depends_on` edge list for a Story.
|
|
103
86
|
*
|
|
104
87
|
* @param {object} story
|
|
105
88
|
* @returns {string[]}
|
|
@@ -113,127 +96,72 @@ function resolveDependsOn(story) {
|
|
|
113
96
|
: [];
|
|
114
97
|
}
|
|
115
98
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// merge rule. Emitted as a `soft` advisory only; never a rejection. A tiny
|
|
135
|
-
// ORPHAN Story (no `depends_on`) stays silent — small orthogonal slices are
|
|
136
|
-
// legitimate.
|
|
137
|
-
mergeCandidateMaxFiles: 3,
|
|
138
|
-
mergeCandidateMaxAcceptance: 4,
|
|
99
|
+
/**
|
|
100
|
+
* Framework capacity constant — not operator-tunable via `.agentrc.json`
|
|
101
|
+
* (same collapse pattern as `maxTickets` / Story #4163).
|
|
102
|
+
*
|
|
103
|
+
* Absolute authored-token ceilings (no delivery-envelope denominator):
|
|
104
|
+
*
|
|
105
|
+
* - Soft 30_000: cohesion check when the ticket itself is already a
|
|
106
|
+
* substantial document. Normal capability Stories stay silent.
|
|
107
|
+
* - Hard 75_000: reject Spec novels unless `wide`.
|
|
108
|
+
* - Merge candidate 1_500: thin `depends_on` fragment (soft only).
|
|
109
|
+
*
|
|
110
|
+
* Cohesion / split policy / conflict advisories remain the primary sizing
|
|
111
|
+
* signal; these ceilings catch pathological ticket verbosity only.
|
|
112
|
+
*/
|
|
113
|
+
export const DEFAULT_MODEL_CAPACITY = Object.freeze({
|
|
114
|
+
softSessionTokens: 30000,
|
|
115
|
+
hardSessionTokens: 75000,
|
|
116
|
+
mergeCandidateMaxSessionTokens: 1500,
|
|
139
117
|
});
|
|
140
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Resolve capacity ceilings, shallow-merging an optional override bag
|
|
121
|
+
* (tests / programmatic only). Pure; used by the validator and the
|
|
122
|
+
* decomposer prompt.
|
|
123
|
+
*
|
|
124
|
+
* @param {object} [capacity]
|
|
125
|
+
* @returns {{ softSessionTokens: number, hardSessionTokens: number, mergeCandidateMaxSessionTokens: number }}
|
|
126
|
+
*/
|
|
127
|
+
export function resolveCapacityCeilings(capacity = DEFAULT_MODEL_CAPACITY) {
|
|
128
|
+
const merged = { ...DEFAULT_MODEL_CAPACITY, ...(capacity ?? {}) };
|
|
129
|
+
return {
|
|
130
|
+
softSessionTokens: merged.softSessionTokens,
|
|
131
|
+
hardSessionTokens: merged.hardSessionTokens,
|
|
132
|
+
mergeCandidateMaxSessionTokens: merged.mergeCandidateMaxSessionTokens,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
141
136
|
/**
|
|
142
137
|
* `DELIVERABLE_GRANULARITY_GUIDANCE` is the **single source of truth** for the
|
|
143
138
|
* deliverable-granularity definition of a Story (Story #3777). It is stated
|
|
144
|
-
* ONCE here and consumed by BOTH the decomposer prompt template
|
|
145
|
-
*
|
|
146
|
-
* the string verbatim) AND the authoring SKILL
|
|
147
|
-
* (`.agents/skills/core/epic-plan-decompose-author/SKILL.md`, whose prose
|
|
148
|
-
* mirrors these sentences). The SKILL cannot import JS, so the
|
|
149
|
-
* `epic-plan-decompose-author` smoke test and the `ticket-decomposer` prompt
|
|
150
|
-
* test both assert the canonical phrasing on each surface — a divergent
|
|
151
|
-
* restatement fails those gates. This reuses the #3760 single-source
|
|
152
|
-
* mechanism (one constant, two surfaces, drift-gated by tests).
|
|
153
|
-
*
|
|
154
|
-
* The definition: a Story is a **capability slice a frontier model delivers
|
|
155
|
-
* and self-verifies in one pass** — a shippable slice a reviewer would accept
|
|
156
|
-
* as a single PR — not a single module or file. Module-level slices fold into
|
|
157
|
-
* the capability they belong to, and a Story whose only consumer is one
|
|
158
|
-
* sibling Story is merged into that sibling (single-consumer merge rule).
|
|
159
|
-
*
|
|
160
|
-
* The `envelopeFloor` sentence is the **soft** complement to the sizing
|
|
161
|
-
* section's envelope framing (Story #4313): the delivery envelope
|
|
162
|
-
* (`maxTokenBudget`) bounds the TOP of a Story, but under-utilizing it is
|
|
163
|
-
* itself a merge signal. It is deliberately prose-only — an illustrative
|
|
164
|
-
* fraction, not a threshold constant, and no validator finding backs it (the
|
|
165
|
-
* mechanical backstop is the `merge-candidate` finding). It names the
|
|
166
|
-
* per-Story delivery-session cost (hydration, branch, PR, review, CI) so the
|
|
167
|
-
* "models one-shot bigger things now" planning assumption is stated, not
|
|
168
|
-
* implicit.
|
|
139
|
+
* ONCE here and consumed by BOTH the decomposer prompt template and the
|
|
140
|
+
* authoring SKILL.
|
|
169
141
|
*/
|
|
170
142
|
export const DELIVERABLE_GRANULARITY_GUIDANCE = Object.freeze({
|
|
171
|
-
// The one-sentence definition of Story granularity.
|
|
172
143
|
definition:
|
|
173
144
|
'A Story is a **capability slice a frontier model delivers and self-verifies in one pass** — a shippable slice a reviewer would accept as a single PR, a capability or user-visible surface, **not a single module or file**. Fold module-level slices into the capability they belong to rather than emitting one Story per module.',
|
|
174
|
-
// The single-consumer merge rule.
|
|
175
145
|
singleConsumerRule:
|
|
176
146
|
'**Single-consumer merge rule.** A Story whose only consumer is one sibling Story should be **merged into that sibling** rather than emitted separately — a single-consumer downstream slice is not its own unit of work.',
|
|
177
|
-
// The envelope-floor heuristic (soft; no validator finding — Story #4313).
|
|
178
147
|
envelopeFloor:
|
|
179
|
-
'**
|
|
148
|
+
'**Thin dependent slices are a merge signal.** A Story that is neither parallel-deliverable nor orthogonal to its siblings — especially a short `depends_on` fragment whose only job is to feed one consumer — should be **merged into its consumer**. Modern frontier models one-shot capability-sized changes, so a chain of small dependent Stories needlessly pays a full delivery session (branch, PR, review, CI) per link. Merge such links up unless a parallelism or orthogonality reason justifies the separate slice.',
|
|
180
149
|
});
|
|
181
150
|
|
|
182
151
|
/**
|
|
183
152
|
* `AUTHORING_ALTITUDE_GUIDANCE` is the **single source of truth** for the
|
|
184
153
|
* binding-vs-advisory authoring altitude (Epic #4131 F8) and the New-File
|
|
185
|
-
* Contract (Story #4272).
|
|
186
|
-
* decomposer prompt template
|
|
187
|
-
* (`.agents/scripts/lib/templates/decomposer-prompts.js`, which interpolates
|
|
188
|
-
* the strings verbatim into the rendered system prompt) AND the authoring
|
|
189
|
-
* SKILL (`.agents/skills/core/epic-plan-decompose-author/SKILL.md`, whose
|
|
190
|
-
* prose mirrors these sentences). The SKILL cannot import JS, so the
|
|
191
|
-
* `ticket-decomposer` prompt test asserts the canonical phrasing on both
|
|
192
|
-
* surfaces — a divergent restatement fails that gate. This reuses the #3777
|
|
193
|
-
* single-source mechanism (one constant, two surfaces, drift-gated by tests).
|
|
194
|
-
*
|
|
195
|
-
* The altitude: `acceptance[]` / `verify[]` are the **binding contract** (the
|
|
196
|
-
* sole definition of "done"); `changes[]` / `references[]` are an **advisory
|
|
197
|
-
* implementation sketch** the executor MAY revise. Author acceptance to assert
|
|
198
|
-
* the **outcome** independent of file layout — never pin an incidental helper
|
|
199
|
-
* name or private path into an acceptance item. The advisory sketch is still
|
|
200
|
-
* validated (base-branch probes, New-File Contract) and never licenses
|
|
201
|
-
* skipping `acceptance[]` / `verify[]` or any `rules/security-baseline.md` MUST.
|
|
154
|
+
* Contract (Story #4272).
|
|
202
155
|
*/
|
|
203
156
|
export const AUTHORING_ALTITUDE_GUIDANCE = Object.freeze({
|
|
204
|
-
// The binding-vs-advisory altitude statement.
|
|
205
157
|
altitude:
|
|
206
158
|
'**Binding contract vs advisory sketch.** `acceptance[]` and `verify[]` are the Story\'s **binding contract** — the executor MUST satisfy them exactly, and they are the only definition of "done." `changes[]` and `references[]` are an **advisory implementation sketch**: your best prediction of the file footprint, which the executor MAY revise when the real codebase diverges from the sketch. Author `acceptance[]` / `verify[]` to assert the **outcome** independent of any one file layout — never pin an incidental implementation detail (an internal helper name, a private file path) into an acceptance item that the advisory `changes[]` is free to reshape; assert the observable behaviour instead.',
|
|
207
|
-
// The advisory-does-not-mean-unvalidated caveat.
|
|
208
159
|
advisoryCaveat:
|
|
209
160
|
"**Advisory does not mean unvalidated.** `changes[]` paths still pass the base-branch file-assumption probes (a `creates` against an existing path still fails), the New-File Contract still holds, and the executor's latitude to revise the approach never licenses skipping `acceptance[]` / `verify[]` or relaxing any `rules/security-baseline.md` MUST.",
|
|
210
|
-
// The New-File Contract.
|
|
211
161
|
newFileContract:
|
|
212
162
|
'**New-File Contract.** Any path named in a Story\'s `goal`, `acceptance`, or `verify` that does NOT already exist on `main` MUST also appear in that Story\'s `changes[]` with `assumption: "creates"`; otherwise the freshness validator rejects the decompose — even when the Story is the one authoring the file.',
|
|
213
163
|
});
|
|
214
164
|
|
|
215
|
-
/**
|
|
216
|
-
* Configuration-constant phrase patterns the `unanchored-constant` heuristic
|
|
217
|
-
* scans Story acceptance criteria for. Each entry matches the *kind* of
|
|
218
|
-
* tunable an implementing agent would otherwise have to context-hop to the
|
|
219
|
-
* Tech Spec (or an ADR) to resolve: a retention window, a rate limit, a
|
|
220
|
-
* timeout, an age cutoff, a bounded window, a per-unit cap, or a quota.
|
|
221
|
-
*
|
|
222
|
-
* The list is the single source of truth for the heuristic and intentionally
|
|
223
|
-
* narrow — it targets the small set of config constants that recur across
|
|
224
|
-
* Stories and that an author can almost always inline as a concrete value at
|
|
225
|
-
* authoring time. Anchored with `\b` word boundaries so substrings inside
|
|
226
|
-
* unrelated prose ("maximum", "timeouts handled elsewhere") don't over-match.
|
|
227
|
-
*
|
|
228
|
-
* Covered phrases (acceptance criterion of Story #3855):
|
|
229
|
-
* - `retention window`
|
|
230
|
-
* - `rate limit` / `rate-limit`
|
|
231
|
-
* - `timeout`
|
|
232
|
-
* - `older than`
|
|
233
|
-
* - `within N` (a bounded count/duration window)
|
|
234
|
-
* - `max ... per ...` (a per-unit cap, e.g. "max requests per second")
|
|
235
|
-
* - `quota`
|
|
236
|
-
*/
|
|
237
165
|
const UNANCHORED_CONSTANT_PATTERNS = Object.freeze([
|
|
238
166
|
/\bretention window\b/i,
|
|
239
167
|
/\brate[ -]limit\b/i,
|
|
@@ -244,25 +172,8 @@ const UNANCHORED_CONSTANT_PATTERNS = Object.freeze([
|
|
|
244
172
|
/\bquota\b/i,
|
|
245
173
|
]);
|
|
246
174
|
|
|
247
|
-
/**
|
|
248
|
-
* Matches a concrete numeric value anywhere in an acceptance criterion. A
|
|
249
|
-
* bare digit run is enough to count as "anchored": `"older than 90 days"`,
|
|
250
|
-
* `"5 req/s"`, `"within 30 minutes"`, and `"max 100 per hour"` all carry a
|
|
251
|
-
* digit, so they are not flagged. Spelled-out small numbers are intentionally
|
|
252
|
-
* NOT treated as anchors — the heuristic nudges authors toward an explicit
|
|
253
|
-
* numeral the implementing agent can copy verbatim.
|
|
254
|
-
*/
|
|
255
175
|
const CONCRETE_VALUE_RE = /\d/;
|
|
256
176
|
|
|
257
|
-
/**
|
|
258
|
-
* Soft, advisory `unanchored-constant` finding (Story #3855). Surfaces a
|
|
259
|
-
* Story acceptance criterion that references a configuration constant
|
|
260
|
-
* (retention window, rate limit, timeout, threshold) without stating a
|
|
261
|
-
* concrete numeric value inline — so an implementing agent doesn't have to
|
|
262
|
-
* context-hop to the Tech Spec or guess. Advisory only: `normalizeTickets`
|
|
263
|
-
* still returns successfully and the finding rides the advisory nudges array
|
|
264
|
-
* alongside the sizing soft findings.
|
|
265
|
-
*/
|
|
266
177
|
function makeUnanchoredConstant(slug, criterion) {
|
|
267
178
|
return {
|
|
268
179
|
kind: 'unanchored-constant',
|
|
@@ -273,21 +184,8 @@ function makeUnanchoredConstant(slug, criterion) {
|
|
|
273
184
|
};
|
|
274
185
|
}
|
|
275
186
|
|
|
276
|
-
/**
|
|
277
|
-
* Scan a single Story's acceptance criteria for unanchored configuration
|
|
278
|
-
* constants. A criterion trips the finding when it matches one of
|
|
279
|
-
* `UNANCHORED_CONSTANT_PATTERNS` and carries NO concrete numeric value
|
|
280
|
-
* (`CONCRETE_VALUE_RE`) — the digit is the signal the author already inlined
|
|
281
|
-
* the value, so a criterion like `"older than 90 days"` is left alone while
|
|
282
|
-
* `"older than the retention window"` is flagged. One finding per offending
|
|
283
|
-
* criterion.
|
|
284
|
-
*/
|
|
285
187
|
function computeUnanchoredConstantFindings(story) {
|
|
286
188
|
const out = [];
|
|
287
|
-
// Read the authoritative top-level `story.acceptance` (the binding
|
|
288
|
-
// contract), falling back to the structured body's acceptance only when the
|
|
289
|
-
// top-level array is absent. This is correct regardless of body shape —
|
|
290
|
-
// string or object (Story #4271).
|
|
291
189
|
const acceptance = resolveAcceptance(story);
|
|
292
190
|
for (const item of acceptance) {
|
|
293
191
|
const criterion = String(item ?? '');
|
|
@@ -302,19 +200,6 @@ function computeUnanchoredConstantFindings(story) {
|
|
|
302
200
|
return out;
|
|
303
201
|
}
|
|
304
202
|
|
|
305
|
-
/**
|
|
306
|
-
* Soft, advisory `missing-reason-to-exist` finding (Story #4273). Surfaces a
|
|
307
|
-
* Story whose body carries no non-empty `reason_to_exist` — the
|
|
308
|
-
* machine-checkable form of the cohesion rule (**one Story = one coherent
|
|
309
|
-
* change with one reason to exist**). `reason_to_exist` is marked REQUIRED by
|
|
310
|
-
* the decomposer prompt and is the field the `epic-plan-consolidate` critic
|
|
311
|
-
* gates on, but that critic is an honor-system LLM check with no runtime
|
|
312
|
-
* backstop. This deterministic finding is the cheap backstop.
|
|
313
|
-
*
|
|
314
|
-
* Severity is `soft` (not a hard reject) so existing `reason_to_exist`-less
|
|
315
|
-
* standalone / audit Stories are surfaced as an advisory nudge rather than
|
|
316
|
-
* blocked — matching the `unanchored-constant` finding's advisory contract.
|
|
317
|
-
*/
|
|
318
203
|
function makeMissingReasonToExist(slug) {
|
|
319
204
|
return {
|
|
320
205
|
kind: 'missing-reason-to-exist',
|
|
@@ -325,17 +210,6 @@ function makeMissingReasonToExist(slug) {
|
|
|
325
210
|
};
|
|
326
211
|
}
|
|
327
212
|
|
|
328
|
-
/**
|
|
329
|
-
* Emit a soft `missing-reason-to-exist` finding when the Story body resolves
|
|
330
|
-
* to no non-empty `reason_to_exist`. The body parser
|
|
331
|
-
* (`story-body/story-body.js`) already normalizes `reason_to_exist` to a
|
|
332
|
-
* non-empty trimmed string or `null`, so reading `body.reason_to_exist` after
|
|
333
|
-
* `resolveStoryBody` is correct regardless of body shape — a serialized
|
|
334
|
-
* **string** body (the production decomposer shape) or an object body
|
|
335
|
-
* (Story #4271). A body that fails to parse resolves to `null` and trips the
|
|
336
|
-
* finding, which is the right advisory signal: the author should re-emit a
|
|
337
|
-
* parseable body carrying the field. One finding per Story.
|
|
338
|
-
*/
|
|
339
213
|
function computeMissingReasonToExistFinding(story) {
|
|
340
214
|
const body = resolveStoryBody(story);
|
|
341
215
|
const reason = body?.reason_to_exist;
|
|
@@ -343,82 +217,31 @@ function computeMissingReasonToExistFinding(story) {
|
|
|
343
217
|
return hasReason ? [] : [makeMissingReasonToExist(story.slug)];
|
|
344
218
|
}
|
|
345
219
|
|
|
346
|
-
|
|
347
|
-
* Soft, advisory `merge-candidate` finding (Story #4312). Surfaces a Story that
|
|
348
|
-
* looks like a dependent fragment rather than a capability slice — the
|
|
349
|
-
* machine-checkable form of the single-consumer merge rule
|
|
350
|
-
* (`DELIVERABLE_GRANULARITY_GUIDANCE.singleConsumerRule`). Never a rejection:
|
|
351
|
-
* `severity: 'soft'`, so it rides the advisory `findings[]` channel and never
|
|
352
|
-
* enters the hard `errors[]` array.
|
|
353
|
-
*
|
|
354
|
-
* The rendered message names the depended-on sibling slug(s) and recommends
|
|
355
|
-
* merging into the consumer, mirroring the tone of the `wide-undeclared` nudge.
|
|
356
|
-
*/
|
|
357
|
-
function makeMergeCandidate(slug, fileCount, acceptanceCount, dependsOn) {
|
|
220
|
+
function makeMergeCandidate(slug, sessionMass, dependsOn) {
|
|
358
221
|
const siblings = dependsOn.map((d) => `"${d}"`).join(', ');
|
|
359
222
|
return {
|
|
360
223
|
kind: 'merge-candidate',
|
|
361
224
|
severity: 'soft',
|
|
362
225
|
ticketSlug: slug,
|
|
363
|
-
|
|
364
|
-
acceptanceCount,
|
|
226
|
+
sessionMass,
|
|
365
227
|
dependsOn,
|
|
366
|
-
message: `Story "${slug}" is a thin dependent slice (
|
|
228
|
+
message: `Story "${slug}" is a thin dependent slice (estimated session mass ${sessionMass} tokens) that depends on sibling(s) ${siblings}. A Story whose only role is to feed one sibling is not its own unit of work — consider merging it into the consumer (single-consumer merge rule) rather than shipping it as a separate slice.`,
|
|
367
229
|
};
|
|
368
230
|
}
|
|
369
231
|
|
|
370
232
|
/**
|
|
371
|
-
*
|
|
372
|
-
* heuristic (Story #4312): footprint ≤ `mergeCandidateMaxFiles` declared
|
|
373
|
-
* `changes[]` files AND ≤ `mergeCandidateMaxAcceptance` acceptance items AND at
|
|
374
|
-
* least one `depends_on` edge to a sibling. All three conditions MUST hold — a
|
|
375
|
-
* tiny ORPHAN Story (no `depends_on`) stays silent because small orthogonal
|
|
376
|
-
* slices are legitimate. Glob entries mark the footprint as unknown-width, so a
|
|
377
|
-
* glob-carrying Story is never a merge candidate.
|
|
378
|
-
*
|
|
379
|
-
* @param {object} story
|
|
380
|
-
* @param {{ fileCount: number, hasGlobs: boolean }} changesAnalysis
|
|
381
|
-
* @param {number} acceptanceCount
|
|
382
|
-
* @param {object} sizing
|
|
383
|
-
* @returns {object[]}
|
|
384
|
-
*/
|
|
385
|
-
function computeMergeCandidateFinding(
|
|
386
|
-
story,
|
|
387
|
-
changesAnalysis,
|
|
388
|
-
acceptanceCount,
|
|
389
|
-
sizing,
|
|
390
|
-
) {
|
|
391
|
-
if (changesAnalysis.hasGlobs) return [];
|
|
392
|
-
const dependsOn = resolveDependsOn(story);
|
|
393
|
-
if (dependsOn.length === 0) return [];
|
|
394
|
-
if (changesAnalysis.fileCount > sizing.mergeCandidateMaxFiles) return [];
|
|
395
|
-
if (acceptanceCount > sizing.mergeCandidateMaxAcceptance) return [];
|
|
396
|
-
return [
|
|
397
|
-
makeMergeCandidate(
|
|
398
|
-
story.slug,
|
|
399
|
-
changesAnalysis.fileCount,
|
|
400
|
-
acceptanceCount,
|
|
401
|
-
dependsOn,
|
|
402
|
-
),
|
|
403
|
-
];
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Returns true when a `changes[]` entry is a glob pattern. Handles both the
|
|
408
|
-
* canonical PathEntry object form `{ path, assumption }` and legacy strings.
|
|
233
|
+
* Returns true when a `changes[]` entry is a glob pattern.
|
|
409
234
|
*/
|
|
410
235
|
function isGlobBullet(bullet) {
|
|
411
|
-
const s = bullet?.path
|
|
236
|
+
const s = bullet?.path;
|
|
412
237
|
return typeof s === 'string' && s.includes('*');
|
|
413
238
|
}
|
|
414
239
|
|
|
415
240
|
/**
|
|
416
|
-
* Extract the path
|
|
417
|
-
* canonical PathEntry object form (path used directly) and the legacy
|
|
418
|
-
* `"<path>: <verb>"` string form (slices on the first colon).
|
|
241
|
+
* Extract the path from a single object-form `changes` entry.
|
|
419
242
|
*/
|
|
420
243
|
function extractChangeBulletPath(bullet) {
|
|
421
|
-
const s =
|
|
244
|
+
const s = bullet?.path ?? null;
|
|
422
245
|
if (!s) return null;
|
|
423
246
|
const colonIdx = s.indexOf(':');
|
|
424
247
|
if (colonIdx <= 0) return /[\\/.]/.test(s) ? s.trim() : null;
|
|
@@ -427,44 +250,91 @@ function extractChangeBulletPath(bullet) {
|
|
|
427
250
|
}
|
|
428
251
|
|
|
429
252
|
/**
|
|
430
|
-
* Analyse the `changes[]` array and return
|
|
431
|
-
* - `fileCount` — number of unique non-glob path-shaped heads
|
|
432
|
-
* - `hasGlobs` — true when at least one bullet is a glob pattern
|
|
433
|
-
*
|
|
434
|
-
* Handles both the canonical PathEntry object form `{ path, assumption }` and
|
|
435
|
-
* the legacy string form via the underlying helpers.
|
|
253
|
+
* Analyse the `changes[]` array and return fileCount / hasGlobs / path texts.
|
|
436
254
|
*/
|
|
437
255
|
function analyseChanges(changes) {
|
|
438
256
|
const paths = new Set();
|
|
257
|
+
const pathTexts = [];
|
|
439
258
|
let hasGlobs = false;
|
|
440
259
|
for (const bullet of changes) {
|
|
441
260
|
if (isGlobBullet(bullet)) {
|
|
442
261
|
hasGlobs = true;
|
|
262
|
+
const globText = bullet?.path ?? '';
|
|
263
|
+
if (globText) pathTexts.push(String(globText));
|
|
443
264
|
continue;
|
|
444
265
|
}
|
|
445
266
|
const path = extractChangeBulletPath(bullet);
|
|
446
|
-
if (path)
|
|
267
|
+
if (path) {
|
|
268
|
+
paths.add(path);
|
|
269
|
+
pathTexts.push(path);
|
|
270
|
+
}
|
|
447
271
|
}
|
|
448
|
-
return { fileCount: paths.size, hasGlobs };
|
|
272
|
+
return { fileCount: paths.size, hasGlobs, pathTexts };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Estimate the plan-time session mass (tokens) for a Story.
|
|
277
|
+
*
|
|
278
|
+
* Session mass is **authored prose only** — `estimateTokens` of the Story's
|
|
279
|
+
* goal / reason / acceptance / verify / slicing / spec / change-path text.
|
|
280
|
+
* File count and AC count do not add delivery-cost proxies (those recreated
|
|
281
|
+
* the retired count ceilings). Glob entries contribute their text but do not
|
|
282
|
+
* imply known width.
|
|
283
|
+
*
|
|
284
|
+
* @param {object} story
|
|
285
|
+
* @param {object} [_capacity] Unused; retained so call sites that still pass
|
|
286
|
+
* a capacity bag keep working. Mass no longer depends on capacity knobs.
|
|
287
|
+
* @returns {{ sessionMass: number, authoredTokens: number, acceptanceCount: number, fileCount: number, hasGlobs: boolean, changesAnalysis: object }}
|
|
288
|
+
*/
|
|
289
|
+
export function estimateStorySessionMass(
|
|
290
|
+
story,
|
|
291
|
+
_capacity = DEFAULT_MODEL_CAPACITY,
|
|
292
|
+
) {
|
|
293
|
+
const body = resolveStoryBody(story);
|
|
294
|
+
const acceptance = resolveAcceptance(story);
|
|
295
|
+
const verify = resolveVerify(story);
|
|
296
|
+
const changes = Array.isArray(body?.changes) ? body.changes : [];
|
|
297
|
+
const changesAnalysis = analyseChanges(changes);
|
|
298
|
+
|
|
299
|
+
const authoredParts = [
|
|
300
|
+
body?.goal,
|
|
301
|
+
body?.reason_to_exist,
|
|
302
|
+
typeof body?.spec === 'string' ? body.spec : '',
|
|
303
|
+
...acceptance.map((a) => String(a ?? '')),
|
|
304
|
+
...verify.map((v) => String(v ?? '')),
|
|
305
|
+
typeof body?.slicing === 'string' ? body.slicing : '',
|
|
306
|
+
...changesAnalysis.pathTexts,
|
|
307
|
+
].filter((p) => typeof p === 'string' && p.length > 0);
|
|
308
|
+
|
|
309
|
+
const authoredTokens = estimateTokens(authoredParts.join('\n'));
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
sessionMass: authoredTokens,
|
|
313
|
+
authoredTokens,
|
|
314
|
+
acceptanceCount: acceptance.length,
|
|
315
|
+
fileCount: changesAnalysis.fileCount,
|
|
316
|
+
hasGlobs: changesAnalysis.hasGlobs,
|
|
317
|
+
changesAnalysis,
|
|
318
|
+
};
|
|
449
319
|
}
|
|
450
320
|
|
|
451
|
-
function makeOversized(slug,
|
|
321
|
+
function makeOversized(slug, observed, ceiling) {
|
|
452
322
|
return {
|
|
453
323
|
kind: 'oversized-task',
|
|
454
324
|
severity: 'hard',
|
|
455
325
|
ticketSlug: slug,
|
|
456
|
-
field,
|
|
326
|
+
field: 'sessionMass',
|
|
457
327
|
observed,
|
|
458
328
|
ceiling,
|
|
459
329
|
};
|
|
460
330
|
}
|
|
461
331
|
|
|
462
|
-
function
|
|
332
|
+
function makeSoftSessionPressure(slug, observed, soft) {
|
|
463
333
|
return {
|
|
464
|
-
kind: 'soft-
|
|
334
|
+
kind: 'soft-session-pressure',
|
|
465
335
|
severity: 'soft',
|
|
466
336
|
ticketSlug: slug,
|
|
467
|
-
field,
|
|
337
|
+
field: 'sessionMass',
|
|
468
338
|
observed,
|
|
469
339
|
soft,
|
|
470
340
|
};
|
|
@@ -472,9 +342,7 @@ function makeSoftWidth(slug, field, observed, soft) {
|
|
|
472
342
|
|
|
473
343
|
/**
|
|
474
344
|
* Returns true when the Story declares itself `wide` with a non-empty reason.
|
|
475
|
-
* A `wide` declaration lifts the
|
|
476
|
-
* change states why it is broad rather than being silently capped. The
|
|
477
|
-
* reason is the only payload — there is no longer a profile enum to match.
|
|
345
|
+
* A `wide` declaration lifts the hard session-mass rejection.
|
|
478
346
|
*/
|
|
479
347
|
function isDeclaredWide(wide) {
|
|
480
348
|
return (
|
|
@@ -485,108 +353,59 @@ function isDeclaredWide(wide) {
|
|
|
485
353
|
);
|
|
486
354
|
}
|
|
487
355
|
|
|
356
|
+
function computeMergeCandidateFinding(story, mass, ceilings) {
|
|
357
|
+
if (mass.hasGlobs) return [];
|
|
358
|
+
const dependsOn = resolveDependsOn(story);
|
|
359
|
+
if (dependsOn.length === 0) return [];
|
|
360
|
+
if (mass.sessionMass > ceilings.mergeCandidateMaxSessionTokens) return [];
|
|
361
|
+
return [makeMergeCandidate(story.slug, mass.sessionMass, dependsOn)];
|
|
362
|
+
}
|
|
363
|
+
|
|
488
364
|
/**
|
|
489
|
-
* Compute the hard + soft
|
|
490
|
-
* - acceptance ceiling (`maxAcceptance`) + soft warn (`softAcceptanceCount`)
|
|
491
|
-
* - file-width ceiling (`hardFiles`) + soft warn (`softFiles`)
|
|
492
|
-
* - the optional `wide` declaration, which lifts the hard file ceiling
|
|
493
|
-
* - glob-awareness (glob entries are unknown-width; numeric ceiling skipped)
|
|
494
|
-
*
|
|
495
|
-
* Cohesion is the primary authoring heuristic (see the decomposer prompt and
|
|
496
|
-
* SKILL); these numeric findings are the backstop. A wide Story declares
|
|
497
|
-
* `wide` with a reason instead of being rejected for width.
|
|
365
|
+
* Compute the hard + soft capacity findings for a single Story.
|
|
498
366
|
*/
|
|
499
|
-
function computeStorySizingFindings(story,
|
|
367
|
+
function computeStorySizingFindings(story, capacity, ceilings) {
|
|
500
368
|
const out = [];
|
|
501
|
-
// Story #4271: normalize the body so the canonical serialized **string**
|
|
502
|
-
// shape the decomposer emits is scored at parity with the pre-serialize
|
|
503
|
-
// object shape. The acceptance ceiling reads the authoritative top-level
|
|
504
|
-
// `story.acceptance` (the binding contract), not `body.acceptance`.
|
|
505
369
|
const body = resolveStoryBody(story);
|
|
506
|
-
const acceptance = resolveAcceptance(story);
|
|
507
|
-
const changes = Array.isArray(body?.changes) ? body.changes : [];
|
|
508
370
|
const declaredWide = isDeclaredWide(body?.wide ?? null);
|
|
509
|
-
const
|
|
371
|
+
const mass = estimateStorySessionMass(story, capacity);
|
|
510
372
|
|
|
511
|
-
// Soft, advisory: flag acceptance criteria that reference a configuration
|
|
512
|
-
// constant without inlining a concrete value (Story #3855). Independent of
|
|
513
|
-
// the numeric sizing layers below — purely an authoring nudge.
|
|
514
373
|
out.push(...computeUnanchoredConstantFindings(story));
|
|
515
|
-
|
|
516
|
-
// Soft, advisory: flag a Story body that carries no non-empty
|
|
517
|
-
// `reason_to_exist` (Story #4273). The decomposer prompt marks the field
|
|
518
|
-
// REQUIRED and the consolidate critic gates on it, but that critic has no
|
|
519
|
-
// runtime backstop — this deterministic finding is the cheap backstop.
|
|
520
|
-
// Independent of the numeric sizing layers below.
|
|
521
374
|
out.push(...computeMissingReasonToExistFinding(story));
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
)
|
|
534
|
-
);
|
|
535
|
-
|
|
536
|
-
// Acceptance mass is advisory-only (Story #4312's under-size heuristic is
|
|
537
|
-
// the merge signal; the former hard `maxAcceptance` rejection is removed).
|
|
538
|
-
// A long binding contract is a re-check-cohesion nudge, never by itself a
|
|
539
|
-
// decomposition error — cohesion and the delivery envelope govern Story
|
|
540
|
-
// size, and the delivery-schedule simulation in the decomposer prompt owns
|
|
541
|
-
// the fragmentation/consolidation judgment.
|
|
542
|
-
if (acceptance.length > sizing.softAcceptanceCount) {
|
|
543
|
-
out.push(
|
|
544
|
-
makeSoftWidth(
|
|
545
|
-
story.slug,
|
|
546
|
-
'acceptance',
|
|
547
|
-
acceptance.length,
|
|
548
|
-
sizing.softAcceptanceCount,
|
|
549
|
-
),
|
|
550
|
-
);
|
|
375
|
+
out.push(...computeMergeCandidateFinding(story, mass, ceilings));
|
|
376
|
+
|
|
377
|
+
// Glob entries mark the change footprint as unknown-width. A non-wide Story
|
|
378
|
+
// carrying globs still gets an informational nudge to declare `wide` when
|
|
379
|
+
// the authored mass alone does not already trip a capacity finding.
|
|
380
|
+
if (mass.hasGlobs && !declaredWide) {
|
|
381
|
+
out.push({
|
|
382
|
+
kind: 'wide-undeclared',
|
|
383
|
+
severity: 'soft',
|
|
384
|
+
ticketSlug: story.slug,
|
|
385
|
+
reason: 'glob-changes',
|
|
386
|
+
});
|
|
551
387
|
}
|
|
552
388
|
|
|
553
|
-
const {
|
|
554
|
-
|
|
555
|
-
// Glob entries mark the Story as unknown-width: a glob cannot be bounded by
|
|
556
|
-
// the numeric ceiling, so skip it. A non-wide Story carrying globs gets an
|
|
557
|
-
// informational nudge to either narrow the change or declare `wide`.
|
|
558
|
-
if (hasGlobs) {
|
|
559
|
-
if (!declaredWide) {
|
|
560
|
-
out.push({
|
|
561
|
-
kind: 'wide-undeclared',
|
|
562
|
-
severity: 'soft',
|
|
563
|
-
ticketSlug: story.slug,
|
|
564
|
-
reason: 'glob-changes',
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
return out;
|
|
568
|
-
}
|
|
389
|
+
const { softSessionTokens, hardSessionTokens } = ceilings;
|
|
569
390
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
makeOversized(story.slug, 'fileCount', fileCount, sizing.hardFiles),
|
|
574
|
-
);
|
|
575
|
-
} else if (fileCount > sizing.softFiles) {
|
|
391
|
+
if (mass.sessionMass > hardSessionTokens && !declaredWide) {
|
|
392
|
+
out.push(makeOversized(story.slug, mass.sessionMass, hardSessionTokens));
|
|
393
|
+
} else if (mass.sessionMass > softSessionTokens) {
|
|
576
394
|
if (declaredWide) {
|
|
577
|
-
// Declared-wide Stories still surface the width as advisory signal.
|
|
578
395
|
out.push(
|
|
579
|
-
|
|
396
|
+
makeSoftSessionPressure(
|
|
397
|
+
story.slug,
|
|
398
|
+
mass.sessionMass,
|
|
399
|
+
softSessionTokens,
|
|
400
|
+
),
|
|
580
401
|
);
|
|
581
402
|
} else {
|
|
582
|
-
// Wide footprint with no `wide` declaration: informational nudge to
|
|
583
|
-
// either merge the Story down (cohesion) or declare `wide` with a reason.
|
|
584
403
|
out.push({
|
|
585
404
|
kind: 'wide-undeclared',
|
|
586
405
|
severity: 'soft',
|
|
587
406
|
ticketSlug: story.slug,
|
|
588
|
-
|
|
589
|
-
|
|
407
|
+
sessionMass: mass.sessionMass,
|
|
408
|
+
softSessionTokens,
|
|
590
409
|
});
|
|
591
410
|
}
|
|
592
411
|
}
|
|
@@ -596,23 +415,20 @@ function computeStorySizingFindings(story, sizing) {
|
|
|
596
415
|
|
|
597
416
|
/**
|
|
598
417
|
* Compute the full structured findings array for a normalized ticket
|
|
599
|
-
* hierarchy.
|
|
600
|
-
* `findings`; the AC-visible `errors[]` channel is the rendered
|
|
601
|
-
* subset where `severity === 'hard'`.
|
|
602
|
-
*
|
|
603
|
-
* 2-tier (Epic #3238): each Story is its own implementation unit and
|
|
604
|
-
* carries the `body` (acceptance / changes / wide) that the sizing layers
|
|
605
|
-
* score. There is no Task tier, so findings are computed directly over
|
|
606
|
-
* `stories`.
|
|
418
|
+
* hierarchy.
|
|
607
419
|
*
|
|
608
|
-
* @param {{ stories: object[],
|
|
420
|
+
* @param {{ stories: object[], capacity?: object }} input
|
|
609
421
|
* @returns {object[]}
|
|
610
422
|
*/
|
|
611
|
-
export function computeSizingFindings({ stories,
|
|
612
|
-
const merged = {
|
|
423
|
+
export function computeSizingFindings({ stories, capacity }) {
|
|
424
|
+
const merged = {
|
|
425
|
+
...DEFAULT_MODEL_CAPACITY,
|
|
426
|
+
...(capacity ?? {}),
|
|
427
|
+
};
|
|
428
|
+
const ceilings = resolveCapacityCeilings(merged);
|
|
613
429
|
const findings = [];
|
|
614
430
|
for (const story of stories ?? []) {
|
|
615
|
-
findings.push(...computeStorySizingFindings(story, merged));
|
|
431
|
+
findings.push(...computeStorySizingFindings(story, merged, ceilings));
|
|
616
432
|
}
|
|
617
433
|
return findings;
|
|
618
434
|
}
|
|
@@ -622,7 +438,7 @@ export function computeSizingFindings({ stories, sizing }) {
|
|
|
622
438
|
*/
|
|
623
439
|
export function renderHardFindingError(finding) {
|
|
624
440
|
if (finding.kind === 'oversized-task') {
|
|
625
|
-
return `
|
|
441
|
+
return `Story "${finding.ticketSlug}" exceeds the session-capacity ceiling: observed ${finding.observed} estimated tokens, max ${finding.ceiling}. Size the Story to what one guarded session can deliver and self-verify, or declare \`wide\` with a one-line reason.`;
|
|
626
442
|
}
|
|
627
|
-
return `
|
|
443
|
+
return `Story "${finding.ticketSlug}" tripped hard finding ${finding.kind}.`;
|
|
628
444
|
}
|