mandrel 2.0.0 → 2.2.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 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +24 -68
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +17 -9
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* story-plan-state.js — read the v2 Story planning checkpoint.
|
|
3
|
+
*
|
|
4
|
+
* `plan-persist.js` upserts one `story-plan-state` structured comment per
|
|
5
|
+
* created Story carrying the persist receipt (when the plan completed, how many
|
|
6
|
+
* Stories it created, and their ids). Story #4542 removed the risk fields it
|
|
7
|
+
* used to carry: the planner-authored verdict, the envelope derived from it,
|
|
8
|
+
* and the review routing computed from that envelope. Nothing read any of them
|
|
9
|
+
* back — review depth is now derived from the diff at close time
|
|
10
|
+
* (`review-depth.js#deriveChangeLevel`), so no checkpoint read sits on the
|
|
11
|
+
* delivery path at all.
|
|
12
|
+
*/
|
|
13
|
+
|
|
1
14
|
import { parseFencedJsonComment } from './structured-comment-parser.js';
|
|
2
15
|
import { findStructuredComment } from './ticketing.js';
|
|
3
16
|
|
|
4
17
|
/**
|
|
5
18
|
* Read the v2 Story planning checkpoint. Missing/malformed comments degrade
|
|
6
|
-
* to null
|
|
19
|
+
* to null — an unplanned Story simply has no persist receipt.
|
|
7
20
|
*/
|
|
8
21
|
export async function readStoryPlanState({
|
|
9
22
|
provider,
|
|
@@ -18,31 +31,3 @@ export async function readStoryPlanState({
|
|
|
18
31
|
const state = parseFencedJsonComment(comment);
|
|
19
32
|
return state && typeof state === 'object' ? state : null;
|
|
20
33
|
}
|
|
21
|
-
|
|
22
|
-
export async function readStoryPlanningRisk(args) {
|
|
23
|
-
const state = await readStoryPlanState(args);
|
|
24
|
-
return state?.planningRisk ?? null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function readStoryPlanningRiskSafe(args) {
|
|
28
|
-
try {
|
|
29
|
-
return await readStoryPlanningRisk(args);
|
|
30
|
-
} catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Prefer an explicit `planningRisk` override; otherwise load the Story
|
|
37
|
-
* checkpoint. Callers that omit the field (or pass `undefined`) get the
|
|
38
|
-
* persisted plan risk; callers that pass `null` keep the neutral posture.
|
|
39
|
-
*/
|
|
40
|
-
export async function resolveStoryPlanningRisk({
|
|
41
|
-
provider,
|
|
42
|
-
storyId,
|
|
43
|
-
planningRisk,
|
|
44
|
-
findCommentFn,
|
|
45
|
-
}) {
|
|
46
|
-
if (planningRisk !== undefined) return planningRisk;
|
|
47
|
-
return readStoryPlanningRiskSafe({ provider, storyId, findCommentFn });
|
|
48
|
-
}
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
* name at least one path-shaped token so vague verbs ("clean up",
|
|
25
25
|
* "refactor") can't slip through.
|
|
26
26
|
*
|
|
27
|
+
* `acceptance` / `verify` are the **top-level machine contract** (Story
|
|
28
|
+
* #4541). The decomposer prompt tells authors to write those lists once at
|
|
29
|
+
* the ticket's top level and omit the matching body sections; persist syncs
|
|
30
|
+
* them into the body at assemble time. Validation runs *before* that sync,
|
|
31
|
+
* so this validator resolves each contract field from the parsed body and
|
|
32
|
+
* falls back to the ticket's top-level array when the body section is
|
|
33
|
+
* absent. Without that fallback the validator rejected the very shape its
|
|
34
|
+
* own prompt prescribes.
|
|
35
|
+
*
|
|
27
36
|
* `body.changes` items must be object-form `{ path: string, assumption: enum }`
|
|
28
37
|
* entries (Story #2636 shape). Plain string bullets are rejected at parse
|
|
29
38
|
* time and by this validator.
|
|
@@ -51,8 +60,8 @@ import { FILE_ASSUMPTION_VALUES } from './file-assumption-enum.js';
|
|
|
51
60
|
|
|
52
61
|
/**
|
|
53
62
|
* Canonical testing-tier labels that a `verify[]` entry must name (in
|
|
54
|
-
* parentheses) to pass plan-time validation. Mirrors the
|
|
55
|
-
*
|
|
63
|
+
* parentheses) to pass plan-time validation. Mirrors the verify-rules contract
|
|
64
|
+
* in `.agents/scripts/lib/templates/decomposer-prompts.js`.
|
|
56
65
|
*
|
|
57
66
|
* Entries that do not end with `(<tier>)` and are not `manual:<reason>` are
|
|
58
67
|
* rejected by `collectVerifyErrors`.
|
|
@@ -164,6 +173,39 @@ function resolveStructuredBody(ticket) {
|
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
175
|
|
|
176
|
+
/**
|
|
177
|
+
* The two contract fields that live at the ticket's top level and are
|
|
178
|
+
* synced into the body by `plan-persist` at assemble time.
|
|
179
|
+
*/
|
|
180
|
+
const CONTRACT_FIELDS = Object.freeze(['acceptance', 'verify']);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Resolve the body's contract fields against the ticket's top-level arrays
|
|
184
|
+
* (Story #4541). The decomposer prompt prescribes authoring `acceptance[]`
|
|
185
|
+
* / `verify[]` **once** at top level and omitting the matching body
|
|
186
|
+
* sections; `assemblePlanStories#syncContractFieldFromTopLevel` performs
|
|
187
|
+
* the sync, but it runs *after* validation. So an absent body section is
|
|
188
|
+
* not a violation when the ticket carries the list at top level — it is the
|
|
189
|
+
* preferred shape. A body section that is present and disagrees with the
|
|
190
|
+
* top level is left alone here: the sync itself fails closed on that
|
|
191
|
+
* mismatch, and duplicating the check would report it twice.
|
|
192
|
+
*
|
|
193
|
+
* @param {object} ticket
|
|
194
|
+
* @param {object} bodyObject Parsed / structured body.
|
|
195
|
+
* @returns {object} A copy of `bodyObject` with the contract fields resolved.
|
|
196
|
+
*/
|
|
197
|
+
function resolveContractFieldsFromTopLevel(ticket, bodyObject) {
|
|
198
|
+
const resolved = { ...bodyObject };
|
|
199
|
+
for (const field of CONTRACT_FIELDS) {
|
|
200
|
+
const bodyValue = Array.isArray(resolved[field]) ? resolved[field] : [];
|
|
201
|
+
if (bodyValue.length > 0) continue;
|
|
202
|
+
const topLevel = Array.isArray(ticket?.[field]) ? ticket[field] : [];
|
|
203
|
+
if (topLevel.length === 0) continue;
|
|
204
|
+
resolved[field] = topLevel.map(String);
|
|
205
|
+
}
|
|
206
|
+
return resolved;
|
|
207
|
+
}
|
|
208
|
+
|
|
167
209
|
/**
|
|
168
210
|
* Validate one Story body and return every violation it exhibits. Empty
|
|
169
211
|
* array means clean. Splits the per-ticket cascade out of
|
|
@@ -182,13 +224,14 @@ function resolveStructuredBody(ticket) {
|
|
|
182
224
|
*/
|
|
183
225
|
export function validateTaskBodyShape(ticket) {
|
|
184
226
|
const prefix = `Story "${ticket.title}" (${ticket.slug})`;
|
|
185
|
-
const { body, error } = resolveStructuredBody(ticket);
|
|
227
|
+
const { body: parsed, error } = resolveStructuredBody(ticket);
|
|
186
228
|
if (error !== null) {
|
|
187
229
|
return [error];
|
|
188
230
|
}
|
|
189
|
-
if (
|
|
190
|
-
return [`${prefix}: body must be an object, got ${typeof
|
|
231
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
232
|
+
return [`${prefix}: body must be an object, got ${typeof parsed}.`];
|
|
191
233
|
}
|
|
234
|
+
const body = resolveContractFieldsFromTopLevel(ticket, parsed);
|
|
192
235
|
const errors = [];
|
|
193
236
|
if (typeof body.goal !== 'string' || body.goal.trim() === '') {
|
|
194
237
|
errors.push(`${prefix}: body.goal must be a non-empty string.`);
|
|
@@ -300,7 +343,9 @@ function collectReferencesErrors(prefix, rawReferences) {
|
|
|
300
343
|
function collectAcceptanceErrors(prefix, rawAcceptance) {
|
|
301
344
|
const acceptance = Array.isArray(rawAcceptance) ? rawAcceptance : [];
|
|
302
345
|
if (acceptance.length === 0) {
|
|
303
|
-
return [
|
|
346
|
+
return [
|
|
347
|
+
`${prefix}: acceptance must list at least one criterion — author it at the ticket's top level (preferred) or in the body's ## Acceptance section.`,
|
|
348
|
+
];
|
|
304
349
|
}
|
|
305
350
|
return [];
|
|
306
351
|
}
|
|
@@ -323,7 +368,7 @@ function collectVerifyErrors(prefix, rawVerify) {
|
|
|
323
368
|
const verify = Array.isArray(rawVerify) ? rawVerify : [];
|
|
324
369
|
if (verify.length === 0) {
|
|
325
370
|
return [
|
|
326
|
-
`${prefix}:
|
|
371
|
+
`${prefix}: verify must list at least one entry — author it at the ticket's top level (preferred) or in the body's ## Verify section. Use "manual:<reason>" only when truly unverifiable in isolation.`,
|
|
327
372
|
];
|
|
328
373
|
}
|
|
329
374
|
const errors = [];
|
|
@@ -5,9 +5,19 @@
|
|
|
5
5
|
* an exclusive, time-bounded claim on a ticket so two concurrent runs do not
|
|
6
6
|
* both drive the same Story. Rather than invent a new state column, the lease
|
|
7
7
|
* rides the ticket's existing **assignees** surface: the single assignee *is*
|
|
8
|
-
* the lease owner. Liveness is decided by the owner's
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* the lease owner. Liveness is decided by the owner's last-heartbeat epoch-ms
|
|
9
|
+
* compared against a configured TTL.
|
|
10
|
+
*
|
|
11
|
+
* **There is no live heartbeat source.** The `story.heartbeat` emitter this
|
|
12
|
+
* module was designed against was structurally inert (it demanded an
|
|
13
|
+
* `epicId >= 1` that v2, which has no Epics, never supplies) and has been
|
|
14
|
+
* deleted. Every caller now reaches `acquireLease` through
|
|
15
|
+
* `lease-guard-shared.acquireLeaseFailClosed` with `anchorHeartbeatToNow`,
|
|
16
|
+
* which pins `heartbeatAt` to `now`: the lease **fails closed**, so any
|
|
17
|
+
* foreign claim reads as live and refuses. A stranded claim is cleared with
|
|
18
|
+
* `--steal`, never by TTL expiry. The TTL and the stale-reclaim branch below
|
|
19
|
+
* are therefore reachable only via an explicit caller-supplied `heartbeatAt`
|
|
20
|
+
* — the seam is kept, the automatic expiry is not real.
|
|
11
21
|
*
|
|
12
22
|
* The three exported operations are deliberately thin and provider-agnostic:
|
|
13
23
|
*
|
|
@@ -31,19 +41,16 @@
|
|
|
31
41
|
* Liveness seam: callers supply the owner's last-heartbeat epoch-ms via the
|
|
32
42
|
* `heartbeatAt` option (a number, or `null`/`undefined` when no heartbeat has
|
|
33
43
|
* ever been recorded for the current owner). Threading the timestamp in keeps
|
|
34
|
-
* this module pure and trivially unit-testable — it does not read
|
|
35
|
-
*
|
|
36
|
-
*
|
|
44
|
+
* this module pure and trivially unit-testable — it does not read any ledger
|
|
45
|
+
* itself. A claim with no heartbeat is treated as stale (reclaimable) by
|
|
46
|
+
* `isClaimLive`; note the live guards never take that branch, per the
|
|
47
|
+
* fail-closed anchoring described above.
|
|
37
48
|
*
|
|
38
49
|
* `now` is injectable (epoch ms) for deterministic tests; it defaults to
|
|
39
50
|
* `Date.now()`.
|
|
40
51
|
*/
|
|
41
52
|
|
|
42
|
-
import { readFileSync } from 'node:fs';
|
|
43
|
-
|
|
44
53
|
import { resolveLeaseTtlMs } from '../config/limits.js';
|
|
45
|
-
import { epicLedgerPath } from '../config/temp-paths.js';
|
|
46
|
-
import { parseLedger } from './lifecycle/trace-logger.js';
|
|
47
54
|
|
|
48
55
|
/**
|
|
49
56
|
* The shipped, non-personal operator-identity placeholder (and its bare,
|
|
@@ -87,76 +94,22 @@ export function normalizeOperatorHandle(raw) {
|
|
|
87
94
|
return trimmed;
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
/**
|
|
91
|
-
* Read the most-recent `story.heartbeat` epoch-ms recorded for a given lease
|
|
92
|
-
* owner from an Epic lifecycle ledger. Returns `null` when the ledger is
|
|
93
|
-
* absent, unreadable, or carries no heartbeat for that owner — which the lease
|
|
94
|
-
* primitive treats as a stale (reclaimable) claim.
|
|
95
|
-
*
|
|
96
|
-
* The ledger is NDJSON; each `story.heartbeat` record carries
|
|
97
|
-
* `payload.operator` (Story #3480) and `payload.timestamp` (ISO-8601). A
|
|
98
|
-
* malformed ledger downgrades to `null` rather than throwing so a corrupt
|
|
99
|
-
* observability artifact never wedges the lease preflight.
|
|
100
|
-
*
|
|
101
|
-
* This is the shared liveness source the lease guards thread into
|
|
102
|
-
* `acquireLease` via `heartbeatAt`; `/plan` and `/deliver` both
|
|
103
|
-
* reuse it so a live foreign claim actually refuses.
|
|
104
|
-
*
|
|
105
|
-
* @param {object} args
|
|
106
|
-
* @param {number} args.epicId
|
|
107
|
-
* @param {string} args.owner Lease owner whose heartbeat to find.
|
|
108
|
-
* @param {object} [args.config] Resolved config (for ledger path).
|
|
109
|
-
* @param {string} [args.ledgerPath] Explicit path override (tests).
|
|
110
|
-
* @param {(eid: number, config?: object) => string} [args.ledgerPathResolver]
|
|
111
|
-
* Injectable resolver (tests). Defaults to `epicLedgerPath`.
|
|
112
|
-
* @param {(p: string) => string} [args.readFile] Injectable reader (tests).
|
|
113
|
-
* @returns {number|null}
|
|
114
|
-
*/
|
|
115
|
-
export function latestHeartbeatForOwner({
|
|
116
|
-
epicId,
|
|
117
|
-
owner,
|
|
118
|
-
config,
|
|
119
|
-
ledgerPath,
|
|
120
|
-
ledgerPathResolver = epicLedgerPath,
|
|
121
|
-
readFile = (p) => readFileSync(p, 'utf8'),
|
|
122
|
-
}) {
|
|
123
|
-
if (typeof owner !== 'string' || owner.length === 0) return null;
|
|
124
|
-
const resolvedPath = ledgerPath ?? ledgerPathResolver(epicId, config);
|
|
125
|
-
|
|
126
|
-
let text;
|
|
127
|
-
try {
|
|
128
|
-
text = readFile(resolvedPath);
|
|
129
|
-
} catch (_err) {
|
|
130
|
-
// No ledger yet (fresh Epic) → no heartbeat → reclaimable.
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let records;
|
|
135
|
-
try {
|
|
136
|
-
records = parseLedger(text);
|
|
137
|
-
} catch (_err) {
|
|
138
|
-
// Corrupt ledger is an observability problem, not a coordination blocker.
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let latest = null;
|
|
143
|
-
for (const record of records) {
|
|
144
|
-
const payload = record?.payload;
|
|
145
|
-
if (!payload || payload.event !== 'story.heartbeat') continue;
|
|
146
|
-
if (payload.operator !== owner) continue;
|
|
147
|
-
const ts = Date.parse(payload.timestamp ?? '');
|
|
148
|
-
if (!Number.isFinite(ts)) continue;
|
|
149
|
-
if (latest === null || ts > latest) latest = ts;
|
|
150
|
-
}
|
|
151
|
-
return latest;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
97
|
/**
|
|
155
98
|
* Decide whether a foreign claim is still "live" given the owner's last
|
|
156
99
|
* heartbeat and the configured TTL. A claim is live when a heartbeat exists
|
|
157
100
|
* and is no older than `ttlMs`. A missing heartbeat (`null`/`undefined`) or a
|
|
158
101
|
* heartbeat older than the TTL is stale and therefore reclaimable.
|
|
159
102
|
*
|
|
103
|
+
* The `heartbeatAt` seam is retained, but there is no longer any in-repo
|
|
104
|
+
* heartbeat *source*: the `story.heartbeat` emitter was structurally inert
|
|
105
|
+
* (it required an `epicId >= 1` that v2 never sets) and was deleted along
|
|
106
|
+
* with the ledger reader that scanned for it. Every live caller reaches this
|
|
107
|
+
* through `lease-guard-shared.acquireLeaseFailClosed` with
|
|
108
|
+
* `anchorHeartbeatToNow`, which pins `heartbeatAt` to `now` so ANY foreign
|
|
109
|
+
* claim reads live and the guard fails closed — a stranded claim is cleared
|
|
110
|
+
* with `--steal`, not by TTL expiry. The parameter stays because that
|
|
111
|
+
* anchoring is expressed through it.
|
|
112
|
+
*
|
|
160
113
|
* @param {object} args
|
|
161
114
|
* @param {number|null|undefined} args.heartbeatAt Owner's last heartbeat (epoch ms).
|
|
162
115
|
* @param {number} args.ttlMs Lease TTL in milliseconds.
|
|
@@ -589,13 +589,53 @@ function registryRegistry(producers, assumptionEntries, patterns, cache) {
|
|
|
589
589
|
}
|
|
590
590
|
|
|
591
591
|
/**
|
|
592
|
-
*
|
|
592
|
+
* Normalize a fan-out probe result into `{ count, files, probe }`.
|
|
593
593
|
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
594
|
+
* The production probe reports its referencing files and the exact command
|
|
595
|
+
* that found them so an operator can reproduce the figure (Story #4547).
|
|
596
|
+
* A bare number stays valid — injected test counters and any consumer
|
|
597
|
+
* counter written against the Story #2962 contract keep working, they just
|
|
598
|
+
* carry no audit trail.
|
|
599
|
+
*/
|
|
600
|
+
function normalizeFanOutProbe(result) {
|
|
601
|
+
if (typeof result === 'number') {
|
|
602
|
+
return { count: result, files: [], probe: null };
|
|
603
|
+
}
|
|
604
|
+
if (result === null || typeof result !== 'object') {
|
|
605
|
+
return { count: 0, files: [], probe: null };
|
|
606
|
+
}
|
|
607
|
+
const files = Array.isArray(result.files) ? result.files : [];
|
|
608
|
+
const count = Number.isFinite(result.count) ? result.count : files.length;
|
|
609
|
+
return { count, files, probe: result.probe ?? null };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Index the basenames this spec *creates*, so a deletion that is really one
|
|
614
|
+
* half of a move can be told apart from a genuine wide-coupling removal.
|
|
615
|
+
*/
|
|
616
|
+
function indexCreatedBasenames(assumptionEntries) {
|
|
617
|
+
const byBasename = new Map();
|
|
618
|
+
for (const entry of assumptionEntries) {
|
|
619
|
+
if (entry.assumption !== 'creates') continue;
|
|
620
|
+
const base = entry.path.slice(entry.path.lastIndexOf('/') + 1);
|
|
621
|
+
if (!byBasename.has(base)) byBasename.set(base, entry.path);
|
|
622
|
+
}
|
|
623
|
+
return byBasename;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Compute `fan-out-warning` findings (Story #2962, reworked in #4547).
|
|
628
|
+
*
|
|
629
|
+
* For each `body.changes` entry whose `assumption` is `"deletes"`, probe the
|
|
630
|
+
* files at the base branch that genuinely import or require the deleted
|
|
631
|
+
* module. When that count exceeds the configured `largeFanOutThreshold`,
|
|
632
|
+
* emit a finding carrying the referencing files and the probe that produced
|
|
633
|
+
* them.
|
|
634
|
+
*
|
|
635
|
+
* The finding also records whether the deletion is **rename-shaped** — the
|
|
636
|
+
* same spec creates a file with the deleted module's basename elsewhere —
|
|
637
|
+
* because the remedy diverges: a move wants its importers repointed in one
|
|
638
|
+
* Story, not a subsystem-by-subsystem migration split across several.
|
|
599
639
|
*
|
|
600
640
|
* The default severity is always `'soft'` — the persist gate enforces a
|
|
601
641
|
* hard refusal via the `--allow-large-fan-out` operator flag, since the
|
|
@@ -613,21 +653,28 @@ function computeFanOutFindings({
|
|
|
613
653
|
if (!Number.isFinite(threshold) || threshold < 0) return [];
|
|
614
654
|
const findings = [];
|
|
615
655
|
const cache = new Map();
|
|
656
|
+
const createdBasenames = indexCreatedBasenames(assumptionEntries);
|
|
616
657
|
for (const entry of assumptionEntries) {
|
|
617
658
|
if (entry.assumption !== 'deletes') continue;
|
|
618
|
-
let
|
|
619
|
-
if (
|
|
620
|
-
|
|
621
|
-
cache.set(entry.path,
|
|
659
|
+
let probed = cache.get(entry.path);
|
|
660
|
+
if (probed === undefined) {
|
|
661
|
+
probed = normalizeFanOutProbe(counter({ path: entry.path }));
|
|
662
|
+
cache.set(entry.path, probed);
|
|
622
663
|
}
|
|
623
|
-
if (count <= threshold) continue;
|
|
664
|
+
if (probed.count <= threshold) continue;
|
|
665
|
+
const base = entry.path.slice(entry.path.lastIndexOf('/') + 1);
|
|
666
|
+
const renameTarget = createdBasenames.get(base);
|
|
624
667
|
findings.push({
|
|
625
668
|
kind: 'fan-out-warning',
|
|
626
669
|
severity,
|
|
627
670
|
taskSlug: entry.taskSlug,
|
|
628
671
|
storySlug: entry.storySlug,
|
|
629
672
|
path: entry.path,
|
|
630
|
-
callSiteCount: count,
|
|
673
|
+
callSiteCount: probed.count,
|
|
674
|
+
callSites: probed.files,
|
|
675
|
+
probe: probed.probe,
|
|
676
|
+
renameShaped: renameTarget !== undefined && renameTarget !== entry.path,
|
|
677
|
+
renameTarget: renameTarget === entry.path ? null : (renameTarget ?? null),
|
|
631
678
|
threshold,
|
|
632
679
|
});
|
|
633
680
|
}
|
|
@@ -701,6 +748,60 @@ export function computeConflictFindings({ stories, policy } = {}) {
|
|
|
701
748
|
];
|
|
702
749
|
}
|
|
703
750
|
|
|
751
|
+
/**
|
|
752
|
+
* Render the audit trail behind a fan-out finding's number, so an operator
|
|
753
|
+
* can check the figure rather than trust it (Story #4547).
|
|
754
|
+
*
|
|
755
|
+
* Every importer is named — the list is deliberately **not** truncated. A
|
|
756
|
+
* gate that fires at 100 importers is precisely when the operator needs the
|
|
757
|
+
* list, and a `…and 109 more` tail would leave the figure uncheckable in
|
|
758
|
+
* exactly the case the gate exists for. This message is a fail-closed stop,
|
|
759
|
+
* not a log line; its length is the point.
|
|
760
|
+
*
|
|
761
|
+
* The probe is reported as what it is — the *candidate* net, which each hit
|
|
762
|
+
* is then re-resolved against. It will report at least as many lines as the
|
|
763
|
+
* gate counts files, so labelling it as the thing that produced the number
|
|
764
|
+
* would send an operator chasing a discrepancy that is by design.
|
|
765
|
+
*
|
|
766
|
+
* Returns `''` for a bare-number counter, which carries no audit trail.
|
|
767
|
+
*/
|
|
768
|
+
export function renderFanOutEvidence(finding) {
|
|
769
|
+
const files = Array.isArray(finding.callSites) ? finding.callSites : [];
|
|
770
|
+
const parts = [];
|
|
771
|
+
if (files.length > 0) {
|
|
772
|
+
parts.push(` Importers (${files.length}):`);
|
|
773
|
+
for (const file of files) parts.push(` ${file}`);
|
|
774
|
+
}
|
|
775
|
+
if (finding.probe) {
|
|
776
|
+
parts.push(
|
|
777
|
+
` Candidate probe (each hit re-resolved against its importer's directory):`,
|
|
778
|
+
` ${finding.probe}`,
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
return parts.length > 0 ? `\n${parts.join('\n')}` : '';
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Render the remedy that actually fits the finding. A rename-shaped
|
|
786
|
+
* deletion has nowhere to split to — the importers just need repointing at
|
|
787
|
+
* the path the same plan creates — so telling the operator to split it
|
|
788
|
+
* across Stories leaves the override as the only exit, which is exactly the
|
|
789
|
+
* habit that defeats the gate (Story #4547).
|
|
790
|
+
*/
|
|
791
|
+
export function renderFanOutRemedy(finding) {
|
|
792
|
+
if (finding.renameShaped && finding.renameTarget) {
|
|
793
|
+
return (
|
|
794
|
+
`This deletion is rename-shaped: the same plan creates "${finding.renameTarget}" under the same basename. ` +
|
|
795
|
+
`Repoint the importer(s) at the new path inside this Story — a move has no subsystems to split across — ` +
|
|
796
|
+
`then rerun --allow-large-fan-out.`
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return (
|
|
800
|
+
`Split the deletion into a subsystem-by-subsystem migration across multiple Stories, ` +
|
|
801
|
+
`or rerun --allow-large-fan-out after confirming the deletion is intentional.`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
704
805
|
/**
|
|
705
806
|
* Render a `'hard'`-severity conflict finding as a human-readable error
|
|
706
807
|
* message. Used by the validator when policy flags upgrade a finding to
|
|
@@ -712,14 +813,18 @@ export function renderHardConflictError(finding) {
|
|
|
712
813
|
return `Shared-editor conflict: "${finding.path}" is written by ${finding.storySlugs.length} concurrent Stories (${stories}). Add depends_on chains between them or split the edits into a dedicated late-wave wiring Story.`;
|
|
713
814
|
}
|
|
714
815
|
if (finding.kind === 'implicit-cross-story-dep') {
|
|
715
|
-
return `Implicit cross-Story dependency:
|
|
816
|
+
return `Implicit cross-Story dependency: Story "${finding.consumer.storySlug}" references "${finding.path}" (produced by Story "${finding.producer.storySlug}") via body.${finding.consumer.sourceField}, but Story "${finding.consumer.storySlug}" has no depends_on link to Story "${finding.producer.storySlug}". Add depends_on: ["${finding.producer.storySlug}"] to the consumer Story or remove the reference.`;
|
|
716
817
|
}
|
|
717
818
|
if (finding.kind === 'cross-cutting-registries') {
|
|
718
819
|
const stories = finding.storySlugs.map((s) => `"${s}"`).join(', ');
|
|
719
820
|
return `Cross-cutting registry conflict: ${finding.storySlugs.length} concurrent Stories (${stories}) edit or register into "${finding.registryPath}". Add depends_on chains between them so the registry updates serialize, or split the registration into a dedicated late-wave wiring Story.`;
|
|
720
821
|
}
|
|
721
822
|
if (finding.kind === 'fan-out-warning') {
|
|
722
|
-
return
|
|
823
|
+
return (
|
|
824
|
+
`Large fan-out: Story "${finding.storySlug}" deletes "${finding.path}" ` +
|
|
825
|
+
`with ${finding.callSiteCount} importer(s) on the base branch (threshold ${finding.threshold}). ` +
|
|
826
|
+
`${renderFanOutRemedy(finding)}${renderFanOutEvidence(finding)}`
|
|
827
|
+
);
|
|
723
828
|
}
|
|
724
829
|
if (finding.kind === 'missing-bdd-scaffold') {
|
|
725
830
|
return `Missing BDD scaffold: Story "${finding.consumer.storySlug}" verifies against "${finding.path}" (created by Story "${finding.producer.storySlug}") via body.${finding.consumer.sourceField}, but "${finding.consumer.storySlug}" has no depends_on path to "${finding.producer.storySlug}" — the .feature file is scaffolded in the same wave (or later), so verification runs before the file exists. Add depends_on: ["${finding.producer.storySlug}"] to the consumer Story so the scaffold lands in an earlier wave.`;
|
|
@@ -14,10 +14,9 @@
|
|
|
14
14
|
*
|
|
15
15
|
* `DEFAULT_MODEL_CAPACITY` is the **single source of truth** for the capacity
|
|
16
16
|
* thresholds. The decomposer prompt template
|
|
17
|
-
* (`.agents/scripts/lib/templates/decomposer-prompts.js`)
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* its threshold sentence from this constant so the two surfaces cannot drift.
|
|
17
|
+
* (`.agents/scripts/lib/templates/decomposer-prompts.js`) generates its
|
|
18
|
+
* threshold sentence from this constant rather than restating divergent
|
|
19
|
+
* numbers, so the two surfaces cannot drift.
|
|
21
20
|
*
|
|
22
21
|
* Capacity model:
|
|
23
22
|
* - Plan-time **session mass** = authored tokens only (`estimateTokens` of
|