mandrel 1.59.0 → 1.61.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 +86 -44
- package/.agents/docs/SDLC.md +135 -141
- package/.agents/docs/configuration.md +77 -20
- package/.agents/docs/quality-gates.md +796 -0
- package/.agents/docs/workflows.md +6 -9
- package/.agents/instructions.md +12 -11
- package/.agents/personas/architect.md +1 -1
- package/.agents/personas/product.md +1 -1
- package/.agents/personas/project-manager.md +14 -14
- package/.agents/personas/technical-writer.md +1 -1
- package/.agents/rules/changelog-style.md +5 -5
- package/.agents/rules/git-conventions.md +3 -3
- package/.agents/runtime-deps.json +2 -2
- package/.agents/schemas/agentrc.schema.json +3 -3
- package/.agents/schemas/dispatch-manifest.json +4 -4
- package/.agents/schemas/epic-spec.schema.json +15 -45
- package/.agents/schemas/lifecycle/README.md +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/README.md +2 -2
- package/.agents/scripts/acceptance-eval.js +1 -1
- package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
- package/.agents/scripts/agents-bootstrap-github.js +23 -119
- package/.agents/scripts/analyze-execution.js +2 -2
- package/.agents/scripts/audit-to-stories.js +1 -1
- package/.agents/scripts/check-doc-links.js +2 -3
- package/.agents/scripts/diagnose-friction.js +1 -1
- package/.agents/scripts/dispatcher.js +2 -2
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/epic-audit-prepare.js +3 -3
- package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
- package/.agents/scripts/epic-deliver-preflight.js +6 -6
- package/.agents/scripts/epic-deliver-prepare.js +1 -1
- package/.agents/scripts/epic-execute-record-wave.js +4 -4
- package/.agents/scripts/epic-plan-healthcheck.js +6 -10
- package/.agents/scripts/epic-plan-spec-validate.js +1 -1
- package/.agents/scripts/epic-reconcile.js +11 -29
- package/.agents/scripts/evidence-gate.js +1 -1
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/hierarchy-gate.js +7 -11
- package/.agents/scripts/lib/ITicketingProvider.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
- package/.agents/scripts/lib/baseline-snapshot.js +7 -7
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +47 -1
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/bootstrap/gh-preflight.js +7 -9
- package/.agents/scripts/lib/bootstrap/manifest.js +21 -1
- package/.agents/scripts/lib/bootstrap/merge-methods.js +31 -16
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +32 -11
- package/.agents/scripts/lib/codebase-snapshot.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
- package/.agents/scripts/lib/config-settings-schema.js +3 -3
- package/.agents/scripts/lib/detect-package-manager.js +72 -0
- package/.agents/scripts/lib/duplicate-search.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
- package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
- package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
- package/.agents/scripts/lib/errors/index.js +4 -4
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +10 -10
- package/.agents/scripts/lib/label-constants.js +3 -4
- package/.agents/scripts/lib/label-taxonomy.js +5 -10
- package/.agents/scripts/lib/onboard/detect-stack.js +10 -10
- package/.agents/scripts/lib/onboard/init-tail.js +218 -0
- package/.agents/scripts/lib/onboard/scaffold-docs.js +18 -3
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
- package/.agents/scripts/lib/orchestration/code-review.js +5 -5
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
- package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
- package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +9 -25
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +8 -8
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +26 -13
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +6 -21
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +8 -8
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
- package/.agents/scripts/lib/orchestration/file-assumptions.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -3
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
- package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
- package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
- package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +2 -2
- package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
- package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +2 -2
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
- package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -12
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +8 -8
- package/.agents/scripts/lib/orchestration/ticketing/state.js +3 -3
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +2 -2
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +1 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +1 -1
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
- package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
- package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
- package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
- package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
- package/.agents/scripts/lib/runtime-deps/preflight.js +6 -6
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/spec/index.js +1 -1
- package/.agents/scripts/lib/spec/loader.js +2 -2
- package/.agents/scripts/lib/spec/state.js +7 -16
- package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
- package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
- package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
- package/.agents/scripts/lib/story-lifecycle.js +8 -8
- package/.agents/scripts/lib/story-plan.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
- package/.agents/scripts/lib/wave-runner/tick.js +1 -1
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +5 -2
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
- package/.agents/scripts/lifecycle-emit.js +1 -1
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/mappers.js +2 -2
- package/.agents/scripts/providers/github/tickets.js +4 -4
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/retro-run.js +2 -2
- package/.agents/scripts/run-lint.js +1 -1
- package/.agents/scripts/single-story-init.js +1 -1
- package/.agents/scripts/stories-wave-tick.js +5 -5
- package/.agents/scripts/story-close.js +1 -1
- package/.agents/scripts/story-init.js +13 -16
- package/.agents/scripts/story-phase.js +5 -5
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/sync-branch-from-base.js +1 -1
- package/.agents/scripts/validate-docs-freshness.js +1 -1
- package/.agents/scripts/wave-tick.js +1 -1
- package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
- package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
- package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
- package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
- package/.agents/skills/core/scope-triage/SKILL.md +9 -10
- package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
- package/.agents/skills/skills.index.json +7 -7
- package/.agents/templates/agent-protocol.md +2 -2
- package/.agents/workflows/agents-update.md +16 -31
- package/.agents/workflows/audit-architecture.md +2 -2
- package/.agents/workflows/audit-clean-code.md +2 -2
- package/.agents/workflows/audit-dependencies.md +1 -1
- package/.agents/workflows/audit-devops.md +1 -1
- package/.agents/workflows/audit-documentation.md +2 -2
- package/.agents/workflows/audit-lighthouse.md +1 -1
- package/.agents/workflows/audit-performance.md +2 -2
- package/.agents/workflows/audit-privacy.md +1 -1
- package/.agents/workflows/audit-quality.md +2 -2
- package/.agents/workflows/audit-security.md +2 -2
- package/.agents/workflows/audit-seo.md +1 -1
- package/.agents/workflows/audit-sre.md +1 -1
- package/.agents/workflows/audit-to-stories.md +10 -10
- package/.agents/workflows/audit-ux-ui.md +1 -1
- package/.agents/workflows/deliver.md +85 -0
- package/.agents/workflows/explain.md +3 -3
- package/.agents/workflows/git-merge-pr.md +1 -1
- package/.agents/workflows/git-pr-all.md +13 -10
- package/.agents/workflows/git-push.md +6 -3
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
- package/.agents/workflows/helpers/agents-sync-config.md +3 -2
- package/.agents/workflows/helpers/code-review.md +5 -5
- package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +43 -43
- package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
- package/.agents/workflows/helpers/diagnose.md +1 -1
- package/.agents/workflows/helpers/epic-audit.md +6 -6
- package/.agents/workflows/helpers/epic-deliver-story.md +13 -13
- package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
- package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
- package/.agents/workflows/helpers/epic-testing.md +3 -3
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
- package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
- package/.agents/workflows/helpers/signals.md +1 -1
- package/.agents/workflows/helpers/single-story-deliver.md +11 -11
- package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
- package/.agents/workflows/plan.md +131 -0
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/qa-run-harness.md +1 -1
- package/README.md +19 -39
- package/bin/mandrel.js +235 -16
- package/docs/CHANGELOG.md +1173 -0
- package/lib/cli/doctor.js +45 -3
- package/lib/cli/init.js +97 -36
- package/lib/cli/registry.js +41 -145
- package/lib/cli/sync.js +122 -23
- package/lib/cli/uninstall.js +42 -7
- package/lib/cli/update.js +524 -210
- package/lib/cli/version-helpers.js +59 -0
- package/package.json +7 -6
- package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
- package/.agents/workflows/onboard.md +0 -208
- package/lib/cli/__tests__/migrate.test.js +0 -268
- package/lib/cli/__tests__/sync-local-zone.test.js +0 -247
- package/lib/cli/__tests__/sync.test.js +0 -372
- package/lib/cli/__tests__/update-major.test.js +0 -217
- package/lib/cli/__tests__/update.test.js +0 -696
- package/lib/cli/__tests__/version-check.test.js +0 -398
- package/lib/migrations/__tests__/index.test.js +0 -216
|
@@ -100,7 +100,7 @@ export function renderBaselineFrictionBody({ rows, epicId, storyId } = {}) {
|
|
|
100
100
|
const triage = [
|
|
101
101
|
'**Triage:**',
|
|
102
102
|
`1. Open each suspect Story above and run \`npm run maintainability:update\` (or \`npm run crap:update\`) on **its** branch, then commit with a \`baseline-refresh:\` subject and re-close it.`,
|
|
103
|
-
`2. Re-run \`/
|
|
103
|
+
`2. Re-run \`/deliver ${storyId}\` once the suspect Story's refresh has merged into \`epic/${epicId}\`.`,
|
|
104
104
|
`3. If the suspect column reads \`_unknown_\`, the path has no commit on \`epic/${epicId}\` — investigate the baseline file directly before refreshing.`,
|
|
105
105
|
];
|
|
106
106
|
|
|
@@ -127,7 +127,7 @@ export async function runGatesAndRefresh(
|
|
|
127
127
|
* Read the parent Epic's judged `planningRisk` envelope off its
|
|
128
128
|
* `epic-plan-state` checkpoint so the Story-scope review can inherit the
|
|
129
129
|
* Epic's review depth (Story #3940). Best-effort and total — it reuses the
|
|
130
|
-
* shared `read` from `epic-plan-state-store.js` (the same reader `/
|
|
130
|
+
* shared `read` from `epic-plan-state-store.js` (the same reader `/plan
|
|
131
131
|
* --resume` and `epic-audit-prepare.js`'s `resolveRiskRoutedLenses` use, no
|
|
132
132
|
* third bespoke reader) and never fails the close:
|
|
133
133
|
*
|
|
@@ -137,7 +137,7 @@ export async function runGatesAndRefresh(
|
|
|
137
137
|
*
|
|
138
138
|
* A `null` result threads through `runStoryCodeReview` → `runCodeReview`
|
|
139
139
|
* unchanged, so depth resolves from diff width alone (`standard`), preserving
|
|
140
|
-
* today's behaviour for an Epic that skipped `/
|
|
140
|
+
* today's behaviour for an Epic that skipped `/plan`.
|
|
141
141
|
*
|
|
142
142
|
* @param {{ provider: object, epicId: number|null|undefined, readPlanStateFn?: typeof readPlanState }} args
|
|
143
143
|
* @returns {Promise<{ overallLevel?: string, axes?: Array<object> }|null>}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Story body schema validator (v5.33+).
|
|
3
3
|
*
|
|
4
|
-
* Enforces the four-section structured body shape on
|
|
4
|
+
* Enforces the four-section structured body shape on 2-tier Stories emitted
|
|
5
5
|
* by the decomposer. The canonical decomposition serializes every Story
|
|
6
6
|
* `body` to a **markdown string** via `serialize()` from
|
|
7
7
|
* `lib/story-body/story-body.js` (the decompose-author skill mandates this,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Only `type: 'story'` tickets are validated; Feature/Epic tickets and
|
|
19
19
|
* null/empty bodies pass through. There is no `type::task` ticket layer in
|
|
20
|
-
* the
|
|
20
|
+
* the 2-tier hierarchy (Epic → Story).
|
|
21
21
|
*
|
|
22
22
|
* Required after parse/normalize: a non-empty `goal`, and non-empty
|
|
23
23
|
* `changes`, `acceptance`, and `verify` arrays — and `changes` items must
|
|
@@ -110,7 +110,7 @@ function vagueVerbWithoutTarget(bullet) {
|
|
|
110
110
|
* - it has no body (null / undefined / empty-or-whitespace string — there
|
|
111
111
|
* is nothing to inspect).
|
|
112
112
|
*
|
|
113
|
-
* Under the
|
|
113
|
+
* Under the 2-tier hierarchy (Epic → Story), Stories carry the
|
|
114
114
|
* implementation scope inline. A canonical decomposition serializes the
|
|
115
115
|
* Story body to a markdown string, so a *string* body is NOT skipped here
|
|
116
116
|
* (Story #3906) — `validateTaskBodyShape` parses it back into structured
|
|
@@ -127,7 +127,7 @@ function vagueVerbWithoutTarget(bullet) {
|
|
|
127
127
|
*/
|
|
128
128
|
function shouldSkipTicket(ticket) {
|
|
129
129
|
if (!ticket) return true;
|
|
130
|
-
// Only Stories carry an inline implementation contract in the
|
|
130
|
+
// Only Stories carry an inline implementation contract in the 2-tier
|
|
131
131
|
// world. Features (and everything else) use narrative bodies.
|
|
132
132
|
if (ticket.type !== 'story') return true;
|
|
133
133
|
const body = ticket.body;
|
|
@@ -197,7 +197,7 @@ export function validateTaskBodyShape(ticket) {
|
|
|
197
197
|
}
|
|
198
198
|
errors.push(...collectChangesErrors(prefix, body.changes));
|
|
199
199
|
errors.push(...collectAcceptanceErrors(prefix, body.acceptance));
|
|
200
|
-
// Tier-suffix validation is always enforced on Story bodies (
|
|
200
|
+
// Tier-suffix validation is always enforced on Story bodies (2-tier world).
|
|
201
201
|
errors.push(...collectVerifyErrors(prefix, body.verify));
|
|
202
202
|
errors.push(...collectReferencesErrors(prefix, body.references));
|
|
203
203
|
return errors;
|
|
@@ -359,7 +359,7 @@ function collectVerifyErrors(prefix, rawVerify) {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
/**
|
|
362
|
-
* Validate every
|
|
362
|
+
* Validate every 2-tier Story in `tickets` whose `body` is a structured
|
|
363
363
|
* object. Returns an array of error strings (one per offending slug); empty
|
|
364
364
|
* array means clean.
|
|
365
365
|
*
|
|
@@ -96,7 +96,7 @@ export function normalizeOperatorHandle(raw) {
|
|
|
96
96
|
* observability artifact never wedges the lease preflight.
|
|
97
97
|
*
|
|
98
98
|
* This is the shared liveness source the lease guards thread into
|
|
99
|
-
* `acquireLease` via `heartbeatAt`; `/
|
|
99
|
+
* `acquireLease` via `heartbeatAt`; `/plan` and `/deliver` both
|
|
100
100
|
* reuse it so a live foreign claim actually refuses.
|
|
101
101
|
*
|
|
102
102
|
* @param {object} args
|
|
@@ -170,7 +170,7 @@ function collectStoryProducerPaths(story) {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
|
-
* Resolve the Story-identifying slug for a
|
|
173
|
+
* Resolve the Story-identifying slug for a 2-tier Story. A Story is its
|
|
174
174
|
* own implementation unit (Epic #3238) — there is no parent Task — so the
|
|
175
175
|
* producer/consumer indices key on the Story's own `slug`.
|
|
176
176
|
*/
|
|
@@ -187,7 +187,7 @@ function storySlugOf(story) {
|
|
|
187
187
|
* producers.
|
|
188
188
|
*
|
|
189
189
|
* `taskSlug` is retained in the entry shape for finding/render
|
|
190
|
-
* compatibility; in the
|
|
190
|
+
* compatibility; in the 2-tier model it carries the Story's own slug since
|
|
191
191
|
* the Story is the implementation unit.
|
|
192
192
|
*/
|
|
193
193
|
function indexProducers(stories) {
|
|
@@ -39,15 +39,6 @@ export const DEFAULT_TASK_SIZING = Object.freeze({
|
|
|
39
39
|
maxAcceptance: 14,
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* Soft prose guidance for how many Stories a Feature typically decomposes
|
|
44
|
-
* into before the Feature scope smells like two Features. Rendered into the
|
|
45
|
-
* decomposer prompt's Feature-count sentence from this single constant so
|
|
46
|
-
* the prompt prose cannot drift from the SSOT module (Story #3874). Advisory
|
|
47
|
-
* only — no validator finding keys off it.
|
|
48
|
-
*/
|
|
49
|
-
export const SOFT_STORIES_PER_FEATURE = 7;
|
|
50
|
-
|
|
51
42
|
/**
|
|
52
43
|
* `DELIVERABLE_GRANULARITY_GUIDANCE` is the **single source of truth** for the
|
|
53
44
|
* deliverable-granularity definition of a Story (Story #3777). It is stated
|
|
@@ -339,7 +330,7 @@ function computeStorySizingFindings(story, sizing) {
|
|
|
339
330
|
* `findings`; the AC-visible `errors[]` channel is the rendered
|
|
340
331
|
* subset where `severity === 'hard'`.
|
|
341
332
|
*
|
|
342
|
-
*
|
|
333
|
+
* 2-tier (Epic #3238): each Story is its own implementation unit and
|
|
343
334
|
* carries the `body` (acceptance / changes / wide) that the sizing layers
|
|
344
335
|
* score. There is no Task tier, so findings are computed directly over
|
|
345
336
|
* `stories`.
|
|
@@ -200,9 +200,8 @@ function makeMemoizedGitRunner(runner) {
|
|
|
200
200
|
* path was deleted between planning and decomposition) — refuse to decompose
|
|
201
201
|
* because the resulting Task would be unimplementable as written.
|
|
202
202
|
*
|
|
203
|
-
* Only
|
|
204
|
-
*
|
|
205
|
-
* the freshness regex would (correctly) ignore.
|
|
203
|
+
* Only Stories are scanned — they are the implementation unit; the Epic
|
|
204
|
+
* carries narrative copy, not implementation paths.
|
|
206
205
|
*
|
|
207
206
|
* @param {object} opts
|
|
208
207
|
* @param {object[]} opts.tickets - Validated ticket hierarchy.
|
|
@@ -416,7 +415,6 @@ function renderMissLine({ slug, path }) {
|
|
|
416
415
|
|
|
417
416
|
function indexTicketsBySlug(tickets) {
|
|
418
417
|
const ticketBySlug = new Map();
|
|
419
|
-
const features = [];
|
|
420
418
|
const stories = [];
|
|
421
419
|
const slugAdjacency = new Map();
|
|
422
420
|
for (const t of tickets) {
|
|
@@ -429,75 +427,37 @@ function indexTicketsBySlug(tickets) {
|
|
|
429
427
|
ticketBySlug.set(t.slug, t);
|
|
430
428
|
}
|
|
431
429
|
slugAdjacency.set(t.slug, t.depends_on ?? []);
|
|
432
|
-
if (t.type === '
|
|
433
|
-
else if (t.type === 'story') stories.push(t);
|
|
430
|
+
if (t.type === 'story') stories.push(t);
|
|
434
431
|
}
|
|
435
|
-
return { ticketBySlug,
|
|
432
|
+
return { ticketBySlug, stories, slugAdjacency };
|
|
436
433
|
}
|
|
437
434
|
|
|
438
|
-
|
|
439
|
-
|
|
435
|
+
/**
|
|
436
|
+
* 2-tier invariant (Story #4041): the decomposer emits Stories only — every
|
|
437
|
+
* ticket in the backlog must be `type: "story"` and at least one must be
|
|
438
|
+
* present. Any other type (the retired `feature`/`task` tiers, or planner
|
|
439
|
+
* hallucinations) HARD-rejects the decomposition.
|
|
440
|
+
*/
|
|
441
|
+
function assertAllTicketsAreStories({ tickets, stories }) {
|
|
442
|
+
const nonStories = (tickets ?? []).filter((t) => t.type !== 'story');
|
|
443
|
+
if (nonStories.length > 0) {
|
|
444
|
+
const list = nonStories
|
|
445
|
+
.map((t) => `"${t.title}" (${t.slug ?? '<no slug>'}, type: ${t.type})`)
|
|
446
|
+
.join(', ');
|
|
440
447
|
throw new Error(
|
|
441
|
-
|
|
448
|
+
`Cross-Validation Failed: ${nonStories.length} ticket(s) are not Stories: ${list}. ` +
|
|
449
|
+
'The 2-tier hierarchy (Epic → Story) admits type "story" only — there is no Feature or Task tier.',
|
|
442
450
|
);
|
|
451
|
+
}
|
|
443
452
|
if (stories.length === 0)
|
|
444
453
|
throw new Error(
|
|
445
454
|
'Cross-Validation Failed: Backlog must contain at least one Story.',
|
|
446
455
|
);
|
|
447
456
|
}
|
|
448
457
|
|
|
449
|
-
function assertHierarchy({ stories, ticketBySlug }) {
|
|
450
|
-
for (const story of stories) {
|
|
451
|
-
if (!story.parent_slug)
|
|
452
|
-
throw new Error(
|
|
453
|
-
`Cross-Validation Failed: Story "${story.title}" must have a parent_slug.`,
|
|
454
|
-
);
|
|
455
|
-
const parent = ticketBySlug.get(story.parent_slug);
|
|
456
|
-
if (!parent || parent.type !== 'feature')
|
|
457
|
-
throw new Error(
|
|
458
|
-
`Cross-Validation Failed: Story "${story.title}" parent must be a Feature.`,
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Deterministic invariant (Story #3777): a Feature MUST decompose into at
|
|
465
|
-
* least two Stories. A single-Story Feature is the work of a Story, not a
|
|
466
|
-
* Feature — the Feature wrapper is dead weight and signals decomposition at
|
|
467
|
-
* module/task granularity rather than deliverable granularity. HARD-reject
|
|
468
|
-
* the decomposition, naming the offending Feature(s) and telling the planner
|
|
469
|
-
* to collapse them, in the same throw-on-violation style as the surrounding
|
|
470
|
-
* hierarchy invariants.
|
|
471
|
-
*/
|
|
472
|
-
function assertNoSingleStoryFeature({ features, stories }) {
|
|
473
|
-
const storyCountByParent = new Map();
|
|
474
|
-
for (const story of stories) {
|
|
475
|
-
if (!story.parent_slug) continue;
|
|
476
|
-
storyCountByParent.set(
|
|
477
|
-
story.parent_slug,
|
|
478
|
-
(storyCountByParent.get(story.parent_slug) ?? 0) + 1,
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
const undersized = features.filter(
|
|
482
|
-
(feature) => (storyCountByParent.get(feature.slug) ?? 0) < 2,
|
|
483
|
-
);
|
|
484
|
-
if (undersized.length === 0) return;
|
|
485
|
-
const list = undersized
|
|
486
|
-
.map((feature) => {
|
|
487
|
-
const count = storyCountByParent.get(feature.slug) ?? 0;
|
|
488
|
-
return `"${feature.title}" (${feature.slug}, ${count} ${count === 1 ? 'Story' : 'Stories'})`;
|
|
489
|
-
})
|
|
490
|
-
.join(', ');
|
|
491
|
-
throw new Error(
|
|
492
|
-
`Cross-Validation Failed: ${undersized.length} Feature(s) decompose into fewer than two Stories: ${list}. ` +
|
|
493
|
-
'Every Feature MUST contain at least two Stories — a single-Story Feature is the work of a Story, not a Feature. ' +
|
|
494
|
-
'Collapse each offending Feature: drop the Feature wrapper and attach its lone Story to a sibling Feature, or merge the Feature into another.',
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
458
|
/**
|
|
499
459
|
* Return true when a Story object carries inline acceptance + verify
|
|
500
|
-
* arrays — the
|
|
460
|
+
* arrays — the inline-contract shape (Epic #3078) where the Story is itself the
|
|
501
461
|
* implementation unit and acceptance / verify live on the Story body
|
|
502
462
|
* rather than in child Task tickets.
|
|
503
463
|
*
|
|
@@ -506,7 +466,7 @@ function assertNoSingleStoryFeature({ features, stories }) {
|
|
|
506
466
|
* `acceptance[]` (no `verify[]`) cannot be implemented without a
|
|
507
467
|
* verification handle, and a Story with only `verify[]` (no
|
|
508
468
|
* `acceptance[]`) carries no observable criterion. Requiring both is the
|
|
509
|
-
* inline-contract invariant every Story must satisfy
|
|
469
|
+
* inline-contract invariant every Story must satisfy.
|
|
510
470
|
*/
|
|
511
471
|
function hasInlineAcceptanceAndVerify(story) {
|
|
512
472
|
if (story === null || typeof story !== 'object') return false;
|
|
@@ -520,7 +480,7 @@ function hasInlineAcceptanceAndVerify(story) {
|
|
|
520
480
|
}
|
|
521
481
|
|
|
522
482
|
function assertEveryStoryHasInlineContract({ stories }) {
|
|
523
|
-
//
|
|
483
|
+
// Every Story is its own implementation
|
|
524
484
|
// unit and MUST carry a non-empty inline contract — top-level
|
|
525
485
|
// `acceptance[]` AND `verify[]`. A Story missing either is the legacy
|
|
526
486
|
// 4-tier shape that expected child Tasks; there is no Task tier any
|
|
@@ -577,12 +537,9 @@ function attachFindingsAndErrors(tickets, findings, errors) {
|
|
|
577
537
|
}
|
|
578
538
|
|
|
579
539
|
export function validateAndNormalizeTickets(tickets, opts = {}) {
|
|
580
|
-
const { ticketBySlug,
|
|
581
|
-
indexTicketsBySlug(tickets);
|
|
540
|
+
const { ticketBySlug, stories, slugAdjacency } = indexTicketsBySlug(tickets);
|
|
582
541
|
|
|
583
|
-
|
|
584
|
-
assertHierarchy({ stories, ticketBySlug });
|
|
585
|
-
assertNoSingleStoryFeature({ features, stories });
|
|
542
|
+
assertAllTicketsAreStories({ tickets, stories });
|
|
586
543
|
assertEveryStoryHasInlineContract({ stories });
|
|
587
544
|
assertNoUnknownDeps({ tickets, ticketBySlug });
|
|
588
545
|
|
|
@@ -680,9 +637,7 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
|
|
|
680
637
|
// Internal helpers exposed for unit tests; not part of the public surface.
|
|
681
638
|
export const _internal = {
|
|
682
639
|
indexTicketsBySlug,
|
|
683
|
-
|
|
684
|
-
assertHierarchy,
|
|
685
|
-
assertNoSingleStoryFeature,
|
|
640
|
+
assertAllTicketsAreStories,
|
|
686
641
|
assertEveryStoryHasInlineContract,
|
|
687
642
|
assertNoUnknownDeps,
|
|
688
643
|
assertAcyclic,
|
|
@@ -291,7 +291,7 @@ async function processCascadeParentLocked(
|
|
|
291
291
|
if (!allDone) return { cascadedTo, failed };
|
|
292
292
|
|
|
293
293
|
// EXCLUSION: Epics do not auto-close via cascade. Epics close via
|
|
294
|
-
// formal /
|
|
294
|
+
// formal /deliver (its own machinery handles branch merges,
|
|
295
295
|
// PR-driven `Closes #N` auto-close, and a recovery transition in
|
|
296
296
|
// `epic-deliver-finalize.js`).
|
|
297
297
|
//
|
|
@@ -305,13 +305,6 @@ async function processCascadeParentLocked(
|
|
|
305
305
|
// defense-in-depth path when a Story's tasklist references a
|
|
306
306
|
// planning ticket directly.
|
|
307
307
|
//
|
|
308
|
-
// Features auto-close via cascade. A Feature is a purely
|
|
309
|
-
// hierarchical grouping — no standalone branch, no merge step.
|
|
310
|
-
// When its last child Story closes, the Feature is complete by
|
|
311
|
-
// definition. Operators who need Feature-level AC verification
|
|
312
|
-
// should encode it in the final child Story, not rely on a manual
|
|
313
|
-
// close step.
|
|
314
|
-
//
|
|
315
308
|
// Reuse the parentSnapshot from the idempotency check above — it is
|
|
316
309
|
// a fresh read (cache was invalidated before the getTicket call) and
|
|
317
310
|
// the parent's type label is invariant within a single cascade lock
|
|
@@ -371,7 +364,7 @@ async function processCascadeParentLocked(
|
|
|
371
364
|
* If yes, transitions parent to DONE and cascades up.
|
|
372
365
|
*
|
|
373
366
|
* Parents run strictly sequentially in input order (Story #4017 —
|
|
374
|
-
* fan-out is <= 1 under the
|
|
367
|
+
* fan-out is <= 1 under the 2-tier hierarchy, so the former
|
|
375
368
|
* shared-ancestor grouping / parallel dispatch was deleted); concurrent
|
|
376
369
|
* transitions against a shared ancestor would race the "all children
|
|
377
370
|
* done?" check. Within each parent, sibling reads fan out via
|
|
@@ -415,7 +408,7 @@ export async function cascadeCompletion(provider, ticketId, opts = {}) {
|
|
|
415
408
|
// resume reconciler can strip the `parent: #N` orchestrator footer
|
|
416
409
|
// from a Story body (see Issue 2 in #2982); without the body marker
|
|
417
410
|
// the cascade silently returned `{ cascadedTo: [], failed: [] }` and
|
|
418
|
-
// left intermediate
|
|
411
|
+
// left intermediate parent tickets stranded OPEN. The native link is
|
|
419
412
|
// independent of body text, so consult it when the first two
|
|
420
413
|
// strategies came back empty.
|
|
421
414
|
if (
|
|
@@ -442,7 +435,7 @@ export async function cascadeCompletion(provider, ticketId, opts = {}) {
|
|
|
442
435
|
return { cascadedTo: [], failed: [] };
|
|
443
436
|
}
|
|
444
437
|
|
|
445
|
-
// Story #4017 — under the
|
|
438
|
+
// Story #4017 — under the 2-tier hierarchy a ticket has at most one
|
|
446
439
|
// parent, so the shared-ancestor grouping / parallel-group dispatch
|
|
447
440
|
// machinery was deleted. Parents (fan-out <= 1
|
|
448
441
|
// in practice; the loop stays general for the body-reference fallback)
|
|
@@ -557,7 +550,7 @@ export async function cascadeParentState(provider, ticketId, opts = {}) {
|
|
|
557
550
|
if (parsedParents.length === 0) return { cascadedTo: [], failed: [] };
|
|
558
551
|
|
|
559
552
|
// Story #4017 — sequential per-parent walk (fan-out <= 1 under the
|
|
560
|
-
//
|
|
553
|
+
// 2-tier hierarchy); see cascadeCompletion for the rationale.
|
|
561
554
|
const cascadedTo = [];
|
|
562
555
|
const failed = [];
|
|
563
556
|
for (const parentId of parsedParents) {
|
|
@@ -76,8 +76,8 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
76
76
|
// downstream workflow steps don't have to infer install state from
|
|
77
77
|
// node_modules presence.
|
|
78
78
|
'story-init',
|
|
79
|
-
// Story #908 — /
|
|
80
|
-
// on each Story per Task transition. The /
|
|
79
|
+
// Story #908 — /deliver upserts a `story-run-progress` snapshot
|
|
80
|
+
// on each Story per Task transition. The /deliver aggregator and
|
|
81
81
|
// the epic-runner progress reporter both read this comment to derive
|
|
82
82
|
// Story-level state without re-fetching ticket labels.
|
|
83
83
|
'story-run-progress',
|
|
@@ -98,7 +98,7 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
98
98
|
// operator can correct drift before Phase 8 decomposes from a stale
|
|
99
99
|
// spec. Advisory: the run continues regardless of the report contents.
|
|
100
100
|
'spec-freshness',
|
|
101
|
-
// Story #2681 — `/
|
|
101
|
+
// Story #2681 — `/deliver` Phase 4 epic-audit helper upserts an
|
|
102
102
|
// `audit-results` comment on the Epic listing the per-lens findings
|
|
103
103
|
// returned by the change-set audit pass. The marker was prescribed by
|
|
104
104
|
// `helpers/epic-audit.md` Step 4 long before it was added to this
|
|
@@ -126,7 +126,7 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
126
126
|
'epic-handoff',
|
|
127
127
|
// Story #2899 (Epic #2880, F13) — `epic-deliver-preflight.js` upserts a
|
|
128
128
|
// `delivery-preflight` comment on the Epic at the start of
|
|
129
|
-
// /
|
|
129
|
+
// /deliver Phase 1, surfacing estimated story count, install cost,
|
|
130
130
|
// wave count, GitHub API request volume, Claude quota burn, and any
|
|
131
131
|
// threshold breaches against `delivery.preflight.max*`. One entry per
|
|
132
132
|
// Epic; re-runs replace prior content.
|
|
@@ -137,7 +137,7 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
137
137
|
// `close-validate.end`. One entry per Epic; re-ticks with the same
|
|
138
138
|
// findings upsert in place (`upsertStructuredComment` diffs by body).
|
|
139
139
|
'recurring-failure-class',
|
|
140
|
-
// Story #3061 (Epic #3051) — the /
|
|
140
|
+
// Story #3061 (Epic #3051) — the /deliver §2e Idle Watchdog
|
|
141
141
|
// subsection instructs the parent host LLM to upsert a `wave-stall`
|
|
142
142
|
// comment on the Epic whenever an in-flight Story has been silent for
|
|
143
143
|
// longer than the configured cadence. `wave-tick.js --check-idle`
|
|
@@ -153,7 +153,7 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
153
153
|
'risk-verdict',
|
|
154
154
|
// Story #4019 — `epic-plan-lease-guard.js` upserts a `plan-lease`
|
|
155
155
|
// comment on the Epic at lease-acquire time, recording the claiming
|
|
156
|
-
// operator and the claim timestamp. `/
|
|
156
|
+
// operator and the claim timestamp. `/plan` emits no
|
|
157
157
|
// `story.heartbeat`, so this claim-time is the liveness signal that
|
|
158
158
|
// makes the documented `--steal` contract decidable: a foreign claim
|
|
159
159
|
// older than the lease TTL is reclaimed automatically; a fresh one
|
|
@@ -268,8 +268,8 @@ export const _structuredCommentCache = new WeakMap();
|
|
|
268
268
|
/**
|
|
269
269
|
* Build a well-formed ticket snapshot for a Story that has zero child
|
|
270
270
|
* Tasks. Story #3097 (Wave-0 additive, Epic #3078 Strategy B) — the
|
|
271
|
-
*
|
|
272
|
-
* Epic →
|
|
271
|
+
* 2-tier hierarchy collapses Epic → Story → Task into
|
|
272
|
+
* Epic → Story, so a Story may legitimately have no Task
|
|
273
273
|
* children. Read-side callers that expect a `subTickets` array on the
|
|
274
274
|
* Story can route through this helper to materialise an empty-children
|
|
275
275
|
* snapshot without paying a provider round-trip and without risk of
|
|
@@ -66,13 +66,13 @@ registerCascadeRunner(async (provider, ticketId, opts) => {
|
|
|
66
66
|
/**
|
|
67
67
|
* Transition a Story ticket directly to a new `agent::*` state without
|
|
68
68
|
* walking a Task cascade. Story #3097 (Wave-0 additive, Epic #3078
|
|
69
|
-
* Strategy B) — in the
|
|
69
|
+
* Strategy B) — in the 2-tier hierarchy a Story has no Task children, so
|
|
70
70
|
* the canonical `transitionTicketState` upward-cascade path
|
|
71
71
|
* (`cascadeParentState`) is the only meaningful walk. This helper is a
|
|
72
|
-
* thin wrapper that pins `cascade: true` (so the parent
|
|
72
|
+
* thin wrapper that pins `cascade: true` (so the parent Epic
|
|
73
73
|
* still receives derived-state updates) and is intentionally a no-op
|
|
74
74
|
* difference from `transitionTicketState` in 4-tier mode — the helper
|
|
75
|
-
* exists so
|
|
75
|
+
* exists so 2-tier callers can opt into a name that documents intent
|
|
76
76
|
* (and so F8 can pivot the implementation to skip the now-impossible
|
|
77
77
|
* Task-fan-in without rewriting call sites). The wrapper preserves every
|
|
78
78
|
* `opts` field the caller supplies; only `cascade` defaults to `true`
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `epic-execute-record-wave.js`.
|
|
4
4
|
*
|
|
5
5
|
* The CLI fires curated webhook events at every wave boundary (started,
|
|
6
|
-
* progress, blocked, unblocked) so the host-LLM-driven `/
|
|
6
|
+
* progress, blocked, unblocked) so the host-LLM-driven `/deliver` path
|
|
7
7
|
* mirrors the wave-loop emits in
|
|
8
8
|
* `lib/orchestration/epic-runner/phases/iterate-waves.js`. Each helper here
|
|
9
9
|
* is fire-and-forget — webhook misconfig or a transient Slack outage must
|
|
@@ -45,7 +45,7 @@ export function buildNotifyFn(injectedNotify, config, provider, defaultNotify) {
|
|
|
45
45
|
* fire-and-forget (the emit helpers swallow webhook misconfiguration), but
|
|
46
46
|
* we still serialise them so the order matches the wave-loop emits in
|
|
47
47
|
* `lib/orchestration/epic-runner/phases/iterate-waves.js` for the host-LLM
|
|
48
|
-
* driven /
|
|
48
|
+
* driven /deliver path.
|
|
49
49
|
*/
|
|
50
50
|
export async function emitWaveBoundaryNotifications({
|
|
51
51
|
injectedNotify,
|
|
@@ -32,7 +32,7 @@ import { parseStoryAgentReturn } from './epic-runner/sub-agent-return.js';
|
|
|
32
32
|
/** Valid wave-level rollup statuses. */
|
|
33
33
|
export const VALID_RESULT_STATUSES = new Set(['complete', 'blocked', 'failed']);
|
|
34
34
|
|
|
35
|
-
/** Per-Story return statuses we accept off `/
|
|
35
|
+
/** Per-Story return statuses we accept off `/deliver` sub-agents. */
|
|
36
36
|
export const VALID_STORY_STATUSES = new Set(['done', 'blocked', 'failed']);
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* plan-phase-cleanup.js — Post-phase temp-file cleanup for `/
|
|
2
|
+
* plan-phase-cleanup.js — Post-phase temp-file cleanup for `/plan`.
|
|
3
3
|
*
|
|
4
4
|
* The spec and decompose phases write several Epic-scoped temp files under
|
|
5
5
|
* the per-Epic tree (`temp/epic-<id>/planner-context.json`,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* auto-fixes. The wrapper prints a human-readable blocker table
|
|
14
14
|
* (`id · summary · fixCommand`) before returning. Code 2 is the
|
|
15
15
|
* project-wide "preflight refused" reservation — see
|
|
16
|
-
* .agents/workflows/epic
|
|
16
|
+
* .agents/workflows/helpers/deliver-epic.md for the rationale.
|
|
17
17
|
*
|
|
18
18
|
* Auto-fixes are logged via `logFixes` before the blocker check so the
|
|
19
19
|
* operator sees the "we corrected X" line even when a separate blocker
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Pure: project a full dispatch manifest into the `{ stories }` shape
|
|
15
|
-
* `renderManifest` accepts. Returns the canonical, non-
|
|
16
|
-
*
|
|
17
|
-
* comment.
|
|
15
|
+
* `renderManifest` accepts. Returns the canonical, non-ungrouped story
|
|
16
|
+
* rows used by the Epic-level dispatch-manifest comment.
|
|
18
17
|
*
|
|
19
18
|
* @param {object} manifest
|
|
20
19
|
* @returns {{ storyId: number|string, wave: number, title: string }[]}
|
|
@@ -22,7 +21,7 @@
|
|
|
22
21
|
export function projectStoriesFromManifest(manifest) {
|
|
23
22
|
const storyManifest = manifest?.storyManifest ?? [];
|
|
24
23
|
return storyManifest
|
|
25
|
-
.filter((s) => s && s.
|
|
24
|
+
.filter((s) => s && s.storyId !== '__ungrouped__')
|
|
26
25
|
.map((s) => ({
|
|
27
26
|
storyId: s.storyId,
|
|
28
27
|
wave: s.earliestWave ?? -1,
|
|
@@ -71,7 +70,7 @@ export function renderManifest({ epicId, stories, generatedAt }) {
|
|
|
71
70
|
`- **Stories:** ${list.length}`,
|
|
72
71
|
`- **Generated:** ${generatedAt}`,
|
|
73
72
|
'',
|
|
74
|
-
'Source of truth for the wave-completeness gate run at `/
|
|
73
|
+
'Source of truth for the wave-completeness gate run at `/deliver`.',
|
|
75
74
|
'',
|
|
76
75
|
'```json',
|
|
77
76
|
JSON.stringify({ stories: list }, null, 2),
|
|
@@ -9,19 +9,19 @@
|
|
|
9
9
|
* Extracted from `manifest-formatter.js` (Story #1849 Task #1869). The
|
|
10
10
|
* shape projection used to be inlined in the formatter; pulling it out
|
|
11
11
|
* isolates the spec → manifest projection from the Markdown renderer and
|
|
12
|
-
* lets the per-
|
|
12
|
+
* lets the per-story guard cascade live behind a single
|
|
13
13
|
* private predicate (`validateSpecShape`) so the orchestrator function's
|
|
14
14
|
* CRAP score drops below 12.
|
|
15
15
|
*
|
|
16
|
-
* Story #3413 (
|
|
16
|
+
* Story #3413 (2-tier cutover, final): the residual per-Task projection
|
|
17
17
|
* (the Task projector, the per-Story Task array, and the Task-count
|
|
18
|
-
* rollup) has been deleted. The walker (`
|
|
18
|
+
* rollup) has been deleted. The walker (`projectStories`) counts
|
|
19
19
|
* Stories directly, and `summary` carries Story-tier counts only
|
|
20
20
|
* (`totalStories` / `doneStories` / `progressPercent`), matching the
|
|
21
21
|
* canonical producer in `lib/orchestration/manifest-builder.js`.
|
|
22
22
|
*
|
|
23
23
|
* Story-level status surfaces on each `storyEntry.status` so downstream
|
|
24
|
-
* renderers read the Story's own `agent::*` label directly — the
|
|
24
|
+
* renderers read the Story's own `agent::*` label directly — the
|
|
25
25
|
* "Stories are first-class lifecycle units" invariant.
|
|
26
26
|
*
|
|
27
27
|
* No fs / network access; pure transform. Caller supplies `state` from
|
|
@@ -38,8 +38,7 @@ import { AGENT_LABELS } from '../label-constants.js';
|
|
|
38
38
|
* "is this thing iterable / object-shaped?" decisions.
|
|
39
39
|
*
|
|
40
40
|
* `level` describes which spec node we are validating:
|
|
41
|
-
* - `'
|
|
42
|
-
* - `'stories'` → a feature's `stories` array
|
|
41
|
+
* - `'stories'` → the spec-level `stories` array
|
|
43
42
|
* - `'story'` → a single Story object (must be a non-null object)
|
|
44
43
|
*
|
|
45
44
|
* Returns `true` when the node satisfies the shape contract for that
|
|
@@ -52,7 +51,6 @@ import { AGENT_LABELS } from '../label-constants.js';
|
|
|
52
51
|
*/
|
|
53
52
|
function validateSpecShape(level, value) {
|
|
54
53
|
switch (level) {
|
|
55
|
-
case 'features':
|
|
56
54
|
case 'stories':
|
|
57
55
|
return Array.isArray(value);
|
|
58
56
|
case 'story':
|
|
@@ -102,8 +100,8 @@ function buildResolvers(state) {
|
|
|
102
100
|
* Private: project a single spec Story into a manifest Story entry. The
|
|
103
101
|
* Story's status is resolved directly from the Story-level label
|
|
104
102
|
* (`state.mapping[slug].lastObservedAgentState`) and surfaces on
|
|
105
|
-
* `storyEntry.status` —
|
|
106
|
-
*
|
|
103
|
+
* `storyEntry.status` — Stories carry their own lifecycle state and are
|
|
104
|
+
* leaves with no child tickets. Caller
|
|
107
105
|
* filters non-object stories with `validateSpecShape('story', ...)`
|
|
108
106
|
* before invoking.
|
|
109
107
|
*
|
|
@@ -129,16 +127,17 @@ function projectStory(story, resolvers) {
|
|
|
129
127
|
}
|
|
130
128
|
|
|
131
129
|
/**
|
|
132
|
-
* Private: walk every
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
130
|
+
* Private: walk every Story in a spec and collect the per-story
|
|
131
|
+
* projections + Story-tier roll-up counters. Keeps the loop machinery
|
|
132
|
+
* out of `buildManifestFromSpec` so the entry point reads as a straight
|
|
133
|
+
* assembly of the result envelope.
|
|
136
134
|
*
|
|
137
|
-
* Under the
|
|
138
|
-
* rollup counts Stories directly:
|
|
139
|
-
* Story and `doneStories` is the
|
|
135
|
+
* Under the 2-tier hierarchy (Story #4041) Stories are direct Epic
|
|
136
|
+
* children and leaves, so the rollup counts Stories directly:
|
|
137
|
+
* `totalStories` is every projected Story and `doneStories` is the
|
|
138
|
+
* subset carrying `agent::done`.
|
|
140
139
|
*
|
|
141
|
-
* @param {object[]}
|
|
140
|
+
* @param {object[]} stories
|
|
142
141
|
* @param {{ resolveId: Function, resolveStatus: Function }} resolvers
|
|
143
142
|
* @returns {{
|
|
144
143
|
* storyManifest: object[],
|
|
@@ -147,23 +146,18 @@ function projectStory(story, resolvers) {
|
|
|
147
146
|
* waveSet: Set<number>,
|
|
148
147
|
* }}
|
|
149
148
|
*/
|
|
150
|
-
function
|
|
149
|
+
function projectStories(stories, resolvers) {
|
|
151
150
|
const storyManifest = [];
|
|
152
151
|
let totalStories = 0;
|
|
153
152
|
let doneStories = 0;
|
|
154
153
|
const waveSet = new Set();
|
|
155
|
-
for (const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
storyManifest.push(storyEntry);
|
|
163
|
-
totalStories++;
|
|
164
|
-
if (storyEntry.status === AGENT_LABELS.DONE) doneStories++;
|
|
165
|
-
if (wave >= 0) waveSet.add(wave);
|
|
166
|
-
}
|
|
154
|
+
for (const story of stories) {
|
|
155
|
+
if (!validateSpecShape('story', story)) continue;
|
|
156
|
+
const { storyEntry, wave } = projectStory(story, resolvers);
|
|
157
|
+
storyManifest.push(storyEntry);
|
|
158
|
+
totalStories++;
|
|
159
|
+
if (storyEntry.status === AGENT_LABELS.DONE) doneStories++;
|
|
160
|
+
if (wave >= 0) waveSet.add(wave);
|
|
167
161
|
}
|
|
168
162
|
return { storyManifest, totalStories, doneStories, waveSet };
|
|
169
163
|
}
|
|
@@ -199,12 +193,12 @@ export function buildManifestFromSpec(spec, opts = {}) {
|
|
|
199
193
|
spec?.epic && typeof spec.epic.id === 'number' ? spec.epic.id : null;
|
|
200
194
|
const epicTitle =
|
|
201
195
|
spec?.epic && typeof spec.epic.title === 'string' ? spec.epic.title : '';
|
|
202
|
-
const
|
|
203
|
-
? spec.
|
|
196
|
+
const stories = validateSpecShape('stories', spec?.stories)
|
|
197
|
+
? spec.stories
|
|
204
198
|
: [];
|
|
205
199
|
|
|
206
|
-
const { storyManifest, totalStories, doneStories, waveSet } =
|
|
207
|
-
|
|
200
|
+
const { storyManifest, totalStories, doneStories, waveSet } = projectStories(
|
|
201
|
+
stories,
|
|
208
202
|
resolvers,
|
|
209
203
|
);
|
|
210
204
|
|