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
|
@@ -41,7 +41,13 @@ export async function flipLabelAndNotify({
|
|
|
41
41
|
config,
|
|
42
42
|
progress,
|
|
43
43
|
}) {
|
|
44
|
-
const labelFlipped = await flipLabel(
|
|
44
|
+
const labelFlipped = await flipLabel(
|
|
45
|
+
provider,
|
|
46
|
+
storyId,
|
|
47
|
+
story,
|
|
48
|
+
progress,
|
|
49
|
+
config,
|
|
50
|
+
);
|
|
45
51
|
if (!labelFlipped) return;
|
|
46
52
|
await fireStoryClosingNotify({
|
|
47
53
|
notifyFn: notifyFn ?? defaultNotify,
|
|
@@ -55,7 +61,7 @@ export async function flipLabelAndNotify({
|
|
|
55
61
|
});
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
async function flipLabel(provider, storyId, story, progress) {
|
|
64
|
+
async function flipLabel(provider, storyId, story, progress, config) {
|
|
59
65
|
try {
|
|
60
66
|
// Story #3385 — flip to `agent::closing`, NOT `agent::done`. The
|
|
61
67
|
// canonical mutator (`transitionTicketState`) only closes the GitHub
|
|
@@ -81,8 +87,13 @@ async function flipLabel(provider, storyId, story, progress) {
|
|
|
81
87
|
// notification that `transitionTicketState` would dispatch is
|
|
82
88
|
// redundant with the typed `story-closing` event that
|
|
83
89
|
// `fireStoryClosingNotify` emits immediately afterwards.
|
|
90
|
+
//
|
|
91
|
+
// `config` is threaded so the on-disk board-metadata cache (issue #4555)
|
|
92
|
+
// lands under the project's configured `tempRoot` rather than the
|
|
93
|
+
// framework default, matching every other transition call site.
|
|
84
94
|
await transitionTicketState(provider, storyId, STATE_LABELS.CLOSING, {
|
|
85
95
|
ticketSnapshot: story,
|
|
96
|
+
config,
|
|
86
97
|
});
|
|
87
98
|
progress?.('LABELS', `🏷️ Story #${storyId} → agent::closing`);
|
|
88
99
|
return true;
|
|
@@ -30,6 +30,8 @@ const FRONTMATTER_DELIMITER = '---';
|
|
|
30
30
|
const POLICY_HEADING_RE = /^## Policy Capsule\s*$/;
|
|
31
31
|
const ANY_H2_RE = /^## /;
|
|
32
32
|
const BULLET_RE = /^- /;
|
|
33
|
+
/** An indented (wrapped) continuation of the bullet above it. */
|
|
34
|
+
const CONTINUATION_RE = /^\s+\S/;
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Repo root resolver. Walks up from the SKILL.md path until we find a
|
|
@@ -85,8 +87,14 @@ function extractFrontmatterBlock(lines, skillPath) {
|
|
|
85
87
|
/**
|
|
86
88
|
* Scan the body for the '## Policy Capsule' heading and count contiguous
|
|
87
89
|
* top-level '- ' bullets that follow it, stopping at the next '## ' heading
|
|
88
|
-
* or end-of-file. Blank lines inside the bullet run do not reset the count
|
|
89
|
-
*
|
|
90
|
+
* or end-of-file. Blank lines inside the bullet run do not reset the count.
|
|
91
|
+
*
|
|
92
|
+
* An **indented** continuation line belongs to the bullet above it — capsule
|
|
93
|
+
* bullets routinely wrap at the document's prose width — so it neither counts
|
|
94
|
+
* as a new bullet nor terminates the run. Only a **flush-left**, non-blank,
|
|
95
|
+
* non-bullet line after the run terminates it (Story #4546: treating any
|
|
96
|
+
* non-bullet line as a terminator counted a wrapped 5-bullet capsule as 1 and
|
|
97
|
+
* tripped the floor in validate-skills.js).
|
|
90
98
|
*/
|
|
91
99
|
function findPolicyCapsule(lines, bodyStart) {
|
|
92
100
|
let headingIndex = -1;
|
|
@@ -114,8 +122,13 @@ function findPolicyCapsule(lines, bodyStart) {
|
|
|
114
122
|
// Blank lines inside or before the bullet run are tolerated.
|
|
115
123
|
continue;
|
|
116
124
|
}
|
|
125
|
+
if (sawBulletRun && CONTINUATION_RE.test(line)) {
|
|
126
|
+
// Indented continuation of the bullet above — same bullet, not a
|
|
127
|
+
// terminator. Wrapped capsule bullets are the norm, not an error.
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
117
130
|
if (sawBulletRun) {
|
|
118
|
-
//
|
|
131
|
+
// Flush-left, non-blank, non-bullet line after the run terminates it.
|
|
119
132
|
break;
|
|
120
133
|
}
|
|
121
134
|
// Leading prose between heading and the first bullet is allowed.
|
|
@@ -42,16 +42,17 @@ import { parseBlockedBy } from './dependency-parser.js';
|
|
|
42
42
|
* Story records (live ticket payloads, fixture tickets, or operator
|
|
43
43
|
* DAG nodes).
|
|
44
44
|
* @param {object} [opts]
|
|
45
|
-
* @param {boolean} [opts.dropForeign=
|
|
46
|
-
* matching
|
|
45
|
+
* @param {boolean} [opts.dropForeign=false] When `false` (the v2 default,
|
|
46
|
+
* matching the `/deliver` path — `stories-wave-tick.js` and the
|
|
47
|
+
* `selectReadySet` core), the operator-DAG contract is preserved: a
|
|
48
|
+
* dependency on an id absent from the input is treated as not-yet-done
|
|
49
|
+
* and withholds the dependent until it completes. When `true` (the
|
|
50
|
+
* pre-v2 Epic-scoped-wrapper semantics), edges pointing at ids outside
|
|
47
51
|
* the supplied story set are dropped so the DAG stays closed over the
|
|
48
|
-
* scheduled set.
|
|
49
|
-
* `selectReadySet` core) passes `false` to preserve the operator-DAG
|
|
50
|
-
* contract, where a dependency on an id absent from the input is treated
|
|
51
|
-
* as not-yet-done and withholds the dependent until it completes.
|
|
52
|
+
* scheduled set.
|
|
52
53
|
* @returns {Map<number, number[]>}
|
|
53
54
|
*/
|
|
54
|
-
export function buildStoryAdjacency(stories, { dropForeign =
|
|
55
|
+
export function buildStoryAdjacency(stories, { dropForeign = false } = {}) {
|
|
55
56
|
const records = Array.isArray(stories) ? stories : [];
|
|
56
57
|
const storyIds = new Set(records.map((s) => Number(s?.id ?? s?.number)));
|
|
57
58
|
const adjacency = new Map();
|
|
@@ -105,10 +105,9 @@ import { FILE_ASSUMPTION_VALUES } from '../orchestration/file-assumption-enum.js
|
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
107
|
* @typedef {object} SerializeOptions
|
|
108
|
-
* @property {boolean} [includeFooter=false] - Include `---\nparent/
|
|
108
|
+
* @property {boolean} [includeFooter=false] - Include `---\nparent/blocked-by` footer.
|
|
109
109
|
* @property {object} [footer] - Footer fields when `includeFooter` is true.
|
|
110
110
|
* @property {number} [footer.parent] - Parent feature issue number.
|
|
111
|
-
* @property {number} [footer.epic] - Epic issue number.
|
|
112
111
|
*/
|
|
113
112
|
|
|
114
113
|
// ---------------------------------------------------------------------------
|
|
@@ -166,13 +165,35 @@ function stripListMarker(line) {
|
|
|
166
165
|
return line.replace(/^-\s+(?:\[\s*[xX ]?\s*\]\s+)?/, '').trim();
|
|
167
166
|
}
|
|
168
167
|
|
|
168
|
+
// Humanized PathEntry bullet (Story #4600): `path` — assumption. This is the
|
|
169
|
+
// shape serialize() now emits for `## Changes` / `## References`; the legacy
|
|
170
|
+
// inline-JSON object bullet remains accepted at parse time indefinitely (live
|
|
171
|
+
// issue bodies are never rewritten).
|
|
172
|
+
const HUMANIZED_PATH_ENTRY_RE = /^`([^`]+)`\s+—\s+(\S+)$/;
|
|
173
|
+
|
|
174
|
+
// AC-<n> presentation prefix on acceptance checkboxes (Story #4600). The
|
|
175
|
+
// numbering is a stable 1-based human handle only — parse() strips it so the
|
|
176
|
+
// top-level acceptance[] machine contract round-trips byte-identical.
|
|
177
|
+
const AC_PREFIX_RE = /^AC-\d+:\s+/;
|
|
178
|
+
|
|
179
|
+
// Visible wide-rationale line (Story #4600): `> **Wide:** <reason>` rendered
|
|
180
|
+
// under `## Goal`. Presentation only — the `<!-- meta -->` block stays the
|
|
181
|
+
// canonical machine carrier, so the parser skips this line wherever it
|
|
182
|
+
// appears (same treatment as the authored-provenance marker).
|
|
183
|
+
const WIDE_MARKER_LINE_RE = /^>\s*\*\*Wide:\*\*/;
|
|
184
|
+
|
|
169
185
|
/**
|
|
170
186
|
* Parse a single `changes` / `references` bullet into a `PathEntry`.
|
|
171
187
|
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
188
|
+
* Accepted markdown shapes (both parsed indefinitely — live issue bodies
|
|
189
|
+
* are never rewritten):
|
|
190
|
+
* - Humanized bullet (canonical serialize() output since Story #4600):
|
|
191
|
+
* `` `src/x.js` — refactors-existing ``
|
|
192
|
+
* - Legacy inline-JSON object bullet:
|
|
193
|
+
* `- { "path": "...", "assumption": "..." }`
|
|
194
|
+
*
|
|
195
|
+
* A structured object entry (from a parsed JSON body that was never
|
|
196
|
+
* serialized to markdown) arrives as-is and is validated directly.
|
|
176
197
|
*
|
|
177
198
|
* @param {string|object} raw
|
|
178
199
|
* @param {string[]} warnings
|
|
@@ -198,6 +219,21 @@ function parsePathEntry(raw, warnings) {
|
|
|
198
219
|
const str = typeof raw === 'string' ? raw.trim() : String(raw).trim();
|
|
199
220
|
if (str.length === 0) return null;
|
|
200
221
|
|
|
222
|
+
// Humanized bullet shape (the canonical serialize() output since
|
|
223
|
+
// Story #4600): `path` — assumption.
|
|
224
|
+
const humanized = str.match(HUMANIZED_PATH_ENTRY_RE);
|
|
225
|
+
if (humanized) {
|
|
226
|
+
const path = humanized[1].trim();
|
|
227
|
+
if (path.length > 0 && FILE_ASSUMPTION_VALUES.includes(humanized[2])) {
|
|
228
|
+
return { path, assumption: humanized[2] };
|
|
229
|
+
}
|
|
230
|
+
// Recognized the humanized shape but the fields are invalid: fail closed.
|
|
231
|
+
throw new StoryBodyParseError(
|
|
232
|
+
`changes/references entry is a humanized bullet but not a valid PathEntry: ${str.slice(0, 120)}`,
|
|
233
|
+
{ field: 'changes', raw: str },
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
201
237
|
// Try to detect inline JSON object shape: `{ "path": "...", "assumption": "..." }`
|
|
202
238
|
if (str.startsWith('{')) {
|
|
203
239
|
try {
|
|
@@ -434,6 +470,14 @@ function splitSections(markdown) {
|
|
|
434
470
|
continue;
|
|
435
471
|
}
|
|
436
472
|
|
|
473
|
+
// The visible `> **Wide:** <reason>` rationale line is presentation only
|
|
474
|
+
// (Story #4600): the meta block remains the canonical carrier for
|
|
475
|
+
// `wide.reason`, so this line must not bleed into the goal (or any other)
|
|
476
|
+
// section. Skip it wherever it appears.
|
|
477
|
+
if (WIDE_MARKER_LINE_RE.test(line)) {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
|
|
437
481
|
if (inPreamble) {
|
|
438
482
|
preambleLines.push(line);
|
|
439
483
|
} else if (currentSection !== null) {
|
|
@@ -638,7 +682,11 @@ export function parse(input) {
|
|
|
638
682
|
sections.get('changes') ?? [],
|
|
639
683
|
warnings,
|
|
640
684
|
);
|
|
641
|
-
|
|
685
|
+
// The AC-<n> checkbox prefix is presentation-only (Story #4600): strip it
|
|
686
|
+
// so acceptance[] round-trips byte-identical to the authored array.
|
|
687
|
+
const acceptance = parseTextListSection(sections.get('acceptance') ?? []).map(
|
|
688
|
+
(a) => a.replace(AC_PREFIX_RE, ''),
|
|
689
|
+
);
|
|
642
690
|
const verify = parseTextListSection(sections.get('verify') ?? []);
|
|
643
691
|
const references = parsePathEntrySection(
|
|
644
692
|
sections.get('references') ?? [],
|
|
@@ -821,8 +869,10 @@ function parseStructuredObject(obj) {
|
|
|
821
869
|
*/
|
|
822
870
|
function serializePathEntry(entry) {
|
|
823
871
|
if (typeof entry === 'string') return entry;
|
|
824
|
-
// Canonical object form: render as
|
|
825
|
-
|
|
872
|
+
// Canonical object form (Story #4600): render as a human-readable bullet —
|
|
873
|
+
// path in backticks, em-dash, assumption. parsePathEntry recognizes this
|
|
874
|
+
// shape (and the legacy inline-JSON shape) for round-trip fidelity.
|
|
875
|
+
return `\`${entry.path}\` — ${entry.assumption}`;
|
|
826
876
|
}
|
|
827
877
|
|
|
828
878
|
/**
|
|
@@ -846,6 +896,18 @@ const SERIALIZE_SECTIONS = [
|
|
|
846
896
|
? `## Goal\n${goal.trim()}`
|
|
847
897
|
: null,
|
|
848
898
|
},
|
|
899
|
+
{
|
|
900
|
+
// Visible wide-rationale line (Story #4600), rendered directly under
|
|
901
|
+
// `## Goal`. Presentation only: the `<!-- meta -->` block remains the
|
|
902
|
+
// canonical machine carrier and the parser skips this line, so `wide`
|
|
903
|
+
// round-trips through the meta block alone. Absent/invalid wide emits
|
|
904
|
+
// nothing, keeping every non-wide body byte-identical to before.
|
|
905
|
+
field: 'wide',
|
|
906
|
+
render: (wide) => {
|
|
907
|
+
const normalized = normalizeWide(wide);
|
|
908
|
+
return normalized === null ? null : `> **Wide:** ${normalized.reason}`;
|
|
909
|
+
},
|
|
910
|
+
},
|
|
849
911
|
{
|
|
850
912
|
// Optional v2 intra-Story delivery slice plan. Single-token `## Slicing`
|
|
851
913
|
// heading (recognized by the `[\w-]+` field-heading regex). Verbatim text
|
|
@@ -874,10 +936,14 @@ const SERIALIZE_SECTIONS = [
|
|
|
874
936
|
: null,
|
|
875
937
|
},
|
|
876
938
|
{
|
|
939
|
+
// Each checkbox carries a stable 1-based `AC-<n>:` handle (Story #4600)
|
|
940
|
+
// so humans and reviewers can reference criteria by number. The prefix
|
|
941
|
+
// is presentation-only — parse() strips it, and validators compare the
|
|
942
|
+
// top-level acceptance[] array, so numbering never affects gating.
|
|
877
943
|
field: 'acceptance',
|
|
878
944
|
render: (acceptance) =>
|
|
879
945
|
Array.isArray(acceptance) && acceptance.length > 0
|
|
880
|
-
? `## Acceptance\n${acceptance.map((a) => `- [ ] ${a}`).join('\n')}`
|
|
946
|
+
? `## Acceptance\n${acceptance.map((a, i) => `- [ ] AC-${i + 1}: ${a}`).join('\n')}`
|
|
881
947
|
: null,
|
|
882
948
|
},
|
|
883
949
|
{
|
|
@@ -967,8 +1033,8 @@ function serializeAuthoredMarker(body) {
|
|
|
967
1033
|
}
|
|
968
1034
|
|
|
969
1035
|
/**
|
|
970
|
-
* Build the optional `---` footer block (`parent` / `
|
|
971
|
-
*
|
|
1036
|
+
* Build the optional `---` footer block (`parent` / `blocked by` lines).
|
|
1037
|
+
* Returns the empty string when `opts.includeFooter` is falsy.
|
|
972
1038
|
*
|
|
973
1039
|
* @param {StoryBody} body
|
|
974
1040
|
* @param {SerializeOptions} opts
|
|
@@ -978,7 +1044,9 @@ function serializeFooter(body, opts) {
|
|
|
978
1044
|
if (!opts.includeFooter) return '';
|
|
979
1045
|
const footerLines = ['---'];
|
|
980
1046
|
if (opts.footer?.parent) footerLines.push(`parent: #${opts.footer.parent}`);
|
|
981
|
-
|
|
1047
|
+
// Story #4545 — no `Epic: #N` branch. `pr-base-guard.js` hard-refuses a
|
|
1048
|
+
// Story body carrying that footer, so composing one here would let the
|
|
1049
|
+
// framework generate work it would then reject at delivery.
|
|
982
1050
|
if (Array.isArray(body.depends_on)) {
|
|
983
1051
|
for (const dep of body.depends_on) {
|
|
984
1052
|
footerLines.push(`blocked by ${dep}`);
|
|
@@ -26,9 +26,8 @@ import {
|
|
|
26
26
|
*/
|
|
27
27
|
export function renderDecomposerSystemPrompt({
|
|
28
28
|
maxTickets = LIMITS_DEFAULTS.maxTickets,
|
|
29
|
-
epicId = null,
|
|
30
29
|
} = {}) {
|
|
31
|
-
return render2TierPrompt({ maxTickets
|
|
30
|
+
return render2TierPrompt({ maxTickets });
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
/**
|
|
@@ -37,7 +36,7 @@ export function renderDecomposerSystemPrompt({
|
|
|
37
36
|
* on the Story body so the executing agent has everything it needs in one
|
|
38
37
|
* ticket. Thematic grouping lives as prose in the Epic body / Tech Spec.
|
|
39
38
|
*/
|
|
40
|
-
function render2TierPrompt({ maxTickets
|
|
39
|
+
function render2TierPrompt({ maxTickets }) {
|
|
41
40
|
// v2 Stage 3: default-single — emit one Story unless the split policy clears.
|
|
42
41
|
// Capacity thresholds are sourced from the single DEFAULT_MODEL_CAPACITY
|
|
43
42
|
// constant (ticket-validator-sizing.js) so the prompt and the validator
|
|
@@ -64,13 +63,6 @@ function render2TierPrompt({ maxTickets, epicId = null }) {
|
|
|
64
63
|
advisoryCaveat,
|
|
65
64
|
newFileContract,
|
|
66
65
|
} = AUTHORING_ALTITUDE_GUIDANCE;
|
|
67
|
-
// The namespaced AC-tag token the wave-0 BDD scaffold section below must
|
|
68
|
-
// require on every scaffolded scenario (Story #4301). When the Epic ID is
|
|
69
|
-
// known at render time, interpolate the concrete tag so the author has no
|
|
70
|
-
// placeholder to get wrong; otherwise fall back to the documented pattern.
|
|
71
|
-
const acTagExample = Number.isInteger(epicId)
|
|
72
|
-
? `@epic-${epicId}-ac-1`
|
|
73
|
-
: '@epic-<id>-ac-N';
|
|
74
66
|
return `You are an expert Senior Project Manager and Orchestrator.
|
|
75
67
|
Your job is to turn a plan seed / Tech Spec into a Story ticket array for an AI Agent to execute.
|
|
76
68
|
|
|
@@ -82,7 +74,9 @@ Your job is to turn a plan seed / Tech Spec into a Story ticket array for an AI
|
|
|
82
74
|
- Thematic grouping is prose in the Story's folded \`## Spec\` / \`## Slicing\`, never sibling tickets for coupled work.
|
|
83
75
|
|
|
84
76
|
### LABEL CONVENTIONS:
|
|
85
|
-
-
|
|
77
|
+
- \`type::story\` is applied automatically by persist — you do not need to emit it, and no other type label is allowed (the retired Feature and Task tiers have no labels under this hierarchy).
|
|
78
|
+
- \`labels[]\` is **optional**. Emit it only to request an *additional* label; persist sanitizes the list before applying it.
|
|
79
|
+
- Do **not** emit \`agent::*\` labels — lifecycle state is runtime-owned, and persist applies \`agent::ready\` itself once every checkpoint is on the ticket.
|
|
86
80
|
- Do **not** emit \`persona::*\` labels — the behavioral persona concept (and its label axis) was removed in v2.
|
|
87
81
|
|
|
88
82
|
### OUTPUT FORMAT:
|
|
@@ -97,7 +91,7 @@ You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown
|
|
|
97
91
|
"body": <string — see STORY BODY SCHEMA below>,
|
|
98
92
|
"acceptance": ["<testable, observable criterion>", ...],
|
|
99
93
|
"verify": ["<exact command or test path> (<tier>)", ...],
|
|
100
|
-
"labels": ["type::story
|
|
94
|
+
"labels": ["<extra-label>"] (optional — type::story is applied automatically; omit this field unless you need an additional label),
|
|
101
95
|
"depends_on": ["slug-of-blocking-dependency"] (optional array of Story slugs that block execution)
|
|
102
96
|
}
|
|
103
97
|
]
|
|
@@ -107,7 +101,9 @@ You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown
|
|
|
107
101
|
### STORY BODY SCHEMA (REQUIRED FOR EVERY STORY):
|
|
108
102
|
\`body\` MUST be a **string** — the serialized markdown produced by \`serialize()\` from \`lib/story-body/story-body.js\`. Do NOT emit \`body\` as a JSON object: an object body throws \`StoryBodyParseError\` in the reconciler (Story #3302) and is discarded by the GitHub provider, producing an empty issue body. Stories are consumed by non-interactive sub-agents that must self-verify from the Story ticket alone — so the ticket must carry everything an agent needs to execute and self-verify.
|
|
109
103
|
|
|
110
|
-
The \`acceptance[]\` and \`verify[]\` arrays live at the **top level** of the Story ticket object — that is the machine contract the validator reads.
|
|
104
|
+
The \`acceptance[]\` and \`verify[]\` arrays live at the **top level** of the Story ticket object — that is the machine contract the validator reads. Author each list **once, at top level**, and **omit** the \`## Acceptance\` / \`## Verify\` sections from the authored \`body\` string: persist syncs the top-level arrays into those sections so the GitHub issue stays a complete executable document. The validator resolves both fields from the top level, so an omitted section is the expected shape, not a violation.
|
|
105
|
+
|
|
106
|
+
If you do write those sections into the \`body\` string anyway, they must mirror the top-level arrays **item for item** — persist fails closed on a disagreement rather than guessing which list is authoritative. Do **not** invent a second criteria list inside \`## Spec\`, and do not author a separate Acceptance Spec / PRD artifact.
|
|
111
107
|
|
|
112
108
|
The serialized \`body\` string renders these markdown sections (in order):
|
|
113
109
|
|
|
@@ -150,6 +146,10 @@ The serialized \`body\` string renders these markdown sections (in order):
|
|
|
150
146
|
- **verify** (top-level array on the ticket object): Each entry MUST name a testing tier in parentheses, drawn from \`unit\` / \`contract\` / \`e2e\` / \`validate\`. Example: \`npm run test -- src/x.test.ts (unit)\`, \`npm run validate (validate)\`. Stories with zero verify entries SHOULD fail validation; if a story is genuinely unverifiable in isolation (e.g., a copy edit auditor will eyeball), the literal entry \`manual:<reason>\` is allowed so the absence is intentional, not lazy. Manual entries without a reason are rejected.
|
|
151
147
|
- **reason to exist** (REQUIRED, encoded as the \`reason_to_exist\` field of the \`<!-- meta: {...} -->\` comment appended to the serialized body string — NOT a top-level ticket field): One sentence stating the single coherent reason this Story exists, distinct from its broader \`## Goal\` prose. Every Story MUST carry a non-empty \`reason_to_exist\`; it is the machine-checkable form of the cohesion rule (**one Story = one coherent change with one reason to exist**) and the \`epic-plan-consolidate\` critic flags any Story whose body carries no non-empty reason to exist. Encode it as \`<!-- meta: {"reason_to_exist": "..."} -->\`.
|
|
152
148
|
- **estimated_test_files** (optional, encoded in the \`<!-- meta: {...} -->\` comment appended to the serialized body string — NOT a top-level ticket field): Integer estimate of how many test files this Story creates or modifies. Omit when the number is not estimable. Informational only — it does not gate the decompose.
|
|
149
|
+
- **Observed-behavior claims open with \`Current state (verified <date>)\`.** Any Spec claim about how the codebase behaves today MUST open with that preamble (e.g. \`Current state (verified 2026-07-17): …\`) so a reader can tell a verified observation from an assumption, and can tell when the observation went stale.
|
|
150
|
+
- **Intent-then-proxy acceptance shape.** When an acceptance item verifies through a proxy check (a grep, a file-exists probe, an exit-code test), state the intent clause before the proxy check — what outcome the check stands in for — so the proxy never becomes the goal (e.g. "the workflow names hygiene findings as re-author input: \`grep -n "textHygiene" …\` exits 0").
|
|
151
|
+
- **Slicing checkpoints are one line each.** Each \`## Slicing\` checkpoint is a single line naming the checkpoint; implementation detail lives in \`## Spec\`, never duplicated into Slicing. A Slicing section outweighing its Spec is a defect the text-hygiene lint flags.
|
|
152
|
+
- **Bodies record decisions, never questions to the operator.** Never persist an open question ("Flag if…", "TBD", "confirm with the operator") into a Story body — the executing sub-agent is non-interactive and cannot answer it. Resolve the unknown before authoring, or restate it as a declarative Key Assumption the agent can act on.
|
|
153
153
|
- **non_goals** (OPTIONAL, in body string as the \`## Non-Goals\` section): A short list of capabilities or changes this Story explicitly does NOT deliver — an advisory negative-scope bound that fences the executing agent away from adjacent work. It is **advisory and NON-GATING**: the validator does not require, count, or reject on it, and an absent or empty section renders nothing. Use the EXACT single-word hyphenated heading spelling \`## Non-Goals\` (a space-separated heading like \`## Out of Scope\` is NOT recognized by the parser and will be dropped). Reach for it when a Story's negative boundary is non-obvious from its \`acceptance[]\` alone; omit it otherwise.
|
|
154
154
|
|
|
155
155
|
#### AUTHORING ALTITUDE — BINDING ACCEPTANCE vs ADVISORY CHANGES:
|
|
@@ -225,9 +225,8 @@ When the Acceptance Spec contains **one or more \`Disposition: new\` rows**, you
|
|
|
225
225
|
- **goal**: contains the literal token \`bdd-scaffold\` (e.g. "bdd-scaffold: create the @skip-tagged feature files the implementation Stories verify against").
|
|
226
226
|
- **depends_on**: EMPTY (\`[]\`) — it runs first, in wave 0.
|
|
227
227
|
- **changes**: one entry per distinct \`.feature\` file named in a \`new\` row, each \`{ "path": "<feature file path>", "assumption": "creates" }\`.
|
|
228
|
-
- **acceptance**: MUST assert (a) every new \`.feature\` file exists
|
|
229
|
-
- **
|
|
230
|
-
- **verify**: a grep/validate command (tier \`validate\`), NOT an e2e runner — verifying that a file exists with the required tags needs no browser/playwright run. Example: \`grep -rL '@skip' tests/features/<area>/*.feature (validate)\` paired with an existence check, AND a check that every new AC ID's namespaced tag (\`${acTagExample}\`) appears in the scaffolded files, e.g. \`grep -q '${acTagExample}' tests/features/<area>/<file>.feature (validate)\` for each new AC row.
|
|
228
|
+
- **acceptance**: MUST assert (a) every new \`.feature\` file exists AND (b) every new scenario within them carries an \`@skip\` tag. Keep these observable (a grep/validate command exits 0, a file exists at a path).
|
|
229
|
+
- **verify**: a grep/validate command (tier \`validate\`), NOT an e2e runner — verifying that a file exists with the required tags needs no browser/playwright run. Example: \`grep -rL '@skip' tests/features/<area>/*.feature (validate)\` paired with an existence check.
|
|
231
230
|
- Each implementation Story whose \`verify[]\` references one of these scaffolded \`.feature\` paths MUST \`depends_on\` the scaffold Story (so the scaffold lands in an earlier wave). Omitting the link trips the soft \`missing-bdd-scaffold\` validator finding.
|
|
232
231
|
|
|
233
232
|
When the Acceptance Spec contains **zero \`new\`-disposition rows** (every row is \`updated\` or \`unchanged\`), do NOT emit a scaffold Story — there is nothing to create.
|
|
@@ -17,12 +17,25 @@
|
|
|
17
17
|
* the NODE_ENV band-aid was removed in Story #3342. Tests that need to
|
|
18
18
|
* exercise the webhook POST inject `opts.fetchImpl` instead, so the
|
|
19
19
|
* request never reaches the real network even if a URL resolves.)
|
|
20
|
+
* - Every `GIT_*` variable is dropped. When the suite runs inside a git
|
|
21
|
+
* hook (husky pre-push via the coverage-capture path), the parent git
|
|
22
|
+
* invocation exports `GIT_DIR` — from a linked worktree, the absolute
|
|
23
|
+
* `<main>/.git/worktrees/<name>` path. A test fixture's `git init`
|
|
24
|
+
* under that env re-initializes the shared gitdir and writes
|
|
25
|
+
* `core.bare=true` into the MAIN checkout's `.git/config`, breaking
|
|
26
|
+
* every worktree at once (#4580). Production git call sites are
|
|
27
|
+
* covered by `cleanGitEnv` in `git-utils.js`; this is the same scrub
|
|
28
|
+
* for test child processes, which may spawn git directly. Tests that
|
|
29
|
+
* need a `GIT_*` variable set it explicitly on their own spawn.
|
|
20
30
|
*
|
|
21
31
|
* @param {NodeJS.ProcessEnv} baseEnv
|
|
22
32
|
* @returns {NodeJS.ProcessEnv}
|
|
23
33
|
*/
|
|
24
34
|
export function buildWebhookSafeTestEnv(baseEnv = process.env) {
|
|
25
|
-
const env =
|
|
35
|
+
const env = Object.fromEntries(
|
|
36
|
+
Object.entries(baseEnv).filter(([k]) => !k.startsWith('GIT_')),
|
|
37
|
+
);
|
|
38
|
+
env.NODE_ENV = baseEnv.NODE_ENV ?? 'test';
|
|
26
39
|
if (env.MANDREL_ALLOW_TEST_WEBHOOKS !== '1') {
|
|
27
40
|
delete env.NOTIFICATION_WEBHOOK_URL;
|
|
28
41
|
}
|
|
@@ -12,13 +12,10 @@ import picomatch from 'picomatch';
|
|
|
12
12
|
*/
|
|
13
13
|
export const INTEGRATION_INCLUDE = [
|
|
14
14
|
'tests/**/*.integration.test.js',
|
|
15
|
-
'tests/epic-execute/epic-execute-record-wave.test.js',
|
|
16
15
|
'tests/hook-chain-reflog-invariant.test.js',
|
|
17
|
-
'tests/push-epic-retry.test.js',
|
|
18
16
|
'tests/contract/check-baselines-regression.test.js',
|
|
19
17
|
'tests/contract/check-baselines-kernel-mismatch.test.js',
|
|
20
18
|
'tests/integration-prime-after-sweep.test.js',
|
|
21
|
-
'tests/concurrency-wiring.test.js',
|
|
22
19
|
'tests/scripts/git-cleanup.test.js',
|
|
23
20
|
'tests/lib/checks/runner-integration.test.js',
|
|
24
21
|
'tests/single-story-close-sync.test.js',
|
|
@@ -107,20 +107,6 @@ export function hasTicketSection(body, kind) {
|
|
|
107
107
|
return locate(body, kind) !== null;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
/**
|
|
111
|
-
* Extract a managed region's content (between the markers, trimmed).
|
|
112
|
-
* Returns `null` when the region is absent.
|
|
113
|
-
*
|
|
114
|
-
* @param {string} body
|
|
115
|
-
* @param {'techSpec'|'acceptanceTable'} kind
|
|
116
|
-
* @returns {string|null}
|
|
117
|
-
*/
|
|
118
|
-
export function extractTicketSection(body, kind) {
|
|
119
|
-
const loc = locate(body, kind);
|
|
120
|
-
if (!loc) return null;
|
|
121
|
-
return body.slice(loc.contentStart, loc.contentEnd).trim();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
110
|
/**
|
|
125
111
|
* Insert or replace a managed region, preserving every byte outside it.
|
|
126
112
|
*
|
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tech Spec #819 §"Evidence record (Story 7)". Each successful gate run
|
|
5
5
|
* writes a record keyed by `{ gateName, commitSha, commandConfigHash }` to
|
|
6
|
-
* a
|
|
6
|
+
* a storyId-anchored path under the resolved `tempRoot`:
|
|
7
7
|
*
|
|
8
|
-
* -
|
|
9
|
-
* `<tempRoot>/epic-<epicId>/validation-evidence.json`
|
|
10
|
-
* - Story-scoped (scopeId === storyId):
|
|
11
|
-
* `<tempRoot>/epic-<epicId>/story-<storyId>/validation-evidence.json`
|
|
8
|
+
* `<tempRoot>/standalone/stories/story-<storyId>/validation-evidence.json`
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* The path is gitignored via `temp/`. v2.0.0 removed the Epic tier along
|
|
11
|
+
* with the Epic-keyed `<tempRoot>/epic-<epicId>/…` keyspace.
|
|
15
12
|
*
|
|
16
13
|
* A subsequent caller can `shouldSkip(...)` to learn whether the same gate
|
|
17
14
|
* has already passed against the current HEAD with an identical
|
|
@@ -34,7 +31,7 @@ import path from 'node:path';
|
|
|
34
31
|
import { fileURLToPath } from 'node:url';
|
|
35
32
|
import Ajv from 'ajv/dist/2020.js';
|
|
36
33
|
import addFormats from 'ajv-formats';
|
|
37
|
-
import {
|
|
34
|
+
import { storyTempDir } from './config/temp-paths.js';
|
|
38
35
|
|
|
39
36
|
export const SCHEMA_VERSION = 1;
|
|
40
37
|
const DEFAULT_TEMP_DIR = 'temp';
|
|
@@ -97,37 +94,28 @@ function requirePositiveInt(value, label) {
|
|
|
97
94
|
|
|
98
95
|
/**
|
|
99
96
|
* Compute the absolute path of the evidence file for `scopeId` under the
|
|
100
|
-
*
|
|
97
|
+
* storyId-anchored temp tree.
|
|
101
98
|
*
|
|
102
|
-
*
|
|
103
|
-
* - `scopeId === epicId` → `<tempRoot>/epic-<epicId>/validation-evidence.json`
|
|
104
|
-
* - `scopeId !== epicId` → treated as a Story id → `<tempRoot>/epic-<epicId>/story-<scopeId>/validation-evidence.json`
|
|
105
|
-
*
|
|
106
|
-
* **Standalone keyspace (Story #4250).** When `opts.standalone === true`,
|
|
99
|
+
* **Standalone keyspace (Story #4250).** `opts.standalone` must be `true`:
|
|
107
100
|
* the Story has no parent Epic, so the evidence file is anchored on the
|
|
108
101
|
* Story id alone at
|
|
109
102
|
* `<tempRoot>/standalone/stories/story-<scopeId>/validation-evidence.json`
|
|
110
|
-
* (the `storyTempDir(null, sid)` layout from Story #2874).
|
|
111
|
-
* `epicId` is ignored — callers MUST NOT feed a `0`/`null` epicId into the
|
|
112
|
-
* Epic-keyed branch (the historical bug this keyspace replaces). Outside
|
|
113
|
-
* standalone mode `epicId` remains required.
|
|
103
|
+
* (the `storyTempDir(null, sid)` layout from Story #2874).
|
|
114
104
|
*
|
|
115
|
-
* The legacy flat `temp/validation-evidence-<scopeId>.json` layout
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* override the default `'temp'`.
|
|
105
|
+
* The legacy flat `temp/validation-evidence-<scopeId>.json` layout and the
|
|
106
|
+
* Epic-keyed `<tempRoot>/epic-<epicId>/…` layout are both gone. The
|
|
107
|
+
* synthetic config bag passed to `storyTempDir` keeps the resolver from
|
|
108
|
+
* doing a disk-bound `.agentrc.json` lookup; bare callers can pass
|
|
109
|
+
* `tempDir` via `opts` to override the default `'temp'`.
|
|
121
110
|
*
|
|
122
111
|
* @param {number|string} scopeId
|
|
123
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
112
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean }} opts
|
|
124
113
|
* @returns {string}
|
|
125
114
|
*/
|
|
126
115
|
export function evidencePath(scopeId, opts = {}) {
|
|
127
|
-
|
|
128
|
-
if (!standalone && opts.epicId == null) {
|
|
116
|
+
if (opts.standalone !== true) {
|
|
129
117
|
throw new Error(
|
|
130
|
-
'[validation-evidence] evidencePath requires opts.
|
|
118
|
+
'[validation-evidence] evidencePath requires opts.standalone (the storyId-anchored keyspace is the only keyspace).',
|
|
131
119
|
);
|
|
132
120
|
}
|
|
133
121
|
const { cwd, tempDir } = resolveOpts(opts);
|
|
@@ -143,18 +131,9 @@ export function evidencePath(scopeId, opts = {}) {
|
|
|
143
131
|
? tempDir
|
|
144
132
|
: path.join(cwd, tempDir);
|
|
145
133
|
const configBag = { project: { paths: { tempRoot: absTempRoot } } };
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// standalone-story sentinel `storyTempDir` accepts (Story #2874).
|
|
150
|
-
dir = storyTempDir(null, scope, configBag);
|
|
151
|
-
} else {
|
|
152
|
-
const epicId = requirePositiveInt(opts.epicId, 'epicId');
|
|
153
|
-
dir =
|
|
154
|
-
scope === epicId
|
|
155
|
-
? epicTempDir(epicId, configBag)
|
|
156
|
-
: storyTempDir(epicId, scope, configBag);
|
|
157
|
-
}
|
|
134
|
+
// Story #4250 — storyId-anchored standalone keyspace. `null` is the
|
|
135
|
+
// standalone-story sentinel `storyTempDir` accepts (Story #2874).
|
|
136
|
+
const dir = storyTempDir(null, scope, configBag);
|
|
158
137
|
return path.join(dir, EVIDENCE_FILENAME);
|
|
159
138
|
}
|
|
160
139
|
|
|
@@ -189,19 +168,17 @@ function emptyDoc(scopeId) {
|
|
|
189
168
|
* cross-scopeId cases — callers don't have to branch on those failure
|
|
190
169
|
* modes; they manifest as `shouldSkip()` returning `skip: false`.
|
|
191
170
|
*
|
|
192
|
-
* `opts.
|
|
193
|
-
*
|
|
194
|
-
* storyId-anchored standalone keyspace.
|
|
171
|
+
* `opts.standalone` is required (Story #4250) — it routes to the
|
|
172
|
+
* storyId-anchored keyspace.
|
|
195
173
|
*
|
|
196
174
|
* @param {number|string} scopeId
|
|
197
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
175
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
|
|
198
176
|
* @returns {{ storyId: number, schemaVersion: number, records: object[] }}
|
|
199
177
|
*/
|
|
200
178
|
export function loadEvidence(scopeId, opts = {}) {
|
|
201
179
|
const resolved = resolveOpts(opts);
|
|
202
180
|
const file = evidencePath(scopeId, {
|
|
203
181
|
...resolved,
|
|
204
|
-
epicId: opts.epicId,
|
|
205
182
|
standalone: opts.standalone,
|
|
206
183
|
});
|
|
207
184
|
if (!resolved.fs.existsSync(file)) return emptyDoc(scopeId);
|
|
@@ -223,9 +200,8 @@ export function loadEvidence(scopeId, opts = {}) {
|
|
|
223
200
|
* Validates the resulting document against the schema before writing — a
|
|
224
201
|
* malformed write throws so the bug surfaces immediately.
|
|
225
202
|
*
|
|
226
|
-
* `opts.
|
|
227
|
-
*
|
|
228
|
-
* storyId-anchored standalone keyspace.
|
|
203
|
+
* `opts.standalone` is required (Story #4250) — it routes to the
|
|
204
|
+
* storyId-anchored keyspace.
|
|
229
205
|
*
|
|
230
206
|
* @param {{
|
|
231
207
|
* storyId: number|string,
|
|
@@ -235,7 +211,7 @@ export function loadEvidence(scopeId, opts = {}) {
|
|
|
235
211
|
* exitCode?: number,
|
|
236
212
|
* durationMs?: number|null,
|
|
237
213
|
* }} input
|
|
238
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
214
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object, now?: Function }} opts
|
|
239
215
|
* @returns {object} The persisted record.
|
|
240
216
|
*/
|
|
241
217
|
export function recordPass(
|
|
@@ -258,7 +234,6 @@ export function recordPass(
|
|
|
258
234
|
const resolved = resolveOpts(opts);
|
|
259
235
|
const evidenceOpts = {
|
|
260
236
|
...resolved,
|
|
261
|
-
epicId: opts.epicId,
|
|
262
237
|
standalone: opts.standalone,
|
|
263
238
|
};
|
|
264
239
|
const doc = loadEvidence(storyId, evidenceOpts);
|
|
@@ -297,13 +272,12 @@ export function recordPass(
|
|
|
297
272
|
* with a machine-readable `reason` so callers can log why the skip didn't
|
|
298
273
|
* fire.
|
|
299
274
|
*
|
|
300
|
-
* `opts.
|
|
301
|
-
*
|
|
302
|
-
* storyId-anchored standalone keyspace. `opts` is forwarded verbatim to
|
|
275
|
+
* `opts.standalone` is required (Story #4250) — it routes to the
|
|
276
|
+
* storyId-anchored keyspace. `opts` is forwarded verbatim to
|
|
303
277
|
* `loadEvidence`, so `standalone` flows through unchanged.
|
|
304
278
|
*
|
|
305
279
|
* @param {{ storyId: number|string, gateName: string, currentSha: string, configHash: string }} input
|
|
306
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
280
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
|
|
307
281
|
* @returns {{ skip: boolean, reason: string, record?: object }}
|
|
308
282
|
*/
|
|
309
283
|
export function shouldSkip(
|
|
@@ -340,19 +314,17 @@ export function shouldSkip(
|
|
|
340
314
|
* start of each Story so a re-run always starts clean. Idempotent —
|
|
341
315
|
* absent file is not an error.
|
|
342
316
|
*
|
|
343
|
-
* `opts.
|
|
344
|
-
*
|
|
345
|
-
* storyId-anchored standalone keyspace.
|
|
317
|
+
* `opts.standalone` is required (Story #4250) — it routes to the
|
|
318
|
+
* storyId-anchored keyspace.
|
|
346
319
|
*
|
|
347
320
|
* @param {number|string} scopeId
|
|
348
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
321
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
|
|
349
322
|
* @returns {{ cleared: boolean, path: string }}
|
|
350
323
|
*/
|
|
351
324
|
export function forceClear(scopeId, opts = {}) {
|
|
352
325
|
const resolved = resolveOpts(opts);
|
|
353
326
|
const file = evidencePath(scopeId, {
|
|
354
327
|
...resolved,
|
|
355
|
-
epicId: opts.epicId,
|
|
356
328
|
standalone: opts.standalone,
|
|
357
329
|
});
|
|
358
330
|
if (!resolved.fs.existsSync(file)) return { cleared: false, path: file };
|