mandrel 1.81.0 → 1.83.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 +46 -5
- package/.agents/docs/SDLC.md +97 -82
- package/.agents/docs/agentrc-reference.json +10 -2
- package/.agents/docs/configuration.md +4 -1
- package/.agents/docs/execution-reference.md +52 -0
- package/.agents/docs/workflows.md +1 -1
- package/.agents/instructions.md +85 -45
- package/.agents/personas/architect.md +8 -5
- package/.agents/personas/engineer-mobile.md +3 -2
- package/.agents/personas/engineer-web.md +3 -2
- package/.agents/personas/engineer.md +6 -5
- package/.agents/personas/product.md +19 -13
- package/.agents/personas/project-manager.md +9 -8
- package/.agents/personas/qa-engineer.md +10 -6
- package/.agents/personas/refactorer.md +3 -2
- package/.agents/personas/technical-writer.md +2 -1
- package/.agents/personas/ux-designer.md +2 -2
- package/.agents/schemas/agentrc.schema.json +41 -3
- package/.agents/schemas/qa-ledger.schema.json +2 -2
- package/.agents/scripts/acceptance-spec-reconciler.js +143 -59
- package/.agents/scripts/epic-deliver-prepare.js +40 -31
- package/.agents/scripts/epic-plan-decompose.js +2 -5
- package/.agents/scripts/epic-plan-spec.js +16 -19
- package/.agents/scripts/hierarchy-gate.js +11 -11
- package/.agents/scripts/lib/ITicketingProvider.js +4 -3
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +1 -1
- package/.agents/scripts/lib/cli-args.js +1 -5
- package/.agents/scripts/lib/codebase-snapshot.js +1 -1
- package/.agents/scripts/lib/config/explain.js +4 -1
- package/.agents/scripts/lib/config/temp-paths.js +1 -4
- package/.agents/scripts/lib/config-settings-schema.js +30 -1
- package/.agents/scripts/lib/epic-body-sections.js +310 -0
- package/.agents/scripts/lib/epic-plan-clarity.js +38 -1
- package/.agents/scripts/lib/epic-plan-ideation.js +15 -3
- package/.agents/scripts/lib/findings/promote-finding.js +3 -3
- package/.agents/scripts/lib/findings/severity.js +5 -6
- package/.agents/scripts/lib/label-constants.js +7 -17
- package/.agents/scripts/lib/label-taxonomy.js +4 -21
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +65 -2
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +105 -9
- package/.agents/scripts/lib/orchestration/doc-reader.js +29 -0
- package/.agents/scripts/lib/orchestration/docs-digest.js +134 -0
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +23 -22
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +7 -10
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +4 -38
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +8 -9
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +11 -5
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +26 -5
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +102 -304
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +32 -29
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +19 -20
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +6 -9
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +3 -4
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +20 -27
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +11 -5
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +22 -59
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +1 -1
- package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-freshness.js +3 -3
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +15 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +122 -1
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -8
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +2 -2
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -2
- package/.agents/scripts/lib/qa/console-allowlist.js +5 -4
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +6 -85
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +144 -8
- package/.agents/scripts/lib/templates/decomposer-prompts.js +14 -8
- package/.agents/scripts/lifecycle-emit.js +1 -1
- package/.agents/scripts/lint-label-vocabulary.js +2 -3
- package/.agents/scripts/providers/github/mappers.js +0 -3
- package/.agents/scripts/providers/github/tickets.js +7 -18
- package/.agents/scripts/single-story-init.js +0 -1
- package/.agents/scripts/story-init.js +1 -29
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +43 -22
- package/.agents/skills/core/epic-plan-consolidate/examples.md +51 -0
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +27 -40
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +47 -0
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +15 -13
- package/.agents/skills/core/epic-plan-premortem/examples.md +53 -0
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +143 -151
- package/.agents/skills/core/epic-plan-spec-author/examples.md +91 -0
- package/.agents/skills/core/hydrate-context/SKILL.md +10 -5
- package/.agents/skills/core/knowledge-transfer/SKILL.md +3 -2
- package/.agents/skills/core/scope-triage/SKILL.md +2 -1
- package/.agents/skills/skills.index.json +8 -8
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +52 -38
- package/.agents/templates/epic-from-idea.md +4 -0
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/code-review.md +11 -9
- package/.agents/workflows/helpers/deliver-epic-reference.md +514 -0
- package/.agents/workflows/helpers/deliver-epic.md +173 -490
- package/.agents/workflows/helpers/epic-audit.md +11 -8
- package/.agents/workflows/helpers/epic-deliver-story.md +45 -27
- package/.agents/workflows/helpers/epic-plan-decompose.md +17 -12
- package/.agents/workflows/helpers/epic-plan-spec.md +68 -68
- package/.agents/workflows/helpers/parallel-tooling.md +2 -1
- package/.agents/workflows/helpers/plan-epic-reference.md +136 -0
- package/.agents/workflows/helpers/plan-epic.md +141 -256
- package/.agents/workflows/helpers/plan-story.md +31 -61
- package/.agents/workflows/helpers/qa-run-scenario.md +194 -0
- package/.agents/workflows/helpers/scope-triage-gate.md +97 -0
- package/.agents/workflows/helpers/single-story-deliver-reference.md +423 -0
- package/.agents/workflows/helpers/single-story-deliver.md +129 -393
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -1
- package/.agents/workflows/plan.md +8 -8
- package/.agents/workflows/qa-assist.md +2 -1
- package/.agents/workflows/qa-explore.md +63 -32
- package/.agents/workflows/qa-run.md +293 -130
- package/docs/CHANGELOG.md +35 -0
- package/package.json +1 -1
- package/.agents/schemas/qa-finding.schema.json +0 -133
- package/.agents/scripts/lib/issue-link-parser.js +0 -74
- package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +0 -116
- package/.agents/scripts/lib/orchestration/planning-state-manager.js +0 -318
- package/.agents/scripts/lib/story-init/hierarchy-tracer.js +0 -57
|
@@ -66,7 +66,6 @@ export const KNOWN_AXES = Object.freeze([
|
|
|
66
66
|
'type',
|
|
67
67
|
'status',
|
|
68
68
|
'persona',
|
|
69
|
-
'context',
|
|
70
69
|
'acceptance',
|
|
71
70
|
'meta',
|
|
72
71
|
'planning',
|
|
@@ -156,11 +155,11 @@ export function findVocabularyViolations(src, axes = KNOWN_AXES) {
|
|
|
156
155
|
// - value is a lowercase identifier (`[a-z][a-z0-9-]*`)
|
|
157
156
|
//
|
|
158
157
|
// The whole-body anchor avoids false positives on:
|
|
159
|
-
// - `
|
|
158
|
+
// - `acceptance::n-a` — canonical label, value contains a
|
|
160
159
|
// hyphen but is not at axis position.
|
|
161
160
|
// - `<type>/<slug>` — template placeholders.
|
|
162
161
|
// - `delivery.maxTokenBudget` — config-key paths with `.`.
|
|
163
|
-
// - `
|
|
162
|
+
// - `delivery.{maxTickets}` — JS destructure shapes.
|
|
164
163
|
// - `agent-protocol.md` — filenames / concept slugs.
|
|
165
164
|
//
|
|
166
165
|
// Real drift like `type/epic` matches because the WHOLE body is
|
|
@@ -11,8 +11,6 @@
|
|
|
11
11
|
* Extracted from `../github.js` in Story #1846 / Task #1859.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { parseLinkedIssues } from '../../lib/issue-link-parser.js';
|
|
15
|
-
|
|
16
14
|
function normalizeLabels(issue) {
|
|
17
15
|
const raw = issue?.labels;
|
|
18
16
|
if (!raw) return [];
|
|
@@ -50,7 +48,6 @@ export function issueToEpic(issue) {
|
|
|
50
48
|
body: issue.body ?? '',
|
|
51
49
|
labels,
|
|
52
50
|
labelSet: new Set(labels),
|
|
53
|
-
linkedIssues: parseLinkedIssues(issue.body),
|
|
54
51
|
};
|
|
55
52
|
}
|
|
56
53
|
|
|
@@ -112,7 +112,7 @@ export class TicketGateway {
|
|
|
112
112
|
this._cache = cache ?? createInlineTicketCache();
|
|
113
113
|
/**
|
|
114
114
|
* Per-instance memo of `getTickets(epicId, filters)` results (Story
|
|
115
|
-
* #3988).
|
|
115
|
+
* #3988). Planning-era healing fetched the same child list twice
|
|
116
116
|
* per planning pass; without this memo each fetch re-pays the full
|
|
117
117
|
* search/list round-trip. Invalidated on every write surface.
|
|
118
118
|
* @type {Map<string, object[]>}
|
|
@@ -322,24 +322,13 @@ export class TicketGateway {
|
|
|
322
322
|
// Mirror the Epic create path (issues.js:160 → `labels: TYPE_LABELS.EPIC`):
|
|
323
323
|
// inject TYPE_LABELS.STORY so a spec that omits the labels array cannot
|
|
324
324
|
// produce an unlabeled, undispatchable Story. Dedupe to avoid duplicates
|
|
325
|
-
// when the caller already carries the label.
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
// ticket class created through this same factory carrying a `context::*`
|
|
329
|
-
// label (and no `type::story`). They MUST NOT be stamped `type::story`:
|
|
330
|
-
// doing so makes every `type::story`-counting consumer — the decompose
|
|
331
|
-
// open-children guard (`assertNoOpenPlanChildren`), the delivery wave
|
|
332
|
-
// builder (`discoverOpenStories`), the plan healthcheck — mis-classify
|
|
333
|
-
// them as deliverable Stories. Skip the injection when the caller's labels
|
|
334
|
-
// already classify the ticket as context.
|
|
325
|
+
// when the caller already carries the label. (Story #4324 retired the
|
|
326
|
+
// `context::*` ticket classes, so every ticket created through this
|
|
327
|
+
// factory is a Story.)
|
|
335
328
|
const callerLabels = ticketData.labels ?? [];
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const labels =
|
|
340
|
-
isContextTicket || callerLabels.includes(TYPE_LABELS.STORY)
|
|
341
|
-
? callerLabels
|
|
342
|
-
: [TYPE_LABELS.STORY, ...callerLabels];
|
|
329
|
+
const labels = callerLabels.includes(TYPE_LABELS.STORY)
|
|
330
|
+
? callerLabels
|
|
331
|
+
: [TYPE_LABELS.STORY, ...callerLabels];
|
|
343
332
|
const result = await this._gh.api({
|
|
344
333
|
method: 'POST',
|
|
345
334
|
endpoint: `/repos/${this.owner}/${this.repo}/issues`,
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* 6. Flip the Story to `agent::executing`.
|
|
24
24
|
*
|
|
25
25
|
* What this script does NOT do (and why):
|
|
26
|
-
* - Skips `traceHierarchy` — no Epic → no PRD/Tech-Spec.
|
|
27
26
|
* - Skips `validateBlockers` against the body's `Blocked by:` markers —
|
|
28
27
|
* pre-flight is still the operator's responsibility, but the Epic-scope
|
|
29
28
|
* blocker chain doesn't fit.
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* pipeline stages from `lib/story-init/`:
|
|
10
10
|
*
|
|
11
11
|
* 1. context-resolver — fetch the Story + optionally mark as recut.
|
|
12
|
-
* 2. hierarchy-tracer — resolve Feature/Epic → PRD / Tech Spec.
|
|
13
12
|
* 3. blocker-validator — refuse to proceed while dependencies are open.
|
|
14
13
|
* 4. task-graph-builder — fetch + topologically sort child Tasks.
|
|
15
14
|
* 5. branch-initializer — materialise the story branch (single-tree
|
|
@@ -53,7 +52,6 @@ import {
|
|
|
53
52
|
planStoryBranchSeed,
|
|
54
53
|
} from './lib/story-init/branch-initializer.js';
|
|
55
54
|
import { resolveContext } from './lib/story-init/context-resolver.js';
|
|
56
|
-
import { traceHierarchy } from './lib/story-init/hierarchy-tracer.js';
|
|
57
55
|
import { transitionStoryToExecuting } from './lib/story-init/state-transitioner.js';
|
|
58
56
|
import { buildTaskGraph } from './lib/story-init/task-graph-builder.js';
|
|
59
57
|
import { createPhaseTimer } from './lib/util/phase-timer.js';
|
|
@@ -88,8 +86,6 @@ export async function runStoryInit({
|
|
|
88
86
|
dryRun: dryRunParam,
|
|
89
87
|
cwd: cwdParam,
|
|
90
88
|
recutOf: recutOfParam,
|
|
91
|
-
prdId: prdIdParam,
|
|
92
|
-
techSpecId: techSpecIdParam,
|
|
93
89
|
injectedProvider,
|
|
94
90
|
injectedConfig,
|
|
95
91
|
} = {}) {
|
|
@@ -100,17 +96,10 @@ export async function runStoryInit({
|
|
|
100
96
|
dryRun: !!dryRunParam,
|
|
101
97
|
cwd: cwdParam ?? null,
|
|
102
98
|
recutOf: recutOfParam ?? null,
|
|
103
|
-
prdId: prdIdParam ?? null,
|
|
104
|
-
techSpecId: techSpecIdParam ?? null,
|
|
105
99
|
}
|
|
106
100
|
: parseSprintArgs();
|
|
107
101
|
const { storyId, dryRun } = parsed;
|
|
108
102
|
const recutOf = recutOfParam ?? parsed.recutOf ?? null;
|
|
109
|
-
// Story #4253: pre-resolved Epic linkages (from the /deliver fan-out's
|
|
110
|
-
// one-time Epic resolution). When both are present, hierarchy-tracer skips
|
|
111
|
-
// the per-Story getEpic round-trip; when absent it resolves them itself.
|
|
112
|
-
const threadedPrdId = prdIdParam ?? parsed.prdId ?? null;
|
|
113
|
-
const threadedTechSpecId = techSpecIdParam ?? parsed.techSpecId ?? null;
|
|
114
103
|
// Worktree-aware cwd resolution: explicit param > --cwd flag > env > PROJECT_ROOT.
|
|
115
104
|
const cwd = path.resolve(cwdParam ?? parsed.cwd ?? PROJECT_ROOT);
|
|
116
105
|
|
|
@@ -155,20 +144,7 @@ export async function runStoryInit({
|
|
|
155
144
|
input: { storyId, recutOf, dryRun },
|
|
156
145
|
});
|
|
157
146
|
|
|
158
|
-
// Stage 2 — hierarchy. When the /deliver fan-out threaded --prd/--tech-spec
|
|
159
|
-
// (both resolved once by the parent), this short-circuits the per-Story
|
|
160
|
-
// getEpic. Absent flags fall back to the legacy getEpic resolution.
|
|
161
|
-
const { prdId, techSpecId } = await traceHierarchy({
|
|
162
|
-
provider,
|
|
163
|
-
logger: stageLogger,
|
|
164
|
-
input: { epicId, prdId: threadedPrdId, techSpecId: threadedTechSpecId },
|
|
165
|
-
});
|
|
166
|
-
|
|
167
147
|
progress('CONTEXT', `Epic: #${epicId}, Parent: #${parentId ?? 'none'}`);
|
|
168
|
-
progress(
|
|
169
|
-
'CONTEXT',
|
|
170
|
-
`PRD: #${prdId ?? 'none'}, Tech Spec: #${techSpecId ?? 'none'}`,
|
|
171
|
-
);
|
|
172
148
|
|
|
173
149
|
// Stage 3 — blockers.
|
|
174
150
|
const { openBlockers } = await validateBlockers({
|
|
@@ -335,8 +311,6 @@ export async function runStoryInit({
|
|
|
335
311
|
installStatus,
|
|
336
312
|
sortedTasks,
|
|
337
313
|
parentId,
|
|
338
|
-
prdId,
|
|
339
|
-
techSpecId,
|
|
340
314
|
dryRun,
|
|
341
315
|
recutOf,
|
|
342
316
|
hierarchy: hierarchyMode,
|
|
@@ -473,8 +447,6 @@ function buildStoryInitResult({
|
|
|
473
447
|
installStatus,
|
|
474
448
|
sortedTasks,
|
|
475
449
|
parentId,
|
|
476
|
-
prdId,
|
|
477
|
-
techSpecId,
|
|
478
450
|
dryRun,
|
|
479
451
|
recutOf,
|
|
480
452
|
hierarchy,
|
|
@@ -506,7 +478,7 @@ function buildStoryInitResult({
|
|
|
506
478
|
labels: t.labels,
|
|
507
479
|
dependencies: t.dependsOn ?? parseBlockedBy(t.body ?? ''),
|
|
508
480
|
})),
|
|
509
|
-
context: { parentId
|
|
481
|
+
context: { parentId },
|
|
510
482
|
dryRun,
|
|
511
483
|
};
|
|
512
484
|
}
|
|
@@ -6,7 +6,7 @@ description: >-
|
|
|
6
6
|
`/plan`, after `epic-plan-decompose-author` writes
|
|
7
7
|
`temp/epic-<Epic_ID>/tickets.json` and before `epic-plan-decompose.js`
|
|
8
8
|
validates and persists it. Reconciles the draft against the Tech Spec
|
|
9
|
-
"Delivery Slicing"
|
|
9
|
+
"Delivery Slicing" ceiling via scope-preserving operations only.
|
|
10
10
|
allowed_tools:
|
|
11
11
|
- Read
|
|
12
12
|
- Write
|
|
@@ -17,24 +17,26 @@ allowed_tools:
|
|
|
17
17
|
|
|
18
18
|
## Policy Capsule
|
|
19
19
|
|
|
20
|
-
- Run only after `epic-plan-decompose-author` has written `temp/epic-<Epic_ID>/tickets.json`; fail loudly if the draft array is missing. Read the
|
|
20
|
+
- Run only after `epic-plan-decompose-author` has written `temp/epic-<Epic_ID>/tickets.json`; fail loudly if the draft array is missing. Read the sectioned Epic body (which carries the folded Tech Spec sections) from `temp/epic-<Epic_ID>/decomposer-context.json` (the same envelope the author skill consumed) — never re-fetch from GitHub, and never call the GitHub API from this Skill.
|
|
21
21
|
- Emit exactly two artifacts inside `temp/epic-<Epic_ID>/`: the **consolidated** `tickets.json` (overwriting the draft array in place) and a human-readable `consolidation-report.md` (the rationale + before/after diff the operator reviews at the HITL gate). Both MUST exist before returning.
|
|
22
22
|
- **Scope conservation is the load-bearing invariant.** You are a *critic*, not a second author: you MUST NOT add scope, invent tickets, or drop acceptance criteria. Every acceptance item and every `verify` entry present in the draft MUST survive into the consolidated array (possibly re-homed onto a merged Story). **This is your contract, not a machine guarantee:** there is **no runtime acceptance-union diff** on your output. The only deterministic runtime backstop the validator applies after you run is the standard ticket-structure validation — it does not re-derive the pre-consolidation acceptance/verify union, so a critic that silently dropped an acceptance item would **not** be caught downstream. (The repo's unit test exercises a *pure model* of the merge over an over-fragmented fixture to document the intended invariant; it does not inspect this Skill's actual output.) Conserve scope yourself, deliberately, on every merge.
|
|
23
23
|
- Your operations are constrained to exactly two shapes: **(1) merge two or more Stories** into one (union their `changes`/`acceptance`/`verify`/`references`, keep one coherent `goal`); **(2) rewire `depends_on`** so the edges still reference surviving sibling-Story slugs. No other mutation is permitted.
|
|
24
|
-
- Consume the Tech Spec **"Delivery Slicing"** section as
|
|
24
|
+
- Consume the Tech Spec **"Delivery Slicing"** section as a **ceiling, not a target**, when one is present: the Architect's proposed N shippable Stories is an *upper bound*, not a floor. Cluster the draft's Stories toward that grouping, and go **below** N when slices form dependent single-consumer chains — a slice whose "Independent? No" row carries no justification (parallelism, risk isolation, or delivery-envelope pressure) folds into its consumer. You **never** split above N. When the section is **absent**, degrade gracefully — apply only the cohesion rules below and leave the rest of the draft shape intact.
|
|
25
25
|
- Apply the same cohesion heuristic the author skill leads with: **one Story = one coherent change with one reason to exist**, and the **single-consumer merge rule** (a Story whose only consumer is one sibling Story is merged into that sibling). Lead every merge decision with the change's reason, not its file count.
|
|
26
26
|
- **Missing reason-to-exist check (cohesion gate).** Every draft Story body MUST carry a non-empty `reason_to_exist` meta field (the parseable form of "one Story = one coherent change with one reason to exist", encoded in the `<!-- meta: {"reason_to_exist": "..."} -->` comment and surfaced as `body.reason_to_exist` by `lib/story-body/story-body.js`). Flag any Story whose body carries **no** non-empty reason to exist: a Story that cannot state its single reason in one sentence is a cohesion smell — it is probably two Stories, or two Stories that should be merged into one. Name each flagged Story in the consolidation report so the operator sees the cohesion gap at the HITL diff gate.
|
|
27
27
|
- After every merge, **rewire `depends_on`**: drop self-edges, collapse edges that now point at the absorbing Story onto itself, and re-point any edge that named a now-deleted slug at its surviving successor. Never leave a `depends_on` referencing a slug absent from the consolidated array — the validator HARD-rejects unknown deps.
|
|
28
|
-
- The consolidation report MUST name each operation applied (merged slugs → surviving slug, rewired edges) with a one-line reason, plus a before/after Story-count line, so the operator can approve or reject at the HITL diff gate before the persist call.
|
|
28
|
+
- The consolidation report MUST name each operation applied (merged slugs → surviving slug, rewired edges) with a one-line reason, plus a before/after Story-count line, so the operator can approve or reject at the HITL diff gate before the persist call. When the consolidated count drops **below** the Delivery-Slicing count (a below-ceiling coarsening), the report MUST surface that fact explicitly — e.g. `Below ceiling: N (Delivery Slicing) → M (consolidated)` — and attach a one-line rationale to each below-ceiling merge (which dependent single-consumer slice folded into which consumer, and why it was unjustified), so the operator sees the coarsening at the Phase 8.3 advisory diff.
|
|
29
29
|
|
|
30
30
|
## Role
|
|
31
31
|
|
|
32
32
|
Senior Project Manager + Orchestrator, acting as a **holistic critic** with
|
|
33
|
-
fresh context
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
fresh context — deliberately *separate* from `epic-plan-decompose-author` (the
|
|
34
|
+
generator) so the pass is a fresh-context review, not a same-pass self-critique.
|
|
35
|
+
|
|
36
|
+
> **Read [`examples.md`](./examples.md) on demand** for the extended rationale:
|
|
37
|
+
> why this critic runs with fresh context, why scope conservation is your
|
|
38
|
+
> contract rather than a runtime guarantee, and how the below-ceiling
|
|
39
|
+
> coarsening case is reported.
|
|
38
40
|
|
|
39
41
|
## When to use
|
|
40
42
|
|
|
@@ -54,10 +56,13 @@ reads:
|
|
|
54
56
|
- `temp/epic-<Epic_ID>/tickets.json` — the **draft** Story array the
|
|
55
57
|
`epic-plan-decompose-author` Skill wrote. This is the consolidation input.
|
|
56
58
|
- `temp/epic-<Epic_ID>/decomposer-context.json` — the authoring envelope
|
|
57
|
-
emitted by `epic-plan-decompose.js --emit-context`. Read `
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
emitted by `epic-plan-decompose.js --emit-context`. Read `epicBody`
|
|
60
|
+
from it — the sectioned Epic body carrying the folded Tech Spec
|
|
61
|
+
sections (there is no separate `techSpec` key — Story #4324). The
|
|
62
|
+
**"Delivery Slicing"**
|
|
63
|
+
section (authored by `epic-plan-spec-author`) is the
|
|
64
|
+
grouping **ceiling** when present (you may merge below it, never split above
|
|
65
|
+
it); degrade gracefully when it is absent.
|
|
61
66
|
|
|
62
67
|
## Outputs
|
|
63
68
|
|
|
@@ -74,16 +79,21 @@ Both files MUST exist before the Skill returns.
|
|
|
74
79
|
|
|
75
80
|
## Procedure
|
|
76
81
|
|
|
77
|
-
### Step 1 — Load the draft and the
|
|
82
|
+
### Step 1 — Load the draft and the ceiling
|
|
78
83
|
|
|
79
84
|
Read `temp/epic-<Epic_ID>/tickets.json` (the draft array) and
|
|
80
|
-
`temp/epic-<Epic_ID>/decomposer-context.json` (for the
|
|
81
|
-
the Tech Spec
|
|
85
|
+
`temp/epic-<Epic_ID>/decomposer-context.json` (for the `epicBody`, which
|
|
86
|
+
carries the Tech Spec sections). Locate
|
|
87
|
+
the **"Delivery Slicing"** section. Pin two facts before mutating
|
|
82
88
|
anything:
|
|
83
89
|
|
|
84
|
-
1. The **
|
|
90
|
+
1. The **grouping ceiling** — the N shippable Stories the Architect proposed in
|
|
85
91
|
Delivery Slicing, or `null` when the section is absent (graceful-degrade
|
|
86
|
-
mode: cohesion rules only).
|
|
92
|
+
mode: cohesion rules only). Treat N as an upper bound you may go below, not a
|
|
93
|
+
floor you must hit. Note which slices are marked "Independent? No" and
|
|
94
|
+
whether each carries a justification (parallelism, risk isolation, or
|
|
95
|
+
delivery-envelope pressure) — an unjustified dependent slice is a
|
|
96
|
+
fold-into-consumer candidate.
|
|
87
97
|
2. The **draft Story count** — so you can spot over-fragmented capability
|
|
88
98
|
clusters.
|
|
89
99
|
|
|
@@ -92,17 +102,23 @@ anything:
|
|
|
92
102
|
Across the draft Story array, decide which Stories merge:
|
|
93
103
|
|
|
94
104
|
- **Over-fragmented capability** → when several draft Stories map to one
|
|
95
|
-
Delivery-Slicing
|
|
105
|
+
Delivery-Slicing slice (or one coherent reason to exist), merge them into a
|
|
96
106
|
single Story: union their `changes` / `acceptance` / `verify` / `references`,
|
|
97
107
|
write one coherent `goal`, and keep the union of labels.
|
|
98
|
-
- **Single-consumer Story** → merge into the one sibling that consumes it.
|
|
108
|
+
- **Single-consumer Story** → merge into the one sibling that consumes it. This
|
|
109
|
+
is the primary lever for going **below** the Delivery-Slicing ceiling: an
|
|
110
|
+
"Independent? No" slice whose only consumer is one sibling folds into that
|
|
111
|
+
sibling unless its row justifies staying separate (parallelism, risk
|
|
112
|
+
isolation, or delivery-envelope pressure). Coarsening below N here is
|
|
113
|
+
expected, not an over-reach — record it as a below-ceiling merge (see Step 4).
|
|
99
114
|
- **Missing reason to exist** → flag any Story whose body carries no non-empty
|
|
100
115
|
`reason_to_exist` meta field. A Story that cannot name its single coherent
|
|
101
116
|
reason is a cohesion smell; record it in the report so the operator can
|
|
102
117
|
re-scope it (merge, split, or have the author supply the reason) at the HITL
|
|
103
118
|
gate.
|
|
104
119
|
|
|
105
|
-
|
|
120
|
+
Never split a draft Story to *reach* N — N is a ceiling, and adding scope is
|
|
121
|
+
out of contract. Record each decision with its one-line reason for the report.
|
|
106
122
|
|
|
107
123
|
### Step 3 — Rewire dependencies and conserve scope
|
|
108
124
|
|
|
@@ -120,7 +136,12 @@ After applying the operations:
|
|
|
120
136
|
|
|
121
137
|
Write the consolidated array to `temp/epic-<Epic_ID>/tickets.json` (2-space
|
|
122
138
|
indent, machine-consumed) and the rationale + before/after diff to
|
|
123
|
-
`temp/epic-<Epic_ID>/consolidation-report.md`.
|
|
139
|
+
`temp/epic-<Epic_ID>/consolidation-report.md`. When the consolidated count
|
|
140
|
+
landed **below** the Delivery-Slicing ceiling, the report MUST call that out
|
|
141
|
+
explicitly — a `Below ceiling: N (Delivery Slicing) → M (consolidated)` line
|
|
142
|
+
plus a one-line rationale per below-ceiling merge (which dependent
|
|
143
|
+
single-consumer slice folded into which consumer, and why it was unjustified) —
|
|
144
|
+
so the operator sees the coarsening at the Phase 8.3 advisory diff.
|
|
124
145
|
|
|
125
146
|
### Step 5 — Hand back to `/plan`
|
|
126
147
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# epic-plan-consolidate — examples & extended rationale
|
|
2
|
+
|
|
3
|
+
Read this file on demand. The SKILL.md carries the operating contract (Policy
|
|
4
|
+
Capsule, Inputs / Outputs, Procedure, Constraints); this file carries the
|
|
5
|
+
extended rationale behind the consolidation pass — why it is a separate,
|
|
6
|
+
fresh-context critic and how the scope-conservation invariant is enforced by
|
|
7
|
+
you rather than by the runtime.
|
|
8
|
+
|
|
9
|
+
## Why a separate fresh-context critic
|
|
10
|
+
|
|
11
|
+
This Skill acts as a **holistic critic** with fresh context (a Senior Project
|
|
12
|
+
Manager + Orchestrator lens). It is deliberately *separate* from
|
|
13
|
+
`epic-plan-decompose-author` (the generator): a same-pass self-critique is the
|
|
14
|
+
weak mode this is built to escape. The generator maps Epic capabilities to
|
|
15
|
+
Stories ~1:1; this critic steps back and looks at the *whole* decomposition
|
|
16
|
+
against the Tech Spec's intentional grouping before any GitHub write.
|
|
17
|
+
|
|
18
|
+
## Scope conservation is your contract, not a machine guarantee
|
|
19
|
+
|
|
20
|
+
Scope conservation is the load-bearing invariant: you MUST NOT add scope,
|
|
21
|
+
invent tickets, or drop acceptance criteria. Every acceptance item and every
|
|
22
|
+
`verify` entry present in the draft MUST survive into the consolidated array
|
|
23
|
+
(possibly re-homed onto a merged Story).
|
|
24
|
+
|
|
25
|
+
**This is your contract, not a machine guarantee.** There is **no runtime
|
|
26
|
+
acceptance-union diff** on your output. The only deterministic runtime backstop
|
|
27
|
+
the validator applies after you run is the standard ticket-structure
|
|
28
|
+
validation — it does not re-derive the pre-consolidation acceptance/verify
|
|
29
|
+
union, so a critic that silently dropped an acceptance item would **not** be
|
|
30
|
+
caught downstream. (The repo's unit test exercises a *pure model* of the merge
|
|
31
|
+
over an over-fragmented fixture to document the intended invariant; it does not
|
|
32
|
+
inspect this Skill's actual output.) Conserve scope yourself, deliberately, on
|
|
33
|
+
every merge — and if you cannot preserve an item, you have over-reached, so back
|
|
34
|
+
the operation out.
|
|
35
|
+
|
|
36
|
+
## Ceiling, not target — the below-ceiling coarsening case
|
|
37
|
+
|
|
38
|
+
Consume the Tech Spec **"Delivery Slicing"** section as a **ceiling, not a
|
|
39
|
+
target** when present: the Architect's proposed N shippable Stories is an
|
|
40
|
+
*upper bound*, not a floor. Cluster the draft's Stories toward that grouping,
|
|
41
|
+
and go **below** N when slices form dependent single-consumer chains — a slice
|
|
42
|
+
whose "Independent? No" row carries no justification (parallelism, risk
|
|
43
|
+
isolation, or delivery-envelope pressure) folds into its consumer. You **never**
|
|
44
|
+
split above N.
|
|
45
|
+
|
|
46
|
+
When the consolidated count drops **below** the Delivery-Slicing count, the
|
|
47
|
+
consolidation report MUST surface that fact explicitly — e.g.
|
|
48
|
+
`Below ceiling: N (Delivery Slicing) → M (consolidated)` — and attach a one-line
|
|
49
|
+
rationale to each below-ceiling merge (which dependent single-consumer slice
|
|
50
|
+
folded into which consumer, and why it was unjustified), so the operator sees
|
|
51
|
+
the coarsening at the Phase 8.3 advisory diff.
|
|
@@ -32,9 +32,10 @@ allowed_tools:
|
|
|
32
32
|
|
|
33
33
|
## Role
|
|
34
34
|
|
|
35
|
-
Senior Project Manager + Orchestrator. The Skill's job is to take
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
Senior Project Manager + Orchestrator. The Skill's job is to take the
|
|
36
|
+
sectioned Epic body (whose managed sections carry the folded Tech Spec and
|
|
37
|
+
Acceptance Table — Story #4324) and emit a flat Story backlog the
|
|
38
|
+
orchestrator can execute autonomously.
|
|
38
39
|
|
|
39
40
|
## When to use
|
|
40
41
|
|
|
@@ -55,10 +56,12 @@ reads:
|
|
|
55
56
|
`node .agents/scripts/epic-plan-decompose.js --epic <Epic_ID> --emit-context`.
|
|
56
57
|
Fields:
|
|
57
58
|
- `epic.id`, `epic.title`
|
|
58
|
-
- `
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
boundary + dependency-DAG
|
|
59
|
+
- `epicBody` — the sectioned Epic body. It carries everything the
|
|
60
|
+
decomposer needs in one document: the `## User Stories` section
|
|
61
|
+
(User-Story extraction), the folded Tech Spec sections
|
|
62
|
+
(`## Delivery Slicing` onward — module boundary + dependency-DAG
|
|
63
|
+
extraction), and the `## Acceptance Table` AC-ID table. There is no
|
|
64
|
+
separate `techSpec` envelope key (Story #4324).
|
|
62
65
|
- `heuristics[]` — risk heuristics surfaced from
|
|
63
66
|
`agentSettings.planning.riskHeuristics`. Apply each one against the
|
|
64
67
|
Stories you are emitting; flag matches via `risk::high` labels.
|
|
@@ -117,7 +120,7 @@ Pin three values explicitly before writing any tickets:
|
|
|
117
120
|
Apply the fully-rendered decomposer system prompt — the `systemPrompt`
|
|
118
121
|
field of the loaded context envelope, produced by
|
|
119
122
|
[`decomposer-prompts.js`](../../../scripts/lib/templates/decomposer-prompts.js)
|
|
120
|
-
— to the
|
|
123
|
+
— to the sectioned Epic body (`epicBody`). That rendered string is the authoritative
|
|
121
124
|
prompt body (with `maxTickets`, `maxTokenBudget`, and the sizing thresholds
|
|
122
125
|
already interpolated); the authoring-guidance sections below complement it
|
|
123
126
|
without restating it. Emit JSON only (no prose, no Markdown fence). The
|
|
@@ -224,6 +227,8 @@ They are NOT top-level ticket fields.
|
|
|
224
227
|
|
|
225
228
|
The first question is **cohesion, not count**: *is this one coherent change with one reason to exist?* File count cannot tell a trivial 10-file mechanical rename from a hard 3-file parser+caller+config change — so lead with the change's reason, not its size. Size against the real one-pass delivery envelope (`maxTokenBudget`): a Story is correctly sized when a single agent can hold its full change, acceptance, and verification in one pass within that budget.
|
|
226
229
|
|
|
230
|
+
The envelope also has a **floor**, not just a ceiling: a Story that would plausibly use well under a third of `maxTokenBudget` and is neither parallel-deliverable nor orthogonal to its siblings is a **merge candidate** — modern frontier models one-shot capability-sized changes, so a chain of small dependent Stories needlessly pays a full per-Story delivery session (hydration, branch, PR, review, CI) per link. This is soft guidance, not a threshold or validator finding; the canonical phrasing lives in `DELIVERABLE_GRANULARITY_GUIDANCE.envelopeFloor` in `ticket-validator-sizing.js`, which the decomposer prompt interpolates — do not restate a divergent version here.
|
|
231
|
+
|
|
227
232
|
- **One Story = one coherent change with one reason to exist.** If you cannot state that reason in a sentence, the Story is probably two Stories.
|
|
228
233
|
- **Single-consumer merge rule.** A Story whose only consumer is one sibling Story should be **merged into that sibling** rather than emitted separately — a single-consumer downstream slice is not its own unit of work.
|
|
229
234
|
- **Split independent, parallelizable work** into sibling Stories — but only when the pieces genuinely have separate reasons to exist.
|
|
@@ -237,11 +242,11 @@ The first question is **cohesion, not count**: *is this one coherent change with
|
|
|
237
242
|
|
|
238
243
|
#### DELIVERY SLICING (consume the Tech Spec target grouping when present)
|
|
239
244
|
|
|
240
|
-
The Tech Spec may carry a `## Delivery Slicing` section authored by the
|
|
241
|
-
Architect, proposing how the
|
|
245
|
+
The Epic body's Tech Spec sections may carry a `## Delivery Slicing` section authored by the
|
|
246
|
+
Architect, proposing how the Epic's enumerated capabilities cluster into N
|
|
242
247
|
shippable Stories. When that section is **present**, treat it as the **target
|
|
243
248
|
grouping**: prefer emitting Stories that match the Architect's proposed
|
|
244
|
-
clusters rather than mapping
|
|
249
|
+
clusters rather than mapping Epic capabilities 1:1. When it is **absent**,
|
|
245
250
|
degrade gracefully — decompose at deliverable granularity using the cohesion
|
|
246
251
|
rules above, exactly as before. The Phase 8 holistic consolidation pass
|
|
247
252
|
(`epic-plan-consolidate`) reconciles your draft against this same Delivery
|
|
@@ -263,7 +268,7 @@ Declaring `wide` with a non-empty reason **lifts the `hardFiles` rejection** —
|
|
|
263
268
|
|
|
264
269
|
#### BRAND / COPY / STYLE WORK
|
|
265
270
|
|
|
266
|
-
- Stories that touch user-visible copy, brand assets, or visual style MUST cite the relevant section of `docs/style-guide.md` in `acceptance` (e.g. `"acceptance": ["Hero copy matches docs/style-guide.md §3 (voice & tone)"]`). If `docs/style-guide.md` does not exist or has no relevant section, state that explicitly: `"acceptance": ["docs/style-guide.md absent — copy reviewed against the inline brand brief in
|
|
271
|
+
- Stories that touch user-visible copy, brand assets, or visual style MUST cite the relevant section of `docs/style-guide.md` in `acceptance` (e.g. `"acceptance": ["Hero copy matches docs/style-guide.md §3 (voice & tone)"]`). If `docs/style-guide.md` does not exist or has no relevant section, state that explicitly: `"acceptance": ["docs/style-guide.md absent — copy reviewed against the inline brand brief in the Epic body"]`. Silence on style sourcing is a smell.
|
|
267
272
|
|
|
268
273
|
#### BINDING ACCEPTANCE vs ADVISORY CHANGES (authoring altitude)
|
|
269
274
|
|
|
@@ -281,11 +286,11 @@ When a Story's acceptance describes a **signed-in / authenticated** persona reac
|
|
|
281
286
|
- Phrase it as: `"From the signed-in home, the persona navigates to Reports → Export and sees the export button"` — not `"GET /reports/export returns the export view"`.
|
|
282
287
|
- This applies to signed-in journeys only; an unauthenticated landing page or a deliberately deep-linkable share URL is exempt — say so in the acceptance item when you take that exemption.
|
|
283
288
|
|
|
284
|
-
### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when the Acceptance
|
|
289
|
+
### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when the Acceptance Table has `new`-disposition rows)
|
|
285
290
|
|
|
286
|
-
The
|
|
291
|
+
The Epic body's `## Acceptance Table` section (columns `AC ID | Outcome | Feature File | Scenario | Disposition`) tags each row's `Disposition` with one of `new | updated | unchanged`. A `new` row names a `.feature` file + scenario that does NOT yet exist on `main`. The framework is features-first: implementation Stories reference those `.feature` paths in their `verify[]` lines, so the files MUST already exist when those Stories run — otherwise verification fails mid-delivery on a missing file (observed gap: Epic #18 in `dsj1984/athportal` had 9 `new` rows and no Story tasked with creating the feature files Stories #1457 / #1466 verified against).
|
|
287
292
|
|
|
288
|
-
When the Acceptance
|
|
293
|
+
When the Acceptance Table contains **one or more `Disposition: new` rows**, you MUST emit **exactly one** dedicated wave-0 scaffold Story whose sole job is to create those `.feature` files with `@skip`-tagged scenarios BEFORE any implementation Story runs:
|
|
289
294
|
|
|
290
295
|
- **goal** (in body string): contains the literal token `bdd-scaffold`.
|
|
291
296
|
- **depends_on**: EMPTY (`[]`) — the scaffold runs first, in wave 0.
|
|
@@ -295,30 +300,12 @@ When the Acceptance Spec contains **one or more `Disposition: new` rows**, you M
|
|
|
295
300
|
- **verify** (top-level array): a grep/validate command (tier `validate`), NOT an e2e runner — verifying that a file exists with the required tags needs no browser/playwright run. Include a check that each new AC ID's namespaced tag is present in the scaffolded files, alongside the `@skip` check.
|
|
296
301
|
- Each implementation Story whose `verify[]` references a scaffolded `.feature` path MUST add `depends_on: ["<scaffold-slug>"]` so the scaffold lands in an earlier wave. Omitting the link trips the soft `missing-bdd-scaffold` finding in `ticket-validator-conflicts.js` (advisory, not a hard block).
|
|
297
302
|
|
|
298
|
-
When the Acceptance
|
|
299
|
-
|
|
300
|
-
**
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
"type": "story",
|
|
305
|
-
"title": "Scaffold @skip-tagged billing feature files",
|
|
306
|
-
"depends_on": [],
|
|
307
|
-
"labels": ["type::story", "persona::qa-engineer"],
|
|
308
|
-
"acceptance": [
|
|
309
|
-
"tests/features/billing/invoice.feature and tests/features/billing/refund.feature both exist on the branch",
|
|
310
|
-
"every Scenario in the two new feature files is preceded by an @skip tag (grep for un-skipped scenarios returns zero matches)",
|
|
311
|
-
"the invoice.feature scenario carries @epic-42-ac-1 and the refund.feature scenario carries @epic-42-ac-2"
|
|
312
|
-
],
|
|
313
|
-
"verify": [
|
|
314
|
-
"test -f tests/features/billing/invoice.feature && test -f tests/features/billing/refund.feature (validate)",
|
|
315
|
-
"test -z \"$(grep -rL '@skip' tests/features/billing/*.feature)\" (validate)",
|
|
316
|
-
"grep -q '@epic-42-ac-1' tests/features/billing/invoice.feature && grep -q '@epic-42-ac-2' tests/features/billing/refund.feature (validate)"
|
|
317
|
-
],
|
|
318
|
-
"body": "## Goal\nbdd-scaffold: create the @skip-tagged, @epic-42-ac-N-tagged feature files the billing-flows implementation Stories verify against, so wave-0 lands them before any implementation Story runs.\n\n## Changes\n- {\"path\": \"tests/features/billing/invoice.feature\", \"assumption\": \"creates\"}\n- {\"path\": \"tests/features/billing/refund.feature\", \"assumption\": \"creates\"}\n\n## Acceptance\n- [ ] tests/features/billing/invoice.feature and tests/features/billing/refund.feature both exist on the branch\n- [ ] every Scenario in the two new feature files is preceded by an @skip tag\n- [ ] the invoice.feature scenario carries @epic-42-ac-1 and the refund.feature scenario carries @epic-42-ac-2\n\n## Verify\n- test -f tests/features/billing/invoice.feature && test -f tests/features/billing/refund.feature (validate)\n- test -z \"$(grep -rL '@skip' tests/features/billing/*.feature)\" (validate)\n- grep -q '@epic-42-ac-1' tests/features/billing/invoice.feature && grep -q '@epic-42-ac-2' tests/features/billing/refund.feature (validate)\n"
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
The implementation Stories that later un-skip and flesh out these scenarios each carry `depends_on: ["scaffold-billing-feature-files"]`, placing them in a later wave than the scaffold. They MUST NOT add the `@epic-42-ac-N` tag themselves — it is already present from the scaffold pass; their job is to remove `@skip` once the scenario passes.
|
|
303
|
+
When the Acceptance Table contains **zero `new`-disposition rows** (every row is `updated` or `unchanged`), do NOT emit a scaffold Story — there is nothing to create.
|
|
304
|
+
|
|
305
|
+
> **Read [`examples.md`](./examples.md) on demand** for a fully worked wave-0
|
|
306
|
+
> scaffold Story (Epic #42, two `new` rows) showing the serialized string
|
|
307
|
+
> `body`, the top-level `acceptance`/`verify` arrays, the empty `depends_on`,
|
|
308
|
+
> and the paired `@skip` + `@epic-<id>-ac-N` tagging.
|
|
322
309
|
|
|
323
310
|
### SCOPE-OVERLAP FLAGGING (docs/runbook downstream of config work)
|
|
324
311
|
|
|
@@ -407,7 +394,7 @@ any logical ordering requirement via Story-level `depends_on`.
|
|
|
407
394
|
- Do **not** call the GitHub API from this Skill. Persistence is the
|
|
408
395
|
script's job; the Skill is pure JSON authoring.
|
|
409
396
|
- Do **not** write outside `temp/epic-<Epic_ID>/`. Reads may cover the
|
|
410
|
-
|
|
397
|
+
Epic body plus any docs the context envelope cites.
|
|
411
398
|
- The decomposer prompt's `${maxTickets}` value is the **reviewability
|
|
412
399
|
budget** (Story #2798). Staying under is the default; exceeding it
|
|
413
400
|
requires both an `over_budget_rationale` in the JSON output and the
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# epic-plan-decompose-author — examples & extended rationale
|
|
2
|
+
|
|
3
|
+
Read this file on demand while authoring the ticket array. The SKILL.md carries
|
|
4
|
+
the operating contract (Policy Capsule, Inputs / Outputs, Procedure, the
|
|
5
|
+
authoring guidance that complements the rendered decomposer prompt, and
|
|
6
|
+
Constraints); this file carries the worked WAVE-0 BDD scaffold Story and the
|
|
7
|
+
extended reasoning behind it.
|
|
8
|
+
|
|
9
|
+
## WAVE-0 BDD scaffold Story — worked example
|
|
10
|
+
|
|
11
|
+
The contract lives in SKILL.md under **WAVE-0 BDD SCAFFOLD STORY**: when the
|
|
12
|
+
Epic body's `## Acceptance Table` carries one or more `Disposition: new` rows,
|
|
13
|
+
emit **exactly one** dedicated wave-0 scaffold Story whose sole job is to create
|
|
14
|
+
those `.feature` files with `@skip`-tagged scenarios (each also carrying its
|
|
15
|
+
namespaced `@epic-<id>-ac-N` tag) BEFORE any implementation Story runs.
|
|
16
|
+
|
|
17
|
+
**Worked example.** Epic #42, Acceptance Table with two `new` rows
|
|
18
|
+
(`AC-1` -> `tests/features/billing/invoice.feature`,
|
|
19
|
+
`AC-2` -> `tests/features/billing/refund.feature`). The scaffold Story below
|
|
20
|
+
uses a serialized string `body`, top-level `acceptance`/`verify` arrays, an
|
|
21
|
+
empty `depends_on`, and tags each scenario with both `@skip` and its namespaced
|
|
22
|
+
`@epic-42-ac-N` tag:
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
"slug": "scaffold-billing-feature-files",
|
|
26
|
+
"type": "story",
|
|
27
|
+
"title": "Scaffold @skip-tagged billing feature files",
|
|
28
|
+
"depends_on": [],
|
|
29
|
+
"labels": ["type::story", "persona::qa-engineer"],
|
|
30
|
+
"acceptance": [
|
|
31
|
+
"tests/features/billing/invoice.feature and tests/features/billing/refund.feature both exist on the branch",
|
|
32
|
+
"every Scenario in the two new feature files is preceded by an @skip tag (grep for un-skipped scenarios returns zero matches)",
|
|
33
|
+
"the invoice.feature scenario carries @epic-42-ac-1 and the refund.feature scenario carries @epic-42-ac-2"
|
|
34
|
+
],
|
|
35
|
+
"verify": [
|
|
36
|
+
"test -f tests/features/billing/invoice.feature && test -f tests/features/billing/refund.feature (validate)",
|
|
37
|
+
"test -z \"$(grep -rL '@skip' tests/features/billing/*.feature)\" (validate)",
|
|
38
|
+
"grep -q '@epic-42-ac-1' tests/features/billing/invoice.feature && grep -q '@epic-42-ac-2' tests/features/billing/refund.feature (validate)"
|
|
39
|
+
],
|
|
40
|
+
"body": "## Goal\nbdd-scaffold: create the @skip-tagged, @epic-42-ac-N-tagged feature files the billing-flows implementation Stories verify against, so wave-0 lands them before any implementation Story runs.\n\n## Changes\n- {\"path\": \"tests/features/billing/invoice.feature\", \"assumption\": \"creates\"}\n- {\"path\": \"tests/features/billing/refund.feature\", \"assumption\": \"creates\"}\n\n## Acceptance\n- [ ] tests/features/billing/invoice.feature and tests/features/billing/refund.feature both exist on the branch\n- [ ] every Scenario in the two new feature files is preceded by an @skip tag\n- [ ] the invoice.feature scenario carries @epic-42-ac-1 and the refund.feature scenario carries @epic-42-ac-2\n\n## Verify\n- test -f tests/features/billing/invoice.feature && test -f tests/features/billing/refund.feature (validate)\n- test -z \"$(grep -rL '@skip' tests/features/billing/*.feature)\" (validate)\n- grep -q '@epic-42-ac-1' tests/features/billing/invoice.feature && grep -q '@epic-42-ac-2' tests/features/billing/refund.feature (validate)\n"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
The implementation Stories that later un-skip and flesh out these scenarios each
|
|
44
|
+
carry `depends_on: ["scaffold-billing-feature-files"]`, placing them in a later
|
|
45
|
+
wave than the scaffold. They MUST NOT add the `@epic-42-ac-N` tag themselves —
|
|
46
|
+
it is already present from the scaffold pass; their job is to remove `@skip`
|
|
47
|
+
once the scenario passes.
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
ticket array an Epic's decompose phase produced. Use during Phase 8 of
|
|
6
6
|
`/plan`, after `epic-plan-decompose-author` / `epic-plan-consolidate` write
|
|
7
7
|
`temp/epic-<Epic_ID>/tickets.json` and before `epic-plan-decompose.js`
|
|
8
|
-
validates and persists it. Reads the
|
|
8
|
+
validates and persists it. Reads the sectioned Epic body AND the cited
|
|
9
9
|
code surfaces, then emits predicted-rework findings before any GitHub write.
|
|
10
10
|
allowed_tools:
|
|
11
11
|
- Read
|
|
@@ -18,7 +18,7 @@ allowed_tools:
|
|
|
18
18
|
|
|
19
19
|
## Policy Capsule
|
|
20
20
|
|
|
21
|
-
- Run only after a draft `temp/epic-<Epic_ID>/tickets.json` exists (authored by `epic-plan-decompose-author`, and consolidated by `epic-plan-consolidate` if that pass ran); fail loudly if the draft array is missing. Read the
|
|
21
|
+
- Run only after a draft `temp/epic-<Epic_ID>/tickets.json` exists (authored by `epic-plan-decompose-author`, and consolidated by `epic-plan-consolidate` if that pass ran); fail loudly if the draft array is missing. Read the sectioned Epic body from `temp/epic-<Epic_ID>/decomposer-context.json` (the same envelope the author skill consumed) — never re-fetch from GitHub, and never call the GitHub API from this Skill.
|
|
22
22
|
- **You MUST read the actual cited code surfaces.** For every Story, open the files named in its `changes[]` / `references[]` (resolve each path against the repo root; use `Read` / `Grep`) and read enough of each to judge whether the Story's `acceptance[]` is verifiable against the real code and whether its `changes[]` assumptions hold. This is the load-bearing difference between this critic and the structural file-assumption gate: that gate proves a path **exists** (or does not); this critic reads what the file actually **contains**. A pre-mortem that did not open the cited files has not run.
|
|
23
23
|
- Emit exactly one artifact: a human-readable `temp/epic-<Epic_ID>/premortem-report.md` — the predicted-rework findings the operator reviews at the Phase 8 HITL diff. It MUST exist before returning.
|
|
24
24
|
- **This critic never writes to GitHub and never persists `tickets.json`.** It is read-and-report only: it does NOT mutate the draft array, does NOT create issues, and does NOT flip any label. Re-authoring on its findings is the author skill's job (the workflow re-runs `epic-plan-decompose-author` on the report before the persist call).
|
|
@@ -28,14 +28,15 @@ allowed_tools:
|
|
|
28
28
|
|
|
29
29
|
## Role
|
|
30
30
|
|
|
31
|
-
Senior Engineer + Architect, acting as a **fresh-context pre-mortem critic
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
a same-pass self-critique
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
Senior Engineer + Architect, acting as a **fresh-context pre-mortem critic** —
|
|
32
|
+
deliberately *separate* from `epic-plan-decompose-author` (the generator) and
|
|
33
|
+
`epic-plan-consolidate` (the scope-preserving merge critic) so it is a
|
|
34
|
+
fresh-context, code-reading review, not a same-pass self-critique.
|
|
35
|
+
|
|
36
|
+
> **Read [`examples.md`](./examples.md) on demand** for the extended rationale:
|
|
37
|
+
> why this critic opens the actual cited code, why it is additive-recommendation
|
|
38
|
+
> (not scope-preserving) unlike consolidation, and the three predicted-rework
|
|
39
|
+
> finding classes in full.
|
|
39
40
|
|
|
40
41
|
## When to use
|
|
41
42
|
|
|
@@ -56,8 +57,9 @@ The workflow passes the Epic ID as the Skill argument. The Skill itself reads:
|
|
|
56
57
|
- `temp/epic-<Epic_ID>/tickets.json` — the **draft** (or consolidated) Story
|
|
57
58
|
array. This is the pre-mortem subject.
|
|
58
59
|
- `temp/epic-<Epic_ID>/decomposer-context.json` — the authoring envelope emitted
|
|
59
|
-
by `epic-plan-decompose.js --emit-context`. Read `
|
|
60
|
-
|
|
60
|
+
by `epic-plan-decompose.js --emit-context`. Read `epicBody` from it —
|
|
61
|
+
the sectioned Epic body carrying the folded Tech Spec sections
|
|
62
|
+
(there is no separate `techSpec` key — Story #4324).
|
|
61
63
|
- **The repository working tree** — the actual files each Story's `changes[]` /
|
|
62
64
|
`references[]` name. Resolve each path against the repo root and read it.
|
|
63
65
|
|
|
@@ -78,7 +80,7 @@ artifact and mutates **no** GitHub state.
|
|
|
78
80
|
### Step 1 — Load the draft and the spec
|
|
79
81
|
|
|
80
82
|
Read `temp/epic-<Epic_ID>/tickets.json` (the Story array) and
|
|
81
|
-
`temp/epic-<Epic_ID>/decomposer-context.json` (for the
|
|
83
|
+
`temp/epic-<Epic_ID>/decomposer-context.json` (for the `epicBody`). If the
|
|
82
84
|
draft array is missing, fail loudly and instruct the caller to run the
|
|
83
85
|
`epic-plan-decompose-author` Skill first.
|
|
84
86
|
|