valent-pipeline 0.5.3 → 0.5.5
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/package.json +1 -1
- package/pipeline/orchestrators/claude-code/plan.workflow.js +13 -5
- package/pipeline/prompts/qa-a.md +1 -0
- package/pipeline/scripts/query-kb.ts +7 -1
- package/pipeline/steps/common/agent-protocol.md +2 -2
- package/pipeline/steps/common/quality-standards.md +14 -0
- package/pipeline/steps/critic/test-review.md +3 -0
- package/pipeline/steps/orchestration/sprint-init.md +8 -3
- package/pipeline/steps/orchestration/sprint-plan.md +11 -3
- package/pipeline/steps/orchestration/sprint-review.md +5 -1
- package/pipeline/steps/orchestration/sprint-size.md +3 -2
- package/pipeline/steps/qa-a/write-spec.md +20 -0
- package/pipeline/steps/retrospective/directives.md +7 -2
- package/pipeline/templates/sprint-status.template.yaml +1 -0
- package/src/lib/sprint.js +28 -1
package/package.json
CHANGED
|
@@ -101,6 +101,7 @@ const PACK_SCHEMA = {
|
|
|
101
101
|
buffer_story_ids: { type: 'array', items: { type: 'string' } },
|
|
102
102
|
points_planned: { type: 'integer' },
|
|
103
103
|
remaining_capacity: { type: 'integer' },
|
|
104
|
+
over_budget: { type: 'boolean' },
|
|
104
105
|
},
|
|
105
106
|
}
|
|
106
107
|
|
|
@@ -291,10 +292,14 @@ const sized = await parallel(
|
|
|
291
292
|
}),
|
|
292
293
|
{ label: `estimate:${est.toLowerCase()}:${g.storyId}`, phase: 'Size', schema: ESTIMATE_SCHEMA, model: modelFor(est) },
|
|
293
294
|
)),
|
|
294
|
-
).then((ests) =>
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
).then((ests) => {
|
|
296
|
+
// Each estimator sizes the WHOLE story from its surface's lens (BEND sees the full story,
|
|
297
|
+
// IAC sees the full story), so the points are the MAX single estimate — NOT the sum.
|
|
298
|
+
// Summing double-counts shared scaffolding: a 2-profile story is not twice the work, and
|
|
299
|
+
// summing systematically over-points multi-profile stories until they exceed velocity.
|
|
300
|
+
const vals = ests.filter(Boolean).map((e) => e.points || 0)
|
|
301
|
+
return { ...g, points: vals.length ? Math.max(...vals) : 0 }
|
|
302
|
+
})
|
|
298
303
|
}),
|
|
299
304
|
)
|
|
300
305
|
const sizedStories = sized.filter(Boolean)
|
|
@@ -313,10 +318,13 @@ phase('Pack')
|
|
|
313
318
|
// Deterministic greedy packing happens in code (src/lib/sprint.js), invoked via the CLI.
|
|
314
319
|
const pack = await agent(
|
|
315
320
|
`Run exactly: \`node .valent-pipeline/bin/cli.js sprint-pack --velocity ${velocity} --backlog ${backlogPath}\` ` +
|
|
316
|
-
`in the project root and return its stdout JSON verbatim (fields: sprint_stories, buffer_story_ids, points_planned, remaining_capacity).`,
|
|
321
|
+
`in the project root and return its stdout JSON verbatim (fields: sprint_stories, buffer_story_ids, points_planned, remaining_capacity, over_budget).`,
|
|
317
322
|
{ label: 'sprint-pack', phase: 'Pack', schema: PACK_SCHEMA, model: modelFor('PACK') },
|
|
318
323
|
)
|
|
319
324
|
log(`packed ${pack.sprint_stories.length} stories (${pack.points_planned} pts); buffer: ${pack.buffer_story_ids.length}`)
|
|
325
|
+
if (pack.over_budget) {
|
|
326
|
+
log(`⚠ sprint ${sprintId} is OVER BUDGET: the highest-priority story exceeds velocity ${velocity} and was planned alone — consider splitting it (${pack.points_planned} pts vs ${velocity} velocity)`)
|
|
327
|
+
}
|
|
320
328
|
|
|
321
329
|
phase('Validate')
|
|
322
330
|
// Write the human plan + machine status artifacts, tag the backlog, then cross-check in code.
|
package/pipeline/prompts/qa-a.md
CHANGED
|
@@ -73,6 +73,7 @@ Before finalizing, verify:
|
|
|
73
73
|
- No behavior is spec'd at multiple tiers
|
|
74
74
|
- Every error test case has a specific error code and message pattern
|
|
75
75
|
- Every P0 test case has DB state verification
|
|
76
|
+
- Every P0/P1 AC on an interactive surface (`api`, `ui`) has a human-readable acceptance scenario realized as a Live Smoke Test (api) or UI Integration Smoke Test (ui) row for QA-B to execute against live infrastructure — never left to unit tests alone
|
|
76
77
|
- Seed data uses factory patterns, not raw SQL
|
|
77
78
|
- NFR requirements are tagged ([NFR-PERF], [NFR-SEC], [NFR-REL])
|
|
78
79
|
- Visual validation checkpoints cover all 5 page states for each page (if UI story)
|
|
@@ -78,10 +78,16 @@ switch (mode) {
|
|
|
78
78
|
|
|
79
79
|
case 'directives': {
|
|
80
80
|
const agent = flags['agent'];
|
|
81
|
+
// ALL-DEV is a cross-cutting directive that applies to every developer agent. Include it when
|
|
82
|
+
// the queried agent is one of them, so a per-agent query still sees shared dev directives.
|
|
83
|
+
const DEV_AGENTS = ['BEND', 'FEND', 'DATA', 'MCP-DEV', 'LIBDEV', 'DOCGEN', 'IAC', 'MOBILE'];
|
|
81
84
|
let rows;
|
|
82
85
|
if (agent) {
|
|
86
|
+
const includeAllDev = DEV_AGENTS.includes(agent.toUpperCase());
|
|
83
87
|
rows = db.prepare(
|
|
84
|
-
|
|
88
|
+
includeAllDev
|
|
89
|
+
? "SELECT id, target_agent, directive, reason FROM correction_directives WHERE (target_agent = ? OR target_agent = 'ALL-DEV') AND status = 'active'"
|
|
90
|
+
: "SELECT id, directive, reason FROM correction_directives WHERE target_agent = ? AND status = 'active'"
|
|
85
91
|
).all(agent) as { id: string; directive: string; reason: string }[];
|
|
86
92
|
} else {
|
|
87
93
|
rows = db.prepare(
|
|
@@ -42,7 +42,7 @@ When `signal_delivery` is `thread`: Lead steers you with the Design Council ques
|
|
|
42
42
|
When you need information about project conventions, architectural patterns, existing code structure, or known pitfalls: self-serve from the knowledge base before exploring the codebase directly. Curated knowledge and correction directives answer in seconds what codebase exploration takes minutes to discover.
|
|
43
43
|
|
|
44
44
|
**How to self-serve:**
|
|
45
|
-
1. Read correction directives from `{correction_directives}` — filter for `status: active` entries
|
|
45
|
+
1. Read correction directives from `{correction_directives}` — filter for `status: active` entries whose `target_agent` is your agent role (or `ALL-DEV` if you are a developer agent).
|
|
46
46
|
2. Read curated knowledge files in `{curated_files_path}` — scan file names and section headers for entries relevant to your current task.
|
|
47
47
|
3. If `{knowledge_mode}` is `sqlite`: query the database via CLI (see your read-inputs step for commands).
|
|
48
48
|
4. If `{story_output_dir}/knowledge-context.md` exists: read it directly — this is a pre-compiled reference containing all relevant knowledge for the current story.
|
|
@@ -51,7 +51,7 @@ Reserve direct codebase exploration for when curated knowledge does not have the
|
|
|
51
51
|
|
|
52
52
|
## Correction Directives
|
|
53
53
|
|
|
54
|
-
Read active correction directives from `{correction_directives}`. If the file does not exist or is empty, proceed without directives -- this is expected for new pipelines. Apply ALL directives
|
|
54
|
+
Read active correction directives from `{correction_directives}`. If the file does not exist or is empty, proceed without directives -- this is expected for new pipelines. Apply ALL directives whose `target_agent` matches your agent role **OR** is `ALL-DEV` when you are a developer agent (BEND, FEND, DATA, MCP-DEV, LIBDEV, DOCGEN, IAC, MOBILE) — `ALL-DEV` is a cross-cutting directive that applies to every developer agent, not one surface. If a directive conflicts with these instructions, the directive takes precedence. Log each applied directive in your YAML frontmatter under `correctionsApplied`.
|
|
55
55
|
|
|
56
56
|
## YAML Frontmatter
|
|
57
57
|
|
|
@@ -10,8 +10,22 @@ These are non-negotiable. CRITIC and QA-B enforce them. Every developer agent (B
|
|
|
10
10
|
- **<1.5 minutes per test** -- any test exceeding this is a design problem, not a timeout problem.
|
|
11
11
|
- **Self-cleaning via fixture auto-teardown** -- tests must not leave state behind. Use framework teardown hooks, not manual cleanup.
|
|
12
12
|
- **Explicit assertions in test bodies** -- never hide assertions in helpers. Every test body must contain at least one visible `expect`/`assert`.
|
|
13
|
+
- **Falsifiable assertions** -- every assertion must be able to FAIL when the behavior is wrong. No unfalsifiable disjunctive gates like `expect(a === 0 || b === 0 || fallback).toBe(true)` — a disjunction with a catch-all that is almost always true asserts nothing. Assert the real invariant directly (e.g. "exactly one container exists AND `SELECT 1` succeeds AND no corruption log"), not a condition that passes regardless of outcome.
|
|
14
|
+
- **Bind to the real artifact under test** -- resolve the actual artifact dynamically (built image id, running service/container name, the file the build emits) and assert against THAT. Never assert against a hardcoded tag/name or an artifact that merely happens to exist on your machine. The test: would it still pass on a clean/CI checkout with no leftover state? If it passes only because of an ambient/side-channel artifact, it is broken. (Prove the artifact exists — `expect(imageId).toBeTruthy()` — before inspecting it.)
|
|
15
|
+
- **Statistical assertions match the spec** -- when the spec states a percentile or sample count (e.g. NFR-PERF "P95 < 200ms"), assert exactly that over real samples with warm-up where appropriate. A single-shot `elapsed < 200` is not a P95 and is flaky — it does not satisfy the spec.
|
|
13
16
|
- **Parallel-safe** -- no shared mutable state between tests. Must run cleanly with `--workers=4`.
|
|
14
17
|
|
|
18
|
+
## Pre-Handoff Self-Check — MANDATORY before declaring done
|
|
19
|
+
|
|
20
|
+
CRITIC enforces every item below and will reject on any High finding, costing a full rework cycle. Run this checklist against your own tests BEFORE writing your handoff; fix anything that fails first. (Each recurred across real stories — this is the gap that buys a guaranteed rework cycle if skipped.)
|
|
21
|
+
|
|
22
|
+
- [ ] **Every P0 qa-test-spec case has a named, implemented test.** Count the spec's P0 cases; count your `it(...)`/`test(...)` blocks; a P0 case with no implementation is a High finding. Do not treat a spec note (e.g. "idempotency") as covered unless there is a named test for it.
|
|
23
|
+
- [ ] **No `if`/`switch`/ternary in any test body** (same path every run).
|
|
24
|
+
- [ ] **No unfalsifiable assertions** — no disjunctive/catch-all gates; each assert can fail.
|
|
25
|
+
- [ ] **Tests bind to the real artifact** — no hardcoded tag/name; would pass on a clean/CI machine with no leftover state.
|
|
26
|
+
- [ ] **Statistical/perf assertions match the spec's wording** (percentile + samples, not single-shot).
|
|
27
|
+
- [ ] **No assertion weakening, no infra mocking on happy paths, no hard waits, assertions visible in test bodies.**
|
|
28
|
+
|
|
15
29
|
## Live Infrastructure Standards
|
|
16
30
|
|
|
17
31
|
- **Live tests against running infrastructure** -- tests hit real systems. No mocking databases, APIs, pipelines, servers, or external services for happy-path verification.
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
- **No test deletion** -- all qa-test-spec test cases must have corresponding tests. A missing test is a High finding.
|
|
22
22
|
- **No hard waits** -- `sleep()`, `setTimeout()`, `page.waitForTimeout()` in tests is a High finding.
|
|
23
23
|
- **No conditionals** -- `if` statements in test bodies is a High finding.
|
|
24
|
+
- **Falsifiable assertions** -- a disjunctive/catch-all gate that is almost always true (e.g. `expect(a === 0 || b === 0 || transient).toBe(true)`) asserts nothing. A test that cannot fail when the behavior is wrong is a High finding. Require the real invariant.
|
|
25
|
+
- **Artifact binding** -- a test that asserts against a hardcoded tag/name, or only passes because of an ambient/leftover artifact on the dev's machine (would fail on a clean/CI checkout), is a High finding. The artifact under test must be resolved dynamically and proven to exist before inspection.
|
|
26
|
+
- **Statistical assertions match spec** -- when the spec states a percentile/sample count (NFR-PERF "P95 < Nms"), a single-shot threshold instead is a Med finding (the assertion does not match the spec).
|
|
24
27
|
- **Explicit assertions** -- assertions hidden inside helper functions is a Med finding. Every test body must contain visible `expect`/`assert`.
|
|
25
28
|
|
|
26
29
|
## Output
|
|
@@ -19,9 +19,14 @@ node .valent-pipeline/bin/cli.js db query-velocity
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
**Velocity rules:**
|
|
22
|
-
|
|
23
|
-
- **
|
|
24
|
-
|
|
22
|
+
|
|
23
|
+
Velocity is your *capacity* — how many points you can ship when capacity is the binding constraint. Only **capacity-constrained** sprints carry that signal. A **supply-constrained** sprint (one that shipped everything eligible with capacity to spare — it ran out of groomed/eligible work, not out of capacity) reflects how much work *existed*, not how much you *could do*, so counting it would falsely ratchet velocity down. Each sprint's status YAML summary records its `constraint:` (`capacity` or `supply`) — see sprint-review.md Step 2.
|
|
24
|
+
|
|
25
|
+
- **Sprint 1:** Use `{sprint_initial_velocity}` from config (default: 60 points).
|
|
26
|
+
- **Later sprints:** Moving average of `points_shipped` over **capacity-constrained sprints only** — exclude every `constraint: supply` sprint.
|
|
27
|
+
- 2-4 capacity-constrained sprints available: average their `points_shipped`.
|
|
28
|
+
- 5+ available: SMA of the last 5 capacity-constrained sprints (older data ages out).
|
|
29
|
+
- **If no capacity-constrained sprint has happened yet** (e.g. early sprints were all supply-constrained because the backlog is a dependency chain): keep `{sprint_initial_velocity}`. **Never lower velocity based on a supply-constrained sprint** — this is the failure that drives a healthy initial velocity down to a tiny number after one small starter story.
|
|
25
30
|
|
|
26
31
|
Record: `current_velocity = {value}` points.
|
|
27
32
|
|
|
@@ -12,9 +12,17 @@ node .valent-pipeline/bin/cli.js sprint-pack --velocity {current_velocity} --bac
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
It emits JSON: `sprint_stories` (packed, in dependency-safe order), `buffer_story_ids`
|
|
15
|
-
(groomed but not packed — the mid-sprint pull buffer, see Step 1b), `points_planned`,
|
|
16
|
-
`remaining_capacity`. Only `groomed` stories are eligible; lower `priority`
|
|
17
|
-
priority; a groomed prerequisite is auto-included before its dependent when it
|
|
15
|
+
(groomed but not packed — the mid-sprint pull buffer, see Step 1b), `points_planned`,
|
|
16
|
+
`remaining_capacity`, and `over_budget`. Only `groomed` stories are eligible; lower `priority`
|
|
17
|
+
number = higher priority; a groomed prerequisite is auto-included before its dependent when it
|
|
18
|
+
fits the budget.
|
|
19
|
+
|
|
20
|
+
**If `over_budget` is `true`:** no story fit the velocity budget, so the highest-priority groomed
|
|
21
|
+
story (plus its groomed prerequisites) was planned ALONE and `points_planned` exceeds velocity
|
|
22
|
+
(`remaining_capacity` is negative). This is the anti-stall path — the sprint still makes progress.
|
|
23
|
+
Surface it to the user: this story is larger than a full sprint and is a **split candidate** — note
|
|
24
|
+
it in the sprint plan and consider asking REQS to break it into smaller stories before a future run.
|
|
25
|
+
Do NOT treat the negative `remaining_capacity` as room to groom more stories.
|
|
18
26
|
|
|
19
27
|
Use this output directly for Steps 1b–8. If the backlog isn't the right input shape, pass an
|
|
20
28
|
explicit story array with `--stories <path>` instead of `--backlog`.
|
|
@@ -20,7 +20,11 @@ Update `sprint-{n}-plan.md` Sprint Summary:
|
|
|
20
20
|
- Points rolled over: sum of unexecuted story points
|
|
21
21
|
- Total elapsed minutes: sum of execution time (not grooming)
|
|
22
22
|
- Velocity this sprint: points_shipped (all shipped stories count toward velocity, including pulls)
|
|
23
|
-
-
|
|
23
|
+
- **Sprint constraint:** classify this sprint so the next sprint-init calibrates velocity correctly:
|
|
24
|
+
- `capacity` — capacity was the binding constraint: stories were left in the groomed buffer or rolled over (more eligible work existed than fit), OR the plan was over-budget (a single oversized story planned alone). These sprints count toward velocity.
|
|
25
|
+
- `supply` — supply was the binding constraint: the sprint shipped all eligible/groomed work with capacity to spare (groomed buffer empty AND positive `capacity_remaining` at plan time). These sprints do NOT count toward velocity (they measure available work, not capacity).
|
|
26
|
+
Record as `constraint:` in the status YAML summary.
|
|
27
|
+
- Updated velocity (SMA-5): recompute the moving average over **capacity-constrained sprints only** (see sprint-init.md Step 2). A `supply`-constrained sprint must not lower velocity.
|
|
24
28
|
- Mid-sprint pulls: count and list (stories pulled from groomed buffer during execution)
|
|
25
29
|
|
|
26
30
|
## Step 3: Finalize Sprint Status YAML
|
|
@@ -34,8 +34,9 @@ For each story with status `groomed`:
|
|
|
34
34
|
- `iac` in profiles → send to IAC
|
|
35
35
|
Multiple profiles can be active (e.g., `[api, data-pipeline]` sends to both BEND and DATA).
|
|
36
36
|
4. Agents write estimation files (`{agent}-estimation.md`)
|
|
37
|
-
5. **Record points:**
|
|
38
|
-
`story_points =
|
|
37
|
+
5. **Record points:** take the **maximum** single estimate, NOT the sum.
|
|
38
|
+
`story_points = max of all agent estimates received`
|
|
39
|
+
Each estimator sizes the *whole* story from its surface's lens (BEND sizes the entire story, IAC sizes the entire story), so their estimates overlap on shared scaffolding. Summing double-counts that overlap and systematically over-points multi-profile stories until they exceed velocity. The max is the best-informed single read. (If you believe two surfaces carry genuinely independent, non-overlapping work, flag it for the Lead rather than silently summing.)
|
|
39
40
|
6. Update story's `story_points` field in `{backlog_path}`
|
|
40
41
|
|
|
41
42
|
## Step 3: Update Sprint State
|
|
@@ -31,6 +31,17 @@ Structure per AC:
|
|
|
31
31
|
- **Edge cases** (P0: required, P1: key boundaries, P2: optional, P3: omit)
|
|
32
32
|
- **Concurrency** (P0: required, P1: if applicable, P2-P3: omit)
|
|
33
33
|
|
|
34
|
+
### The Given-When-Then cases are human-readable acceptance scenarios — QA-B executes them
|
|
35
|
+
|
|
36
|
+
Your Given-When-Then cases are not just developer test stubs; they are the **human-readable acceptance contract** for the story, written in plain user/consumer language (what a person does and observes), independent of implementation. They describe behavior the way a real developer or QA would verify it by hand.
|
|
37
|
+
|
|
38
|
+
For **interactive surfaces this is mandatory and first-class** — it is how the pipeline tests like a real-world human developer, not just by trusting unit tests:
|
|
39
|
+
|
|
40
|
+
- **`api` profile →** realize each acceptance scenario as a row in the **Live Smoke Tests** table (see `api.md`): real HTTP method/URL/body → expected status + response, plus a verification request for every mutation. QA-B starts the real server and drives these with real requests.
|
|
41
|
+
- **`ui` profile →** realize each user-facing scenario as a row in the **UI Integration Smoke Tests** table (see `ui.md`): user action → real API call → expected UI state → DB verification, backed by a real-browser E2E test. QA-B runs these against the live UI + API + DB (and PMCP validates the visual checkpoints).
|
|
42
|
+
|
|
43
|
+
These acceptance scenarios are **owned and executed by QA-B against live infrastructure** — they are the real-world, black-box evidence JUDGE relies on, distinct from (and never replaced by) the developer's own unit tests. Every P0/P1 AC on an interactive surface MUST have one. The Step 9b quality bar below governs the *automated test code*; it complements these acceptance scenarios — it does not replace them.
|
|
44
|
+
|
|
34
45
|
## Step 4: Database State Verification
|
|
35
46
|
|
|
36
47
|
Per-risk DB verification:
|
|
@@ -72,6 +83,15 @@ For each NFR-sensitive path: `[NFR-PERF]` response time + load patterns; `[NFR-S
|
|
|
72
83
|
- If `ui` in `{testing_profiles}` → **MANDATORY.** Read `uxa-spec.md`. If `uxa-spec.md` is missing, send `[BLOCKER]` to Lead — do NOT proceed without it. For each page state define: Checkpoint ID (VV-{NNN}), Page/Route, State (Default/Loading/Empty/Error/Success or custom), AC Reference, Area labels in scope, Screenshot filename (`{story_id}_VV-{NNN}_{page}_{state}.png`), Expected visual elements, Setup instructions, Pass criteria. Write to `{story_output_dir}/visual-validation-checklist.md`.
|
|
73
84
|
- If `ui` NOT in `{testing_profiles}` → skip, note "N/A — no UI profile."
|
|
74
85
|
|
|
86
|
+
## Step 9b: Test Quality Bar — make every case implementable AND falsifiable
|
|
87
|
+
|
|
88
|
+
The dev agents implement exactly what you specify, and CRITIC rejects tests that are weak, unfalsifiable, or bound to the wrong artifact. Spec the bar IN so it is built right the first time (each rule below traces to a real rework cycle):
|
|
89
|
+
|
|
90
|
+
- **Every P0 case is a named, must-implement test** — never leave a P0 (or an idempotency/concurrency requirement for stateful resources) as a prose note. If it matters, give it its own case ID and assertion target so the dev agent implements it directly. For stateful resources (volumes, DBs), enumerate an explicit idempotency case (apply twice → assert no-recreate / no re-init / data intact).
|
|
91
|
+
- **Falsifiable assertion target** — state the real invariant and the exact expected value (status code, row/column value, count, error code + message regex). Forbid disjunctive/catch-all expectations: never spec "passes if A or B or fallback." The assertion must be able to fail when the behavior is wrong.
|
|
92
|
+
- **Bind to the real artifact** — when a case inspects a built artifact (image, container, file), specify that the test resolves it dynamically (the compose-built image id, the actual service/container name) and proves it exists before inspecting it — never a hardcoded tag. Note it must pass on a clean/CI machine with no leftover state.
|
|
93
|
+
- **NFR-PERF wording is statistical** — specify the percentile AND sample count + warm-up (e.g. "P95 < 200ms over 20 sequential samples after one warm-up request"), not a single-shot threshold.
|
|
94
|
+
|
|
75
95
|
## Step 10: Write Final Outputs
|
|
76
96
|
|
|
77
97
|
- Write to `{story_output_dir}/qa-test-spec.md` and `{story_output_dir}/visual-validation-checklist.md` (if applicable)
|
|
@@ -12,13 +12,18 @@ If pattern touches an invariant, do NOT write a CD. Instead:
|
|
|
12
12
|
|
|
13
13
|
For patterns NOT conflicting with invariants:
|
|
14
14
|
|
|
15
|
+
**Scope the directive to the failure mode, not the agent that tripped it.** Before setting `target_agent`, ask: *could another agent hit this same pattern?* Many failure modes are cross-cutting — test quality (missing P0 cases, conditionals/unfalsifiable assertions in tests, artifact-binding, hard waits), handoff-format violations, infra-mocking — and apply to **every** developer agent, not just the one that happened to surface it this sprint. For those:
|
|
16
|
+
- Set `target_agent: ALL-DEV` (BEND, FEND, DATA, MCP-DEV, LIBDEV, DOCGEN, IAC, MOBILE) so the lesson generalizes; do NOT pin a cross-cutting test-quality directive to the single agent that tripped it.
|
|
17
|
+
- Prefer reinforcing the shared contract: if the pattern is a standing rule, the durable fix is `.valent-pipeline/steps/common/quality-standards.md` (the dev self-check) or the QA-A spec / CRITIC checklist — note that in the directive's `reason`. A per-agent directive that restates a shared standard is noise.
|
|
18
|
+
- Only use a single `target_agent` when the pattern is genuinely specific to that agent's surface (e.g. an IAC-only compose idiom, a FEND-only component pattern).
|
|
19
|
+
|
|
15
20
|
**ADD** new directives:
|
|
16
21
|
```yaml
|
|
17
22
|
- id: CD-{batch_number}-{seq}
|
|
18
23
|
status: active
|
|
19
|
-
target_agent: {agent}
|
|
24
|
+
target_agent: {agent | ALL-DEV}
|
|
20
25
|
directive: "{what to do differently}"
|
|
21
|
-
reason: "{evidence from batch}"
|
|
26
|
+
reason: "{evidence from batch; if it restates a shared standard, name the file it belongs in}"
|
|
22
27
|
impact_level: low | medium | high
|
|
23
28
|
created_batch: {batch_number}
|
|
24
29
|
last_reinforced_batch: {batch_number}
|
package/src/lib/sprint.js
CHANGED
|
@@ -31,13 +31,19 @@ function depsOf(story) {
|
|
|
31
31
|
* @param {Array} stories - candidate stories: { id, points|story_points, priority, depends_on, status }
|
|
32
32
|
* @param {number} velocity - capacity in story points
|
|
33
33
|
* @returns {{ sprint_stories: string[], buffer_story_ids: string[], points_planned: number,
|
|
34
|
-
* remaining_capacity: number, velocity: number }}
|
|
34
|
+
* remaining_capacity: number, velocity: number, over_budget: boolean }}
|
|
35
35
|
*
|
|
36
36
|
* Only `groomed` stories are eligible (matches the source). Lower `priority` number = higher
|
|
37
37
|
* priority; missing priority sorts last. A prerequisite is auto-included only when it is also
|
|
38
38
|
* `groomed` and fits the remaining budget — deps already `shipped`/done are assumed satisfied
|
|
39
39
|
* and silently skipped, exactly as the prose specifies.
|
|
40
40
|
*
|
|
41
|
+
* Anti-stall: if no story fits the budget at all (the smallest groomed story is larger than the
|
|
42
|
+
* whole velocity), the highest-priority groomed story is planned ALONE — with its groomed
|
|
43
|
+
* prerequisites pulled in first — accepting an over-budget sprint (`over_budget: true`,
|
|
44
|
+
* `remaining_capacity` may go negative). This prevents an oversized story from stalling the
|
|
45
|
+
* project forever; the planner should surface it as a split candidate.
|
|
46
|
+
*
|
|
41
47
|
* Preserved quirk: if a story's dependency chain does not fully fit, the deps already added
|
|
42
48
|
* for it stay in the sprint (capacity is not rolled back) and the dependent is skipped. This
|
|
43
49
|
* matches the source pseudocode; `validateSprint` is the safety net for ordering consistency.
|
|
@@ -85,6 +91,26 @@ export function packSprint(stories, velocity) {
|
|
|
85
91
|
// else: skip this story, try smaller ones to fill capacity
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
// Anti-stall: if NOTHING fit the budget but groomed work exists, the smallest eligible story is
|
|
95
|
+
// larger than the entire velocity. Returning an empty sprint would stall the project forever
|
|
96
|
+
// (that story can never be packed, so the loop never progresses). Plan the highest-priority
|
|
97
|
+
// groomed story alone — pulling in its groomed prerequisites first so order stays dependency-safe
|
|
98
|
+
// — accepting an over-budget sprint. `over_budget` is flagged so the planner can surface
|
|
99
|
+
// "this story exceeds velocity; planned alone — consider splitting it."
|
|
100
|
+
let overBudget = false;
|
|
101
|
+
if (sprintStories.length === 0 && byPriority.length > 0) {
|
|
102
|
+
const forceAdd = (story) => {
|
|
103
|
+
if (inSprint.has(story.id)) return;
|
|
104
|
+
for (const depId of depsOf(story)) {
|
|
105
|
+
const dep = byId.get(depId);
|
|
106
|
+
if (dep && dep.status === 'groomed' && !inSprint.has(depId)) forceAdd(dep);
|
|
107
|
+
}
|
|
108
|
+
add(story);
|
|
109
|
+
};
|
|
110
|
+
forceAdd(byPriority[0]);
|
|
111
|
+
overBudget = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
88
114
|
const buffer = groomed.filter((s) => !inSprint.has(s.id)).map((s) => s.id);
|
|
89
115
|
const pointsPlanned = sprintStories.reduce((sum, id) => sum + pointsOf(byId.get(id)), 0);
|
|
90
116
|
|
|
@@ -94,6 +120,7 @@ export function packSprint(stories, velocity) {
|
|
|
94
120
|
points_planned: pointsPlanned,
|
|
95
121
|
remaining_capacity: remaining,
|
|
96
122
|
velocity,
|
|
123
|
+
over_budget: overBudget,
|
|
97
124
|
};
|
|
98
125
|
}
|
|
99
126
|
|